Skip to content

Commit a98aa54

Browse files
stephanwleedna2github
authored andcommitted
timeseries: match selection using same logic (tensorflow#5334)
Previously, to allow regex string also match run selection, we have extend the utility selector to match run selection against the regex. The logic, however, was massively flawed--instead of matching against name of a run, experiment, alias, and legacy run name, the implementation matched against runId which coincidentally contained run name but was supposed to be an opaque id. This caused an obvious match like `/^foo/` not to match anything especially when an opaque id is not created to start with "foo". In the end, this change basically re-implements the run selection based on regex more correct. In the process, we refactored how a run is matched against regex so the behavior is more congruent in our app.
1 parent 90c9335 commit a98aa54

File tree

9 files changed

+441
-103
lines changed

9 files changed

+441
-103
lines changed

tensorboard/webapp/experiments/store/testing.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
EXPERIMENTS_FEATURE_KEY,
1818
ExperimentsState,
1919
State,
20+
ExperimentsDataState,
2021
} from './experiments_types';
2122

2223
/**
@@ -34,6 +35,17 @@ export function buildExperiment(override?: Partial<Experiment>) {
3435
};
3536
}
3637

38+
export function buildExperimentState(
39+
override?: Partial<ExperimentsDataState>
40+
): ExperimentsState {
41+
return {
42+
data: {
43+
experimentMap: {},
44+
...override,
45+
},
46+
};
47+
}
48+
3749
/**
3850
* Get application state from an experiment state.
3951
*/

tensorboard/webapp/runs/views/runs_table/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ tf_ng_module(
6767
"//tensorboard/webapp/types",
6868
"//tensorboard/webapp/types:ui",
6969
"//tensorboard/webapp/util:colors",
70+
"//tensorboard/webapp/util:matcher",
7071
"//tensorboard/webapp/widgets/filter_input",
7172
"//tensorboard/webapp/widgets/range_input",
7273
"@npm//@angular/common",

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

Lines changed: 23 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ import {
1616
ChangeDetectionStrategy,
1717
Component,
1818
Input,
19-
OnInit,
2019
OnDestroy,
20+
OnInit,
2121
} from '@angular/core';
2222
import {createSelector, Store} from '@ngrx/store';
2323
import {combineLatest, Observable, of, Subject} from 'rxjs';
@@ -32,9 +32,19 @@ import {
3232
takeUntil,
3333
} from 'rxjs/operators';
3434

35-
import {DataLoadState, LoadState} from '../../../types/data';
3635
import * as alertActions from '../../../alert/actions';
3736
import {State} from '../../../app_state';
37+
import {
38+
actions as hparamsActions,
39+
selectors as hparamsSelectors,
40+
} from '../../../hparams';
41+
import {
42+
DiscreteFilter,
43+
DiscreteHparamValue,
44+
DiscreteHparamValues,
45+
DomainType,
46+
IntervalFilter,
47+
} from '../../../hparams/types';
3848
import {
3949
getCurrentRouteRunSelection,
4050
getEnabledColorGroup,
@@ -48,18 +58,9 @@ import {
4858
getRunSelectorSort,
4959
getRunsLoadState,
5060
} from '../../../selectors';
51-
import {
52-
actions as hparamsActions,
53-
selectors as hparamsSelectors,
54-
} from '../../../hparams';
55-
import {
56-
DiscreteFilter,
57-
IntervalFilter,
58-
DiscreteHparamValue,
59-
DiscreteHparamValues,
60-
DomainType,
61-
} from '../../../hparams/types';
61+
import {DataLoadState, LoadState} from '../../../types/data';
6262
import {SortDirection} from '../../../types/ui';
63+
import {matchRunToRegex} from '../../../util/matcher';
6364
import {
6465
runColorChanged,
6566
runPageSelectionToggled,
@@ -69,9 +70,8 @@ import {
6970
runSelectorSortChanged,
7071
runTableShown,
7172
} from '../../actions';
72-
import {SortKey, SortType} from '../../types';
7373
import {MAX_NUM_RUNS_TO_ENABLE_BY_DEFAULT} from '../../store/runs_types';
74-
74+
import {SortKey, SortType} from '../../types';
7575
import {
7676
HparamColumn,
7777
IntervalFilterChange,
@@ -440,36 +440,18 @@ export class RunsTableContainer implements OnInit, OnDestroy {
440440
return items;
441441
}
442442

443-
let regex: RegExp | null = null;
444-
445-
// Do not break all the future updates because of malformed
446-
// regexString. User can be still modifying it.
447-
try {
448-
regex = regexString ? new RegExp(regexString) : null;
449-
} catch (e) {}
450-
451-
if (!regex) {
452-
return [];
453-
}
454-
455443
const shouldIncludeExperimentName = this.columns.includes(
456444
RunsTableColumn.EXPERIMENT_NAME
457445
);
458446
return items.filter((item) => {
459-
// For some reason, TypeScript is faulty and cannot coerce the type to
460-
// non-null inspite the `if (!regex)` above.
461-
regex = regex!;
462-
463-
if (!shouldIncludeExperimentName) {
464-
return regex.test(item.run.name);
465-
}
466-
467-
const legacyRunName = `${item.experimentName}/${item.run.name}`;
468-
return (
469-
regex.test(item.run.name) ||
470-
regex.test(item.experimentAlias) ||
471-
regex.test(item.experimentName) ||
472-
regex.test(legacyRunName)
447+
return matchRunToRegex(
448+
{
449+
runName: item.run.name,
450+
experimentAlias: item.experimentAlias,
451+
experimentName: item.experimentName,
452+
},
453+
regexString,
454+
shouldIncludeExperimentName
473455
);
474456
});
475457
}),

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

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1472,7 +1472,7 @@ describe('runs_table', () => {
14721472
});
14731473
});
14741474

1475-
it('filters by original experiment name and legacy run name', () => {
1475+
it('filters by legacy experiment alias and run name put together', () => {
14761476
selectSpy
14771477
.withArgs(getExperiment, {experimentId: 'rowling'})
14781478
.and.returnValue(
@@ -1530,18 +1530,15 @@ describe('runs_table', () => {
15301530
['LotR', 'The Silmarillion'],
15311531
]);
15321532

1533-
// Alias for Harry Potter contains "z". If we were matching against
1534-
// alias + run name, Harry Potter should not match below.
1535-
// This regex matches:
1536-
// - (Harry P)*o*tter/The Chamber of S(ecrets)
1537-
// - (The L)ord of the rings/The S(ilmarillion)
1538-
// and does not match
1539-
// - HPz/The Chamber of S(ecrets) because of "[^z]+".
1533+
// Alias for Harry Potter contains "z". Since legacy Polymer-based
1534+
// tf-run-selector and the new Angular run selector match regex
1535+
// against '<alias>/<run name>' instead of '<experiment>/<run name>',
1536+
// below regex matches:
1537+
// - LotR/The S(ilmarillion)
15401538
store.overrideSelector(getRunSelectorRegexFilter, 'o[^z]+/.+S[ei]');
15411539
store.refreshState();
15421540
fixture.detectChanges();
15431541
expect(getTableRowTextContent(fixture)).toEqual([
1544-
['HPz', 'The Chamber Of Secrets'],
15451542
['LotR', 'The Silmarillion'],
15461543
]);
15471544
});

tensorboard/webapp/util/BUILD

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,29 @@ tf_ts_library(
5454
],
5555
)
5656

57+
tf_ts_library(
58+
name = "matcher",
59+
srcs = [
60+
"matcher.ts",
61+
],
62+
deps = [
63+
"//tensorboard/webapp/runs:types",
64+
],
65+
)
66+
5767
tf_ts_library(
5868
name = "ui_selectors",
5969
srcs = [
6070
"ui_selectors.ts",
6171
],
6272
deps = [
6373
":colors",
74+
":matcher",
6475
"//tensorboard/webapp:app_state",
76+
"//tensorboard/webapp/app_routing:types",
6577
"//tensorboard/webapp/app_routing/store",
78+
"//tensorboard/webapp/experiments/store:selectors",
79+
"//tensorboard/webapp/runs:types",
6680
"//tensorboard/webapp/runs/store:selectors",
6781
"@npm//@ngrx/store",
6882
],
@@ -91,6 +105,7 @@ tf_ts_library(
91105
testonly = True,
92106
srcs = [
93107
"lang_test.ts",
108+
"matcher_test.ts",
94109
"ngrx_test.ts",
95110
"string_test.ts",
96111
"ui_selectors_test.ts",
@@ -99,6 +114,7 @@ tf_ts_library(
99114
deps = [
100115
":colors",
101116
":lang",
117+
":matcher",
102118
":ngrx",
103119
":string",
104120
":ui_selectors",
@@ -107,6 +123,7 @@ tf_ts_library(
107123
"//tensorboard/webapp/app_routing:testing",
108124
"//tensorboard/webapp/app_routing:types",
109125
"//tensorboard/webapp/app_routing/store:testing",
126+
"//tensorboard/webapp/experiments/store:testing",
110127
"//tensorboard/webapp/runs/store:testing",
111128
"@npm//@ngrx/store",
112129
"@npm//@types/jasmine",

tensorboard/webapp/util/matcher.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/* Copyright 2021 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+
/**
16+
* @fileoverview Helps matching runs.
17+
*/
18+
19+
export interface RunMatchable {
20+
runName: string;
21+
experimentName: string;
22+
experimentAlias: string;
23+
}
24+
25+
/**
26+
* Matches an entry based on regex and business logic.
27+
*
28+
* - Regex matches name of a run.
29+
* - When `shouldMatchExperiment` is specified, it matches regex against one of
30+
* experiment name, experiment alias, and legacy run name which is generated
31+
* with "<exp alias>/<run name>".
32+
* - An empty regex string always returns true.
33+
* - Invalid regex always return false.
34+
* - Regex matches are case insensitive.
35+
* - Regex matches are not anchored.
36+
*/
37+
export function matchRunToRegex(
38+
runMatchable: RunMatchable,
39+
regexString: string,
40+
shouldMatchExperiment: boolean
41+
): boolean {
42+
if (!regexString) return true;
43+
44+
let regex: RegExp;
45+
try {
46+
regex = new RegExp(regexString, 'i');
47+
} catch {
48+
return false;
49+
}
50+
51+
const matchables = [runMatchable.runName];
52+
if (shouldMatchExperiment) {
53+
matchables.push(
54+
runMatchable.experimentName,
55+
runMatchable.experimentAlias,
56+
`${runMatchable.experimentAlias}/${runMatchable.runName}`
57+
);
58+
}
59+
return matchables.some((matchable) => regex!.test(matchable));
60+
}

0 commit comments

Comments
 (0)