Skip to content

Commit 139d76d

Browse files
stephanwleedna2github
authored andcommitted
time series: deduplicate tags in the search (tensorflow#5249)
Image cards are a bit odd; unlike others, the same tuple of run and tag can appear more than once and that is because it has the third axis--sample. When there are more than one sample for a run+tag, the tag filters previously showed the duplicate entry which this can de-duplicates.
1 parent 8a1b9ce commit 139d76d

File tree

2 files changed

+60
-14
lines changed

2 files changed

+60
-14
lines changed

tensorboard/webapp/metrics/views/main_view/filter_input_container.ts

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,17 @@ limitations under the License.
1414
==============================================================================*/
1515
import {ChangeDetectionStrategy, Component} from '@angular/core';
1616
import {Store} from '@ngrx/store';
17-
import {combineLatest, of} from 'rxjs';
18-
import {combineLatestWith, filter, map, switchMap} from 'rxjs/operators';
17+
import {Observable} from 'rxjs';
18+
import {combineLatestWith, filter, map} from 'rxjs/operators';
1919

2020
import {State} from '../../../app_state';
2121
import {
2222
getMetricsTagFilter,
2323
getNonEmptyCardIdsWithMetadata,
2424
} from '../../../selectors';
2525
import {metricsTagFilterChanged} from '../../actions';
26-
import {compareTagNames} from '../utils';
27-
28-
/** @typehack */ import * as _typeHackRxjs from 'rxjs';
2926
import {getMetricsFilteredPluginTypes} from '../../store';
27+
import {compareTagNames} from '../utils';
3028

3129
@Component({
3230
selector: 'metrics-tag-filter',
@@ -43,9 +41,11 @@ import {getMetricsFilteredPluginTypes} from '../../store';
4341
export class MetricsFilterInputContainer {
4442
constructor(private readonly store: Store<State>) {}
4543

46-
readonly tagFilter$ = this.store.select(getMetricsTagFilter);
44+
readonly tagFilter$: Observable<string> = this.store.select(
45+
getMetricsTagFilter
46+
);
4747

48-
readonly isTagFilterRegexValid$ = this.tagFilter$.pipe(
48+
readonly isTagFilterRegexValid$: Observable<boolean> = this.tagFilter$.pipe(
4949
map((tagFilterString) => {
5050
try {
5151
// tslint:disable-next-line:no-unused-expression Check for validity of filter.
@@ -57,7 +57,7 @@ export class MetricsFilterInputContainer {
5757
})
5858
);
5959

60-
readonly completions$ = this.store
60+
readonly completions$: Observable<string[]> = this.store
6161
.select(getNonEmptyCardIdsWithMetadata)
6262
.pipe(
6363
combineLatestWith(this.store.select(getMetricsFilteredPluginTypes)),
@@ -68,12 +68,10 @@ export class MetricsFilterInputContainer {
6868
})
6969
.map(({tag}) => tag);
7070
}),
71-
switchMap((cardList) => {
72-
return combineLatest([
73-
of(cardList),
74-
this.store.select(getMetricsTagFilter),
75-
]);
76-
}),
71+
// De-duplicate using Set since Image cards has a notion of Sample and
72+
// the same `run` and `tag` can appear more than once.
73+
map((tags) => [...new Set(tags)]),
74+
combineLatestWith(this.store.select(getMetricsTagFilter)),
7775
map<[string[], string], [string[], RegExp | null]>(
7876
([tags, tagFilter]) => {
7977
try {

tensorboard/webapp/metrics/views/main_view/filter_input_test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,54 @@ describe('metrics filter input', () => {
198198
).toEqual(['tagA', 'tagC/woof']);
199199
});
200200

201+
it('filters de-duplicating tags', () => {
202+
store.overrideSelector(
203+
selectors.getMetricsFilteredPluginTypes,
204+
new Set<PluginType>([])
205+
);
206+
store.overrideSelector(selectors.getNonEmptyCardIdsWithMetadata, [
207+
{
208+
cardId: 'card1',
209+
plugin: PluginType.SCALARS,
210+
tag: 'tagA',
211+
runId: null,
212+
},
213+
{
214+
cardId: 'card1',
215+
plugin: PluginType.IMAGES,
216+
tag: 'tagB/Images',
217+
runId: 'run1',
218+
sample: 0,
219+
},
220+
{
221+
cardId: 'card1',
222+
plugin: PluginType.IMAGES,
223+
tag: 'tagB/Images',
224+
runId: 'run1',
225+
sample: 1,
226+
},
227+
{
228+
cardId: 'card1',
229+
plugin: PluginType.IMAGES,
230+
tag: 'tagB/Images',
231+
runId: 'run1',
232+
sample: 2,
233+
},
234+
]);
235+
store.overrideSelector(selectors.getMetricsTagFilter, '');
236+
const fixture = TestBed.createComponent(MetricsFilterInputContainer);
237+
fixture.detectChanges();
238+
239+
const input = fixture.debugElement.query(By.css('input'));
240+
input.nativeElement.focus();
241+
fixture.detectChanges();
242+
243+
const optionBefore = getAutocompleteOptions(overlayContainer);
244+
expect(
245+
optionBefore.map((option) => option.nativeElement.textContent)
246+
).toEqual(['tagA', 'tagB/Images']);
247+
});
248+
201249
it('filters by regex', () => {
202250
store.overrideSelector(selectors.getMetricsTagFilter, '[/I]m');
203251
const fixture = TestBed.createComponent(MetricsFilterInputContainer);

0 commit comments

Comments
 (0)