Skip to content

Commit e80961b

Browse files
authored
Save smooth plot values across sessions (#4220)
1 parent 08b29c9 commit e80961b

File tree

19 files changed

+398
-59
lines changed

19 files changed

+398
-59
lines changed

extension/src/persistence/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export enum PersistenceKey {
1717
PLOTS_CUSTOM_ORDER = 'plotCustomOrder:',
1818
PLOT_SECTION_COLLAPSED = 'plotSectionCollapsed:',
1919
PLOT_SELECTED_METRICS = 'plotSelectedMetrics:',
20+
PLOTS_SMOOTH_PLOT_VALUES = 'plotSmoothPlotValues:',
2021
PLOT_TEMPLATE_ORDER = 'plotTemplateOrder:'
2122
}
2223

extension/src/plots/model/index.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ import {
3232
CustomPlotsData,
3333
DEFAULT_HEIGHT,
3434
DEFAULT_NB_ITEMS_PER_ROW,
35-
PlotHeight
35+
PlotHeight,
36+
SmoothPlotValues
3637
} from '../webview/contract'
3738
import {
3839
EXPERIMENT_WORKSPACE_ID,
@@ -74,6 +75,7 @@ export class PlotsModel extends ModelWithPersistence {
7475

7576
private comparisonData: ComparisonData = {}
7677
private comparisonOrder: string[]
78+
private smoothPlotValues: SmoothPlotValues = {}
7779

7880
private revisionData: RevisionData = {}
7981
private templates: TemplateAccumulator = {}
@@ -102,6 +104,10 @@ export class PlotsModel extends ModelWithPersistence {
102104
)
103105
this.comparisonOrder = this.revive(PersistenceKey.PLOT_COMPARISON_ORDER, [])
104106
this.customPlotsOrder = this.revive(PersistenceKey.PLOTS_CUSTOM_ORDER, [])
107+
this.smoothPlotValues = this.revive(
108+
PersistenceKey.PLOTS_SMOOTH_PLOT_VALUES,
109+
{}
110+
)
105111

106112
this.cleanupOutdatedCustomPlotsState()
107113
this.cleanupOutdatedTrendsState()
@@ -312,6 +318,15 @@ export class PlotsModel extends ModelWithPersistence {
312318
)
313319
}
314320

321+
public setSmoothPlotValues(id: string, value: number) {
322+
this.smoothPlotValues[id] = value
323+
this.persist(PersistenceKey.PLOTS_SMOOTH_PLOT_VALUES, this.smoothPlotValues)
324+
}
325+
326+
public getSmoothPlotValues() {
327+
return this.smoothPlotValues
328+
}
329+
315330
public getNbItemsPerRowOrWidth(section: PlotsSection) {
316331
if (this.nbItemsPerRowOrWidth[section]) {
317332
return this.nbItemsPerRowOrWidth[section]

extension/src/plots/webview/contract.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,10 +141,13 @@ export type TemplatePlotSection = {
141141
entries: TemplatePlotEntry[]
142142
}
143143

144+
export type SmoothPlotValues = { [id: string]: number }
145+
144146
export interface TemplatePlotsData {
145147
plots: TemplatePlotSection[]
146148
nbItemsPerRow: number
147149
height: PlotHeight
150+
smoothPlotValues: SmoothPlotValues
148151
}
149152

150153
export type ComparisonPlot = {

extension/src/plots/webview/messages.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,11 @@ export class WebviewMessages {
118118
)
119119
case MessageFromWebviewType.TOGGLE_EXPERIMENT:
120120
return this.setExperimentStatus(message.payload)
121+
case MessageFromWebviewType.SET_SMOOTH_PLOT_VALUE:
122+
return this.setSmoothPlotValues(
123+
message.payload.id,
124+
message.payload.value
125+
)
121126
case MessageFromWebviewType.ZOOM_PLOT:
122127
if (message.payload) {
123128
const imagePath = this.revertCorrectUrl(message.payload)
@@ -200,6 +205,16 @@ export class WebviewMessages {
200205
)
201206
}
202207

208+
private setSmoothPlotValues(id: string, value: number) {
209+
this.plots.setSmoothPlotValues(id, value)
210+
this.sendTemplatePlots()
211+
sendTelemetryEvent(
212+
EventName.VIEWS_PLOTS_SET_SMOOTH_PLOT_VALUE,
213+
undefined,
214+
undefined
215+
)
216+
}
217+
203218
private setCustomPlotsOrder(plotIds: string[]) {
204219
const customPlotsOrderWithId = this.plots
205220
.getCustomPlotsOrder()
@@ -287,7 +302,8 @@ export class WebviewMessages {
287302
nbItemsPerRow: this.plots.getNbItemsPerRowOrWidth(
288303
PlotsSection.TEMPLATE_PLOTS
289304
),
290-
plots
305+
plots,
306+
smoothPlotValues: this.plots.getSmoothPlotValues()
291307
}
292308
}
293309

extension/src/telemetry/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export const EventName = Object.assign(
7979
VIEWS_PLOTS_SECTION_TOGGLE: 'views.plots.toggleSection',
8080
VIEWS_PLOTS_SELECT_EXPERIMENTS: 'view.plots.selectExperiments',
8181
VIEWS_PLOTS_SELECT_PLOTS: 'view.plots.selectPlots',
82+
VIEWS_PLOTS_SET_SMOOTH_PLOT_VALUE: 'view.plots.setSmoothPlotValues',
8283
VIEWS_PLOTS_ZOOM_PLOT: 'views.plots.zoomPlot',
8384
VIEWS_REORDER_PLOTS_CUSTOM: 'views.plots.customReordered',
8485
VIEWS_REORDER_PLOTS_TEMPLATES: 'views.plots.templatesReordered',
@@ -274,6 +275,7 @@ export interface IEventNamePropertyMapping {
274275
[EventName.VIEWS_PLOTS_ZOOM_PLOT]: { isImage: boolean }
275276
[EventName.VIEWS_REORDER_PLOTS_CUSTOM]: undefined
276277
[EventName.VIEWS_REORDER_PLOTS_TEMPLATES]: undefined
278+
[EventName.VIEWS_PLOTS_SET_SMOOTH_PLOT_VALUE]: undefined
277279

278280
[EventName.VIEWS_PLOTS_PATH_TREE_OPENED]: DvcRootCount
279281

extension/src/test/fixtures/plotsDiff/index.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -733,13 +733,15 @@ export const getMinimalWebviewMessage = () => ({
733733
plots: extendedSpecs(basicVega),
734734
nbItemsPerRow: DEFAULT_NB_ITEMS_PER_ROW,
735735
height: DEFAULT_PLOT_HEIGHT,
736-
revisions: getRevisions()
736+
revisions: getRevisions(),
737+
smoothPlotValues: {}
737738
})
738739

739740
export const getTemplateWebviewMessage = (): TemplatePlotsData => ({
740741
plots: extendedSpecs({ ...basicVega, ...require('./vega').default }),
741742
nbItemsPerRow: DEFAULT_NB_ITEMS_PER_ROW,
742-
height: DEFAULT_PLOT_HEIGHT
743+
height: DEFAULT_PLOT_HEIGHT,
744+
smoothPlotValues: {}
743745
})
744746

745747
export const getManyTemplatePlotsWebviewMessage = (
@@ -749,7 +751,8 @@ export const getManyTemplatePlotsWebviewMessage = (
749751
...multipleVega(length)
750752
}),
751753
nbItemsPerRow: DEFAULT_NB_ITEMS_PER_ROW,
752-
height: DEFAULT_PLOT_HEIGHT
754+
height: DEFAULT_PLOT_HEIGHT,
755+
smoothPlotValues: {}
753756
})
754757

755758
export const MOCK_IMAGE_MTIME = 946684800000

extension/src/test/suite/plots/index.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,6 +1038,39 @@ suite('Plots Test Suite', () => {
10381038
)
10391039
})
10401040

1041+
it('should handle an update smooth plot values message from the webview', async () => {
1042+
const { plots, plotsModel } = await buildPlots({
1043+
disposer: disposable,
1044+
plotsDiff: plotsDiffFixture
1045+
})
1046+
const templatePlot = templatePlotsFixture.plots[0].entries[0]
1047+
1048+
const webview = await plots.showWebview()
1049+
const mockMessageReceived = getMessageReceivedEmitter(webview)
1050+
const mockSendTelemetryEvent = stub(Telemetry, 'sendTelemetryEvent')
1051+
const mockSetSmoothPlotValues = stub(plotsModel, 'setSmoothPlotValues')
1052+
1053+
mockMessageReceived.fire({
1054+
payload: {
1055+
id: templatePlot.id,
1056+
value: 0.5
1057+
},
1058+
type: MessageFromWebviewType.SET_SMOOTH_PLOT_VALUE
1059+
})
1060+
1061+
expect(mockSendTelemetryEvent).to.be.called
1062+
expect(mockSendTelemetryEvent).to.be.calledWithExactly(
1063+
EventName.VIEWS_PLOTS_SET_SMOOTH_PLOT_VALUE,
1064+
undefined,
1065+
undefined
1066+
)
1067+
expect(mockSetSmoothPlotValues).to.be.called
1068+
expect(mockSetSmoothPlotValues).to.be.calledWithExactly(
1069+
templatePlot.id,
1070+
0.5
1071+
)
1072+
})
1073+
10411074
it('should handle the CLI throwing an error', async () => {
10421075
const { data, errorsModel, mockPlotsDiff, plots, plotsModel } =
10431076
await buildPlots({ disposer: disposable, plotsDiff: plotsDiffFixture })

extension/src/webview/contract.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export enum MessageFromWebviewType {
4040
RESIZE_COLUMN = 'resize-column',
4141
RESIZE_PLOTS = 'resize-plots',
4242
SAVE_STUDIO_TOKEN = 'save-studio-token',
43+
SET_SMOOTH_PLOT_VALUE = 'update-smooth-plot-value',
4344
SHOW_EXPERIMENT_LOGS = 'show-experiment-logs',
4445
SHOW_WALKTHROUGH = 'show-walkthrough',
4546
STOP_EXPERIMENTS = 'stop-experiments',
@@ -232,6 +233,10 @@ export type MessageFromWebview =
232233
| { type: MessageFromWebviewType.SHOW_WALKTHROUGH }
233234
| { type: MessageFromWebviewType.SHOW_SCM_PANEL }
234235
| { type: MessageFromWebviewType.INSTALL_DVC }
236+
| {
237+
type: MessageFromWebviewType.SET_SMOOTH_PLOT_VALUE
238+
payload: { id: string; value: number }
239+
}
235240
| { type: MessageFromWebviewType.UPGRADE_DVC }
236241
| { type: MessageFromWebviewType.SETUP_WORKSPACE }
237242
| { type: MessageFromWebviewType.OPEN_STUDIO }

webview/src/plots/components/App.test.tsx

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2147,5 +2147,123 @@ describe('App', () => {
21472147
fireEvent(panel, clickEvent)
21482148
expect(clickEvent.stopPropagation).toHaveBeenCalledTimes(1)
21492149
})
2150+
2151+
describe('Smooth Plots', () => {
2152+
const waitSetValuePostMessage = (value: number) =>
2153+
waitFor(
2154+
() =>
2155+
expect(mockPostMessage).toHaveBeenCalledWith({
2156+
payload: { id: smoothId, value },
2157+
type: MessageFromWebviewType.SET_SMOOTH_PLOT_VALUE
2158+
}),
2159+
{ timeout: 5000 }
2160+
)
2161+
it('should send a message to save the value when a vega panel slider is interacted with', async () => {
2162+
renderAppWithOptionalData({ template: withVegaPanels })
2163+
2164+
const smoothPlot = screen.getByTestId(`plot_${smoothId}`)
2165+
await waitForVega(smoothPlot)
2166+
2167+
// eslint-disable-next-line testing-library/no-node-access
2168+
const slider = smoothPlot.querySelector(
2169+
'.vega-bindings input[name="smooth"]'
2170+
)
2171+
expect(slider).toBeInTheDocument()
2172+
2173+
fireEvent.change(slider as HTMLInputElement, { target: { value: 0.4 } })
2174+
2175+
await waitSetValuePostMessage(0.4)
2176+
})
2177+
2178+
it('should send a message to save the value when a zoomed in plot vega panel slider is interacted with', async () => {
2179+
renderAppWithOptionalData({ template: withVegaPanels })
2180+
2181+
const smoothPlot = within(
2182+
screen.getByTestId(`plot_${smoothId}`)
2183+
).getByRole('button')
2184+
fireEvent.click(smoothPlot)
2185+
2186+
const popup = screen.getByTestId('zoomed-in-plot')
2187+
await waitForVega(popup)
2188+
2189+
// eslint-disable-next-line testing-library/no-node-access
2190+
const slider = popup.querySelector(
2191+
'.vega-bindings input[name="smooth"]'
2192+
)
2193+
expect(slider).toBeInTheDocument()
2194+
2195+
fireEvent.change(slider as HTMLInputElement, { target: { value: 0.4 } })
2196+
2197+
await waitSetValuePostMessage(0.4)
2198+
})
2199+
2200+
it('should set a vega panel slider value when given a default value', async () => {
2201+
renderAppWithOptionalData({
2202+
template: { ...withVegaPanels, smoothPlotValues: { [smoothId]: 0.6 } }
2203+
})
2204+
2205+
const smoothPlot = screen.getByTestId(`plot_${smoothId}`)
2206+
await waitForVega(smoothPlot)
2207+
2208+
// eslint-disable-next-line testing-library/no-node-access
2209+
const slider = smoothPlot.querySelector(
2210+
'.vega-bindings input[name="smooth"]'
2211+
)
2212+
expect(slider).toBeInTheDocument()
2213+
2214+
expect(slider).toHaveValue('0.6')
2215+
})
2216+
2217+
it('should set the zoomed in plot vega panel slider value when given a default value', async () => {
2218+
renderAppWithOptionalData({
2219+
template: { ...withVegaPanels, smoothPlotValues: { [smoothId]: 0.6 } }
2220+
})
2221+
2222+
const smoothPlot = within(
2223+
screen.getByTestId(`plot_${smoothId}`)
2224+
).getByRole('button')
2225+
fireEvent.click(smoothPlot)
2226+
2227+
const popup = screen.getByTestId('zoomed-in-plot')
2228+
await waitForVega(popup)
2229+
2230+
// eslint-disable-next-line testing-library/no-node-access
2231+
const slider = popup.querySelector(
2232+
'.vega-bindings input[name="smooth"]'
2233+
)
2234+
expect(slider).toBeInTheDocument()
2235+
2236+
expect(slider).toHaveValue('0.6')
2237+
})
2238+
2239+
it('should update a vega panel slider value when given a new value', async () => {
2240+
renderAppWithOptionalData({
2241+
template: { ...withVegaPanels }
2242+
})
2243+
2244+
const smoothPlot = screen.getByTestId(`plot_${smoothId}`)
2245+
2246+
await waitForVega(smoothPlot)
2247+
2248+
// eslint-disable-next-line testing-library/no-node-access
2249+
const slider = smoothPlot.querySelector(
2250+
'.vega-bindings input[name="smooth"]'
2251+
)
2252+
2253+
expect(slider).toBeInTheDocument()
2254+
expect(slider).toHaveValue('0.2')
2255+
2256+
sendSetDataMessage({
2257+
template: {
2258+
...withVegaPanels,
2259+
smoothPlotValues: { [smoothId]: 0.7 }
2260+
}
2261+
})
2262+
2263+
await waitFor(() => expect(slider).toHaveValue('0.7'), {
2264+
timeout: 5000
2265+
})
2266+
})
2267+
})
21502268
})
21512269
})

webview/src/plots/components/Plots.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,11 @@ const PlotsContent = () => {
5555
dispatch(setZoomedInPlot(undefined))
5656
}}
5757
>
58-
<ZoomedInPlot id={zoomedInPlot.id} props={zoomedInPlot.plot} />
58+
<ZoomedInPlot
59+
isTemplatePlot={zoomedInPlot.isTemplatePlot}
60+
id={zoomedInPlot.id}
61+
props={zoomedInPlot.plot}
62+
/>
5963
</Modal>
6064
)
6165

0 commit comments

Comments
 (0)