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
3 changes: 3 additions & 0 deletions extension/resources/walkthrough/install-dvc.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@ right environment:
<p align="center">
<img src="images/install-dvc-setup-wizard.png" alt="DVC Setup Wizard" />
</p>

> **Note**: The correct Python interpreter must be set for the current workspace
> when relying on the Python extension for auto environment activation.
6 changes: 5 additions & 1 deletion extension/src/extensions/python.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Event, Uri } from 'vscode'
import { commands, Event, Uri } from 'vscode'
import { executeProcess } from '../processExecution'
import { getExtensionAPI, isInstalled } from '../vscode/extensions'

Expand Down Expand Up @@ -54,3 +54,7 @@ export const getOnDidChangePythonExecutionDetails = async () => {
}

export const isPythonExtensionInstalled = () => isInstalled(PYTHON_EXTENSION_ID)

export const selectPythonInterpreter = () => {
commands.executeCommand('python.setInterpreter')
}
73 changes: 71 additions & 2 deletions extension/src/setup.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { resolve } from 'path'
import { extensions, Extension } from 'vscode'
import { join, resolve } from 'path'
import { extensions, Extension, commands } from 'vscode'
import { setup, setupWorkspace } from './setup'
import { flushPromises } from './test/util/jest'
import {
Expand All @@ -16,15 +16,43 @@ import {
import { getFirstWorkspaceFolder } from './vscode/workspaceFolders'
import { Toast } from './vscode/toast'
import { Response } from './vscode/response'
import { VscodePython } from './extensions/python'
import { executeProcess } from './processExecution'

jest.mock('vscode')
jest.mock('./vscode/config')
jest.mock('./vscode/resourcePicker')
jest.mock('./vscode/quickPick')
jest.mock('./vscode/toast')
jest.mock('./vscode/workspaceFolders')
jest.mock('./processExecution')

const mockedExtensions = jest.mocked(extensions)
const mockedCommands = jest.mocked(commands)
const mockedExecuteCommand = jest.fn()
mockedCommands.executeCommand = mockedExecuteCommand

const mockedExecuteProcess = jest.mocked(executeProcess)

const mockedGetExtension = jest.fn()
mockedExtensions.getExtension = mockedGetExtension

const mockedReady = jest.fn()

const mockedSettings = {
getExecutionDetails: () => ({
execCommand: [join('some', 'bin', 'path')]
})
}

const mockedVscodePythonAPI = {
ready: mockedReady,
settings: mockedSettings
} as unknown as VscodePython

const mockedVscodePython = {
activate: () => Promise.resolve(mockedVscodePythonAPI)
}

const mockedCanRunCli = jest.fn()
const mockedHasRoots = jest.fn()
Expand Down Expand Up @@ -256,8 +284,14 @@ describe('setup', () => {
mockedHasRoots.mockReturnValueOnce(true)
mockedCanRunCli.mockRejectedValueOnce(new Error('command not found: dvc'))
mockedWarnWithOptions.mockResolvedValueOnce(undefined)
mockedExecuteProcess.mockImplementation(({ executable }) =>
Promise.resolve(executable)
)
mockedReady.mockResolvedValue(true)
mockedGetExtension.mockReturnValue(mockedVscodePython)

await setup(extension)
await flushPromises()
expect(mockedSetRoots).toBeCalledTimes(1)
expect(mockedGetConfigValue).toBeCalledTimes(1)
expect(mockedWarnWithOptions).toBeCalledTimes(1)
Expand All @@ -270,13 +304,42 @@ describe('setup', () => {
mockedHasRoots.mockReturnValueOnce(true)
mockedCanRunCli.mockRejectedValueOnce(new Error('command not found: dvc'))
mockedWarnWithOptions.mockResolvedValueOnce(Response.SETUP_WORKSPACE)
mockedExecuteProcess.mockImplementation(({ executable }) =>
Promise.resolve(executable)
)
mockedReady.mockResolvedValue(true)
mockedGetExtension.mockReturnValue(mockedVscodePython)

await setup(extension)
await flushPromises()
expect(mockedSetRoots).toBeCalledTimes(1)
expect(mockedGetConfigValue).toBeCalledTimes(1)
expect(mockedWarnWithOptions).toBeCalledTimes(1)
expect(mockedSetupWorkspace).toBeCalledTimes(1)
expect(mockedExecuteCommand).not.toBeCalled()
expect(mockedSetUserConfigValue).not.toBeCalled()
expect(mockedResetMembers).toBeCalledTimes(1)
expect(mockedInitialize).not.toBeCalled()
})

it('should try to select the python interpreter if the workspace contains a DVC project, the cli cannot be found and the user decides to select the python interpreter', async () => {
mockedGetFirstWorkspaceFolder.mockReturnValueOnce(mockedCwd)
mockedHasRoots.mockReturnValueOnce(true)
mockedCanRunCli.mockRejectedValueOnce(new Error('command not found: dvc'))
mockedWarnWithOptions.mockResolvedValueOnce(Response.SELECT_INTERPRETER)
mockedExecuteProcess.mockImplementation(({ executable }) =>
Promise.resolve(executable)
)
mockedReady.mockResolvedValue(true)
mockedGetExtension.mockReturnValue(mockedVscodePython)

await setup(extension)
await flushPromises()
expect(mockedSetRoots).toBeCalledTimes(1)
expect(mockedGetConfigValue).toBeCalledTimes(1)
expect(mockedWarnWithOptions).toBeCalledTimes(1)
expect(mockedSetupWorkspace).toBeCalledTimes(0)
expect(mockedExecuteCommand).toBeCalledTimes(1)
expect(mockedSetUserConfigValue).not.toBeCalled()
expect(mockedResetMembers).toBeCalledTimes(1)
expect(mockedInitialize).not.toBeCalled()
Expand All @@ -287,13 +350,19 @@ describe('setup', () => {
mockedHasRoots.mockReturnValueOnce(true)
mockedCanRunCli.mockRejectedValueOnce(new Error('command not found: dvc'))
mockedWarnWithOptions.mockResolvedValueOnce(Response.NEVER)
mockedExecuteProcess.mockImplementation(({ executable }) =>
Promise.resolve(executable)
)
mockedReady.mockResolvedValue(true)
mockedGetExtension.mockReturnValue(mockedVscodePython)

await setup(extension)
await flushPromises()
expect(mockedSetRoots).toBeCalledTimes(1)
expect(mockedGetConfigValue).toBeCalledTimes(1)
expect(mockedWarnWithOptions).toBeCalledTimes(1)
expect(mockedSetupWorkspace).not.toBeCalled()
expect(mockedExecuteCommand).not.toBeCalled()
expect(mockedSetUserConfigValue).toBeCalledTimes(1)
expect(mockedResetMembers).toBeCalledTimes(1)
expect(mockedInitialize).not.toBeCalled()
Expand Down
49 changes: 40 additions & 9 deletions extension/src/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ import { getFirstWorkspaceFolder } from './vscode/workspaceFolders'
import { Response } from './vscode/response'
import { getSelectTitle, Title } from './vscode/title'
import { Toast } from './vscode/toast'
import { isPythonExtensionInstalled } from './extensions/python'
import {
getPythonBinPath,
isPythonExtensionInstalled,
selectPythonInterpreter
} from './extensions/python'

const setConfigPath = async (
option: ConfigKey,
Expand Down Expand Up @@ -159,22 +163,49 @@ export const setupWorkspace = async (): Promise<boolean> => {
return pickCliPath()
}

const getToastText = async (
isPythonExtensionInstalled: boolean
): Promise<string> => {
const text = 'An error was thrown when trying to access the CLI.'
if (!isPythonExtensionInstalled) {
return text
}
const binPath = await getPythonBinPath()

return (
text +
` For auto Python environment activation ensure the correct interpreter is set. Active Python interpreter: ${binPath}. `
)
}

const getToastOptions = (isPythonExtensionInstalled: boolean): Response[] => {
return isPythonExtensionInstalled
? [Response.SETUP_WORKSPACE, Response.SELECT_INTERPRETER, Response.NEVER]
: [Response.SETUP_WORKSPACE, Response.NEVER]
}

const warnUserCLIInaccessible = async (
extension: IExtension
): Promise<void> => {
if (getConfigValue<boolean>(ConfigKey.DO_NOT_SHOW_CLI_UNAVAILABLE)) {
return
}

const isMsPythonInstalled = isPythonExtensionInstalled()
const warningText = await getToastText(isMsPythonInstalled)

const response = await Toast.warnWithOptions(
'An error was thrown when trying to access the CLI.',
Response.SETUP_WORKSPACE,
Response.NEVER
warningText,
...getToastOptions(isMsPythonInstalled)
)
if (response === Response.SETUP_WORKSPACE) {
extension.setupWorkspace()
}
if (response === Response.NEVER) {
setUserConfigValue(ConfigKey.DO_NOT_SHOW_CLI_UNAVAILABLE, true)

switch (response) {
case Response.SELECT_INTERPRETER:
return selectPythonInterpreter()
case Response.SETUP_WORKSPACE:
return extension.setupWorkspace()
case Response.NEVER:
return setUserConfigValue(ConfigKey.DO_NOT_SHOW_CLI_UNAVAILABLE, true)
}
}

Expand Down
1 change: 1 addition & 0 deletions extension/src/vscode/response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export enum Response {
NEVER = "Don't Show Again",
NO = 'No',
SELECT_MOST_RECENT = 'Select Most Recent',
SELECT_INTERPRETER = 'Select Python Interpreter',
SETUP_WORKSPACE = 'Setup The Workspace',
SHOW = 'Show',
TURN_OFF = 'Turn Off',
Expand Down