Skip to content

Commit 651c2f6

Browse files
authored
Add multi-select versions of exp push (#3792)
1 parent cf1249e commit 651c2f6

File tree

10 files changed

+222
-56
lines changed

10 files changed

+222
-56
lines changed

extension/package.json

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,12 @@
511511
"category": "DVC",
512512
"icon": "$(repo-push)"
513513
},
514+
{
515+
"title": "Push",
516+
"command": "dvc.views.experimentsTree.pushExperiment",
517+
"category": "DVC",
518+
"icon": "$(repo-push)"
519+
},
514520
{
515521
"title": "Show Logs",
516522
"command": "dvc.views.experiments.showLogs",
@@ -863,6 +869,14 @@
863869
"command": "dvc.views.experiments.branchExperiment",
864870
"when": "false"
865871
},
872+
{
873+
"command": "dvc.views.experiments.pushExperiment",
874+
"when": "false"
875+
},
876+
{
877+
"command": "dvc.views.experimentsTree.pushExperiment",
878+
"when": "false"
879+
},
866880
{
867881
"command": "dvc.views.experiments.queueExperiment",
868882
"when": "false"
@@ -887,10 +901,6 @@
887901
"command": "dvc.views.experiments.resetAndRunCheckpointExperiment",
888902
"when": "false"
889903
},
890-
{
891-
"command": "dvc.views.experiments.pushExperiment",
892-
"when": "false"
893-
},
894904
{
895905
"command": "dvc.views.experiments.showLogs",
896906
"when": "false"
@@ -1152,7 +1162,7 @@
11521162
"when": "view == dvc.views.experimentsTree && dvc.commands.available && viewItem =~ /^(experiment|queued)$/ && !dvc.experiment.running"
11531163
},
11541164
{
1155-
"command": "dvc.views.experiments.pushExperiment",
1165+
"command": "dvc.views.experimentsTree.pushExperiment",
11561166
"group": "1_share@0",
11571167
"when": "view == dvc.views.experimentsTree && dvc.commands.available && viewItem == experiment && !dvc.experiment.running"
11581168
},

extension/src/experiments/commands/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ const convertUrlTextToLink = (stdout: string) => {
4444

4545
export const getPushExperimentCommand =
4646
(internalCommands: InternalCommands, setup: Setup) =>
47-
({ dvcRoot, id }: { dvcRoot: string; id: string }) => {
47+
({ dvcRoot, ids }: { dvcRoot: string; ids: string[] }) => {
4848
const studioAccessToken = setup.getStudioAccessToken()
4949
if (
5050
!(
@@ -58,14 +58,14 @@ export const getPushExperimentCommand =
5858
return Toast.showProgress('exp push', async progress => {
5959
progress.report({ increment: 0 })
6060

61-
progress.report({ increment: 25, message: `Pushing ${id}...` })
61+
progress.report({ increment: 25, message: `Pushing ${ids.join(' ')}...` })
6262

6363
const remainingProgress = 75
6464

6565
const stdout = await internalCommands.executeCommand(
6666
AvailableCommands.EXP_PUSH,
6767
dvcRoot,
68-
id
68+
...ids
6969
)
7070

7171
progress.report({

extension/src/experiments/model/collect.ts

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -328,10 +328,10 @@ export const collectExperiments = (
328328
return acc
329329
}
330330

331-
type DeletableExperimentAccumulator = { [dvcRoot: string]: Set<string> }
331+
type ExperimentTypesAccumulator = { [dvcRoot: string]: Set<string> }
332332

333333
const initializeAccumulatorRoot = (
334-
acc: DeletableExperimentAccumulator,
334+
acc: ExperimentTypesAccumulator,
335335
dvcRoot: string
336336
) => {
337337
if (!acc[dvcRoot]) {
@@ -340,12 +340,12 @@ const initializeAccumulatorRoot = (
340340
}
341341

342342
const collectExperimentItem = (
343-
acc: DeletableExperimentAccumulator,
344-
deletable: Set<string>,
343+
acc: ExperimentTypesAccumulator,
344+
types: Set<string>,
345345
experimentItem: ExperimentItem
346346
) => {
347347
const { dvcRoot, type, id, label } = experimentItem
348-
if (!deletable.has(type)) {
348+
if (!types.has(type)) {
349349
return
350350
}
351351
initializeAccumulatorRoot(acc, dvcRoot)
@@ -357,18 +357,17 @@ const collectExperimentItem = (
357357
acc[dvcRoot].add(id)
358358
}
359359

360-
export const collectDeletable = (
361-
experimentItems: (string | ExperimentItem)[]
362-
): DeletableExperimentAccumulator => {
363-
const deletable = new Set([ExperimentType.EXPERIMENT, ExperimentType.QUEUED])
364-
365-
const acc: DeletableExperimentAccumulator = {}
360+
export const collectExperimentType = (
361+
experimentItems: (string | ExperimentItem)[],
362+
types: Set<ExperimentType>
363+
): ExperimentTypesAccumulator => {
364+
const acc: ExperimentTypesAccumulator = {}
366365
for (const experimentItem of experimentItems) {
367366
if (typeof experimentItem === 'string') {
368367
continue
369368
}
370369

371-
collectExperimentItem(acc, deletable, experimentItem)
370+
collectExperimentItem(acc, types, experimentItem)
372371
}
373372

374373
return acc

extension/src/experiments/model/tree.ts

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
Uri
1010
} from 'vscode'
1111
import { ExperimentType } from '.'
12-
import { collectDeletable, ExperimentItem } from './collect'
12+
import { collectExperimentType, ExperimentItem } from './collect'
1313
import { MAX_SELECTED_EXPERIMENTS } from './status'
1414
import { getDataFromColumnPaths } from './util'
1515
import { WorkspaceExperiments } from '../workspace'
@@ -69,7 +69,7 @@ export class ExperimentsTree
6969
this.experiments = experiments
7070
this.resourceLocator = resourceLocator
7171

72-
this.registerWorkaroundCommand()
72+
this.registerWorkaroundCommands()
7373

7474
this.updateDescriptionOnChange()
7575
}
@@ -123,24 +123,44 @@ export class ExperimentsTree
123123
)
124124
}
125125

126-
private registerWorkaroundCommand() {
126+
private registerWorkaroundCommands() {
127+
const callCommandWithSelected = async (
128+
command:
129+
| RegisteredCliCommands.EXPERIMENT_VIEW_REMOVE
130+
| RegisteredCliCommands.EXPERIMENT_VIEW_PUSH,
131+
experimentItem: ExperimentItem | string,
132+
types: ExperimentType[]
133+
) => {
134+
const selected = [
135+
...this.getSelectedExperimentItems(),
136+
experimentItem
137+
] as (string | ExperimentItem)[]
138+
139+
const acc = collectExperimentType(selected, new Set(types))
140+
141+
for (const [dvcRoot, ids] of Object.entries(acc)) {
142+
await commands.executeCommand(command, { dvcRoot, ids: [...ids] })
143+
}
144+
}
145+
127146
commands.registerCommand(
128147
'dvc.views.experimentsTree.removeExperiment',
129-
async experimentItem => {
130-
const selected = [
131-
...this.getSelectedExperimentItems(),
132-
experimentItem
133-
] as (string | ExperimentItem)[]
134-
135-
const deletable = collectDeletable(selected)
136-
137-
for (const [dvcRoot, ids] of Object.entries(deletable)) {
138-
await commands.executeCommand(
139-
RegisteredCliCommands.EXPERIMENT_VIEW_REMOVE,
140-
{ dvcRoot, ids }
141-
)
142-
}
143-
}
148+
(experimentItem: ExperimentItem) =>
149+
callCommandWithSelected(
150+
RegisteredCliCommands.EXPERIMENT_VIEW_REMOVE,
151+
experimentItem,
152+
[ExperimentType.EXPERIMENT, ExperimentType.QUEUED]
153+
)
154+
)
155+
156+
commands.registerCommand(
157+
'dvc.views.experimentsTree.pushExperiment',
158+
(experimentItem: ExperimentItem) =>
159+
callCommandWithSelected(
160+
RegisteredCliCommands.EXPERIMENT_VIEW_PUSH,
161+
experimentItem,
162+
[ExperimentType.EXPERIMENT]
163+
)
144164
)
145165
}
146166

extension/src/experiments/webview/messages.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ export class WebviewMessages {
197197
case MessageFromWebviewType.PUSH_EXPERIMENT:
198198
return commands.executeCommand(
199199
RegisteredCliCommands.EXPERIMENT_VIEW_PUSH,
200-
{ dvcRoot: this.dvcRoot, id: message.payload }
200+
{ dvcRoot: this.dvcRoot, ids: message.payload }
201201
)
202202

203203
case MessageFromWebviewType.SHOW_EXPERIMENT_LOGS:

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -671,7 +671,7 @@ suite('Experiments Test Suite', () => {
671671
)
672672

673673
mockMessageReceived.fire({
674-
payload: mockExpId,
674+
payload: [mockExpId],
675675
type: MessageFromWebviewType.PUSH_EXPERIMENT
676676
})
677677

@@ -712,7 +712,7 @@ suite('Experiments Test Suite', () => {
712712
)
713713

714714
mockMessageReceived.fire({
715-
payload: mockExpId,
715+
payload: [mockExpId],
716716
type: MessageFromWebviewType.PUSH_EXPERIMENT
717717
})
718718

extension/src/test/suite/experiments/model/tree.test.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { ExperimentType } from '../../../../experiments/model'
1212
import { UNSELECTED } from '../../../../experiments/model/status'
1313
import {
1414
bypassProcessManagerDebounce,
15+
bypassProgressCloseDelay,
1516
getFirstArgOfLastCall,
1617
getMockNow,
1718
getTimeSafeDisposer,
@@ -345,6 +346,99 @@ suite('Experiments Tree Test Suite', () => {
345346
)
346347
})
347348

349+
it('should be able to push an experiment with dvc.views.experimentsTree.pushExperiment', async () => {
350+
bypassProgressCloseDelay()
351+
const mockExperimentId = 'exp-to-push'
352+
const mockExperiment = {
353+
dvcRoot: dvcDemoPath,
354+
id: mockExperimentId,
355+
type: ExperimentType.EXPERIMENT
356+
}
357+
358+
const mockExpPush = stub(DvcExecutor.prototype, 'expPush').resolves('')
359+
360+
stubPrivatePrototypeMethod(
361+
ExperimentsTree,
362+
'getSelectedExperimentItems'
363+
).returns([mockExperiment])
364+
365+
await commands.executeCommand(
366+
'dvc.views.experimentsTree.pushExperiment',
367+
mockExperiment
368+
)
369+
370+
expect(mockExpPush).to.be.calledWithExactly(dvcDemoPath, mockExperimentId)
371+
})
372+
373+
it('should be able to push the provided experiment with dvc.views.experimentsTree.pushExperiment (if no experiments are selected)', async () => {
374+
bypassProgressCloseDelay()
375+
const mockExperiment = 'exp-to-push'
376+
377+
const mockExpPush = stub(DvcExecutor.prototype, 'expPush').resolves('')
378+
379+
stubPrivatePrototypeMethod(
380+
ExperimentsTree,
381+
'getSelectedExperimentItems'
382+
).returns([])
383+
384+
await commands.executeCommand(
385+
'dvc.views.experimentsTree.pushExperiment',
386+
{
387+
dvcRoot: dvcDemoPath,
388+
id: mockExperiment,
389+
type: ExperimentType.EXPERIMENT
390+
}
391+
)
392+
393+
expect(mockExpPush).to.be.calledWithExactly(dvcDemoPath, mockExperiment)
394+
})
395+
396+
it('should be able to push multiple experiments with dvc.views.experimentsTree.pushExperiment', async () => {
397+
bypassProgressCloseDelay()
398+
const mockFirstExperimentId = 'first-exp-pushed'
399+
const mockSecondExperimentId = 'second-exp-pushed'
400+
const mockQueuedExperimentLabel = 'queued-excluded'
401+
402+
const mockExpPush = stub(DvcExecutor.prototype, 'expPush').resolves('')
403+
404+
stubPrivatePrototypeMethod(
405+
ExperimentsTree,
406+
'getSelectedExperimentItems'
407+
).returns([
408+
dvcDemoPath,
409+
{
410+
dvcRoot: dvcDemoPath,
411+
label: mockQueuedExperimentLabel,
412+
type: ExperimentType.QUEUED
413+
},
414+
{
415+
dvcRoot: dvcDemoPath,
416+
id: mockFirstExperimentId,
417+
type: ExperimentType.EXPERIMENT
418+
},
419+
{
420+
dvcRoot: dvcDemoPath,
421+
id: 'workspace-excluded',
422+
type: ExperimentType.WORKSPACE
423+
}
424+
])
425+
426+
await commands.executeCommand(
427+
'dvc.views.experimentsTree.pushExperiment',
428+
{
429+
dvcRoot: dvcDemoPath,
430+
id: mockSecondExperimentId,
431+
type: ExperimentType.EXPERIMENT
432+
}
433+
)
434+
435+
expect(mockExpPush).to.be.calledWithExactly(
436+
dvcDemoPath,
437+
mockFirstExperimentId,
438+
mockSecondExperimentId
439+
)
440+
})
441+
348442
it('should be able to apply an experiment to the workspace with dvc.views.experiments.applyExperiment', async () => {
349443
const { experiments } = buildExperiments(disposable)
350444

extension/src/webview/contract.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ export type MessageFromWebview =
155155
| { type: MessageFromWebviewType.SHOW_EXPERIMENT_LOGS; payload: string }
156156
| {
157157
type: MessageFromWebviewType.PUSH_EXPERIMENT
158-
payload: string
158+
payload: string[]
159159
}
160160
| {
161161
type: MessageFromWebviewType.REMOVE_COLUMN_SORT

0 commit comments

Comments
 (0)