diff --git a/projects/common/src/utilities/lang/lang-utils.ts b/projects/common/src/utilities/lang/lang-utils.ts index da62acc3b..3c4f80514 100644 --- a/projects/common/src/utilities/lang/lang-utils.ts +++ b/projects/common/src/utilities/lang/lang-utils.ts @@ -35,3 +35,7 @@ const ignoreFunctions = (first: unknown, second: unknown) => { // tslint:disable-next-line: no-null-undefined-union export const isNonEmptyString = (str: string | undefined | null): str is string => str !== undefined && str !== null && str !== ''; + +export const hasOwnProperty = (obj: X, prop: Y): obj is X & Record => + // Since Typescript doesn't know how to type guard native hasOwnProperty, we wrap it here. + obj.hasOwnProperty(prop); diff --git a/projects/components/src/table/controls/table-controls-api.ts b/projects/components/src/table/controls/table-controls-api.ts index cde3e74e6..1b5cdc995 100644 --- a/projects/components/src/table/controls/table-controls-api.ts +++ b/projects/components/src/table/controls/table-controls-api.ts @@ -1,58 +1,83 @@ import { Dictionary } from '@hypertrace/common'; -import { SelectOption } from '../../select/select-option'; +import { FilterOperator } from '../../filtering/filter/filter-operators'; import { TableFilter } from '../table-api'; -export interface SelectControl { - placeholder?: string; - default?: SelectOption; - options: SelectOption[]; +export const enum TableControlOptionType { + Filter = 'filter', + Property = 'property', + Unset = 'unset' } -export interface SelectChange { - select: SelectControl; - value: TableControlOption; -} +export type TableControlOption = TableUnsetControlOption | TableFilterControlOption | TablePropertyControlOption; -export interface CheckboxControl { +export interface TableFilterControlOption { + type: TableControlOptionType.Filter; label: string; - value: boolean; - options: TableCheckboxOptions; + metaValue: TableFilter; } -export interface CheckboxChange { - checkbox: CheckboxControl; - option: TableControlOption; +export interface TableUnsetControlOption { + type: TableControlOptionType.Unset; + label: string; + metaValue: string; } -export const enum TableControlOptionType { - Filter = 'filter', - Property = 'property', - UnsetFilter = 'unset-filter' +export interface TablePropertyControlOption { + type: TableControlOptionType.Property; + label: string; + metaValue: Dictionary; } -export type TableControlOption = - | TableUnsetFilterControlOption - | TableFilterControlOption - | TablePropertyControlOption; +/* + * Select Control + */ -interface TableControlOptionBase { - value?: T; +export interface TableSelectControl { + placeholder: string; + options: TableSelectControlOption[]; } -export interface TableUnsetFilterControlOption extends TableControlOptionBase { - type: TableControlOptionType.UnsetFilter; - metaValue: string; + +export interface TableSelectChange { + select: TableSelectControl; + values: TableSelectControlOption[]; } -export interface TableFilterControlOption extends TableControlOptionBase { - type: TableControlOptionType.Filter; - metaValue: TableFilter; +export type TableSelectControlOption = TableFilterControlOption; + +/* + * Checkbox Control + */ + +export interface TableCheckboxControl { + label: string; + value: boolean; + options: TableCheckboxOptions; } -export interface TablePropertyControlOption extends TableControlOptionBase { - type: TableControlOptionType.Property; - metaValue: Dictionary; +export interface TableCheckboxChange { + checkbox: TableCheckboxControl; + option: TableCheckboxControlOption; } -export type TableCheckboxControlOption = TableControlOption & { label: string }; +export type TableCheckboxControlOption = TableControlOption & { + value: T; +}; export type TableCheckboxOptions = [TableCheckboxControlOption, TableCheckboxControlOption]; + +/* + * Util + */ + +export const toInFilter = (tableFilters: TableFilter[]): TableFilter => + tableFilters.reduce((previousValue, currentValue) => { + if (currentValue.operator !== FilterOperator.Equals || previousValue.field !== currentValue.field) { + throw Error('Filters must all contain same field and use = operator'); + } + + return { + field: previousValue.field, + operator: FilterOperator.In, + value: [...(Array.isArray(previousValue.value) ? previousValue.value : [previousValue.value]), currentValue.value] + }; + }); diff --git a/projects/components/src/table/controls/table-controls.component.test.ts b/projects/components/src/table/controls/table-controls.component.test.ts index 7e1f03cfb..0a473b844 100644 --- a/projects/components/src/table/controls/table-controls.component.test.ts +++ b/projects/components/src/table/controls/table-controls.component.test.ts @@ -1,6 +1,6 @@ import { fakeAsync } from '@angular/core/testing'; import { SubscriptionLifecycle } from '@hypertrace/common'; -import { SelectComponent } from '@hypertrace/components'; +import { MultiSelectComponent } from '@hypertrace/components'; import { createHostFactory, mockProvider } from '@ngneat/spectator/jest'; import { MockComponent } from 'ng-mocks'; import { SearchBoxComponent } from '../../search-box/search-box.component'; @@ -14,7 +14,7 @@ describe('Table Controls component', () => { providers: [mockProvider(SubscriptionLifecycle)], declarations: [ MockComponent(SearchBoxComponent), - MockComponent(SelectComponent), + MockComponent(MultiSelectComponent), MockComponent(ToggleGroupComponent) ], template: ` @@ -67,11 +67,11 @@ describe('Table Controls component', () => { options: [ { label: 'first1', - value: 'first-1' + metaValue: 'first-1' }, { label: 'second1', - value: 'second-1' + metaValue: 'second-1' } ] } @@ -79,7 +79,7 @@ describe('Table Controls component', () => { } }); - expect(spectator.query(SelectComponent)?.placeholder).toEqual('test1'); + expect(spectator.query(MultiSelectComponent)?.placeholder).toEqual('test1'); }); test('should emit selection when selected', () => { @@ -106,10 +106,12 @@ describe('Table Controls component', () => { } }); - spectator.triggerEventHandler(SelectComponent, 'selectedChange', { - label: 'first1', - value: 'first-1' - }); + spectator.triggerEventHandler(MultiSelectComponent, 'selectedChange', [ + { + label: 'first1', + value: 'first-1' + } + ]); expect(onChangeSpy).toHaveBeenCalled(); }); diff --git a/projects/components/src/table/controls/table-controls.component.ts b/projects/components/src/table/controls/table-controls.component.ts index 7bae09da2..4fb0bc007 100644 --- a/projects/components/src/table/controls/table-controls.component.ts +++ b/projects/components/src/table/controls/table-controls.component.ts @@ -14,9 +14,15 @@ import { Subject } from 'rxjs'; import { debounceTime } from 'rxjs/operators'; import { IconSize } from '../../icon/icon-size'; import { MultiSelectJustify } from '../../multi-select/multi-select-justify'; -import { TriggerLabelDisplayMode } from '../../multi-select/multi-select.component'; +import { MultiSelectSearchMode, TriggerLabelDisplayMode } from '../../multi-select/multi-select.component'; import { ToggleItem } from '../../toggle-group/toggle-item'; -import { CheckboxChange, CheckboxControl, SelectChange, SelectControl, TableControlOption } from './table-controls-api'; +import { + TableCheckboxChange, + TableCheckboxControl, + TableSelectChange, + TableSelectControl, + TableSelectControlOption +} from './table-controls-api'; @Component({ selector: 'ht-table-controls', @@ -36,19 +42,20 @@ import { CheckboxChange, CheckboxControl, SelectChange, SelectControl, TableCont > - - + @@ -86,6 +93,7 @@ import { CheckboxChange, CheckboxControl, SelectChange, SelectControl, TableCont }) export class TableControlsComponent implements OnChanges { public readonly DEFAULT_SEARCH_PLACEHOLDER: string = 'Search...'; + @Input() public searchEnabled?: boolean; @@ -93,10 +101,10 @@ export class TableControlsComponent implements OnChanges { public searchPlaceholder?: string; @Input() - public selectControls?: SelectControl[] = []; + public selectControls?: TableSelectControl[] = []; @Input() - public checkboxControls?: CheckboxControl[] = []; + public checkboxControls?: TableCheckboxControl[] = []; @Input() public activeFilterItem?: ToggleItem; @@ -111,10 +119,10 @@ export class TableControlsComponent implements OnChanges { public readonly searchChange: EventEmitter = new EventEmitter(); @Output() - public readonly selectChange: EventEmitter = new EventEmitter(); + public readonly selectChange: EventEmitter = new EventEmitter(); @Output() - public readonly checkboxChange: EventEmitter = new EventEmitter(); + public readonly checkboxChange: EventEmitter = new EventEmitter(); @Output() public readonly viewChange: EventEmitter = new EventEmitter(); @@ -179,21 +187,21 @@ export class TableControlsComponent implements OnChanges { this.searchDebounceSubject.next(text); } - public onSelectChange(select: SelectControl, value: TableControlOption): void { + public onMultiSelectChange(select: TableSelectControl, selections: TableSelectControlOption[]): void { this.selectChange.emit({ select: select, - value: value + values: selections }); } - public onCheckboxChange(checks: string[]): void { - const diff = this.checkboxDiffer?.diff(checks); + public onCheckboxChange(checked: string[]): void { + const diff = this.checkboxDiffer?.diff(checked); if (!diff) { return; } diff.forEachAddedItem(addedItem => { - const found: CheckboxControl | undefined = this.checkboxControls?.find( + const found: TableCheckboxControl | undefined = this.checkboxControls?.find( control => control.label === addedItem.item ); if (found) { @@ -205,7 +213,7 @@ export class TableControlsComponent implements OnChanges { }); diff.forEachRemovedItem(removedItem => { - const found: CheckboxControl | undefined = this.checkboxControls?.find( + const found: TableCheckboxControl | undefined = this.checkboxControls?.find( control => control.label === removedItem.item ); if (found) { @@ -216,7 +224,7 @@ export class TableControlsComponent implements OnChanges { } }); - this.checkboxSelections = checks; + this.checkboxSelections = checked; this.checkboxDiffer?.diff(this.checkboxSelections); } diff --git a/projects/distributed-tracing/src/shared/dashboard/data/graphql/graphql-table-control-options-data-source.model.ts b/projects/distributed-tracing/src/shared/dashboard/data/graphql/graphql-table-control-options-data-source.model.ts index 21e87169c..a0ca6bbcf 100644 --- a/projects/distributed-tracing/src/shared/dashboard/data/graphql/graphql-table-control-options-data-source.model.ts +++ b/projects/distributed-tracing/src/shared/dashboard/data/graphql/graphql-table-control-options-data-source.model.ts @@ -1,9 +1,7 @@ +import { TableControlOption } from '@hypertrace/components'; import { Observable } from 'rxjs'; -import { LabeledTableControlOption } from '../../widgets/table/table-widget-control.model'; import { GraphQlDataSourceModel } from './graphql-data-source.model'; -export abstract class GraphqlTableControlOptionsDataSourceModel extends GraphQlDataSourceModel< - LabeledTableControlOption[] -> { - public abstract getData(): Observable; +export abstract class GraphqlTableControlOptionsDataSourceModel extends GraphQlDataSourceModel { + public abstract getData(): Observable; } diff --git a/projects/distributed-tracing/src/shared/dashboard/widgets/table/table-widget-control-checkbox-option.model.ts b/projects/distributed-tracing/src/shared/dashboard/widgets/table/table-widget-control-checkbox-option.model.ts index 3e57ebcb4..7f887bec5 100644 --- a/projects/distributed-tracing/src/shared/dashboard/widgets/table/table-widget-control-checkbox-option.model.ts +++ b/projects/distributed-tracing/src/shared/dashboard/widgets/table/table-widget-control-checkbox-option.model.ts @@ -1,4 +1,5 @@ -import { TableCheckboxOptions, TableControlOption } from '@hypertrace/components'; +import { hasOwnProperty } from '@hypertrace/common'; +import { TableCheckboxControlOption, TableCheckboxOptions, TableControlOption } from '@hypertrace/components'; import { BOOLEAN_PROPERTY, Model, ModelProperty } from '@hypertrace/hyperdash'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; @@ -7,7 +8,7 @@ import { TableWidgetControlModel } from './table-widget-control.model'; @Model({ type: 'table-widget-checkbox-option' }) -export class TableWidgetControlCheckboxOptionModel extends TableWidgetControlModel { +export class TableWidgetControlCheckboxOptionModel extends TableWidgetControlModel { @ModelProperty({ key: 'checked', displayName: 'Checked', @@ -19,7 +20,7 @@ export class TableWidgetControlCheckboxOptionModel extends TableWidgetControlMod public getOptions(): Observable { return super.getOptions().pipe( map(options => { - if (!this.isValidCheckboxControlOption(options)) { + if (!this.isValidCheckboxControlOptions(options)) { throw Error(`Invalid table widget checkbox data source for options '${JSON.stringify(options)}'`); } @@ -31,7 +32,10 @@ export class TableWidgetControlCheckboxOptionModel extends TableWidgetControlMod ); } - private isValidCheckboxControlOption(options: TableControlOption[]): options is TableCheckboxOptions { - return options.length === 2 && options.every(option => typeof option.value === 'boolean'); + private isValidCheckboxControlOptions(options: TableControlOption[]): options is TableCheckboxOptions { + return ( + options.length === 2 && + options.every(option => hasOwnProperty(option, 'value') && typeof option.value === 'boolean') + ); } } diff --git a/projects/distributed-tracing/src/shared/dashboard/widgets/table/table-widget-control-select-option.model.ts b/projects/distributed-tracing/src/shared/dashboard/widgets/table/table-widget-control-select-option.model.ts index 0e900581d..5022a54dc 100644 --- a/projects/distributed-tracing/src/shared/dashboard/widgets/table/table-widget-control-select-option.model.ts +++ b/projects/distributed-tracing/src/shared/dashboard/widgets/table/table-widget-control-select-option.model.ts @@ -1,14 +1,34 @@ +import { TableControlOption, TableControlOptionType, TableSelectControlOption } from '@hypertrace/components'; import { Model, ModelProperty, STRING_PROPERTY } from '@hypertrace/hyperdash'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; import { TableWidgetControlModel } from './table-widget-control.model'; @Model({ type: 'table-widget-select-option' }) -export class TableWidgetControlSelectOptionModel extends TableWidgetControlModel { +export class TableWidgetControlSelectOptionModel extends TableWidgetControlModel { @ModelProperty({ key: 'placeholder', displayName: 'Placeholder', - type: STRING_PROPERTY.type + type: STRING_PROPERTY.type, + required: true }) - public placeholder?: string; + public placeholder!: string; + + public getOptions(): Observable { + return super.getOptions().pipe( + map(options => { + if (!this.isValidSelectControlOptions(options)) { + throw Error(`Invalid table widget select data source for options '${JSON.stringify(options)}'`); + } + + return options; + }) + ); + } + + private isValidSelectControlOptions(options: TableControlOption[]): options is TableSelectControlOption[] { + return options.every(option => option.type === TableControlOptionType.Filter); + } } diff --git a/projects/distributed-tracing/src/shared/dashboard/widgets/table/table-widget-control.model.ts b/projects/distributed-tracing/src/shared/dashboard/widgets/table/table-widget-control.model.ts index 0931a4437..f96e00fb0 100644 --- a/projects/distributed-tracing/src/shared/dashboard/widgets/table/table-widget-control.model.ts +++ b/projects/distributed-tracing/src/shared/dashboard/widgets/table/table-widget-control.model.ts @@ -5,7 +5,7 @@ import { uniqWith } from 'lodash-es'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; -export abstract class TableWidgetControlModel { +export abstract class TableWidgetControlModel { @ModelProperty({ key: 'sort', type: BOOLEAN_PROPERTY.type @@ -29,24 +29,24 @@ export abstract class TableWidgetControlModel { @ModelInject(MODEL_API) protected readonly api!: ModelApi; - public getOptions(): Observable { - return this.api.getData().pipe( - map(options => (this.uniqueValues ? this.filterUniqueValues(options) : options)), + public getOptions(): Observable { + return this.api.getData().pipe( + map(options => (this.uniqueValues ? this.filterUnique(options) : options)), map(options => (this.sort ? this.applySort(options) : options)) ); } - private filterUniqueValues(options: LabeledTableControlOption[]): LabeledTableControlOption[] { - return uniqWith(options, (a, b) => a.value === b.value); + protected filterUnique(options: T[]): T[] { + return uniqWith(options, (a, b) => a.label === b.label); } - private applySort(options: LabeledTableControlOption[]): LabeledTableControlOption[] { + protected applySort(options: T[]): T[] { return options.sort((a, b) => { // Unset option always at the top - if (a.type === TableControlOptionType.UnsetFilter) { + if (a.type === TableControlOptionType.Unset) { return -1; } - if (b.type === TableControlOptionType.UnsetFilter) { + if (b.type === TableControlOptionType.Unset) { return 1; } @@ -54,7 +54,3 @@ export abstract class TableWidgetControlModel { }); } } - -export type LabeledTableControlOption = TableControlOption & { - label: string; -}; diff --git a/projects/distributed-tracing/src/shared/dashboard/widgets/table/table-widget-renderer.component.ts b/projects/distributed-tracing/src/shared/dashboard/widgets/table/table-widget-renderer.component.ts index 23a7ed8d1..c94d1ed8e 100644 --- a/projects/distributed-tracing/src/shared/dashboard/widgets/table/table-widget-renderer.component.ts +++ b/projects/distributed-tracing/src/shared/dashboard/widgets/table/table-widget-renderer.component.ts @@ -8,21 +8,24 @@ import { PreferenceService } from '@hypertrace/common'; import { - CheckboxChange, - CheckboxControl, FilterAttribute, FilterOperator, - SelectChange, - SelectControl, StatefulTableRow, + TableCheckboxChange, + TableCheckboxControl, TableColumnConfig, + TableControlOption, TableControlOptionType, TableDataSource, TableFilter, + TableFilterControlOption, TableRow, + TableSelectChange, + TableSelectControl, TableSelectionMode, TableStyle, - ToggleItem + ToggleItem, + toInFilter } from '@hypertrace/components'; import { WidgetRenderer } from '@hypertrace/dashboards'; import { Renderer } from '@hypertrace/hyperdash'; @@ -35,7 +38,6 @@ import { MetadataService } from '../../../services/metadata/metadata.service'; import { InteractionHandler } from '../../interaction/interaction-handler'; import { TableWidgetBaseModel } from './table-widget-base.model'; import { SpecificationBackedTableColumnDef } from './table-widget-column.model'; -import { LabeledTableControlOption } from './table-widget-control.model'; import { TableWidgetViewToggleModel } from './table-widget-view-toggle.model'; import { TableWidgetModel } from './table-widget.model'; @@ -98,8 +100,8 @@ export class TableWidgetRendererComponent implements OnInit { public viewItems: ToggleItem[] = []; - public selectControls$!: Observable; - public checkboxControls$!: Observable; + public selectControls$!: Observable; + public checkboxControls$!: Observable; public metadata$!: Observable; public columnConfigs$!: Observable; @@ -171,17 +173,10 @@ export class TableWidgetRendererComponent // Fetch the values for the selectFilter dropdown selectControlModel.getOptions().pipe( take(1), - map(options => { - const selectOptions = options.map(option => ({ - label: option.label, - value: option - })); - - return { - placeholder: selectControlModel.placeholder, - options: selectOptions - }; - }) + map(options => ({ + placeholder: selectControlModel.placeholder, + options: options + })) ) ) ); @@ -203,7 +198,7 @@ export class TableWidgetRendererComponent ) ) ).pipe( - tap((checkboxControls: CheckboxControl[]) => { + tap((checkboxControls: TableCheckboxControl[]) => { // Apply initial values for checkboxes checkboxControls.forEach(checkboxControl => { this.onCheckboxChange({ @@ -276,25 +271,19 @@ export class TableWidgetRendererComponent ); } - public onSelectChange(changed: SelectChange): void { - switch (changed.value.type) { - case TableControlOptionType.UnsetFilter: - this.selectFilterSubject.next( - this.selectFilterSubject.getValue().filter(existingFilter => existingFilter.field !== changed.value.metaValue) - ); - break; - case TableControlOptionType.Filter: - this.selectFilterSubject.next(this.mergeFilters(changed.value.metaValue)); - break; - case TableControlOptionType.Property: - this.queryPropertiesSubject.next(this.mergeQueryProperties(changed.value.metaValue)); - break; - default: - assertUnreachable(changed.value); + public onSelectChange(changed: TableSelectChange): void { + if (changed.values.length === 0) { + // tslint:disable-next-line: no-void-expression + return this.selectFilterSubject.next(this.removeFilters(changed.select.options[0].metaValue.field)); } + + const tableFilters: TableFilter[] = changed.values.map((option: TableFilterControlOption) => option.metaValue); + + // tslint:disable-next-line: no-void-expression + return this.selectFilterSubject.next(this.mergeFilters(toInFilter(tableFilters))); } - public onCheckboxChange(changed: CheckboxChange): void { + public onCheckboxChange(changed: TableCheckboxChange): void { switch (changed.option.type) { case TableControlOptionType.Property: this.queryPropertiesSubject.next(this.mergeQueryProperties(changed.option.metaValue)); @@ -302,7 +291,7 @@ export class TableWidgetRendererComponent case TableControlOptionType.Filter: this.selectFilterSubject.next(this.mergeFilters(changed.option.metaValue)); break; - case TableControlOptionType.UnsetFilter: + case TableControlOptionType.Unset: this.selectFilterSubject.next(this.removeFilters(changed.option.metaValue)); break; default: @@ -317,8 +306,8 @@ export class TableWidgetRendererComponent take(1), map(options => { options.forEach(option => { - if (this.isLabeledOptionMatch(option, changed.option as LabeledTableControlOption)) { - checkboxControlModel.checked = changed.option.value === true; + if (this.isLabeledOptionMatch(option, changed.option)) { + checkboxControlModel.checked = changed.option.value; } }); @@ -333,8 +322,8 @@ export class TableWidgetRendererComponent ); } - private isLabeledOptionMatch(option1: LabeledTableControlOption, option2: LabeledTableControlOption): boolean { - return option1.label === option2.label && option1.value === option2.value; + private isLabeledOptionMatch(option1: TableControlOption, option2: TableControlOption): boolean { + return option1.label === option2.label; } public onSearchChange(text: string): void { diff --git a/projects/observability/src/pages/apis/endpoints/endpoint-list.dashboard.ts b/projects/observability/src/pages/apis/endpoints/endpoint-list.dashboard.ts index b7cbcf2bd..80df7bc97 100644 --- a/projects/observability/src/pages/apis/endpoints/endpoint-list.dashboard.ts +++ b/projects/observability/src/pages/apis/endpoints/endpoint-list.dashboard.ts @@ -26,10 +26,9 @@ export const endpointListDashboard: DashboardDefaultConfiguration = { { type: 'table-widget-select-option', 'unique-values': true, - placeholder: 'All Services', + placeholder: 'Services', data: { type: 'entities-attribute-options-data-source', - 'unset-label': 'All Services', // Use API so we can inherit API filters entity: ObservabilityEntityType.Api, attribute: { diff --git a/projects/observability/src/shared/dashboard/data/graphql/entity/attribute/entities-attribute-options-data-source.model.ts b/projects/observability/src/shared/dashboard/data/graphql/entity/attribute/entities-attribute-options-data-source.model.ts index 086dc9232..c4a13ca65 100644 --- a/projects/observability/src/shared/dashboard/data/graphql/entity/attribute/entities-attribute-options-data-source.model.ts +++ b/projects/observability/src/shared/dashboard/data/graphql/entity/attribute/entities-attribute-options-data-source.model.ts @@ -1,6 +1,5 @@ -import { FilterOperator, TableControlOptionType } from '@hypertrace/components'; -import { LabeledTableControlOption } from '@hypertrace/distributed-tracing'; -import { Model, ModelProperty, STRING_PROPERTY } from '@hypertrace/hyperdash'; +import { FilterOperator, TableControlOptionType, TableSelectControlOption } from '@hypertrace/components'; +import { Model } from '@hypertrace/hyperdash'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { EntitiesAttributeDataSourceModel } from './entities-attribute-data-source.model'; @@ -9,32 +8,19 @@ import { EntitiesAttributeDataSourceModel } from './entities-attribute-data-sour type: 'entities-attribute-options-data-source' }) export class EntitiesAttributeOptionsDataSourceModel extends EntitiesAttributeDataSourceModel { - @ModelProperty({ - key: 'unset-label', - type: STRING_PROPERTY.type, - required: true - }) - public unsetLabel: string = 'All'; - - public getData(): Observable { + public getData(): Observable { return super.getData().pipe( - map((values: unknown[]) => [ - { - type: TableControlOptionType.UnsetFilter, - label: this.unsetLabel, - metaValue: this.specification.name - }, - ...values.map(value => ({ + map((values: unknown[]) => + values.map(value => ({ type: TableControlOptionType.Filter as const, label: String(value), - value: value, metaValue: { field: this.specification.name, operator: FilterOperator.Equals, value: value } })) - ]) + ) ); } }