Skip to content

Commit 37478d8

Browse files
authored
Simplify DVC CLI Location/Version Logic (#3784)
* Simplify incompatible version toasts * Configure extension status button to open setup on click
1 parent 5f0bd31 commit 37478d8

File tree

6 files changed

+92
-102
lines changed

6 files changed

+92
-102
lines changed

extension/src/cli/dvc/discovery.ts

Lines changed: 45 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
import {
2-
LATEST_TESTED_CLI_VERSION,
3-
MAX_CLI_VERSION,
4-
MIN_CLI_VERSION
5-
} from './contract'
1+
import { LATEST_TESTED_CLI_VERSION } from './contract'
62
import { CliCompatible, isVersionCompatible } from './version'
73
import { IExtensionSetup } from '../../interfaces'
84
import { Toast } from '../../vscode/toast'
@@ -12,90 +8,77 @@ import {
128
getConfigValue,
139
setUserConfigValue
1410
} from '../../vscode/config'
15-
import { getPythonBinPath } from '../../extensions/python'
1611
import { getFirstWorkspaceFolder } from '../../vscode/workspaceFolders'
1712
import { delay } from '../../util/time'
1813
import { SetupSection } from '../../setup/webview/contract'
1914

20-
export const warnUnableToVerifyVersion = () =>
21-
Toast.warnWithOptions(
15+
const warnWithSetupAction = async (
16+
setup: IExtensionSetup,
17+
warningText: string
18+
): Promise<void> => {
19+
const response = await Toast.warnWithOptions(warningText, Response.SHOW_SETUP)
20+
21+
if (response === Response.SHOW_SETUP) {
22+
return setup.showSetup(SetupSection.DVC)
23+
}
24+
}
25+
26+
const warnUnableToVerifyVersion = (setup: IExtensionSetup) => {
27+
void warnWithSetupAction(
28+
setup,
2229
'The extension cannot initialize as we were unable to verify the DVC CLI version.'
2330
)
31+
}
2432

25-
export const warnVersionIncompatible = (
26-
version: string,
27-
update: 'CLI' | 'extension'
28-
): void => {
29-
void Toast.warnWithOptions(
30-
`The extension cannot initialize because you are using version ${version} of the DVC CLI. The expected version is ${MIN_CLI_VERSION} <= DVC < ${MAX_CLI_VERSION}. Please upgrade to the most recent version of the ${update} and reload this window.`
33+
const warnVersionIncompatible = (setup: IExtensionSetup): void => {
34+
void warnWithSetupAction(
35+
setup,
36+
'The extension cannot initialize because the DVC CLI version is incompatible.'
3137
)
3238
}
3339

34-
export const warnAheadOfLatestTested = (): void => {
40+
const warnAheadOfLatestTested = (): void => {
3541
void Toast.warnWithOptions(
3642
`The located DVC CLI is at least a minor version ahead of the latest version the extension was tested with (${LATEST_TESTED_CLI_VERSION}). This could lead to unexpected behaviour. Please upgrade to the most recent version of the extension and reload this window.`
3743
)
3844
}
3945

4046
const warnUserCLIInaccessible = async (
41-
setup: IExtensionSetup,
42-
warningText: string
47+
setup: IExtensionSetup
4348
): Promise<void> => {
4449
if (getConfigValue<boolean>(ConfigKey.DO_NOT_SHOW_CLI_UNAVAILABLE)) {
4550
return
4651
}
4752

4853
const response = await Toast.warnWithOptions(
49-
warningText,
54+
'An error was thrown when trying to access the CLI.',
5055
Response.SHOW_SETUP,
5156
Response.NEVER
5257
)
5358

5459
switch (response) {
5560
case Response.SHOW_SETUP:
56-
return setup.showSetup(SetupSection.EXPERIMENTS)
61+
return setup.showSetup(SetupSection.DVC)
5762
case Response.NEVER:
5863
return setUserConfigValue(ConfigKey.DO_NOT_SHOW_CLI_UNAVAILABLE, true)
5964
}
6065
}
6166

62-
const warnUserCLIInaccessibleAnywhere = async (
63-
setup: IExtensionSetup,
64-
globalDvcVersion: string | undefined
65-
): Promise<void> => {
66-
const binPath = await getPythonBinPath()
67-
68-
return warnUserCLIInaccessible(
69-
setup,
70-
`The extension is unable to initialize. The CLI was not located using the interpreter provided by the Python extension. ${
71-
globalDvcVersion ? globalDvcVersion + ' is' : 'The CLI is also not'
72-
} installed globally. For auto Python environment activation, ensure the correct interpreter is set. Active Python interpreter: ${
73-
binPath || 'unset'
74-
}.`
75-
)
76-
}
77-
7867
const warnUser = (
7968
setup: IExtensionSetup,
80-
cliCompatible: CliCompatible,
81-
version: string | undefined
69+
cliCompatible: CliCompatible
8270
): void => {
8371
if (!setup.shouldWarnUserIfCLIUnavailable()) {
8472
return
8573
}
8674
switch (cliCompatible) {
87-
case CliCompatible.NO_BEHIND_MIN_VERSION:
88-
return warnVersionIncompatible(version as string, 'CLI')
75+
case CliCompatible.NO_INCOMPATIBLE:
76+
return warnVersionIncompatible(setup)
8977
case CliCompatible.NO_CANNOT_VERIFY:
90-
void warnUnableToVerifyVersion()
78+
void warnUnableToVerifyVersion(setup)
9179
return
92-
case CliCompatible.NO_MAJOR_VERSION_AHEAD:
93-
return warnVersionIncompatible(version as string, 'extension')
9480
case CliCompatible.NO_NOT_FOUND:
95-
void warnUserCLIInaccessible(
96-
setup,
97-
'An error was thrown when trying to access the CLI.'
98-
)
81+
void warnUserCLIInaccessible(setup)
9982
return
10083
case CliCompatible.YES_MINOR_VERSION_AHEAD_OF_TESTED:
10184
return warnAheadOfLatestTested()
@@ -128,7 +111,6 @@ const getVersionDetails = async (
128111
): Promise<
129112
CanRunCli & {
130113
cliCompatible: CliCompatible
131-
version: string | undefined
132114
}
133115
> => {
134116
const version = await setup.getCliVersion(cwd, tryGlobalCli)
@@ -140,11 +122,12 @@ const getVersionDetails = async (
140122
const processVersionDetails = (
141123
setup: IExtensionSetup,
142124
cliCompatible: CliCompatible,
143-
version: string | undefined,
144125
isAvailable: boolean,
145-
isCompatible: boolean | undefined
126+
isCompatible: boolean | undefined,
127+
version: string | undefined
146128
): CanRunCli => {
147-
warnUser(setup, cliCompatible, version)
129+
warnUser(setup, cliCompatible)
130+
148131
return {
149132
isAvailable,
150133
isCompatible,
@@ -159,21 +142,17 @@ const tryGlobalFallbackVersion = async (
159142
const tryGlobal = await getVersionDetails(setup, cwd, true)
160143
const { cliCompatible, isAvailable, isCompatible, version } = tryGlobal
161144

162-
if (setup.shouldWarnUserIfCLIUnavailable() && !isCompatible) {
163-
void warnUserCLIInaccessibleAnywhere(setup, version)
164-
}
165-
if (
166-
setup.shouldWarnUserIfCLIUnavailable() &&
167-
cliCompatible === CliCompatible.YES_MINOR_VERSION_AHEAD_OF_TESTED
168-
) {
169-
warnAheadOfLatestTested()
170-
}
171-
172145
if (isCompatible) {
173146
setup.unsetPythonBinPath()
174147
}
175148

176-
return { isAvailable, isCompatible, version }
149+
return processVersionDetails(
150+
setup,
151+
cliCompatible,
152+
isAvailable,
153+
isCompatible,
154+
version
155+
)
177156
}
178157

179158
const extensionCanAutoRunCli = async (
@@ -190,12 +169,13 @@ const extensionCanAutoRunCli = async (
190169
if (pythonCliCompatible === CliCompatible.NO_NOT_FOUND) {
191170
return tryGlobalFallbackVersion(setup, cwd)
192171
}
172+
193173
return processVersionDetails(
194174
setup,
195175
pythonCliCompatible,
196-
pythonVersion,
197176
pythonVersionIsAvailable,
198-
pythonVersionIsCompatible
177+
pythonVersionIsCompatible,
178+
pythonVersion
199179
)
200180
}
201181

@@ -213,9 +193,9 @@ export const extensionCanRunCli = async (
213193
return processVersionDetails(
214194
setup,
215195
cliCompatible,
216-
version,
217196
isAvailable,
218-
isCompatible
197+
isCompatible,
198+
version
219199
)
220200
}
221201

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -127,34 +127,34 @@ describe('isVersionCompatible', () => {
127127
)
128128
})
129129

130-
it('should return behind min version if the provided version is a patch version before the minimum expected version', () => {
130+
it('should return behind incompatible if the provided version is a patch version before the minimum expected version', () => {
131131
const isCompatible = isVersionCompatible(
132132
[minMajor, minMinor, minPatch - 1].join('.')
133133
)
134134

135-
expect(isCompatible).toStrictEqual(CliCompatible.NO_BEHIND_MIN_VERSION)
135+
expect(isCompatible).toStrictEqual(CliCompatible.NO_INCOMPATIBLE)
136136
})
137137

138-
it('should return behind min version if the provided minor version is before the minimum expected version', () => {
138+
it('should return behind incompatible if the provided minor version is before the minimum expected version', () => {
139139
const isCompatible = isVersionCompatible(
140140
[minMajor, minMinor - 1, minPatch + 100].join('.')
141141
)
142142

143-
expect(isCompatible).toStrictEqual(CliCompatible.NO_BEHIND_MIN_VERSION)
143+
expect(isCompatible).toStrictEqual(CliCompatible.NO_INCOMPATIBLE)
144144
})
145145

146-
it('should return behind min version if the provided major version is before the minimum expected version', () => {
146+
it('should return behind incompatible if the provided major version is before the minimum expected version', () => {
147147
const isCompatible = isVersionCompatible(
148148
[minMajor - 1, minMinor + 1000, minPatch + 100].join('.')
149149
)
150150

151-
expect(isCompatible).toStrictEqual(CliCompatible.NO_BEHIND_MIN_VERSION)
151+
expect(isCompatible).toStrictEqual(CliCompatible.NO_INCOMPATIBLE)
152152
})
153153

154-
it('should return major ahead if the provided major version is above the expected major version', () => {
154+
it('should return incompatible if the provided major version is above the expected major version', () => {
155155
const isCompatible = isVersionCompatible('3.0.0')
156156

157-
expect(isCompatible).toStrictEqual(CliCompatible.NO_MAJOR_VERSION_AHEAD)
157+
expect(isCompatible).toStrictEqual(CliCompatible.NO_INCOMPATIBLE)
158158
})
159159

160160
it('should return cannot verify if the provided version is malformed', () => {

extension/src/cli/dvc/version.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@ import {
55
} from './contract'
66

77
export enum CliCompatible {
8-
NO_BEHIND_MIN_VERSION = 'no-behind-min-version',
98
NO_CANNOT_VERIFY = 'no-cannot-verify',
10-
NO_MAJOR_VERSION_AHEAD = 'no-major-version-ahead',
9+
NO_INCOMPATIBLE = 'no-incompatible',
1110
NO_NOT_FOUND = 'no-not-found',
1211
YES_MINOR_VERSION_AHEAD_OF_TESTED = 'yes-minor-version-ahead-of-tested',
1312
YES = 'yes'
@@ -49,23 +48,20 @@ const checkCLIVersion = (currentSemVer: {
4948
minor: currentMinor,
5049
patch: currentPatch
5150
} = currentSemVer
52-
53-
if (currentMajor >= Number(MAX_CLI_VERSION)) {
54-
return CliCompatible.NO_MAJOR_VERSION_AHEAD
55-
}
56-
5751
const {
5852
major: minMajor,
5953
minor: minMinor,
6054
patch: minPatch
6155
} = extractSemver(MIN_CLI_VERSION) as ParsedSemver
6256

63-
if (
57+
const isAheadMaxVersion = currentMajor >= Number(MAX_CLI_VERSION)
58+
const isBehindMinVersion =
6459
currentMajor < minMajor ||
6560
currentMinor < minMinor ||
6661
(currentMinor === minMinor && currentPatch < Number(minPatch))
67-
) {
68-
return CliCompatible.NO_BEHIND_MIN_VERSION
62+
63+
if (isAheadMaxVersion || isBehindMinVersion) {
64+
return CliCompatible.NO_INCOMPATIBLE
6965
}
7066

7167
return cliIsCompatible(currentMajor, currentMinor)

extension/src/setup/runner.test.ts

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,7 @@ import { Toast } from '../vscode/toast'
1919
import { Response } from '../vscode/response'
2020
import { VscodePython } from '../extensions/python'
2121
import { executeProcess } from '../process/execution'
22-
import {
23-
LATEST_TESTED_CLI_VERSION,
24-
MAX_CLI_VERSION,
25-
MIN_CLI_VERSION
26-
} from '../cli/dvc/contract'
22+
import { LATEST_TESTED_CLI_VERSION, MIN_CLI_VERSION } from '../cli/dvc/contract'
2723
import { extractSemver, ParsedSemver } from '../cli/dvc/version'
2824
import { delay } from '../util/time'
2925
import { Title } from '../vscode/title'
@@ -455,6 +451,24 @@ describe('run', () => {
455451
expect(mockedInitialize).toHaveBeenCalledTimes(1)
456452
})
457453

454+
it('should send a specific message to the user if the extension is unable to verify the version', async () => {
455+
const unverifyableVersion = 'not a valid version'
456+
mockedGetFirstWorkspaceFolder.mockReturnValueOnce(mockedCwd)
457+
mockedShouldWarnUserIfCLIUnavailable.mockReturnValueOnce(true)
458+
mockedGetCliVersion.mockResolvedValueOnce(unverifyableVersion)
459+
460+
await run(setup)
461+
await flushPromises()
462+
expect(mockedWarnWithOptions).toHaveBeenCalledTimes(1)
463+
expect(mockedWarnWithOptions).toHaveBeenCalledWith(
464+
'The extension cannot initialize as we were unable to verify the DVC CLI version.',
465+
Response.SHOW_SETUP
466+
)
467+
expect(mockedGetCliVersion).toHaveBeenCalledTimes(1)
468+
expect(mockedResetMembers).toHaveBeenCalledTimes(1)
469+
expect(mockedInitialize).not.toHaveBeenCalled()
470+
})
471+
458472
it('should send a specific message to the user if the Python extension is being used, the CLI is not available in the virtual environment and the global CLI is not compatible', async () => {
459473
const belowMinVersion = '2.0.0'
460474
mockedGetFirstWorkspaceFolder.mockReturnValueOnce(mockedCwd)
@@ -472,9 +486,8 @@ describe('run', () => {
472486
await flushPromises()
473487
expect(mockedWarnWithOptions).toHaveBeenCalledTimes(1)
474488
expect(mockedWarnWithOptions).toHaveBeenCalledWith(
475-
`The extension is unable to initialize. The CLI was not located using the interpreter provided by the Python extension. ${belowMinVersion} is installed globally. For auto Python environment activation, ensure the correct interpreter is set. Active Python interpreter: ${mockedPythonPath}.`,
476-
Response.SHOW_SETUP,
477-
Response.NEVER
489+
'The extension cannot initialize because the DVC CLI version is incompatible.',
490+
Response.SHOW_SETUP
478491
)
479492
expect(mockedGetCliVersion).toHaveBeenCalledTimes(2)
480493
expect(mockedResetMembers).toHaveBeenCalledTimes(1)
@@ -539,7 +552,8 @@ describe('run', () => {
539552
await flushPromises()
540553
expect(mockedWarnWithOptions).toHaveBeenCalledTimes(1)
541554
expect(mockedWarnWithOptions).toHaveBeenCalledWith(
542-
`The extension cannot initialize because you are using version ${MajorAhead} of the DVC CLI. The expected version is ${MIN_CLI_VERSION} <= DVC < ${MAX_CLI_VERSION}. Please upgrade to the most recent version of the extension and reload this window.`
555+
'The extension cannot initialize because the DVC CLI version is incompatible.',
556+
'Setup'
543557
)
544558
expect(mockedGetCliVersion).toHaveBeenCalledTimes(1)
545559
expect(mockedResetMembers).toHaveBeenCalledTimes(1)
@@ -564,7 +578,7 @@ describe('run', () => {
564578
await flushPromises()
565579
expect(mockedWarnWithOptions).toHaveBeenCalledTimes(1)
566580
expect(mockedWarnWithOptions).toHaveBeenCalledWith(
567-
`The extension is unable to initialize. The CLI was not located using the interpreter provided by the Python extension. The CLI is also not installed globally. For auto Python environment activation, ensure the correct interpreter is set. Active Python interpreter: ${mockedPythonPath}.`,
581+
'An error was thrown when trying to access the CLI.',
568582
Response.SHOW_SETUP,
569583
Response.NEVER
570584
)

extension/src/status.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,13 @@ export class Status extends Disposable {
9393

9494
if (this.available) {
9595
return {
96-
command: RegisteredCommands.EXTENSION_SETUP_WORKSPACE,
97-
title: Title.SETUP_WORKSPACE
96+
command: RegisteredCommands.SETUP_SHOW,
97+
title: Title.SHOW_SETUP
9898
}
9999
}
100100

101101
return {
102-
command: RegisteredCommands.SETUP_SHOW,
102+
command: RegisteredCommands.SETUP_SHOW_DVC,
103103
title: Title.SHOW_SETUP
104104
}
105105
}

0 commit comments

Comments
 (0)