Skip to content

Commit 62ac08a

Browse files
committed
create filter dialogue component and fix existing typing error
1 parent 060fecb commit 62ac08a

13 files changed

+432
-31
lines changed

tensorboard/webapp/hparams/BUILD

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ tf_ts_library(
2222
],
2323
deps = [
2424
"//tensorboard/webapp/runs/data_source",
25+
"//tensorboard/webapp/widgets/data_table:types",
2526
],
2627
)
2728

tensorboard/webapp/hparams/_types.ts

+2-20
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ See the License for the specific language governing permissions and
1313
limitations under the License.
1414
==============================================================================*/
1515
import {
16-
DiscreteHparamValues,
17-
DomainType,
1816
HparamSpec,
1917
MetricSpec,
2018
} from '../runs/data_source/runs_data_source_types';
2119

20+
export {DiscreteFilter, IntervalFilter} from '../widgets/data_table/types';
21+
2222
export {
2323
DatasetType,
2424
DiscreteHparamValue,
@@ -33,21 +33,3 @@ export interface HparamAndMetricSpec {
3333
hparams: HparamSpec[];
3434
metrics: MetricSpec[];
3535
}
36-
37-
export interface DiscreteFilter {
38-
type: DomainType.DISCRETE;
39-
includeUndefined: boolean;
40-
possibleValues: DiscreteHparamValues;
41-
// Subset of `possibleValues`
42-
filterValues: DiscreteHparamValues;
43-
}
44-
45-
export interface IntervalFilter {
46-
type: DomainType.INTERVAL;
47-
includeUndefined: boolean;
48-
minValue: number;
49-
maxValue: number;
50-
// Filter values have to be in between min and max values (inclusive).
51-
filterLowerValue: number;
52-
filterUpperValue: number;
53-
}

tensorboard/webapp/runs/data_source/BUILD

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ tf_ng_module(
1313
],
1414
deps = [
1515
":backend_types",
16+
"//tensorboard/webapp/widgets/data_table:types",
1617
"//tensorboard/webapp/webapp_data_source:http_client",
1718
"@npm//@angular/core",
1819
"@npm//rxjs",
@@ -24,7 +25,7 @@ tf_ts_library(
2425
srcs = [
2526
"runs_backend_types.ts",
2627
],
27-
visibility = ["//visibility:private"],
28+
visibility = ["//tensorboard/webapp/runs/data_source:__subpackages__", "//tensorboard/webapp/widgets/data_table:__subpackages__",],
2829
)
2930

3031
tf_ng_module(

tensorboard/webapp/runs/data_source/runs_data_source.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ import {
2020
TBHttpClient,
2121
} from '../../webapp_data_source/tb_http_client';
2222
import * as backendTypes from './runs_backend_types';
23+
import {DomainType} from '../../widgets/data_table/types';
2324
import {
2425
Domain,
25-
DomainType,
2626
HparamsAndMetadata,
2727
HparamSpec,
2828
HparamValue,

tensorboard/webapp/runs/data_source/runs_data_source_types.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ import {Injectable} from '@angular/core';
1616
import {Observable} from 'rxjs';
1717
import * as backendTypes from './runs_backend_types';
1818

19+
import {DomainType} from '../../widgets/data_table/types';
20+
export {DomainType} from '../../widgets/data_table/types';
21+
1922
export {
2023
BackendHparamsValueType as HparamsValueType,
2124
DatasetType,
@@ -41,11 +44,6 @@ export interface RunToHparamsAndMetrics {
4144
};
4245
}
4346

44-
export enum DomainType {
45-
DISCRETE,
46-
INTERVAL,
47-
}
48-
4947
interface IntervalDomain {
5048
type: DomainType.INTERVAL;
5149
minValue: number;

tensorboard/webapp/runs/views/runs_table/runs_table_container.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -192,8 +192,8 @@ function sortTableDataItems(
192192
let bValue = b[sort.name];
193193

194194
if (sort.name === 'experimentAlias') {
195-
aValue = (aValue as ExperimentAlias).aliasNumber;
196-
bValue = (bValue as ExperimentAlias).aliasNumber;
195+
aValue = a.experimentAlias!.aliasNumber;
196+
bValue = b.experimentAlias!.aliasNumber;
197197
}
198198

199199
if (aValue === bValue) {
@@ -381,7 +381,9 @@ export class RunsTableContainer implements OnInit, OnDestroy {
381381
...Object.fromEntries(runTableItem.hparams.entries()),
382382
id: runTableItem.run.id,
383383
run: runTableItem.run.name,
384-
experimentAlias: runTableItem.experimentAlias,
384+
// There appears to be a bug in TypeScript where this value needs to
385+
// be both string | number | boolean AND ExperimentAlias.
386+
experimentAlias: runTableItem.experimentAlias as any,
385387
selected: runTableItem.selected,
386388
color: runTableItem.runColor,
387389
};

tensorboard/webapp/widgets/data_table/BUILD

+42
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,16 @@ tf_sass_binary(
5151
],
5252
)
5353

54+
tf_sass_binary(
55+
name = "filter_dialogue_styles",
56+
src = "filter_dialogue.scss",
57+
strict_deps = False,
58+
deps = [
59+
"//tensorboard/webapp:angular_material_sass_deps",
60+
"//tensorboard/webapp/theme",
61+
],
62+
)
63+
5464
tf_ng_module(
5565
name = "data_table",
5666
srcs = [
@@ -71,12 +81,14 @@ tf_ng_module(
7181
deps = [
7282
":column_selector",
7383
":data_table_header",
84+
":filter_dialogue",
7485
":types",
7586
"//tensorboard/webapp/angular:expect_angular_material_button",
7687
"//tensorboard/webapp/angular:expect_angular_material_icon",
7788
"//tensorboard/webapp/metrics/views/card_renderer:scalar_card_types",
7889
"//tensorboard/webapp/widgets/custom_modal",
7990
"//tensorboard/webapp/widgets/line_chart_v2/lib:formatter",
91+
"//tensorboard/webapp/widgets/range_input:types",
8092
"@npm//@angular/common",
8193
"@npm//@angular/core",
8294
"@npm//rxjs",
@@ -123,11 +135,37 @@ tf_ng_module(
123135
],
124136
)
125137

138+
tf_ng_module(
139+
name = "filter_dialogue",
140+
srcs = [
141+
"filter_dialogue.ts",
142+
"filter_dialogue_module.ts",
143+
],
144+
assets = [
145+
"filter_dialogue.ng.html",
146+
":filter_dialogue_styles",
147+
],
148+
deps = [
149+
":types",
150+
"//tensorboard/webapp/angular:expect_angular_material_button",
151+
"//tensorboard/webapp/angular:expect_angular_material_checkbox",
152+
"//tensorboard/webapp/widgets/filter_input",
153+
"//tensorboard/webapp/widgets/range_input",
154+
"//tensorboard/webapp/widgets/range_input:types",
155+
"@npm//@angular/common",
156+
"@npm//@angular/core",
157+
"@npm//@angular/forms",
158+
],
159+
)
160+
126161
tf_ts_library(
127162
name = "types",
128163
srcs = [
129164
"types.ts",
130165
],
166+
deps = [
167+
"//tensorboard/webapp/experiments:types",
168+
],
131169
)
132170

133171
tf_ts_library(
@@ -137,16 +175,20 @@ tf_ts_library(
137175
"column_selector_test.ts",
138176
"content_cell_component_test.ts",
139177
"data_table_test.ts",
178+
"filter_dialogue_test.ts",
140179
"header_cell_component_test.ts",
141180
],
142181
deps = [
143182
":column_selector",
144183
":data_table",
184+
":filter_dialogue",
145185
":types",
146186
"//tensorboard/webapp/angular:expect_angular_core_testing",
187+
"//tensorboard/webapp/angular:expect_angular_material_checkbox",
147188
"//tensorboard/webapp/angular:expect_angular_platform_browser_animations",
148189
"//tensorboard/webapp/testing:mat_icon",
149190
"//tensorboard/webapp/widgets/custom_modal",
191+
"//tensorboard/webapp/widgets/range_input",
150192
"@npm//@angular/core",
151193
"@npm//@angular/forms",
152194
"@npm//@angular/platform-browser",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<!--
2+
@license
3+
Copyright 2023 The TensorFlow Authors. All Rights Reserved.
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
Unless required by applicable law or agreed to in writing, software
9+
distributed under the License is distributed on an "AS IS" BASIS,
10+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
See the License for the specific language governing permissions and
12+
limitations under the License.
13+
-->
14+
<div class="filter-dialogue">
15+
<div
16+
*ngIf="filter.type === DomainType.DISCRETE"
17+
class="discrete-filters-area"
18+
>
19+
<tb-filter-input
20+
*ngIf="filter.possibleValues"
21+
[value]="discreteValueFilter"
22+
(keyup)="discreteValueKeyUp($event)"
23+
placeholder="Filter Discrete Values (regex)"
24+
></tb-filter-input>
25+
<div *ngIf="!getPossibleValues().length" class="no-matches">
26+
No Matching Values
27+
</div>
28+
<div
29+
*ngFor="let value of getPossibleValues(); index"
30+
mat-menu-item
31+
class="filter-menu-checkbox-row"
32+
role="menuitemcheckbox"
33+
(click)="$event.stopPropagation()"
34+
>
35+
<mat-checkbox
36+
[checked]="filter.filterValues.includes(value)"
37+
(change)="discreteFilterChanged.emit(value)"
38+
><span>{{ value }}</span></mat-checkbox
39+
>
40+
</div>
41+
</div>
42+
43+
<div
44+
*ngIf="filter.type === DomainType.INTERVAL"
45+
(click)="$event.stopPropagation()"
46+
class="range-input-container"
47+
disableRipple
48+
mat-menu-item
49+
>
50+
<tb-range-input
51+
[min]="filter.minValue"
52+
[max]="filter.maxValue"
53+
[lowerValue]="filter.filterLowerValue"
54+
[upperValue]="filter.filterUpperValue"
55+
(rangeValuesChanged)="intervalFilterChanged.emit($event)"
56+
></tb-range-input>
57+
</div>
58+
59+
<mat-checkbox
60+
[checked]="filter.includeUndefined"
61+
(change)="includeUndefinedToggled.emit()"
62+
>Include Undefined</mat-checkbox
63+
>
64+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/* Copyright 2023 The TensorFlow Authors. All Rights Reserved.
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.
14+
==============================================================================*/
15+
@use '@angular/material' as mat;
16+
@import 'tensorboard/webapp/theme/tb_theme';
17+
18+
.filter-dialogue {
19+
padding: 8px;
20+
border-radius: 4px;
21+
border: 1px solid;
22+
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
23+
border-color: mat.get-color-from-palette($tb-foreground, border);
24+
background-color: mat.get-color-from-palette($tb-background, background);
25+
26+
@include tb-dark-theme {
27+
border-color: mat.get-color-from-palette($tb-dark-foreground, border);
28+
background-color: mat.get-color-from-palette(
29+
$tb-dark-background,
30+
'background'
31+
);
32+
}
33+
34+
.discrete-filters-area {
35+
max-height: 100px;
36+
overflow-y: auto;
37+
}
38+
39+
.no-matches {
40+
// 24px is the width of the checkbox so the text should match the
41+
// indentation of the selectable filters.
42+
padding: 8px 24px;
43+
}
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/* Copyright 2023 The TensorFlow Authors. All Rights Reserved.
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.
14+
==============================================================================*/
15+
import {Component, EventEmitter, Input, Output} from '@angular/core';
16+
import {
17+
DomainType,
18+
DiscreteFilter,
19+
IntervalFilter,
20+
DiscreteFilterValue,
21+
} from './types';
22+
import {RangeValues} from '../range_input/types';
23+
24+
@Component({
25+
selector: 'tb-data-table-filter',
26+
templateUrl: 'filter_dialogue.ng.html',
27+
styleUrls: ['filter_dialogue.css'],
28+
})
29+
export class FilterDialogue {
30+
DomainType = DomainType;
31+
32+
discreteValueFilter: string = '';
33+
34+
@Input() filter!: DiscreteFilter | IntervalFilter;
35+
36+
@Output() discreteFilterChanged = new EventEmitter<DiscreteFilterValue>();
37+
38+
@Output() intervalFilterChanged = new EventEmitter<RangeValues>();
39+
40+
@Output() includeUndefinedToggled = new EventEmitter<void>();
41+
42+
getPossibleValues() {
43+
const values: DiscreteFilterValue[] =
44+
(this.filter as DiscreteFilter).possibleValues ?? [];
45+
if (!this.discreteValueFilter) {
46+
return values;
47+
}
48+
return values.filter((value) =>
49+
value.toString().match(this.discreteValueFilter)
50+
);
51+
}
52+
53+
discreteValueKeyUp(event: KeyboardEvent) {
54+
this.discreteValueFilter = (event.target! as HTMLInputElement).value;
55+
}
56+
}

0 commit comments

Comments
 (0)