Skip to content

Commit e125bec

Browse files
authored
exp rename: Add exp rename to experiments table (#4616)
* Add initial experiment rename capability * Adjust rename command to quickPick experiment name * Update package.json * Add version checking; Move rename to view commands * Add automated tests for rename * Update tests * Update rename tests; Update warning message
1 parent d7ce917 commit e125bec

File tree

14 files changed

+179
-15
lines changed

14 files changed

+179
-15
lines changed

extension/src/cli/dvc/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ export enum ExperimentSubCommand {
8989
GARBAGE_COLLECT = 'gc',
9090
PUSH = 'push',
9191
REMOVE = 'remove',
92+
RENAME = 'rename',
9293
RUN = 'run'
9394
}
9495

extension/src/cli/dvc/executor.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,4 +661,25 @@ describe('CliExecutor', () => {
661661
)
662662
})
663663
})
664+
665+
describe('rename', () => {
666+
it('should call createProcess with the correct parameters to rename the experiment', async () => {
667+
const cwd = __dirname
668+
669+
const stdout = 'Experiment renamed successfully.'
670+
671+
mockedCreateProcess.mockReturnValueOnce(getMockedProcess(stdout))
672+
673+
const output = await dvcExecutor.expRename(cwd, 'old-name', 'new-name')
674+
675+
expect(output).toStrictEqual(stdout)
676+
677+
expect(mockedCreateProcess).toHaveBeenCalledWith({
678+
args: ['exp', 'rename', 'old-name', 'new-name'],
679+
cwd,
680+
env: mockedEnv,
681+
executable: 'dvc'
682+
})
683+
})
684+
})
664685
})

extension/src/cli/dvc/executor.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export const autoRegisteredCommands = {
2121
EXP_QUEUE: 'expRunQueue',
2222
EXP_REMOVE: 'expRemove',
2323
EXP_REMOVE_QUEUE: 'expRemoveQueue',
24+
EXP_RENAME: 'expRename',
2425
INIT: 'init',
2526
IS_SCM_COMMAND_RUNNING: 'isScmCommandRunning',
2627
MOVE: 'move',
@@ -90,6 +91,15 @@ export class DvcExecutor extends DvcCli {
9091
return this.expRemove(cwd, ExperimentFlag.QUEUE)
9192
}
9293

94+
public expRename(cwd: string, experimentName: string, newName: string) {
95+
return this.executeExperimentProcess(
96+
cwd,
97+
ExperimentSubCommand.RENAME,
98+
experimentName,
99+
newName
100+
)
101+
}
102+
93103
public expRunQueue(cwd: string, ...args: Args) {
94104
return this.executeExperimentProcess(
95105
cwd,

extension/src/cli/dvc/version.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,25 @@ export const extractSemver = (stdout: string): ParsedSemver | undefined => {
1818
return { major: Number(major), minor: Number(minor), patch: Number(patch) }
1919
}
2020

21-
const checkCLIVersion = (currentSemVer: {
22-
major: number
23-
minor: number
24-
patch: number
25-
}): CliCompatible => {
21+
const checkCLIVersion = (
22+
currentSemVer: {
23+
major: number
24+
minor: number
25+
patch: number
26+
},
27+
minSemVer = MIN_CLI_VERSION
28+
): CliCompatible => {
2629
const {
2730
major: currentMajor,
2831
minor: currentMinor,
2932
patch: currentPatch
3033
} = currentSemVer
34+
minSemVer = minSemVer || MIN_CLI_VERSION
3135
const {
3236
major: minMajor,
3337
minor: minMinor,
3438
patch: minPatch
35-
} = extractSemver(MIN_CLI_VERSION) as ParsedSemver
39+
} = extractSemver(minSemVer) as ParsedSemver
3640

3741
const isBehindMinVersion =
3842
currentMajor < minMajor ||
@@ -47,7 +51,8 @@ const checkCLIVersion = (currentSemVer: {
4751
}
4852

4953
export const isVersionCompatible = (
50-
version: string | undefined
54+
version: string | undefined,
55+
requiredVersion = MIN_CLI_VERSION
5156
): CliCompatible => {
5257
if (!version) {
5358
return CliCompatible.NO_NOT_FOUND
@@ -64,6 +69,10 @@ export const isVersionCompatible = (
6469
return CliCompatible.NO_CANNOT_VERIFY
6570
}
6671

72+
if (requiredVersion) {
73+
return checkCLIVersion(currentSemVer, requiredVersion)
74+
}
75+
6776
return checkCLIVersion(currentSemVer)
6877
}
6978

extension/src/commands/external.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export enum RegisteredCliCommands {
1616
EXPERIMENT_VIEW_BRANCH = 'dvc.views.experiments.branchExperiment',
1717
EXPERIMENT_VIEW_PUSH = 'dvc.views.experiments.pushExperiment',
1818
EXPERIMENT_VIEW_REMOVE = 'dvc.views.experiments.removeExperiment',
19+
EXPERIMENT_VIEW_RENAME = 'dvc.views.experiments.renameExperiment',
1920
EXPERIMENT_VIEW_SHOW_LOGS = 'dvc.views.experiments.showLogs',
2021

2122
EXPERIMENT_VIEW_QUEUE = 'dvc.views.experiments.queueExperiment',

extension/src/experiments/commands/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ export const getBranchExperimentCommand =
1717
(cwd: string, name: string, input: string) =>
1818
experiments.runCommand(AvailableCommands.EXP_BRANCH, cwd, name, input)
1919

20+
export const getRenameExperimentCommand =
21+
(experiments: WorkspaceExperiments) =>
22+
(cwd: string, oldName: string, newName: string) =>
23+
experiments.runCommand(AvailableCommands.EXP_RENAME, cwd, oldName, newName)
24+
2025
const promptToAddStudioToken = async () => {
2126
const response = await Toast.askShowOrCloseOrNever(
2227
`Experiments can be automatically shared to [Studio](${STUDIO_URL}) by setting the studio.token in your config.`

extension/src/experiments/commands/register.ts

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { getBranchExperimentCommand, getPushExperimentCommand } from '.'
1+
import {
2+
getBranchExperimentCommand,
3+
getPushExperimentCommand,
4+
getRenameExperimentCommand
5+
} from '.'
26
import { WorkspaceExperiments } from '../workspace'
37
import { AvailableCommands, InternalCommands } from '../../commands/internal'
48
import {
@@ -9,6 +13,10 @@ import { Title } from '../../vscode/title'
913
import { Context, getDvcRootFromContext } from '../../vscode/context'
1014
import { Setup } from '../../setup'
1115
import { showSetupOrExecuteCommand } from '../../commands/util'
16+
import { CliCompatible, isVersionCompatible } from '../../cli/dvc/version'
17+
import { Toast } from '../../vscode/toast'
18+
import { Response } from '../../vscode/response'
19+
import { SetupSection } from '../../setup/webview/contract'
1220

1321
type ExperimentDetails = { dvcRoot: string; id: string }
1422

@@ -121,13 +129,46 @@ const registerExperimentNameCommands = (
121129

122130
const registerExperimentInputCommands = (
123131
experiments: WorkspaceExperiments,
124-
internalCommands: InternalCommands
132+
internalCommands: InternalCommands,
133+
setup: Setup
125134
): void => {
126135
internalCommands.registerExternalCliCommand(
127136
RegisteredCliCommands.EXPERIMENT_BRANCH,
128137
() => experiments.createExperimentBranch()
129138
)
130139

140+
internalCommands.registerExternalCliCommand(
141+
RegisteredCliCommands.EXPERIMENT_VIEW_RENAME,
142+
async ({ dvcRoot, id }: ExperimentDetails) => {
143+
const cliVersion = await setup.getCliVersion(dvcRoot)
144+
const REQUIRED_CLI_VERSION = '3.22.0'
145+
146+
if (
147+
!(
148+
isVersionCompatible(cliVersion, REQUIRED_CLI_VERSION) ===
149+
CliCompatible.YES
150+
)
151+
) {
152+
const response = await Toast.warnWithOptions(
153+
'To rename experiments, you need DVC version 3.22.0 or greater. Please update your DVC installation.',
154+
Response.SHOW_SETUP
155+
)
156+
if (response === Response.SHOW_SETUP) {
157+
return setup.showSetup(SetupSection.DVC)
158+
}
159+
return
160+
}
161+
162+
return experiments.getInputAndRun(
163+
getRenameExperimentCommand(experiments),
164+
Title.ENTER_NEW_EXPERIMENT_NAME,
165+
id,
166+
dvcRoot,
167+
id
168+
)
169+
}
170+
)
171+
131172
internalCommands.registerExternalCliCommand(
132173
RegisteredCliCommands.EXPERIMENT_VIEW_BRANCH,
133174
({ dvcRoot, id }: ExperimentDetails) =>
@@ -265,7 +306,7 @@ export const registerExperimentCommands = (
265306
) => {
266307
registerExperimentCwdCommands(experiments, internalCommands)
267308
registerExperimentNameCommands(experiments, internalCommands)
268-
registerExperimentInputCommands(experiments, internalCommands)
309+
registerExperimentInputCommands(experiments, internalCommands, setup)
269310
registerExperimentQuickPickCommands(experiments, internalCommands, setup)
270311
registerExperimentRunCommands(experiments, internalCommands, setup)
271312

extension/src/experiments/webview/messages.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,11 @@ export class WebviewMessages {
134134
RegisteredCliCommands.EXPERIMENT_VIEW_BRANCH,
135135
{ dvcRoot: this.dvcRoot, id: message.payload }
136136
)
137+
case MessageFromWebviewType.RENAME_EXPERIMENT:
138+
return commands.executeCommand(
139+
RegisteredCliCommands.EXPERIMENT_VIEW_RENAME,
140+
{ dvcRoot: this.dvcRoot, id: message.payload }
141+
)
137142
case MessageFromWebviewType.MODIFY_WORKSPACE_PARAMS_AND_QUEUE:
138143
return commands.executeCommand(
139144
RegisteredCliCommands.EXPERIMENT_VIEW_QUEUE,

extension/src/telemetry/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ export interface IEventNamePropertyMapping {
161161
[EventName.EXPERIMENTS_REFRESH]: undefined
162162
[EventName.EXPERIMENT_REMOVE]: undefined
163163
[EventName.EXPERIMENT_REMOVE_QUEUE]: undefined
164+
[EventName.EXPERIMENT_VIEW_RENAME]: undefined
164165
[EventName.EXPERIMENT_RESUME]: undefined
165166
[EventName.EXPERIMENT_RUN]: undefined
166167
[EventName.EXPERIMENT_RESET_AND_RUN]: undefined

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,40 @@ suite('Experiments Test Suite', () => {
561561
)
562562
}).timeout(WEBVIEW_TEST_TIMEOUT)
563563

564+
it('should be able to handle a message to rename an experiment', async () => {
565+
const { mockMessageReceived } =
566+
await stubWorkspaceGettersWebview(disposable)
567+
568+
const mockNewExperimentName = 'new-experiment-name'
569+
const inputEvent = getInputBoxEvent(mockNewExperimentName)
570+
571+
stub(Setup.prototype, 'getCliVersion').resolves('3.22.0')
572+
573+
const mockRenameExperiment = stub(DvcExecutor.prototype, 'expRename')
574+
575+
const mockRenameCalled = new Promise(resolve =>
576+
mockRenameExperiment.callsFake(() => {
577+
resolve(undefined)
578+
return Promise.resolve('Renamed experiments:')
579+
})
580+
)
581+
582+
const mockExperimentId = 'exp-e7a67'
583+
584+
mockMessageReceived.fire({
585+
payload: mockExperimentId,
586+
type: MessageFromWebviewType.RENAME_EXPERIMENT
587+
})
588+
589+
await Promise.all([inputEvent, mockRenameCalled])
590+
expect(mockRenameExperiment).to.be.calledOnce
591+
expect(mockRenameExperiment).to.be.calledWithExactly(
592+
dvcDemoPath,
593+
mockExperimentId,
594+
mockNewExperimentName
595+
)
596+
}).timeout(WEBVIEW_TEST_TIMEOUT)
597+
564598
it('should handle a message to show the logs of an experiment', async () => {
565599
const { experiments } = buildExperiments({ disposer: disposable })
566600
await experiments.isReady()

0 commit comments

Comments
 (0)