Skip to content

Commit f2cae86

Browse files
authored
Correctly drop cached plots data whenever moving between branches and commits (#1804)
1 parent c8c3a22 commit f2cae86

File tree

3 files changed

+135
-6
lines changed

3 files changed

+135
-6
lines changed

extension/src/plots/model/collect.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,3 +533,18 @@ export const collectSelectedTemplatePlots = (
533533
}
534534
return acc.length > 0 ? acc : undefined
535535
}
536+
537+
export const collectBranchRevisionDetails = (
538+
branchShas: {
539+
id: string
540+
sha: string | undefined
541+
}[]
542+
) => {
543+
const branchRevisions: Record<string, string> = {}
544+
for (const { id, sha } of branchShas) {
545+
if (sha) {
546+
branchRevisions[id] = sha
547+
}
548+
}
549+
return branchRevisions
550+
}

extension/src/plots/model/index.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Memento } from 'vscode'
2+
import isEqual from 'lodash.isequal'
23
import {
34
collectCheckpointPlotsData,
45
collectData,
@@ -9,7 +10,8 @@ import {
910
collectTemplates,
1011
ComparisonData,
1112
RevisionData,
12-
TemplateAccumulator
13+
TemplateAccumulator,
14+
collectBranchRevisionDetails
1315
} from './collect'
1416
import {
1517
CheckpointPlotData,
@@ -315,15 +317,27 @@ export class PlotsModel extends ModelWithPersistence {
315317
revisions,
316318
this.revisionData
317319
)
320+
321+
for (const fetchedRev of this.fetchedRevs) {
322+
if (!revisions.includes(fetchedRev)) {
323+
this.fetchedRevs.delete(fetchedRev)
324+
}
325+
}
318326
}
319327

320328
private removeStaleBranches() {
321-
for (const { id, sha } of this.experiments.getBranchRevisions()) {
322-
if (sha && this.branchRevisions[id] !== sha) {
329+
const currentBranchRevisions = collectBranchRevisionDetails(
330+
this.experiments.getBranchRevisions()
331+
)
332+
for (const id of Object.keys(this.branchRevisions)) {
333+
if (this.branchRevisions[id] !== currentBranchRevisions[id]) {
323334
this.deleteRevisionData(id)
324-
this.branchRevisions[id] = sha
325335
}
326336
}
337+
if (!isEqual(this.branchRevisions, currentBranchRevisions)) {
338+
this.deleteRevisionData('workspace')
339+
}
340+
this.branchRevisions = currentBranchRevisions
327341
}
328342

329343
private deleteRevisionData(id: string) {

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

Lines changed: 102 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { join } from 'path'
22
import merge from 'lodash.merge'
3+
import isEqual from 'lodash.isequal'
34
import cloneDeep from 'lodash.clonedeep'
45
import { afterEach, beforeEach, describe, it, suite } from 'mocha'
56
import { expect } from 'chai'
@@ -18,11 +19,12 @@ import {
1819
getMockNow,
1920
getMessageReceivedEmitter
2021
} from '../util'
21-
import { dvcDemoPath } from '../../util'
22+
import { basePlotsUrl, dvcDemoPath } from '../../util'
2223
import {
2324
DEFAULT_SECTION_COLLAPSED,
2425
PlotsData as TPlotsData,
2526
PlotSize,
27+
PlotsType,
2628
Section,
2729
TemplatePlotGroup
2830
} from '../../../plots/webview/contract'
@@ -46,6 +48,7 @@ suite('Plots Test Suite', () => {
4648
return closeAllEditors()
4749
})
4850

51+
// eslint-disable-next-line sonarjs/cognitive-complexity
4952
describe('Plots', () => {
5053
it('should call plots diff once on instantiation with missing revisions if there are no plots', async () => {
5154
const { mockPlotsDiff, messageSpy, plots, data } = await buildPlots(
@@ -154,9 +157,106 @@ suite('Plots Test Suite', () => {
154157
await dataUpdateEvent
155158

156159
expect(mockPlotsDiff).to.be.calledOnce
157-
expect(mockPlotsDiff).to.be.calledWithExactly(dvcDemoPath, 'main')
160+
expect(mockPlotsDiff).to.be.calledWithExactly(
161+
dvcDemoPath,
162+
'main',
163+
'workspace'
164+
)
158165
})
159166

167+
it('should re-fetch data when moving between branches', async () => {
168+
const mockNow = getMockNow()
169+
const { data, experiments, mockPlotsDiff, plots, plotsModel } =
170+
await buildPlots(disposable, plotsDiffFixture)
171+
mockPlotsDiff.resetHistory()
172+
173+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
174+
const mockSendPlots = stub(plots as any, 'sendPlots')
175+
176+
mockPlotsDiff
177+
.onFirstCall()
178+
.resolves({
179+
[join('plots', 'acc.png')]: [
180+
{
181+
revisions: ['workspace'],
182+
type: PlotsType.IMAGE,
183+
url: join(basePlotsUrl, 'workspace_plots_acc.png')
184+
},
185+
{
186+
revisions: ['another-branch'],
187+
type: PlotsType.IMAGE,
188+
url: join(basePlotsUrl, 'another-branch_plots_acc.png')
189+
}
190+
]
191+
})
192+
.onSecondCall()
193+
.resolves(plotsDiffFixture)
194+
195+
const differentBranch = {
196+
baseline: merge(
197+
cloneDeep(
198+
expShowFixture['53c3851f46955fa3e2b8f6e1c52999acc8c9ea77'][
199+
'4fb124aebddb2adf1545030907687fa9a4c80e70'
200+
]
201+
),
202+
{ data: { name: 'another-branch' } }
203+
)
204+
}
205+
206+
const updatedExpShowFixture = {
207+
'9235a02880a0372545e5f7f4d79a5d9eee6331ac': differentBranch,
208+
workspace: differentBranch
209+
}
210+
211+
const branchChangedEvent = new Promise(resolve =>
212+
disposable.track(
213+
data.onDidUpdate(() => {
214+
if (plotsModel) {
215+
resolve(undefined)
216+
}
217+
})
218+
)
219+
)
220+
const plotsSentEvent = new Promise(resolve =>
221+
mockSendPlots.callsFake(() => {
222+
if (isEqual(plotsModel.getMissingRevisions(), [])) {
223+
resolve(undefined)
224+
}
225+
})
226+
)
227+
228+
bypassProcessManagerDebounce(mockNow)
229+
experiments.setState(updatedExpShowFixture)
230+
231+
await branchChangedEvent
232+
await plotsSentEvent
233+
234+
expect(mockPlotsDiff).to.be.calledOnce
235+
expect(mockPlotsDiff).to.be.calledWithExactly(
236+
dvcDemoPath,
237+
'another-branch',
238+
'workspace'
239+
)
240+
241+
bypassProcessManagerDebounce(mockNow, 2)
242+
const dataUpdateEvent = new Promise(resolve =>
243+
disposable.track(data.onDidUpdate(() => resolve(undefined)))
244+
)
245+
246+
experiments.setState(expShowFixture)
247+
await dataUpdateEvent
248+
249+
expect(mockPlotsDiff).to.be.calledTwice
250+
expect(mockPlotsDiff).to.be.calledWithExactly(
251+
dvcDemoPath,
252+
'1ba7bcd',
253+
'42b8736',
254+
'4fb124a',
255+
'main',
256+
'workspace'
257+
)
258+
}).timeout(WEBVIEW_TEST_TIMEOUT)
259+
160260
it('should remove the temporary plots directory on dispose', async () => {
161261
const { mockRemoveDir, plots } = await buildPlots(
162262
disposable,

0 commit comments

Comments
 (0)