Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,12 @@
"category": "DVC",
"icon": "$(play)"
},
{
"title": "%command.views.experiments.shareExperimentAsBranch%",
"command": "dvc.views.experiments.shareExperimentAsBranch",
"category": "DVC",
"icon": "$(repo-push)"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

},
{
"title": "%command.views.experimentsTree.selectExperiments%",
"command": "dvc.views.experimentsTree.selectExperiments",
Expand Down Expand Up @@ -783,6 +789,10 @@
"command": "dvc.views.experiments.resetAndRunCheckpointExperiment",
"when": "false"
},
{
"command": "dvc.views.experiments.shareExperimentAsBranch",
"when": "false"
},
{
"command": "dvc.views.experimentsFilterByTree.removeAllFilters",
"when": "false"
Expand Down Expand Up @@ -1069,24 +1079,29 @@
"group": "inline@3",
"when": "view == dvc.views.experimentsTree && dvc.commands.available && viewItem =~ /^(experiment|queued)$/ && !dvc.experiment.running"
},
{
"command": "dvc.views.experiments.shareExperimentAsBranch",
"group": "1_share@1",
"when": "view == dvc.views.experimentsTree && dvc.commands.available && viewItem =~ /^(checkpoint|experiment)$/ && !dvc.experiment.running"
},
{
"command": "dvc.views.experiments.runExperiment",
"group": "1_modify@1",
"group": "2_modify@1",
"when": "view == dvc.views.experimentsTree && dvc.commands.available && viewItem =~ /^(workspace|branch|experiment|queued)$/ && !dvc.experiment.running && !dvc.experiment.checkpoints"
},
{
"command": "dvc.views.experiments.resetAndRunCheckpointExperiment",
"group": "1_modify@1",
"group": "2_modify@1",
"when": "view == dvc.views.experimentsTree && dvc.commands.available && viewItem =~ /^(workspace|branch|experiment|queued)$/ && !dvc.experiment.running && dvc.experiment.checkpoints"
},
{
"command": "dvc.views.experiments.resumeCheckpointExperiment",
"group": "1_modify@2",
"group": "2_modify@2",
"when": "view == dvc.views.experimentsTree && dvc.commands.available && viewItem =~ /^(workspace|branch|experiment|queued)$/ && !dvc.experiment.running && dvc.experiment.checkpoints"
},
{
"command": "dvc.views.experiments.queueExperiment",
"group": "1_modify@3",
"group": "2_modify@3",
"when": "view == dvc.views.experimentsTree && dvc.commands.available && viewItem =~ /^(workspace|branch|experiment|queued)$/ && !dvc.experiment.running"
},
{
Expand Down
1 change: 1 addition & 0 deletions extension/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"command.views.experiments.runExperiment": "Modify Param(s) and Run",
"command.views.experiments.resumeCheckpointExperiment": "Modify Param(s) and Resume",
"command.views.experiments.resetAndRunCheckpointExperiment": "Modify Param(s), Reset and Run",
"command.views.experiments.shareExperimentAsBranch": "Share as Branch",
"command.views.experimentsTree.selectExperiments": "Select Experiments to Display in Plots",
"command.views.plotsPathsTree.selectPlots": "Select Plots to Display",
"command.views.plotsPathsTree.refreshPlots": "Refresh Plots for Selected Experiments",
Expand Down
1 change: 1 addition & 0 deletions extension/src/commands/external.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export enum RegisteredCliCommands {
EXPERIMENT_VIEW_APPLY = 'dvc.views.experiments.applyExperiment',
EXPERIMENT_VIEW_BRANCH = 'dvc.views.experiments.branchExperiment',
EXPERIMENT_VIEW_REMOVE = 'dvc.views.experiments.removeExperiment',
EXPERIMENT_VIEW_SHARE_AS_BRANCH = 'dvc.views.experiments.shareExperimentAsBranch',

EXPERIMENT_VIEW_QUEUE = 'dvc.views.experiments.queueExperiment',
EXPERIMENT_VIEW_RESUME = 'dvc.views.experiments.resumeCheckpointExperiment',
Expand Down
43 changes: 41 additions & 2 deletions extension/src/experiments/commands/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import {
RegisteredCommands
} from '../../commands/external'
import { Title } from '../../vscode/title'
import { gitPushBranch } from '../../git'
import { Toast } from '../../vscode/toast'
import { Args } from '../../cli/constants'

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

Expand Down Expand Up @@ -153,7 +156,12 @@ const registerExperimentInputCommands = (
RegisteredCliCommands.EXPERIMENT_BRANCH,
() =>
experiments.getCwdExpNameAndInputThenRun(
AvailableCommands.EXPERIMENT_BRANCH,
(cwd, ...args: Args) =>
experiments.runCommand(
AvailableCommands.EXPERIMENT_BRANCH,
cwd,
...args
),
Title.ENTER_BRANCH_NAME
)
)
Expand All @@ -162,7 +170,38 @@ const registerExperimentInputCommands = (
RegisteredCliCommands.EXPERIMENT_VIEW_BRANCH,
({ dvcRoot, id }: ExperimentDetails) =>
experiments.getExpNameAndInputThenRun(
AvailableCommands.EXPERIMENT_BRANCH,
(name: string, input: string) =>
experiments.runCommand(
AvailableCommands.EXPERIMENT_BRANCH,
dvcRoot,
name,
input
),
Title.ENTER_BRANCH_NAME,
dvcRoot,
id
)
)

internalCommands.registerExternalCliCommand(
RegisteredCliCommands.EXPERIMENT_VIEW_SHARE_AS_BRANCH,
({ dvcRoot, id }: ExperimentDetails) =>
experiments.getExpNameAndInputThenRun(
async (name: string, input: string) => {
await experiments.runCommand(
AvailableCommands.EXPERIMENT_BRANCH,
dvcRoot,
name,
input
)
await experiments.runCommand(
AvailableCommands.EXPERIMENT_APPLY,
dvcRoot,
name
)
await experiments.runCommand(AvailableCommands.PUSH, dvcRoot)
return Toast.showOutput(gitPushBranch(dvcRoot, input))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[C] This makes me think that we should be showing all of the git commands that we run inside of our DVC output channel.

I will follow up and give Git its own reader/executor.

},
Title.ENTER_BRANCH_NAME,
dvcRoot,
id
Expand Down
6 changes: 6 additions & 0 deletions extension/src/experiments/webview/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@ export class WebviewMessages {
case MessageFromWebviewType.OPEN_PLOTS_WEBVIEW:
return commands.executeCommand(RegisteredCommands.PLOTS_SHOW)

case MessageFromWebviewType.SHARE_EXPERIMENT_AS_BRANCH:
return commands.executeCommand(
RegisteredCliCommands.EXPERIMENT_VIEW_SHARE_AS_BRANCH,
{ dvcRoot: this.dvcRoot, id: message.payload }
)

default:
Logger.error(`Unexpected message: ${JSON.stringify(message)}`)
}
Expand Down
12 changes: 8 additions & 4 deletions extension/src/experiments/workspace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { buildMockMemento } from '../test/util'
import { buildMockedEventEmitter } from '../test/util/jest'
import { OutputChannel } from '../vscode/outputChannel'
import { Title } from '../vscode/title'
import { Args } from '../cli/constants'

const mockedShowWebview = jest.fn()
const mockedDisposable = jest.mocked(Disposable)
Expand Down Expand Up @@ -185,7 +186,7 @@ describe('Experiments', () => {
})
})

describe('getExpNameAndInputThenRun', () => {
describe('getCwdExpNameAndInputThenRun', () => {
it('should call the correct function with the correct parameters if a project and experiment are picked and an input provided', async () => {
mockedQuickPickOne.mockResolvedValueOnce(mockedDvcRoot)
mockedPickCurrentExperiment.mockResolvedValueOnce({
Expand All @@ -195,7 +196,8 @@ describe('Experiments', () => {
mockedGetInput.mockResolvedValueOnce('abc123')

await workspaceExperiments.getCwdExpNameAndInputThenRun(
mockedCommandId,
(cwd: string, ...args: Args) =>
workspaceExperiments.runCommand(mockedCommandId, cwd, ...args),
'enter your password please' as Title
)

Expand All @@ -209,7 +211,8 @@ describe('Experiments', () => {
mockedQuickPickOne.mockResolvedValueOnce(undefined)

await workspaceExperiments.getCwdExpNameAndInputThenRun(
mockedCommandId,
(cwd: string, ...args: Args) =>
workspaceExperiments.runCommand(mockedCommandId, cwd, ...args),
'please name the branch' as Title
)

Expand All @@ -227,7 +230,8 @@ describe('Experiments', () => {
mockedGetInput.mockResolvedValueOnce(undefined)

await workspaceExperiments.getCwdExpNameAndInputThenRun(
mockedCommandId,
(cwd: string, ...args: Args) =>
workspaceExperiments.runCommand(mockedCommandId, cwd, ...args),
'please enter your bank account number and sort code' as Title
)

Expand Down
37 changes: 18 additions & 19 deletions extension/src/experiments/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,10 +222,10 @@ export class WorkspaceExperiments extends BaseWorkspaceWebviews<
}
}

public getCwdExpNameAndInputThenRun = async (
commandId: CommandId,
public async getCwdExpNameAndInputThenRun(
runCommand: (cwd: string, ...args: Args) => Promise<void>,
title: Title
) => {
) {
const cwd = await this.getFocusedOrOnlyOrPickProject()
if (!cwd) {
return
Expand All @@ -236,11 +236,11 @@ export class WorkspaceExperiments extends BaseWorkspaceWebviews<
if (!experiment) {
return
}
return this.getInputAndRun(commandId, cwd, title, experiment.name)
return this.getInputAndRun(runCommand, title, cwd, experiment.name)
}

public getExpNameAndInputThenRun(
commandId: CommandId,
runCommand: (...args: Args) => Promise<void>,
title: Title,
cwd: string,
id: string
Expand All @@ -251,20 +251,7 @@ export class WorkspaceExperiments extends BaseWorkspaceWebviews<
return
}

return this.getInputAndRun(commandId, cwd, title, name)
}

public async getInputAndRun(
commandId: CommandId,
cwd: string,
title: Title,
...args: Args
) {
const input = await getInput(title)
if (!input) {
return
}
return this.runCommand(commandId, cwd, ...args, input)
return this.getInputAndRun(runCommand, title, name)
}

public getExpNameThenRun(commandId: CommandId, cwd: string, id: string) {
Expand Down Expand Up @@ -360,6 +347,18 @@ export class WorkspaceExperiments extends BaseWorkspaceWebviews<
return this.runCommand(commandId, cwd, experiment.name)
}

private async getInputAndRun(
runCommand: (...args: Args) => Promise<void>,
title: Title,
...args: Args
) {
const input = await getInput(title)
if (!input) {
return
}
return runCommand(...args, input)
}

private pickCurrentExperiment(cwd: string) {
return this.getRepository(cwd).pickCurrentExperiment()
}
Expand Down
10 changes: 10 additions & 0 deletions extension/src/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,13 @@ export const gitStageAll = async (cwd: string) => {
executable: 'git'
})
}

export const gitPushBranch = (
cwd: string,
branchName: string
): Promise<string> =>
executeProcess({
args: ['push', '-u', 'origin', branchName],
cwd,
executable: 'git'
})
1 change: 1 addition & 0 deletions extension/src/telemetry/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ export interface IEventNamePropertyMapping {
[EventName.EXPERIMENT_VIEW_APPLY]: undefined
[EventName.EXPERIMENT_VIEW_BRANCH]: undefined
[EventName.EXPERIMENT_VIEW_REMOVE]: undefined
[EventName.EXPERIMENT_VIEW_SHARE_AS_BRANCH]: undefined
[EventName.EXPERIMENT_TOGGLE]: undefined

[EventName.EXPERIMENT_VIEW_QUEUE]: undefined
Expand Down
61 changes: 61 additions & 0 deletions extension/src/test/suite/experiments/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ import { Title } from '../../../vscode/title'
import { ExperimentFlag } from '../../../cli/constants'
import { WorkspaceExperiments } from '../../../experiments/workspace'
import { CliExecutor } from '../../../cli/executor'
import * as Git from '../../../git'
import { shortenForLabel } from '../../../util/string'

suite('Experiments Test Suite', () => {
const disposable = Disposable.fn()
Expand Down Expand Up @@ -475,6 +477,65 @@ suite('Experiments Test Suite', () => {
)
}).timeout(WEBVIEW_TEST_TIMEOUT)

it('should handle a message to share an experiment as a new branch', async () => {
const { experiments } = buildExperiments(disposable)
await experiments.isReady()

const testCheckpointId = 'd1343a87c6ee4a2e82d19525964d2fb2cb6756c9'
const testCheckpointLabel = shortenForLabel(testCheckpointId)
const mockBranch = 'it-is-a-branch-shared-to-the-remote'
const inputEvent = getInputBoxEvent(mockBranch)

const mockExperimentBranch = stub(
CliExecutor.prototype,
'experimentBranch'
).resolves(
`Git branch '${mockBranch}' has been created from experiment '${testCheckpointId}'.
To switch to the new branch run:
git checkout ${mockBranch}`
)
const mockExperimentApply = stub(
CliExecutor.prototype,
'experimentApply'
).resolves(
`Changes for experiment '${testCheckpointId}' have been applied to your current workspace.`
)
const mockPush = stub(CliExecutor.prototype, 'push').resolves(
'10 files updated.'
)
const mockGitPush = stub(Git, 'gitPushBranch')
const branchPushedToRemote = new Promise(resolve =>
mockGitPush.callsFake(() => {
resolve(undefined)
return Promise.resolve(`${mockBranch} pushed to remote`)
})
)

stub(WorkspaceExperiments.prototype, 'getRepository').returns(experiments)

const webview = await experiments.showWebview()
const mockMessageReceived = getMessageReceivedEmitter(webview)

mockMessageReceived.fire({
payload: testCheckpointId,
type: MessageFromWebviewType.SHARE_EXPERIMENT_AS_BRANCH
})

await inputEvent
await branchPushedToRemote
expect(mockExperimentBranch).to.be.calledWithExactly(
dvcDemoPath,
testCheckpointLabel,
mockBranch
)
expect(mockExperimentApply).to.be.calledWithExactly(
dvcDemoPath,
testCheckpointLabel
)
expect(mockPush).to.be.calledWithExactly(dvcDemoPath)
expect(mockGitPush).to.be.calledWithExactly(dvcDemoPath, mockBranch)
}).timeout(WEBVIEW_TEST_TIMEOUT)

it("should be able to handle a message to modify an experiment's params and queue an experiment", async () => {
const { experiments, cliExecutor } = buildExperiments(disposable)

Expand Down
5 changes: 5 additions & 0 deletions extension/src/webview/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export enum MessageFromWebviewType {
SELECT_EXPERIMENTS = 'select-experiments',
SELECT_COLUMNS = 'select-columns',
SELECT_PLOTS = 'select-plots',
SHARE_EXPERIMENT_AS_BRANCH = 'share-experiment-as-branch',
TOGGLE_METRIC = 'toggle-metric',
TOGGLE_PLOTS_SECTION = 'toggle-plots-section',
VARY_EXPERIMENT_PARAMS_AND_QUEUE = 'vary-experiment-params-and-queue',
Expand Down Expand Up @@ -146,6 +147,10 @@ export type MessageFromWebview =
| { type: MessageFromWebviewType.FOCUS_FILTERS_TREE }
| { type: MessageFromWebviewType.FOCUS_SORTS_TREE }
| { type: MessageFromWebviewType.OPEN_PLOTS_WEBVIEW }
| {
type: MessageFromWebviewType.SHARE_EXPERIMENT_AS_BRANCH
payload: string
}

export type MessageToWebview<T extends WebviewData> = {
type: MessageToWebviewType.SET_DATA
Expand Down
Loading