Skip to content

Commit 4eaf1b7

Browse files
committed
create data source for persisting card interactions to localStorage
1 parent f7680b4 commit 4eaf1b7

File tree

4 files changed

+230
-0
lines changed

4 files changed

+230
-0
lines changed

tensorboard/webapp/metrics/data_source/BUILD

+26
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,18 @@ tf_ng_module(
2525
],
2626
)
2727

28+
tf_ng_module(
29+
name = "card_interactions_data_source",
30+
srcs = [
31+
"card_interactions_data_source.ts",
32+
"card_interactions_data_source_module.ts",
33+
],
34+
deps = [
35+
"//tensorboard/webapp/metrics/store:types",
36+
"@npm//@angular/core",
37+
],
38+
)
39+
2840
tf_ts_library(
2941
name = "types",
3042
srcs = [
@@ -70,3 +82,17 @@ tf_ts_library(
7082
"@npm//rxjs",
7183
],
7284
)
85+
86+
tf_ts_library(
87+
name = "card_interactions_data_source_test",
88+
testonly = True,
89+
srcs = [
90+
"card_interactions_data_source_test.ts",
91+
],
92+
deps = [
93+
":card_interactions_data_source",
94+
"//tensorboard/webapp/angular:expect_angular_core_testing",
95+
"//tensorboard/webapp/metrics:internal_types",
96+
"@npm//@types/jasmine",
97+
],
98+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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 {Injectable} from '@angular/core';
16+
import {CardInteractions} from '../store/metrics_types';
17+
18+
const CARD_INTERACTIONS_KEY = 'tb-card-interactions';
19+
20+
const MAX_RECORDS: Record<keyof CardInteractions, number> = {
21+
pins: 10,
22+
clicks: 10,
23+
tagFilters: 10,
24+
};
25+
26+
@Injectable()
27+
export class CardInteractionsDataSource {
28+
saveCardInteractions(cardInteractions: CardInteractions) {
29+
const trimmedInteractions: CardInteractions = {
30+
pins: cardInteractions.pins.slice(
31+
cardInteractions.pins.length - MAX_RECORDS.pins
32+
),
33+
clicks: cardInteractions.clicks.slice(
34+
cardInteractions.clicks.length - MAX_RECORDS.clicks
35+
),
36+
tagFilters: cardInteractions.tagFilters.slice(
37+
cardInteractions.tagFilters.length - MAX_RECORDS.tagFilters
38+
),
39+
};
40+
localStorage.setItem(
41+
CARD_INTERACTIONS_KEY,
42+
JSON.stringify(trimmedInteractions)
43+
);
44+
}
45+
46+
getCardInteractions(): CardInteractions {
47+
const existingInteractions = localStorage.getItem(CARD_INTERACTIONS_KEY);
48+
if (existingInteractions) {
49+
return JSON.parse(existingInteractions) as CardInteractions;
50+
}
51+
return {
52+
tagFilters: [],
53+
pins: [],
54+
clicks: [],
55+
};
56+
}
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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 {NgModule} from '@angular/core';
16+
import {CardInteractionsDataSource} from './card_interactions_data_source';
17+
18+
@NgModule({
19+
imports: [],
20+
providers: [CardInteractionsDataSource],
21+
})
22+
export class MetricsCardInteractionsDataSourceModule {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
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 {TestBed} from '@angular/core/testing';
16+
import {CardInteractionsDataSource} from './card_interactions_data_source';
17+
import {PluginType} from '../internal_types';
18+
19+
describe('CardInteractionsDataSource Test', () => {
20+
let mockStorage: Record<string, string>;
21+
let dataSource: CardInteractionsDataSource;
22+
23+
beforeEach(async () => {
24+
await TestBed.configureTestingModule({
25+
providers: [CardInteractionsDataSource],
26+
});
27+
28+
dataSource = TestBed.inject(CardInteractionsDataSource);
29+
30+
mockStorage = {};
31+
spyOn(window.localStorage, 'setItem').and.callFake(
32+
(key: string, value: string) => {
33+
if (key !== 'tb-card-interactions') {
34+
throw new Error('incorrect key used');
35+
}
36+
37+
mockStorage[key] = value;
38+
}
39+
);
40+
41+
spyOn(window.localStorage, 'getItem').and.callFake((key: string) => {
42+
if (key !== 'tb-card-interactions') {
43+
throw new Error('incorrect key used');
44+
}
45+
46+
return mockStorage[key];
47+
});
48+
});
49+
50+
describe('saveCardInteractions', () => {
51+
it('only saves 10 pins', () => {
52+
dataSource.saveCardInteractions({
53+
clicks: [],
54+
tagFilters: [],
55+
pins: Array.from({length: 12}).map((_, index) => ({
56+
cardId: `card-${index}`,
57+
runId: null,
58+
tag: 'foo',
59+
plugin: PluginType.SCALARS,
60+
})),
61+
});
62+
63+
expect(dataSource.getCardInteractions().pins.length).toEqual(10);
64+
});
65+
66+
it('only saves 10 clicks', () => {
67+
dataSource.saveCardInteractions({
68+
pins: [],
69+
tagFilters: [],
70+
clicks: Array.from({length: 12}).map((_, index) => ({
71+
cardId: `card-${index}`,
72+
runId: null,
73+
tag: 'foo',
74+
plugin: PluginType.SCALARS,
75+
})),
76+
});
77+
78+
expect(dataSource.getCardInteractions().clicks.length).toEqual(10);
79+
});
80+
81+
it('only saves 10 tagFilgers', () => {
82+
dataSource.saveCardInteractions({
83+
clicks: [],
84+
tagFilters: Array.from({length: 12}).map((_, index) =>
85+
index.toString()
86+
),
87+
pins: [],
88+
});
89+
90+
expect(dataSource.getCardInteractions().tagFilters.length).toEqual(10);
91+
});
92+
});
93+
94+
describe('getCardInteractions', () => {
95+
it('returns all default state when key is not set', () => {
96+
expect(dataSource.getCardInteractions()).toEqual({
97+
tagFilters: [],
98+
pins: [],
99+
clicks: [],
100+
});
101+
});
102+
103+
it('returns previously written value', () => {
104+
dataSource.saveCardInteractions({
105+
tagFilters: ['foo'],
106+
clicks: [
107+
{cardId: '1', runId: null, tag: 'foo', plugin: PluginType.SCALARS},
108+
],
109+
pins: [
110+
{cardId: '2', runId: null, tag: 'bar', plugin: PluginType.SCALARS},
111+
],
112+
});
113+
114+
expect(dataSource.getCardInteractions()).toEqual({
115+
tagFilters: ['foo'],
116+
clicks: [
117+
{cardId: '1', runId: null, tag: 'foo', plugin: PluginType.SCALARS},
118+
],
119+
pins: [
120+
{cardId: '2', runId: null, tag: 'bar', plugin: PluginType.SCALARS},
121+
],
122+
});
123+
});
124+
});
125+
});

0 commit comments

Comments
 (0)