Skip to content

Commit d5f94aa

Browse files
authored
Add progress ring to plots ribbon while data is loading (#2841)
* add progress ring to ribbon while plots are loading * override revisions when there is a selected running checkpoint experiment * prevent workspace infinite loop * style progress ring * revert stale changes * restyle progress ring in ribbon * fetch all available revisions from experiments for id to label mapping * refactor code * only push workspace into revs if it is not already included
1 parent 6e3c2e7 commit d5f94aa

File tree

17 files changed

+416
-96
lines changed

17 files changed

+416
-96
lines changed

extension/src/plots/data/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ export class PlotsData extends BaseData<{
5959
...args
6060
)
6161

62+
if (!revs.includes('workspace') && args.length < 2) {
63+
revs.push('workspace')
64+
}
65+
6266
this.notifyChanged({ data, revs })
6367

6468
const files = this.collectFiles({ data })

extension/src/plots/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,7 @@ export class Plots extends BaseRepository<TPlotsData> {
140140
this.paths.hasPaths() &&
141141
definedAndNonEmpty(this.plots.getUnfetchedRevisions())
142142
) {
143-
this.webviewMessages.sendCheckpointPlotsMessage()
144-
return this.data.managedUpdate()
143+
this.data.managedUpdate()
145144
}
146145

147146
return this.webviewMessages.sendWebviewMessage()

extension/src/plots/model/collect.test.ts

Lines changed: 114 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,23 @@ import {
99
collectTemplates,
1010
collectMetricOrder,
1111
collectWorkspaceRunningCheckpoint,
12-
collectWorkspaceRaceConditionData
12+
collectWorkspaceRaceConditionData,
13+
collectOverrideRevisionDetails
1314
} from './collect'
1415
import plotsDiffFixture from '../../test/fixtures/plotsDiff/output'
1516
import expShowFixture from '../../test/fixtures/expShow/base/output'
1617
import modifiedFixture from '../../test/fixtures/expShow/modified/output'
1718
import checkpointPlotsFixture from '../../test/fixtures/expShow/base/checkpointPlots'
18-
import { ExperimentsOutput } from '../../cli/dvc/contract'
19+
import { ExperimentsOutput, ExperimentStatus } from '../../cli/dvc/contract'
1920
import {
2021
definedAndNonEmpty,
2122
sameContents,
2223
uniqueValues
2324
} from '../../util/array'
2425
import { TemplatePlot } from '../webview/contract'
2526
import { getCLIBranchId } from '../../test/fixtures/plotsDiff/util'
27+
import { SelectedExperimentWithColor } from '../../experiments/model'
28+
import { Experiment } from '../../experiments/webview/contract'
2629

2730
const logsLossPath = join('logs', 'loss.tsv')
2831

@@ -358,3 +361,112 @@ describe('collectWorkspaceRaceConditionData', () => {
358361
)
359362
})
360363
})
364+
365+
describe('collectOverrideRevisionDetails', () => {
366+
it('should override the revision details for running checkpoint tips', () => {
367+
const runningId = 'b'
368+
369+
const { overrideOrder, overrideRevisions, unfinishedRunningExperiments } =
370+
collectOverrideRevisionDetails(
371+
['a', 'b', 'c', 'd'],
372+
[
373+
{ label: 'a' },
374+
{
375+
checkpoint_tip: 'b',
376+
displayColor: '#13adc7',
377+
id: runningId,
378+
label: 'b',
379+
sha: 'b',
380+
status: ExperimentStatus.RUNNING
381+
},
382+
{ label: 'c' },
383+
{ label: 'd' }
384+
] as SelectedExperimentWithColor[],
385+
new Set(['a', 'c', 'd', 'e']),
386+
new Set(),
387+
(id: string) => ({ [runningId]: [{ label: 'e' }] as Experiment[] }[id])
388+
)
389+
expect(overrideOrder).toStrictEqual(['a', 'e', 'c', 'd'])
390+
expect(overrideRevisions).toStrictEqual([
391+
{ label: 'a' },
392+
{
393+
displayColor: '#13adc7',
394+
label: 'e'
395+
},
396+
{ label: 'c' },
397+
{ label: 'd' }
398+
])
399+
expect(unfinishedRunningExperiments).toStrictEqual(new Set([runningId]))
400+
})
401+
402+
it('should override the revision details for finished but unfetched checkpoint tips', () => {
403+
const justFinishedRunningId = 'exp-was-running'
404+
const { overrideOrder, overrideRevisions, unfinishedRunningExperiments } =
405+
collectOverrideRevisionDetails(
406+
['a', 'b', 'c', 'd'],
407+
[
408+
{ label: 'a' },
409+
{
410+
checkpoint_tip: 'b',
411+
displayColor: '#13adc7',
412+
id: justFinishedRunningId,
413+
label: 'b',
414+
sha: 'b',
415+
status: ExperimentStatus.SUCCESS
416+
},
417+
{ label: 'c' },
418+
{ label: 'd' }
419+
] as SelectedExperimentWithColor[],
420+
new Set(['a', 'c', 'd', 'e']),
421+
new Set([justFinishedRunningId]),
422+
(id: string) =>
423+
({ [justFinishedRunningId]: [{ label: 'e' }] as Experiment[] }[id])
424+
)
425+
expect(overrideOrder).toStrictEqual(['a', 'e', 'c', 'd'])
426+
expect(overrideRevisions).toStrictEqual([
427+
{ label: 'a' },
428+
{
429+
displayColor: '#13adc7',
430+
label: 'e'
431+
},
432+
{ label: 'c' },
433+
{ label: 'd' }
434+
])
435+
expect(unfinishedRunningExperiments).toStrictEqual(
436+
new Set([justFinishedRunningId])
437+
)
438+
})
439+
440+
it('should remove the id from the unfinishedRunningExperiments set once the revision has been fetched', () => {
441+
const justFinishedRunningId = 'exp-was-running'
442+
const { overrideOrder, overrideRevisions, unfinishedRunningExperiments } =
443+
collectOverrideRevisionDetails(
444+
['a', 'b', 'c', 'd'],
445+
[
446+
{ label: 'a' },
447+
{
448+
checkpoint_tip: 'b',
449+
displayColor: '#13adc7',
450+
id: justFinishedRunningId,
451+
label: 'b',
452+
sha: 'b',
453+
status: ExperimentStatus.SUCCESS
454+
},
455+
{ label: 'c' },
456+
{ label: 'd' }
457+
] as SelectedExperimentWithColor[],
458+
new Set(['a', 'b', 'c', 'd', 'e']),
459+
new Set([justFinishedRunningId]),
460+
(id: string) =>
461+
({ [justFinishedRunningId]: [{ label: 'e' }] as Experiment[] }[id])
462+
)
463+
expect(overrideOrder).toStrictEqual(['a', 'b', 'c', 'd'])
464+
expect(overrideRevisions).toStrictEqual([
465+
{ label: 'a' },
466+
expect.objectContaining({ label: 'b' }),
467+
{ label: 'c' },
468+
{ label: 'd' }
469+
])
470+
expect(unfinishedRunningExperiments).toStrictEqual(new Set([]))
471+
})
472+
})

extension/src/plots/model/collect.ts

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ import {
2828
decodeColumn,
2929
appendColumnToPath
3030
} from '../../experiments/columns/paths'
31-
import { MetricOrParamColumns } from '../../experiments/webview/contract'
31+
import {
32+
Experiment,
33+
isRunning,
34+
MetricOrParamColumns
35+
} from '../../experiments/webview/contract'
3236
import { addToMapArray } from '../../util/map'
3337
import { TemplateOrder } from '../paths/collect'
3438
import { extendVegaSpec, isMultiViewPlot } from '../vega/util'
@@ -42,6 +46,7 @@ import {
4246
unmergeConcatenatedFields
4347
} from '../multiSource/collect'
4448
import { StrokeDashEncoding } from '../multiSource/constants'
49+
import { SelectedExperimentWithColor } from '../../experiments/model'
4550

4651
type CheckpointPlotAccumulator = {
4752
iterations: Record<string, number>
@@ -711,3 +716,112 @@ export const collectBranchRevisionDetails = (
711716
}
712717
return branchRevisions
713718
}
719+
720+
const getMostRecentFetchedCheckpointRevision = (
721+
selectedRevision: SelectedExperimentWithColor,
722+
fetchedRevs: Set<string>,
723+
checkpoints: Experiment[] | undefined
724+
): SelectedExperimentWithColor => {
725+
const mostRecent =
726+
checkpoints?.find(({ label }) => fetchedRevs.has(label)) || selectedRevision
727+
return {
728+
...mostRecent,
729+
displayColor: selectedRevision.displayColor
730+
} as SelectedExperimentWithColor
731+
}
732+
733+
const overrideRevisionDetail = (
734+
orderMapping: { [label: string]: string },
735+
selectedWithFetchedRunningCheckpointRevs: SelectedExperimentWithColor[],
736+
selectedRevision: SelectedExperimentWithColor,
737+
fetchedRevs: Set<string>,
738+
unfinishedRunningExperiments: Set<string>,
739+
checkpoints: Experiment[] | undefined
740+
) => {
741+
const { id, status, label } = selectedRevision
742+
743+
if (isRunning(status)) {
744+
unfinishedRunningExperiments.add(id)
745+
}
746+
747+
const mostRecent = getMostRecentFetchedCheckpointRevision(
748+
selectedRevision,
749+
fetchedRevs,
750+
checkpoints
751+
)
752+
orderMapping[label] = mostRecent.label
753+
selectedWithFetchedRunningCheckpointRevs.push(mostRecent)
754+
}
755+
756+
const collectRevisionDetail = (
757+
orderMapping: { [label: string]: string },
758+
selectedWithFetchedRunningCheckpointRevs: SelectedExperimentWithColor[],
759+
selectedRevision: SelectedExperimentWithColor,
760+
fetchedRevs: Set<string>,
761+
unfinishedRunningExperiments: Set<string>,
762+
getCheckpoints: (id: string) => Experiment[] | undefined
763+
// eslint-disable-next-line sonarjs/cognitive-complexity
764+
) => {
765+
const { label, status, id, checkpoint_tip, sha } = selectedRevision
766+
767+
const isCheckpointTip = sha === checkpoint_tip
768+
const running = isRunning(status)
769+
const preventRevisionsDisappearingAtEnd =
770+
isCheckpointTip && unfinishedRunningExperiments.has(id)
771+
772+
if (
773+
!fetchedRevs.has(label) &&
774+
(running || preventRevisionsDisappearingAtEnd)
775+
) {
776+
return overrideRevisionDetail(
777+
orderMapping,
778+
selectedWithFetchedRunningCheckpointRevs,
779+
selectedRevision,
780+
fetchedRevs,
781+
unfinishedRunningExperiments,
782+
getCheckpoints(id)
783+
)
784+
}
785+
786+
if (!running && isCheckpointTip) {
787+
unfinishedRunningExperiments.delete(id)
788+
}
789+
790+
orderMapping[label] = label
791+
selectedWithFetchedRunningCheckpointRevs.push(selectedRevision)
792+
}
793+
794+
export const collectOverrideRevisionDetails = (
795+
comparisonOrder: string[],
796+
selectedRevisions: SelectedExperimentWithColor[],
797+
fetchedRevs: Set<string>,
798+
unfinishedRunningExperiments: Set<string>,
799+
getCheckpoints: (id: string) => Experiment[] | undefined
800+
): {
801+
overrideOrder: string[]
802+
overrideRevisions: SelectedExperimentWithColor[]
803+
unfinishedRunningExperiments: Set<string>
804+
} => {
805+
const orderMapping: { [label: string]: string } = {}
806+
const selectedWithFetchedRunningCheckpointRevs: SelectedExperimentWithColor[] =
807+
[]
808+
809+
for (const selectedRevision of selectedRevisions) {
810+
collectRevisionDetail(
811+
orderMapping,
812+
selectedWithFetchedRunningCheckpointRevs,
813+
selectedRevision,
814+
fetchedRevs,
815+
unfinishedRunningExperiments,
816+
getCheckpoints
817+
)
818+
}
819+
820+
return {
821+
overrideOrder: comparisonOrder
822+
.map(label => orderMapping[label])
823+
.filter(Boolean),
824+
overrideRevisions: selectedWithFetchedRunningCheckpointRevs,
825+
unfinishedRunningExperiments
826+
}
827+
}

0 commit comments

Comments
 (0)