Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Setup "dvc is unavailable" section text content #4098

Merged
merged 13 commits into from
Jun 14, 2023
35 changes: 34 additions & 1 deletion extension/src/extensions/python.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { extensions } from 'vscode'
import {
getPythonBinPath,
getOnDidChangePythonExecutionDetails,
VscodePython
VscodePython,
isActivePythonEnvGlobal
} from './python'
import { executeProcess } from '../process/execution'

Expand All @@ -17,6 +18,7 @@ mockedExtensions.getExtension = mockedGetExtension

const mockedReady = jest.fn()
const mockedOnDidChangeExecutionDetails = jest.fn()
const mockedGetActiveEnvironmentPath = jest.fn()
let mockedExecCommand: string[] | undefined

const mockedSettings = {
Expand All @@ -26,7 +28,16 @@ const mockedSettings = {
onDidChangeExecutionDetails: mockedOnDidChangeExecutionDetails
}

const mockedEnvironments = {
getActiveEnvironmentPath: mockedGetActiveEnvironmentPath,
known: [
{ id: '/usr/bin/python' },
{ environment: { type: 'VirtualEnvironment' }, id: '/.venv/bin/python' }
]
}

const mockedVscodePythonAPI = {
environments: mockedEnvironments,
ready: mockedReady,
settings: mockedSettings
} as unknown as VscodePython
Expand Down Expand Up @@ -63,6 +74,28 @@ describe('getPythonBinPath', () => {
})
})

describe('isActivePythonEnvGlobal', () => {
it('should return true if active env is global', async () => {
mockedGetActiveEnvironmentPath.mockReturnValueOnce({
id: '/usr/bin/python'
})

const result = await isActivePythonEnvGlobal()

expect(result).toStrictEqual(true)
})

it('should return false if active env is not global', async () => {
mockedGetActiveEnvironmentPath.mockReturnValueOnce({
id: '/.venv/bin/python'
})

const result = await isActivePythonEnvGlobal()

expect(result).toStrictEqual(false)
})
})

describe('getOnDidChangePythonExecutionDetails', () => {
it('should return the listener if the python ready promise rejects', async () => {
mockedReady.mockRejectedValueOnce(undefined)
Expand Down
23 changes: 22 additions & 1 deletion extension/src/extensions/python.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,24 @@ interface Settings {
}
}

type EnvironmentVariables = { readonly [key: string]: string | undefined }
type EnvironmentVariables = { readonly [key: string]: undefined }
type EnvironmentVariablesChangeEvent = {
readonly env: EnvironmentVariables
}

interface Environment {
id: string
environment?: {
type: string
}
}

export interface VscodePython {
ready: Thenable<void>
settings: Settings
environments: {
known: Environment[]
getActiveEnvironmentPath: () => { id: string }
onDidEnvironmentVariablesChange: Event<EnvironmentVariablesChangeEvent>
getEnvironmentVariables(): EnvironmentVariables
}
Expand Down Expand Up @@ -56,6 +65,18 @@ export const getPYTHONPATH = async (): Promise<string | undefined> => {
return api?.environments?.getEnvironmentVariables().PYTHONPATH
}

export const isActivePythonEnvGlobal = async (): Promise<
boolean | undefined
> => {
const api = await getPythonExtensionAPI()
if (!api?.environments) {
return
}
const envPath = api.environments.getActiveEnvironmentPath()
const activeEnv = api.environments.known.find(({ id }) => id === envPath.id)
return activeEnv && !activeEnv.environment
}

export const getOnDidChangePythonExecutionDetails = async () => {
const api = await getPythonExtensionAPI()
return api?.settings?.onDidChangeExecutionDetails
Expand Down
57 changes: 45 additions & 12 deletions extension/src/setup/autoInstall.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { getPythonExecutionDetails } from '../extensions/python'
import {
getPythonExecutionDetails,
isActivePythonEnvGlobal
} from '../extensions/python'
import { findPythonBin, getDefaultPython, installPackages } from '../python'
import { ConfigKey, getConfigValue } from '../vscode/config'
import { getFirstWorkspaceFolder } from '../vscode/workspaceFolders'
Expand All @@ -16,9 +19,12 @@ export const findPythonBinForInstall = async (): Promise<
)
}

const getProcessGlobalArgs = (isGlobal: boolean) => (isGlobal ? ['--user'] : [])

const showUpgradeProgress = (
root: string,
pythonBinPath: string
pythonBinPath: string,
isGlobalEnv: boolean
): Thenable<unknown> =>
Toast.showProgress('Upgrading DVC', async progress => {
progress.report({ increment: 0 })
Expand All @@ -28,7 +34,12 @@ const showUpgradeProgress = (
try {
await Toast.runCommandAndIncrementProgress(
async () => {
await installPackages(root, pythonBinPath, 'dvc')
await installPackages(
root,
pythonBinPath,
...getProcessGlobalArgs(isGlobalEnv),
'dvc'
)
return 'Upgraded successfully'
},
progress,
Expand All @@ -43,15 +54,21 @@ const showUpgradeProgress = (

const showInstallProgress = (
root: string,
pythonBinPath: string
pythonBinPath: string,
isGlobalEnv: boolean
): Thenable<unknown> =>
Toast.showProgress('Installing packages', async progress => {
progress.report({ increment: 0 })

try {
await Toast.runCommandAndIncrementProgress(
async () => {
await installPackages(root, pythonBinPath, 'dvclive')
await installPackages(
root,
pythonBinPath,
...getProcessGlobalArgs(isGlobalEnv),
'dvclive'
)
return 'DVCLive Installed'
},
progress,
Expand All @@ -64,7 +81,12 @@ const showInstallProgress = (
try {
await Toast.runCommandAndIncrementProgress(
async () => {
await installPackages(root, pythonBinPath, 'dvc')
await installPackages(
root,
pythonBinPath,
...getProcessGlobalArgs(isGlobalEnv),
'dvc'
)
return 'DVC Installed'
},
progress,
Expand All @@ -78,10 +100,17 @@ const showInstallProgress = (
})

const getArgsAndRunCommand = async (
command: (root: string, pythonBinPath: string) => Thenable<unknown>
isPythonExtensionUsed: boolean,
command: (
root: string,
pythonBinPath: string,
isGlobalEnv: boolean
) => Thenable<unknown>
): Promise<unknown> => {
const pythonBinPath = await findPythonBinForInstall()
const root = getFirstWorkspaceFolder()
const isPythonEnvGlobal =
isPythonExtensionUsed && (await isActivePythonEnvGlobal())

if (!root) {
return Toast.showError(
Expand All @@ -95,13 +124,17 @@ const getArgsAndRunCommand = async (
)
}

return command(root, pythonBinPath)
return command(root, pythonBinPath, !!isPythonEnvGlobal)
}

export const autoInstallDvc = (): Promise<unknown> => {
return getArgsAndRunCommand(showInstallProgress)
export const autoInstallDvc = (
isPythonExtensionUsed: boolean
): Promise<unknown> => {
return getArgsAndRunCommand(isPythonExtensionUsed, showInstallProgress)
}

export const autoUpgradeDvc = (): Promise<unknown> => {
return getArgsAndRunCommand(showUpgradeProgress)
export const autoUpgradeDvc = (
isPythonExtensionUsed: boolean
): Promise<unknown> => {
return getArgsAndRunCommand(isPythonExtensionUsed, showUpgradeProgress)
}
11 changes: 10 additions & 1 deletion extension/src/setup/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,11 @@ import { Title } from '../vscode/title'
import { getDVCAppDir } from '../util/appdirs'
import { getOptions } from '../cli/dvc/options'
import { isAboveLatestTestedVersion } from '../cli/dvc/version'
import { createPythonEnv, selectPythonInterpreter } from '../extensions/python'
import {
createPythonEnv,
isActivePythonEnvGlobal,
selectPythonInterpreter
} from '../extensions/python'

export class Setup
extends BaseRepository<TSetupData>
Expand Down Expand Up @@ -396,12 +400,16 @@ export class Setup

const pythonBinPath = await findPythonBinForInstall()

const isPythonEnvironmentGlobal =
isPythonExtensionUsed && (await isActivePythonEnvGlobal())

this.webviewMessages.sendWebviewMessage({
canGitInitialize,
cliCompatible: this.getCliCompatible(),
dvcCliDetails,
hasData,
isAboveLatestTestedVersion: isAboveLatestTestedVersion(this.cliVersion),
isPythonEnvironmentGlobal,
isPythonExtensionUsed,
isStudioConnected: this.studioIsConnected,
needsGitCommit,
Expand All @@ -420,6 +428,7 @@ export class Setup
() => this.getWebview(),
() => this.initializeGit(),
(offline: boolean) => this.updateStudioOffline(offline),
() => this.isPythonExtensionUsed(),
() => this.updatePythonEnvironment()
)
this.dispose.track(
Expand Down
1 change: 1 addition & 0 deletions extension/src/setup/webview/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type SetupData = {
cliCompatible: boolean | undefined
dvcCliDetails: DvcCliDetails | undefined
hasData: boolean | undefined
isPythonEnvironmentGlobal: boolean | undefined
isPythonExtensionUsed: boolean
isStudioConnected: boolean
needsGitCommit: boolean
Expand Down
17 changes: 12 additions & 5 deletions extension/src/setup/webview/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,31 @@ import {
import { BaseWebview } from '../../webview'
import { sendTelemetryEvent } from '../../telemetry'
import { EventName } from '../../telemetry/constants'
import { autoInstallDvc, autoUpgradeDvc } from '../autoInstall'
import {
RegisteredCliCommands,
RegisteredCommands
} from '../../commands/external'
import { openUrl } from '../../vscode/external'
import { autoInstallDvc, autoUpgradeDvc } from '../autoInstall'

export class WebviewMessages {
private readonly getWebview: () => BaseWebview<TSetupData> | undefined
private readonly initializeGit: () => void
private readonly updateStudioOffline: (offline: boolean) => Promise<void>
private readonly isPythonExtensionUsed: () => Promise<boolean>
private readonly updatePythonEnv: () => Promise<void>

constructor(
getWebview: () => BaseWebview<TSetupData> | undefined,
initializeGit: () => void,
updateStudioOffline: (shareLive: boolean) => Promise<void>,
isPythonExtensionUsed: () => Promise<boolean>,
updatePythonEnv: () => Promise<void>
) {
this.getWebview = getWebview
this.initializeGit = initializeGit
this.updateStudioOffline = updateStudioOffline
this.isPythonExtensionUsed = isPythonExtensionUsed
this.updatePythonEnv = updatePythonEnv
}

Expand Down Expand Up @@ -112,16 +115,20 @@ export class WebviewMessages {
return this.updatePythonEnv()
}

private upgradeDvc() {
private async upgradeDvc() {
sendTelemetryEvent(EventName.VIEWS_SETUP_UPGRADE_DVC, undefined, undefined)

return autoUpgradeDvc()
const isPythonExtensionUsed = await this.isPythonExtensionUsed()

return autoUpgradeDvc(isPythonExtensionUsed)
}

private installDvc() {
private async installDvc() {
sendTelemetryEvent(EventName.VIEWS_SETUP_INSTALL_DVC, undefined, undefined)

return autoInstallDvc()
const isPythonExtensionUsed = await this.isPythonExtensionUsed()

return autoInstallDvc(isPythonExtensionUsed)
}

private openStudio() {
Expand Down
Loading