Skip to content

Commit bd3cfa4

Browse files
authored
Add environment selection information to the DVC status bar item (#2969)
* add environment selection information to the DVC status bar item * replace variable with existing config enum * update env details on every change
1 parent 5f2359f commit bd3cfa4

File tree

3 files changed

+167
-26
lines changed

3 files changed

+167
-26
lines changed

extension/src/extension.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,13 +134,14 @@ export class Extension extends Disposable implements IExtension {
134134
)
135135

136136
this.status = this.dispose.track(
137-
new Status([
137+
new Status(
138+
this.config,
138139
this.dvcExecutor,
139140
this.dvcReader,
140141
this.dvcRunner,
141142
this.gitExecutor,
142143
this.gitReader
143-
])
144+
)
144145
)
145146

146147
this.experiments = this.dispose.track(

extension/src/status.ts

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,27 @@ import { Disposable } from './class/dispose'
33
import { ICli } from './cli'
44
import { RegisteredCommands } from './commands/external'
55
import { Title } from './vscode/title'
6+
import { Config } from './config'
67

78
export class Status extends Disposable {
89
private readonly statusBarItem: StatusBarItem = this.dispose.track(
910
window.createStatusBarItem()
1011
)
1112

13+
private readonly config: Config
14+
1215
private workers = 0
1316
private available = false
1417

15-
constructor(cliInteractors: ICli[]) {
18+
constructor(config: Config, ...cliInteractors: ICli[]) {
1619
super()
1720

1821
this.statusBarItem.text = this.getText(false)
1922
this.statusBarItem.show()
2023
this.statusBarItem.tooltip = 'DVC Extension Status'
2124

25+
this.config = config
26+
2227
for (const cli of cliInteractors) {
2328
this.dispose.track(
2429
cli.onDidStartProcess(() => {
@@ -34,34 +39,40 @@ export class Status extends Disposable {
3439
}
3540
}
3641

37-
public setAvailability = (available: boolean) => {
42+
public async setAvailability(available: boolean) {
3843
this.available = available
44+
await this.config.isReady()
3945
this.setState(!!this.workers)
4046
}
4147

42-
private getText = (isWorking: boolean) => {
48+
private getText(isWorking: boolean, envIndicator?: string) {
49+
const suffix = envIndicator ? `DVC ${envIndicator}` : 'DVC'
50+
4351
if (!this.available) {
44-
return '$(circle-slash) DVC'
52+
return `$(circle-slash) ${suffix}`
4553
}
4654
if (isWorking) {
47-
return '$(loading~spin) DVC'
55+
return `$(loading~spin) ${suffix}`
4856
}
49-
return '$(circle-large-outline) DVC'
57+
return `$(circle-large-outline) ${suffix}`
5058
}
5159

52-
private setState = (isWorking: boolean) => {
53-
this.statusBarItem.text = this.getText(isWorking)
60+
private setState(isWorking: boolean) {
61+
const { indicator, tooltip } = this.getEnvDetails()
62+
this.statusBarItem.text = this.getText(isWorking, indicator)
63+
this.statusBarItem.tooltip = tooltip
64+
5465
this.statusBarItem.color = this.getColor()
5566

5667
this.statusBarItem.command = this.getCommand()
5768
}
5869

59-
private addWorker = () => {
70+
private addWorker() {
6071
this.workers = this.workers + 1
6172
this.setState(true)
6273
}
6374

64-
private removeWorker = () => {
75+
private removeWorker() {
6576
this.workers = Math.max(this.workers - 1, 0)
6677
if (!this.workers) {
6778
this.setState(false)
@@ -84,4 +95,25 @@ export class Status extends Disposable {
8495
title: Title.SETUP_WORKSPACE
8596
}
8697
}
98+
99+
private getEnvDetails() {
100+
const dvcPath = this.config.getCliPath()
101+
if (dvcPath) {
102+
return { indicator: '(Global)', tooltip: dvcPath }
103+
}
104+
105+
if (this.config.isPythonExtensionUsed()) {
106+
return {
107+
indicator: '(Auto)',
108+
tooltip: 'Interpreter set by Python extension'
109+
}
110+
}
111+
112+
const pythonBinPath = this.config.getPythonBinPath()
113+
if (pythonBinPath) {
114+
return { indicator: '(Manual)', tooltip: pythonBinPath }
115+
}
116+
117+
return { indicator: '(Global)', tooltip: 'dvc' }
118+
}
87119
}

extension/src/test/suite/status.test.ts

Lines changed: 122 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { resolve } from 'path'
12
import { afterEach, beforeEach, describe, it, suite } from 'mocha'
23
import { expect } from 'chai'
34
import { fake, restore, stub } from 'sinon'
@@ -10,10 +11,9 @@ import { DvcCli } from '../../cli/dvc'
1011
import { Config } from '../../config'
1112
import { RegisteredCommands } from '../../commands/external'
1213
import { Title } from '../../vscode/title'
14+
import { ConfigKey } from '../../vscode/config'
1315

1416
suite('Status Test Suite', () => {
15-
const dvcPathOption = 'dvc.dvcPath'
16-
1717
const disposable = Disposable.fn()
1818

1919
beforeEach(() => {
@@ -22,16 +22,19 @@ suite('Status Test Suite', () => {
2222

2323
afterEach(async () => {
2424
disposable.dispose()
25-
await workspace.getConfiguration().update(dvcPathOption, undefined, false)
25+
await workspace
26+
.getConfiguration()
27+
.update(ConfigKey.DVC_PATH, undefined, false)
2628
return closeAllEditors()
2729
})
2830

2931
describe('Status', () => {
30-
const disabledText = '$(circle-slash) DVC'
31-
const loadingText = '$(loading~spin) DVC'
32-
const waitingText = '$(circle-large-outline) DVC'
32+
const preReadyDisabledText = '$(circle-slash) DVC'
33+
const disabledText = `${preReadyDisabledText} (Global)`
34+
const loadingText = '$(loading~spin) DVC (Global)'
35+
const waitingText = '$(circle-large-outline) DVC (Global)'
3336

34-
it('should show the correct status of the cli', () => {
37+
it('should show the correct status of the cli', async () => {
3538
const cwd = __dirname
3639
const processCompleted = disposable.track(new EventEmitter<CliResult>())
3740
const processStarted = disposable.track(new EventEmitter<CliStarted>())
@@ -50,7 +53,17 @@ suite('Status Test Suite', () => {
5053
'createStatusBarItem'
5154
).returns(mockStatusBarItem)
5255

53-
const status = disposable.track(new Status([cli]))
56+
const status = disposable.track(
57+
new Status(
58+
{
59+
getCliPath: () => undefined,
60+
getPythonBinPath: () => undefined,
61+
isPythonExtensionUsed: () => false,
62+
isReady: () => Promise.resolve()
63+
} as unknown as Config,
64+
cli
65+
)
66+
)
5467

5568
const firstFinishedCommand = {
5669
command: 'one is still running',
@@ -64,10 +77,10 @@ suite('Status Test Suite', () => {
6477
}
6578

6679
expect(mockCreateStatusBarItem).to.be.calledOnce
67-
expect(mockStatusBarItem.text).to.equal(disabledText)
80+
expect(mockStatusBarItem.text).to.equal(preReadyDisabledText)
6881
expect(mockStatusBarItem.command).to.equal(undefined)
6982

70-
status.setAvailability(true)
83+
await status.setAvailability(true)
7184

7285
expect(mockStatusBarItem.text).to.equal(waitingText)
7386
expect(mockStatusBarItem.command).to.equal(undefined)
@@ -102,7 +115,7 @@ suite('Status Test Suite', () => {
102115
expect(mockStatusBarItem.text).to.equal(waitingText)
103116
expect(mockStatusBarItem.command).to.equal(undefined)
104117

105-
status.setAvailability(false)
118+
await status.setAvailability(false)
106119

107120
expect(mockStatusBarItem.text).to.equal(disabledText)
108121
expect(mockStatusBarItem.command).to.deep.equal({
@@ -111,7 +124,7 @@ suite('Status Test Suite', () => {
111124
})
112125
})
113126

114-
it('should floor the number of workers at 0', () => {
127+
it('should floor the number of workers at 0', async () => {
115128
const processCompleted = disposable.track(new EventEmitter<CliResult>())
116129
const processStarted = disposable.track(new EventEmitter<CliStarted>())
117130

@@ -127,7 +140,17 @@ suite('Status Test Suite', () => {
127140
} as unknown as StatusBarItem
128141
stub(window, 'createStatusBarItem').returns(mockStatusBarItem)
129142

130-
const status = disposable.track(new Status([cli]))
143+
const status = disposable.track(
144+
new Status(
145+
{
146+
getCliPath: () => undefined,
147+
getPythonBinPath: () => undefined,
148+
isPythonExtensionUsed: () => false,
149+
isReady: () => Promise.resolve()
150+
} as unknown as Config,
151+
cli
152+
)
153+
)
131154

132155
const mockCliResult = {
133156
command: 'there is nothing currently running',
@@ -137,7 +160,7 @@ suite('Status Test Suite', () => {
137160
pid: 200
138161
}
139162

140-
status.setAvailability(true)
163+
await status.setAvailability(true)
141164

142165
processCompleted.fire(mockCliResult)
143166
processCompleted.fire(mockCliResult)
@@ -155,5 +178,90 @@ suite('Status Test Suite', () => {
155178
})
156179
expect(mockStatusBarItem.text).to.equal(loadingText)
157180
})
181+
182+
it('should return the correct values dependent on the current settings', async () => {
183+
const mockedIsPythonExtensionUsed = stub()
184+
const mockGetCliPath = stub()
185+
const mockGetPythonBinPath = stub()
186+
187+
const mockStatusBarItem = {
188+
dispose: fake(),
189+
show: fake(),
190+
text: ''
191+
} as unknown as StatusBarItem
192+
stub(window, 'createStatusBarItem').returns(mockStatusBarItem)
193+
194+
const status = disposable.track(
195+
new Status({
196+
getCliPath: mockGetCliPath,
197+
getPythonBinPath: mockGetPythonBinPath,
198+
isPythonExtensionUsed: mockedIsPythonExtensionUsed,
199+
isReady: () => Promise.resolve()
200+
} as unknown as Config)
201+
)
202+
203+
const setupMocks = (
204+
isPythonExtensionUsed: boolean,
205+
cliPath: string | undefined,
206+
pythonBinPath: string | undefined
207+
) => {
208+
mockedIsPythonExtensionUsed.resetBehavior()
209+
mockGetCliPath.resetBehavior()
210+
mockGetPythonBinPath.resetBehavior()
211+
212+
mockedIsPythonExtensionUsed.returns(isPythonExtensionUsed)
213+
mockGetCliPath.returns(cliPath)
214+
mockGetPythonBinPath.returns(pythonBinPath)
215+
}
216+
217+
setupMocks(false, undefined, undefined)
218+
219+
await status.setAvailability(true)
220+
221+
expect(mockStatusBarItem.text).to.equal(waitingText)
222+
expect(mockStatusBarItem.tooltip).to.equal('dvc')
223+
224+
const mockPythonPath = resolve('a', 'virtual', 'environment')
225+
226+
setupMocks(true, undefined, mockPythonPath)
227+
228+
await status.setAvailability(true)
229+
230+
expect(mockStatusBarItem.text).to.equal(
231+
'$(circle-large-outline) DVC (Auto)'
232+
)
233+
expect(mockStatusBarItem.tooltip).to.equal(
234+
'Interpreter set by Python extension'
235+
)
236+
237+
setupMocks(false, undefined, mockPythonPath)
238+
239+
await status.setAvailability(true)
240+
241+
expect(mockStatusBarItem.text).to.equal(
242+
'$(circle-large-outline) DVC (Manual)'
243+
)
244+
expect(mockStatusBarItem.tooltip).to.equal(mockPythonPath)
245+
246+
const mockDvcPath = resolve('path', 'to', 'dvc')
247+
248+
setupMocks(false, mockDvcPath, mockPythonPath)
249+
250+
await status.setAvailability(true)
251+
252+
expect(mockStatusBarItem.text).to.equal(
253+
'$(circle-large-outline) DVC (Global)'
254+
)
255+
expect(mockStatusBarItem.tooltip).to.equal(mockDvcPath)
256+
257+
setupMocks(false, 'dvc', mockPythonPath)
258+
259+
await status.setAvailability(true)
260+
261+
expect(mockStatusBarItem.text).to.equal(
262+
'$(circle-large-outline) DVC (Global)'
263+
)
264+
expect(mockStatusBarItem.tooltip).to.equal('dvc')
265+
})
158266
})
159267
})

0 commit comments

Comments
 (0)