Skip to content

Commit 1c9fe13

Browse files
authored
Show Experiment Remote Status (#4324)
* prototype solution * add remote exp refs test fixture * review and refactor * address review comments
1 parent 7bb8a5f commit 1c9fe13

File tree

28 files changed

+368
-68
lines changed

28 files changed

+368
-68
lines changed

extension/src/cli/git/reader.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { resolve } from 'path'
22
import { GitCli } from '.'
3-
import { Command, Flag } from './constants'
3+
import { Command, DEFAULT_REMOTE, Flag } from './constants'
44
import { getOptions } from './options'
55
import { typeCheckCommands } from '..'
66
import { trimAndSplit } from '../../util/stdout'
@@ -10,6 +10,7 @@ export const autoRegisteredCommands = {
1010
GIT_GET_BRANCHES: 'getBranches',
1111
GIT_GET_COMMIT_MESSAGES: 'getCommitMessages',
1212
GIT_GET_NUM_COMMITS: 'getNumCommits',
13+
GIT_GET_REMOTE_EXPERIMENT_REFS: 'getRemoteExperimentRefs',
1314
GIT_GET_REMOTE_URL: 'getRemoteUrl',
1415
GIT_GET_REPOSITORY_ROOT: 'getGitRepositoryRoot',
1516
GIT_HAS_CHANGES: 'hasChanges',
@@ -68,6 +69,18 @@ export class GitReader extends GitCli {
6869
}
6970
}
7071

72+
public async getRemoteExperimentRefs(cwd: string): Promise<string> {
73+
const options = getOptions({
74+
args: [Command.LS_REMOTE, DEFAULT_REMOTE, 'refs/exps/*'],
75+
cwd
76+
})
77+
try {
78+
return await this.executeProcess(options)
79+
} catch {
80+
return ''
81+
}
82+
}
83+
7184
public async getRemoteUrl(cwd: string): Promise<string> {
7285
const options = getOptions({ args: [Command.LS_REMOTE, Flag.GET_URL], cwd })
7386
try {

extension/src/data/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export type ExperimentsOutput = {
1313
availableNbCommits: { [branch: string]: number }
1414
expShow: ExpShowOutput
1515
gitLog: string
16+
remoteExpRefs: string
1617
rowOrder: { branch: string; sha: string }[]
1718
}
1819

extension/src/experiments/commands/index.ts

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,11 @@ const convertUrlTextToLink = (stdout: string) => {
4343
}
4444

4545
export const getPushExperimentCommand =
46-
(internalCommands: InternalCommands, setup: Setup) =>
46+
(
47+
experiments: WorkspaceExperiments,
48+
internalCommands: InternalCommands,
49+
setup: Setup
50+
) =>
4751
({ dvcRoot, ids }: { dvcRoot: string; ids: string[] }) => {
4852
const studioAccessToken = setup.getStudioAccessToken()
4953
if (
@@ -56,23 +60,38 @@ export const getPushExperimentCommand =
5660
}
5761

5862
return Toast.showProgress('exp push', async progress => {
63+
const repository = experiments.getRepository(dvcRoot)
64+
65+
const updateOnCompletion = () => {
66+
return repository.unsetPushing(ids)
67+
}
68+
5969
progress.report({ increment: 0 })
6070

6171
progress.report({ increment: 25, message: `Pushing ${ids.join(' ')}...` })
6272

6373
const remainingProgress = 75
6474

65-
const stdout = await internalCommands.executeCommand(
66-
AvailableCommands.EXP_PUSH,
67-
dvcRoot,
68-
...ids
69-
)
75+
repository.setPushing(ids)
76+
77+
try {
78+
const stdout = await internalCommands.executeCommand(
79+
AvailableCommands.EXP_PUSH,
80+
dvcRoot,
81+
...ids
82+
)
83+
84+
void updateOnCompletion()
7085

71-
progress.report({
72-
increment: remainingProgress,
73-
message: convertUrlTextToLink(stdout)
74-
})
86+
progress.report({
87+
increment: remainingProgress,
88+
message: convertUrlTextToLink(stdout)
89+
})
7590

76-
return Toast.delayProgressClosing(15000)
91+
return Toast.delayProgressClosing(15000)
92+
} catch (error: unknown) {
93+
void updateOnCompletion()
94+
throw error
95+
}
7796
})
7897
}

extension/src/experiments/commands/register.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ export const registerExperimentCommands = (
277277

278278
internalCommands.registerExternalCliCommand(
279279
RegisteredCliCommands.EXPERIMENT_VIEW_PUSH,
280-
getPushExperimentCommand(internalCommands, setup)
280+
getPushExperimentCommand(experiments, internalCommands, setup)
281281
)
282282

283283
internalCommands.registerExternalCliCommand(

extension/src/experiments/data/index.ts

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,19 @@ export class ExperimentsData extends BaseData<ExperimentsOutput> {
4242
}
4343

4444
public async update(): Promise<void> {
45+
const [{ availableNbCommits, expShow, gitLog, rowOrder }, remoteExpRefs] =
46+
await Promise.all([this.updateExpShow(), this.updateRemoteExpRefs()])
47+
48+
return this.notifyChanged({
49+
availableNbCommits,
50+
expShow,
51+
gitLog,
52+
remoteExpRefs,
53+
rowOrder
54+
})
55+
}
56+
57+
private async updateExpShow() {
4558
await this.updateBranches()
4659
const branches = this.experiments.getBranchesToShow()
4760
let gitLog = ''
@@ -66,12 +79,7 @@ export class ExperimentsData extends BaseData<ExperimentsOutput> {
6679
)
6780

6881
this.collectFiles({ expShow })
69-
70-
return this.notifyChanged({ availableNbCommits, expShow, gitLog, rowOrder })
71-
}
72-
73-
protected collectFiles({ expShow }: { expShow: ExpShowOutput }) {
74-
this.collectedFiles = collectFiles(expShow, this.collectedFiles)
82+
return { availableNbCommits, expShow, gitLog, rowOrder }
7583
}
7684

7785
private async collectGitLogAndOrder(
@@ -119,6 +127,10 @@ export class ExperimentsData extends BaseData<ExperimentsOutput> {
119127
this.experiments.setBranches(allBranches)
120128
}
121129

130+
private collectFiles({ expShow }: { expShow: ExpShowOutput }) {
131+
this.collectedFiles = collectFiles(expShow, this.collectedFiles)
132+
}
133+
122134
private async watchExpGitRefs(): Promise<void> {
123135
const gitRoot = await this.internalCommands.executeCommand(
124136
AvailableCommands.GIT_GET_REPOSITORY_ROOT,
@@ -149,4 +161,11 @@ export class ExperimentsData extends BaseData<ExperimentsOutput> {
149161
}
150162
)
151163
}
164+
165+
private updateRemoteExpRefs() {
166+
return this.internalCommands.executeCommand(
167+
AvailableCommands.GIT_GET_REMOTE_EXPERIMENT_REFS,
168+
this.dvcRoot
169+
)
170+
}
152171
}

extension/src/experiments/index.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,8 @@ export class Experiments extends BaseRepository<TableData> {
177177
availableNbCommits,
178178
expShow,
179179
gitLog,
180-
rowOrder
180+
rowOrder,
181+
remoteExpRefs
181182
}: ExperimentsOutput) {
182183
const hadCheckpoints = this.hasCheckpoints()
183184
const dvcLiveOnly = await this.checkSignalFile()
@@ -188,7 +189,8 @@ export class Experiments extends BaseRepository<TableData> {
188189
gitLog,
189190
dvcLiveOnly,
190191
rowOrder,
191-
availableNbCommits
192+
availableNbCommits,
193+
remoteExpRefs
192194
)
193195
])
194196

@@ -199,6 +201,16 @@ export class Experiments extends BaseRepository<TableData> {
199201
return this.notifyChanged()
200202
}
201203

204+
public setPushing(ids: string[]) {
205+
this.experiments.setPushing(ids)
206+
return this.notifyChanged()
207+
}
208+
209+
public unsetPushing(ids: string[]) {
210+
this.experiments.unsetPushing(ids)
211+
return this.update()
212+
}
213+
202214
public hasCheckpoints() {
203215
return this.experiments.hasCheckpoints()
204216
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { collectExperiments } from './collect'
22
import { generateTestExpShowOutput } from '../../test/util/experiments'
33
import { ExpShowOutput } from '../../cli/dvc/contract'
44

5-
const DEFAULT_DATA: [string, boolean] = ['', false]
5+
const DEFAULT_DATA: [string, boolean, string] = ['', false, '']
66

77
describe('collectExperiments', () => {
88
it('should return an empty array if no commits are present', () => {
@@ -13,7 +13,7 @@ describe('collectExperiments', () => {
1313
expect(commits).toStrictEqual([])
1414
})
1515

16-
const expShowWithTwoCommits: [ExpShowOutput, string, boolean] = [
16+
const expShowWithTwoCommits: [ExpShowOutput, string, boolean, string] = [
1717
generateTestExpShowOutput(
1818
{},
1919
{

extension/src/experiments/model/collect.ts

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { extractColumns } from '../columns/extract'
99
import {
1010
CommitData,
1111
Experiment,
12+
GitRemoteStatus,
1213
RunningExperiment,
1314
isQueued,
1415
isRunning
@@ -29,6 +30,7 @@ import { RegisteredCommands } from '../../commands/external'
2930
import { Resource } from '../../resourceLocator'
3031
import { shortenForLabel } from '../../util/string'
3132
import { COMMITS_SEPARATOR } from '../../cli/git/constants'
33+
import { trimAndSplit } from '../../util/stdout'
3234

3335
export type ExperimentItem = {
3436
command?: {
@@ -233,6 +235,21 @@ const collectExecutorInfo = (
233235
}
234236
}
235237

238+
const collectRemoteStatus = (
239+
experiment: Experiment,
240+
remoteExpShas: Set<string>
241+
): void => {
242+
if (
243+
!experiment.sha ||
244+
![undefined, ExperimentStatus.SUCCESS].includes(experiment.status)
245+
) {
246+
return
247+
}
248+
experiment.gitRemoteStatus = remoteExpShas.has(experiment.sha)
249+
? GitRemoteStatus.ON_REMOTE
250+
: GitRemoteStatus.NOT_ON_REMOTE
251+
}
252+
236253
const collectRunningExperiment = (
237254
acc: ExperimentsAccumulator,
238255
experiment: Experiment
@@ -249,7 +266,8 @@ const collectRunningExperiment = (
249266
const collectExpRange = (
250267
acc: ExperimentsAccumulator,
251268
baseline: Experiment,
252-
expRange: ExpRange
269+
expRange: ExpRange,
270+
remoteExpShas: Set<string>
253271
): void => {
254272
const { revs, executor } = expRange
255273
if (!revs?.length) {
@@ -289,6 +307,7 @@ const collectExpRange = (
289307
}
290308

291309
collectExecutorInfo(experiment, executor)
310+
collectRemoteStatus(experiment, remoteExpShas)
292311
collectRunningExperiment(acc, experiment)
293312

294313
addToMapArray(acc.experimentsByCommit, baselineId, experiment)
@@ -340,10 +359,20 @@ const collectCliError = (
340359
}
341360
}
342361

362+
const collectRemoteExpShas = (remoteExpRefs: string): Set<string> => {
363+
const remoteExpShas = new Set<string>()
364+
for (const ref of trimAndSplit(remoteExpRefs)) {
365+
const [sha] = ref.split(/\s/)
366+
remoteExpShas.add(sha)
367+
}
368+
return remoteExpShas
369+
}
370+
343371
export const collectExperiments = (
344372
expShow: ExpShowOutput,
345373
gitLog: string,
346-
dvcLiveOnly: boolean
374+
dvcLiveOnly: boolean,
375+
remoteExpRefs: string
347376
): ExperimentsAccumulator => {
348377
const acc: ExperimentsAccumulator = {
349378
cliError: undefined,
@@ -358,6 +387,7 @@ export const collectExperiments = (
358387
}
359388

360389
const commitData = collectCommitsData(gitLog)
390+
const remoteExpShas = collectRemoteExpShas(remoteExpRefs)
361391

362392
for (const expState of expShow) {
363393
const baseline = collectExpState(acc, expState, commitData)
@@ -368,7 +398,7 @@ export const collectExperiments = (
368398
}
369399

370400
for (const expRange of experiments) {
371-
collectExpRange(acc, baseline, expRange)
401+
collectExpRange(acc, baseline, expRange, remoteExpShas)
372402
}
373403
}
374404

0 commit comments

Comments
 (0)