Skip to content

Commit cf544ae

Browse files
authored
Show a setup screen when project has no commits (#3253)
* add an extra step to our setup that shows if a user has initialized git and dvc but still has no commits * take off notification that appears after dvc initializes
1 parent 47661a1 commit cf544ae

File tree

11 files changed

+186
-23
lines changed

11 files changed

+186
-23
lines changed

extension/src/cli/git/constants.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@ export enum Command {
1919
LS_FILES = 'ls-files',
2020
PUSH = 'push',
2121
RESET = 'reset',
22-
REV_PARSE = 'rev-parse'
22+
REV_PARSE = 'rev-parse',
23+
REV_LIST = 'rev-list'
2324
}
2425

2526
export enum Flag {
27+
ALL = '--all',
2628
DIRECTORIES = '-d',
2729
DIRECTORY = '--directory',
2830
DOT = '.',

extension/src/cli/git/reader.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,19 @@ export class GitReader extends GitCli {
3131
return !!output
3232
}
3333

34+
public async hasNoCommits(cwd: string) {
35+
const options = getOptions(
36+
cwd,
37+
Command.REV_LIST,
38+
Flag.NUMBER,
39+
'1',
40+
Flag.ALL
41+
)
42+
const output = await this.executeProcess(options)
43+
44+
return !output
45+
}
46+
3447
public async getCommitMessages(cwd: string, sha: string): Promise<string> {
3548
const options = getOptions(
3649
cwd,

extension/src/setup/index.ts

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ import { EventName } from '../telemetry/constants'
4343
import { WorkspaceScale } from '../telemetry/collect'
4444
import { gitPath } from '../cli/git/constants'
4545
import { DOT_DVC } from '../cli/dvc/constants'
46-
import { Toast } from '../vscode/toast'
4746

4847
export type SetupWebviewWebview = BaseWebview<TSetupData>
4948

@@ -241,17 +240,21 @@ export class Setup
241240

242241
const canGitInitialize = await this.canGitInitialize(needsGitInitialized)
243242

243+
const needsGitCommit =
244+
needsGitInitialized || (await this.needsGitCommit(needsGitInitialized))
245+
244246
const pythonBinPath = await findPythonBinForInstall()
245247

246-
this.webviewMessages.sendWebviewMessage(
247-
this.cliCompatible,
248-
needsGitInitialized,
248+
this.webviewMessages.sendWebviewMessage({
249249
canGitInitialize,
250+
cliCompatible: this.cliCompatible,
251+
hasData,
252+
isPythonExtensionInstalled: isPythonExtensionInstalled(),
253+
needsGitCommit,
254+
needsGitInitialized,
250255
projectInitialized,
251-
isPythonExtensionInstalled(),
252-
getBinDisplayText(pythonBinPath),
253-
hasData
254-
)
256+
pythonBinPath: getBinDisplayText(pythonBinPath)
257+
})
255258
}
256259

257260
private createWebviewMessageHandler() {
@@ -311,9 +314,6 @@ export class Setup
311314
const root = getFirstWorkspaceFolder()
312315
if (root) {
313316
await this.dvcExecutor.init(root)
314-
void Toast.infoWithOptions(
315-
'Initialized DVC repository. You can now [commit the changes to git](command:workbench.view.scm).'
316-
)
317317
}
318318
}
319319

@@ -354,6 +354,18 @@ export class Setup
354354
}
355355
}
356356

357+
private needsGitCommit(needsGitInit: boolean) {
358+
if (needsGitInit) {
359+
return true
360+
}
361+
362+
const cwd = getFirstWorkspaceFolder()
363+
if (!cwd) {
364+
return true
365+
}
366+
return this.gitReader.hasNoCommits(cwd)
367+
}
368+
357369
private registerCommands(internalCommands: InternalCommands) {
358370
internalCommands.registerExternalCliCommand(
359371
RegisteredCliCommands.INIT,

extension/src/setup/webview/contract.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export type SetupData = {
44
hasData: boolean | undefined
55
isPythonExtensionInstalled: boolean
66
needsGitInitialized: boolean | undefined
7+
needsGitCommit: boolean
78
projectInitialized: boolean
89
pythonBinPath: string | undefined
910
}

extension/src/setup/webview/messages.ts

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,31 @@ export class WebviewMessages {
2727
this.initializeGit = initializeGit
2828
}
2929

30-
public sendWebviewMessage(
31-
cliCompatible: boolean | undefined,
32-
needsGitInitialized: boolean | undefined,
33-
canGitInitialize: boolean,
34-
projectInitialized: boolean,
35-
isPythonExtensionInstalled: boolean,
36-
pythonBinPath: string | undefined,
30+
public sendWebviewMessage({
31+
cliCompatible,
32+
needsGitInitialized,
33+
canGitInitialize,
34+
needsGitCommit,
35+
projectInitialized,
36+
isPythonExtensionInstalled,
37+
pythonBinPath,
38+
hasData
39+
}: {
40+
cliCompatible: boolean | undefined
41+
needsGitInitialized: boolean | undefined
42+
canGitInitialize: boolean
43+
projectInitialized: boolean
44+
needsGitCommit: boolean
45+
isPythonExtensionInstalled: boolean
46+
pythonBinPath: string | undefined
3747
hasData: boolean | undefined
38-
) {
48+
}) {
3949
void this.getWebview()?.show({
4050
canGitInitialize,
4151
cliCompatible,
4252
hasData,
4353
isPythonExtensionInstalled,
54+
needsGitCommit,
4455
needsGitInitialized,
4556
projectInitialized,
4657
pythonBinPath
@@ -57,6 +68,8 @@ export class WebviewMessages {
5768
return this.initializeDvc()
5869
case MessageFromWebviewType.INITIALIZE_GIT:
5970
return this.initializeGit()
71+
case MessageFromWebviewType.SHOW_SCM_PANEL:
72+
return commands.executeCommand('workbench.view.scm')
6073
case MessageFromWebviewType.SELECT_PYTHON_INTERPRETER:
6174
return this.selectPythonInterpreter()
6275
case MessageFromWebviewType.INSTALL_DVC:

extension/src/test/suite/setup/index.test.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,23 @@ suite('Setup Test Suite', () => {
138138
expect(mockExecuteCommand).to.be.calledWithExactly(setInterpreterCommand)
139139
}).timeout(WEBVIEW_TEST_TIMEOUT)
140140

141+
it('should handle a show source control panel message from the webview', async () => {
142+
const { messageSpy, setup, mockExecuteCommand } = buildSetup(disposable)
143+
const showScmPanelCommand = 'workbench.view.scm'
144+
145+
const webview = await setup.showWebview()
146+
await webview.isReady()
147+
148+
const mockMessageReceived = getMessageReceivedEmitter(webview)
149+
150+
messageSpy.resetHistory()
151+
mockMessageReceived.fire({
152+
type: MessageFromWebviewType.SHOW_SCM_PANEL
153+
})
154+
155+
expect(mockExecuteCommand).to.be.calledWithExactly(showScmPanelCommand)
156+
}).timeout(WEBVIEW_TEST_TIMEOUT)
157+
141158
it('should handle a setup the workspace message from the webview', async () => {
142159
const { messageSpy, setup, mockExecuteCommand } = buildSetup(disposable)
143160

@@ -222,6 +239,7 @@ suite('Setup Test Suite', () => {
222239
cliCompatible: undefined,
223240
hasData: false,
224241
isPythonExtensionInstalled: false,
242+
needsGitCommit: true,
225243
needsGitInitialized: true,
226244
projectInitialized: false,
227245
pythonBinPath: undefined
@@ -258,6 +276,7 @@ suite('Setup Test Suite', () => {
258276
cliCompatible: true,
259277
hasData: false,
260278
isPythonExtensionInstalled: false,
279+
needsGitCommit: true,
261280
needsGitInitialized: true,
262281
projectInitialized: false,
263282
pythonBinPath: undefined
@@ -269,6 +288,7 @@ suite('Setup Test Suite', () => {
269288
disposable,
270289
false,
271290
true,
291+
false,
272292
false
273293
)
274294

@@ -299,12 +319,56 @@ suite('Setup Test Suite', () => {
299319
cliCompatible: true,
300320
hasData: false,
301321
isPythonExtensionInstalled: false,
322+
needsGitCommit: false,
302323
needsGitInitialized: false,
303324
projectInitialized: false,
304325
pythonBinPath: undefined
305326
})
306327
}).timeout(WEBVIEW_TEST_TIMEOUT)
307328

329+
it('should send the expected message to the webview when there is no commits in the git repository', async () => {
330+
const { config, setup, messageSpy } = buildSetup(
331+
disposable,
332+
false,
333+
false,
334+
false,
335+
true
336+
)
337+
338+
await config.isReady()
339+
340+
setup.setCliCompatible(true)
341+
setup.setAvailable(true)
342+
await setup.setRoots()
343+
344+
messageSpy.restore()
345+
const mockSendMessage = stub(BaseWebview.prototype, 'show')
346+
347+
const messageSent = new Promise(resolve =>
348+
mockSendMessage.callsFake(data => {
349+
resolve(undefined)
350+
return Promise.resolve(!!data)
351+
})
352+
)
353+
354+
const webview = await setup.showWebview()
355+
await webview.isReady()
356+
357+
await messageSent
358+
359+
expect(mockSendMessage).to.be.calledOnce
360+
expect(mockSendMessage).to.be.calledWithExactly({
361+
canGitInitialize: false,
362+
cliCompatible: true,
363+
hasData: false,
364+
isPythonExtensionInstalled: false,
365+
needsGitCommit: true,
366+
needsGitInitialized: false,
367+
projectInitialized: true,
368+
pythonBinPath: undefined
369+
})
370+
}).timeout(WEBVIEW_TEST_TIMEOUT)
371+
308372
it('should setup the appropriate watchers', async () => {
309373
const { setup, mockRunSetup, onDidChangeWorkspace } =
310374
await buildSetupWithWatchers(disposable)

extension/src/test/suite/setup/util.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,16 @@ export const TEMP_DIR = join(dvcDemoPath, 'temp-empty-watcher-dir')
2828
const buildSetupDependencies = (
2929
disposer: Disposer,
3030
mockDvcRoot: string | undefined,
31-
mockGitRoot: string | undefined
31+
mockGitRoot: string | undefined,
32+
noGitCommits = true
3233
) => {
3334
const mockEmitter = disposer.track(new EventEmitter())
3435
const mockEvent = mockEmitter.event
3536

3637
const mockRoot = stub().resolves(mockDvcRoot)
3738
const mockVersion = stub().resolves(MIN_CLI_VERSION)
3839
const mockGetGitRepositoryRoot = stub().resolves(mockGitRoot)
40+
const mockHasNoCommits = stub().resolves(noGitCommits)
3941

4042
const mockInitializeDvc = fake()
4143
const mockInitializeGit = fake()
@@ -67,6 +69,7 @@ const buildSetupDependencies = (
6769
} as unknown as GitExecutor,
6870
mockGitReader: {
6971
getGitRepositoryRoot: mockGetGitRepositoryRoot,
72+
hasNoCommits: mockHasNoCommits,
7073
onDidCompleteProcess: mockEvent,
7174
onDidStartProcess: mockEvent
7275
} as unknown as GitReader,
@@ -87,7 +90,8 @@ export const buildSetup = (
8790
disposer: Disposer,
8891
hasData = false,
8992
noDvcRoot = true,
90-
noGitRoot = true
93+
noGitRoot = true,
94+
noGitCommits = true
9195
) => {
9296
const { config, messageSpy, resourceLocator } = buildDependencies(disposer)
9397

@@ -108,7 +112,7 @@ export const buildSetup = (
108112
mockInternalCommands,
109113
mockRunSetup,
110114
mockVersion
111-
} = buildSetupDependencies(disposer, mockDvcRoot, mockGitRoot)
115+
} = buildSetupDependencies(disposer, mockDvcRoot, mockGitRoot, noGitCommits)
112116
stub(FileSystem, 'findDvcRootPaths').resolves(
113117
[mockDvcRoot].filter(Boolean) as string[]
114118
)

extension/src/webview/contract.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export enum MessageFromWebviewType {
5252
CHECK_CLI_COMPATIBLE = 'check-cli-compatible',
5353
INITIALIZE_DVC = 'initialize-dvc',
5454
INITIALIZE_GIT = 'initialize-git',
55+
SHOW_SCM_PANEL = 'show-scm-panel',
5556
INSTALL_DVC = 'install-dvc',
5657
SETUP_WORKSPACE = 'setup-workspace'
5758
}
@@ -192,6 +193,7 @@ export type MessageFromWebview =
192193
| { type: MessageFromWebviewType.CHECK_CLI_COMPATIBLE }
193194
| { type: MessageFromWebviewType.INITIALIZE_DVC }
194195
| { type: MessageFromWebviewType.INITIALIZE_GIT }
196+
| { type: MessageFromWebviewType.SHOW_SCM_PANEL }
195197
| { type: MessageFromWebviewType.INSTALL_DVC }
196198
| { type: MessageFromWebviewType.SETUP_WORKSPACE }
197199

webview/src/setup/components/App.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { CliIncompatible } from './CliIncompatible'
88
import { CliUnavailable } from './CliUnavailable'
99
import { ProjectUninitialized } from './ProjectUninitialized'
1010
import { NoData } from './NoData'
11+
import { NeedsGitCommit } from './NeedsGitCommit'
1112
import { useVsCodeMessaging } from '../../shared/hooks/useVsCodeMessaging'
1213
import { sendMessage } from '../../shared/vscode'
1314
import { EmptyState } from '../../shared/components/emptyState/EmptyState'
@@ -24,6 +25,7 @@ export const App: React.FC = () => {
2425
const [canGitInitialize, setCanGitInitialized] = useState<
2526
boolean | undefined
2627
>(false)
28+
const [needsGitCommit, setNeedsGitCommit] = useState<boolean>(false)
2729
const [pythonBinPath, setPythonBinPath] = useState<string | undefined>(
2830
undefined
2931
)
@@ -39,6 +41,7 @@ export const App: React.FC = () => {
3941
setHasData(data.data.hasData)
4042
setIsPythonExtensionInstalled(data.data.isPythonExtensionInstalled)
4143
setNeedsGitInitialized(data.data.needsGitInitialized)
44+
setNeedsGitCommit(data.data.needsGitCommit)
4245
setProjectInitialized(data.data.projectInitialized)
4346
setPythonBinPath(data.data.pythonBinPath)
4447
},
@@ -48,6 +51,7 @@ export const App: React.FC = () => {
4851
setHasData,
4952
setIsPythonExtensionInstalled,
5053
setNeedsGitInitialized,
54+
setNeedsGitCommit,
5155
setProjectInitialized,
5256
setPythonBinPath
5357
]
@@ -70,6 +74,10 @@ export const App: React.FC = () => {
7074
})
7175
}
7276

77+
const showScmPanel = () => {
78+
sendMessage({ type: MessageFromWebviewType.SHOW_SCM_PANEL })
79+
}
80+
7381
const installDvc = () => {
7482
sendMessage({ type: MessageFromWebviewType.INSTALL_DVC })
7583
}
@@ -109,6 +117,10 @@ export const App: React.FC = () => {
109117
)
110118
}
111119

120+
if (needsGitCommit) {
121+
return <NeedsGitCommit showScmPanel={showScmPanel} />
122+
}
123+
112124
if (hasData === undefined) {
113125
return <EmptyState>Loading Project...</EmptyState>
114126
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import React from 'react'
2+
import { EmptyState } from '../../shared/components/emptyState/EmptyState'
3+
import { Button } from '../../shared/components/button/Button'
4+
5+
type NeedsGitCommitProps = { showScmPanel: () => void }
6+
7+
export const NeedsGitCommit: React.FC<NeedsGitCommitProps> = ({
8+
showScmPanel
9+
}) => (
10+
<EmptyState>
11+
<div>
12+
<h1>No Git commits detected</h1>
13+
<p>
14+
At least one commit is required to enable DVC&apos;s experiments and
15+
plots functionality.
16+
</p>
17+
<Button text="Create a Commit" onClick={showScmPanel} />
18+
</div>
19+
</EmptyState>
20+
)

0 commit comments

Comments
 (0)