Skip to content

Commit 29850f6

Browse files
authored
[Global Pins] Saves all currently pinned cards if the user has enabled global pins. (#6848)
## Motivation for features / changes Feature request during the teamfood: `Clicking disable pins -> pinning a card -> clicking enable pins` doesn't save the currently pinned card. It seems reasonable to save all currently pinned cards when the setting is activated. This PR saves all currently pinned cards when user clicks enable global pins. ## Technical description of changes * 97c6324 Added `saveScalarPins` to the data source * c6be93b Modified `addOrRemovePinsOnToggle` logic to include adding pins logic * Previously, if a user disabled the global pin feature, all pins would be removed. * This PR added logic that will cause all currently pinned cards to be saved to local storage when the user activates the feature. ## Screenshots of UI changes (or N/A) N/A ## Detailed steps to verify changes work correctly (as executed by you) Unit test passes ## Alternate designs / implementations considered (or N/A) N/A
1 parent d4c5c82 commit 29850f6

File tree

5 files changed

+92
-22
lines changed

5 files changed

+92
-22
lines changed

tensorboard/webapp/metrics/data_source/saved_pins_data_source.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,16 @@ export class SavedPinsDataSource {
3030
);
3131
}
3232

33+
saveScalarPins(tags: Tag[]): void {
34+
const existingPins = this.getSavedScalarPins();
35+
const newTags = tags.filter((v) => !existingPins.includes(v));
36+
existingPins.push(...newTags);
37+
window.localStorage.setItem(
38+
SAVED_SCALAR_PINS_KEY,
39+
JSON.stringify(existingPins)
40+
);
41+
}
42+
3343
removeScalarPin(tag: Tag): void {
3444
const existingPins = this.getSavedScalarPins();
3545
window.localStorage.setItem(

tensorboard/webapp/metrics/data_source/saved_pins_data_source_test.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ describe('SavedPinsDataSource Test', () => {
8585
expect(dataSource.getSavedScalarPins()).toEqual(['tag1', 'tag2']);
8686
});
8787

88-
it('does not addd the provided tag if it already exists', () => {
88+
it('does not add the provided tag if it already exists', () => {
8989
window.localStorage.setItem(
9090
SAVED_SCALAR_PINS_KEY,
9191
JSON.stringify(['tag1', 'tag2'])
@@ -97,6 +97,36 @@ describe('SavedPinsDataSource Test', () => {
9797
});
9898
});
9999

100+
describe('saveScalarPins', () => {
101+
it('stores the provided tags in the local storage', () => {
102+
dataSource.saveScalarPins(['tag1', 'tag2']);
103+
104+
expect(dataSource.getSavedScalarPins()).toEqual(['tag1', 'tag2']);
105+
});
106+
107+
it('adds the provided tags to the existing list', () => {
108+
window.localStorage.setItem(
109+
SAVED_SCALAR_PINS_KEY,
110+
JSON.stringify(['tag1'])
111+
);
112+
113+
dataSource.saveScalarPins(['tag2']);
114+
115+
expect(dataSource.getSavedScalarPins()).toEqual(['tag1', 'tag2']);
116+
});
117+
118+
it('does not add the tag if it already exists', () => {
119+
window.localStorage.setItem(
120+
SAVED_SCALAR_PINS_KEY,
121+
JSON.stringify(['tag1', 'tag2'])
122+
);
123+
124+
dataSource.saveScalarPins(['tag2', 'tag3']);
125+
126+
expect(dataSource.getSavedScalarPins()).toEqual(['tag1', 'tag2', 'tag3']);
127+
});
128+
});
129+
100130
describe('removeScalarPin', () => {
101131
it('removes the given tag if it exists', () => {
102132
dataSource.saveScalarPin('tag3');

tensorboard/webapp/metrics/effects/index.ts

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import {
4343
TimeSeriesRequest,
4444
TimeSeriesResponse,
4545
SavedPinsDataSource,
46+
Tag,
4647
} from '../data_source/index';
4748
import {
4849
getCardLoadState,
@@ -356,26 +357,29 @@ export class MetricsEffects implements OnInitEffects {
356357
})
357358
);
358359

359-
private readonly removeSavedPinsOnDisable$ = this.actions$.pipe(
360+
private readonly addOrRemovePinsOnToggle$ = this.actions$.pipe(
360361
ofType(actions.metricsEnableSavingPinsToggled),
361362
withLatestFrom(
363+
this.store.select(selectors.getPinnedCardsWithMetadata),
362364
this.store.select(selectors.getEnableGlobalPins),
363365
this.store.select(selectors.getShouldPersistSettings),
364366
this.store.select(selectors.getMetricsSavingPinsEnabled)
365367
),
366368
filter(
367-
([
368-
,
369-
enableGlobalPins,
370-
getShouldPersistSettings,
371-
getMetricsSavingPinsEnabled,
372-
]) =>
373-
enableGlobalPins &&
374-
getShouldPersistSettings &&
375-
!getMetricsSavingPinsEnabled
369+
([, , enableGlobalPins, getShouldPersistSettings]) =>
370+
enableGlobalPins && getShouldPersistSettings
376371
),
377-
tap(() => {
378-
this.savedPinsDataSource.removeAllScalarPins();
372+
tap(([, pinnedCards, , , getMetricsSavingPinsEnabled]) => {
373+
if (getMetricsSavingPinsEnabled) {
374+
const tags: Tag[] = pinnedCards
375+
.map((card) => {
376+
return card.plugin === PluginType.SCALARS ? card.tag : null;
377+
})
378+
.filter((v): v is Tag => v !== null);
379+
this.savedPinsDataSource.saveScalarPins(tags);
380+
} else {
381+
this.savedPinsDataSource.removeAllScalarPins();
382+
}
379383
})
380384
);
381385

@@ -427,7 +431,7 @@ export class MetricsEffects implements OnInitEffects {
427431
/**
428432
* Subscribes to: metricsEnableSavingPinsToggled.
429433
*/
430-
this.removeSavedPinsOnDisable$
434+
this.addOrRemovePinsOnToggle$
431435
);
432436
},
433437
{dispatch: false}

tensorboard/webapp/metrics/effects/metrics_effects_test.ts

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import {
4545
appStateFromMetricsState,
4646
buildDataSourceTagMetadata,
4747
buildMetricsState,
48+
createCardMetadata,
4849
createScalarStepData,
4950
provideTestingMetricsDataSource,
5051
provideTestingSavedPinsDataSource,
@@ -1061,50 +1062,73 @@ describe('metrics effects', () => {
10611062
});
10621063
});
10631064

1064-
describe('removeSavedPinsOnDisable', () => {
1065+
describe('addOrRemovePinsOnToggle', () => {
10651066
let removeAllScalarPinsSpy: jasmine.Spy;
1067+
let saveScalarPinsSpy: jasmine.Spy;
10661068

10671069
beforeEach(() => {
10681070
removeAllScalarPinsSpy = spyOn(
10691071
savedPinsDataSource,
10701072
'removeAllScalarPins'
10711073
);
1074+
saveScalarPinsSpy = spyOn(savedPinsDataSource, 'saveScalarPins');
1075+
store.overrideSelector(selectors.getPinnedCardsWithMetadata, [
1076+
{
1077+
cardId: 'card1',
1078+
...createCardMetadata(PluginType.SCALARS),
1079+
tag: 'tag1',
1080+
},
1081+
{
1082+
cardId: 'card2',
1083+
...createCardMetadata(PluginType.IMAGES),
1084+
tag: 'tag2',
1085+
},
1086+
{
1087+
cardId: 'card3',
1088+
...createCardMetadata(PluginType.SCALARS),
1089+
tag: 'tag3',
1090+
},
1091+
]);
10721092
store.overrideSelector(selectors.getEnableGlobalPins, true);
10731093
store.overrideSelector(selectors.getMetricsSavingPinsEnabled, false);
10741094
store.refreshState();
10751095
});
10761096

1077-
it('removes all pins by calling metricsEnableSavingPinsToggled method', () => {
1097+
it('removes all pins if getMetricsSavingPinsEnabled is false', () => {
10781098
actions$.next(actions.metricsEnableSavingPinsToggled());
10791099

10801100
expect(removeAllScalarPinsSpy).toHaveBeenCalled();
1101+
expect(saveScalarPinsSpy).not.toHaveBeenCalled();
10811102
});
10821103

1083-
it('does not remove pins if getEnableGlobalPins is false', () => {
1084-
store.overrideSelector(selectors.getEnableGlobalPins, false);
1104+
it('add existing pins if getMetricsSavingPinsEnabled is true', () => {
1105+
store.overrideSelector(selectors.getMetricsSavingPinsEnabled, true);
10851106
store.refreshState();
10861107

10871108
actions$.next(actions.metricsEnableSavingPinsToggled());
10881109

1110+
expect(saveScalarPinsSpy).toHaveBeenCalledWith(['tag1', 'tag3']);
10891111
expect(removeAllScalarPinsSpy).not.toHaveBeenCalled();
10901112
});
10911113

1092-
it('does not remove pins if getShouldPersistSettings is false', () => {
1093-
store.overrideSelector(selectors.getShouldPersistSettings, false);
1114+
it('does not add or remove pins if getEnableGlobalPins is false', () => {
1115+
store.overrideSelector(selectors.getEnableGlobalPins, false);
10941116
store.refreshState();
10951117

10961118
actions$.next(actions.metricsEnableSavingPinsToggled());
10971119

10981120
expect(removeAllScalarPinsSpy).not.toHaveBeenCalled();
1121+
expect(saveScalarPinsSpy).not.toHaveBeenCalled();
10991122
});
11001123

1101-
it('does not remove pins if getMetricsSavingPinsEnabled is true', () => {
1102-
store.overrideSelector(selectors.getMetricsSavingPinsEnabled, true);
1124+
it('does not add or remove pins if getShouldPersistSettings is false', () => {
1125+
store.overrideSelector(selectors.getShouldPersistSettings, false);
11031126
store.refreshState();
11041127

11051128
actions$.next(actions.metricsEnableSavingPinsToggled());
11061129

11071130
expect(removeAllScalarPinsSpy).not.toHaveBeenCalled();
1131+
expect(saveScalarPinsSpy).not.toHaveBeenCalled();
11081132
});
11091133
});
11101134
});

tensorboard/webapp/metrics/testing.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,8 @@ export function buildStepIndexMetadata(
422422
export class TestingSavedPinsDataSource {
423423
saveScalarPin(tag: Tag) {}
424424

425+
saveScalarPins(tag: Tag[]) {}
426+
425427
removeScalarPin(tag: Tag) {}
426428

427429
getSavedScalarPins() {

0 commit comments

Comments
 (0)