diff --git a/extension/package.json b/extension/package.json
index 4cb8a996ee..ad35a0f42f 100644
--- a/extension/package.json
+++ b/extension/package.json
@@ -1449,7 +1449,7 @@
},
{
"view": "dvc.views.welcome",
- "contents": "New to the extension?\n[Show Walkthrough](command:dvc.getStarted)\n\nThe extension is currently unable to initialize.\n[Show Setup](command:dvc.showExperimentsSetup)",
+ "contents": "New to the extension?\n[Show Walkthrough](command:dvc.getStarted)\n\nThe extension is currently unable to initialize.\n[Show Setup](command:dvc.showDvcSetup)",
"when": "true"
},
{
@@ -1491,7 +1491,7 @@
{
"id": "dvc.installDVC",
"title": "Install DVC",
- "description": "This extension requires DVC to be installed.\n\n[Show Setup](command:dvc.showExperimentsSetup)\n",
+ "description": "This extension requires DVC to be installed.\n\n[Show Setup](command:dvc.showDvcSetup)\n",
"media": {
"markdown": "resources/walkthrough/install-dvc.md"
},
diff --git a/extension/resources/walkthrough/install-dvc.md b/extension/resources/walkthrough/install-dvc.md
index 851417e200..58d78bddb5 100644
--- a/extension/resources/walkthrough/install-dvc.md
+++ b/extension/resources/walkthrough/install-dvc.md
@@ -16,7 +16,7 @@ DVC icon like this in the status bar:
If you see instead the crossed circle icon, click on the icon or follow the
-[Setup](command:dvc.showExperimentsSetup) wizard.
+[Setup](command:dvc.showDvcSetup) wizard.
> **Note**: The correct Python interpreter must be set for the current workspace
> when relying on the Python extension for auto environment activation.
diff --git a/extension/src/commands/external.ts b/extension/src/commands/external.ts
index f92b6ddae0..2f2dbd4ed9 100644
--- a/extension/src/commands/external.ts
+++ b/extension/src/commands/external.ts
@@ -97,6 +97,7 @@ export enum RegisteredCommands {
SETUP_SHOW = 'dvc.showSetup',
SETUP_SHOW_EXPERIMENTS = 'dvc.showExperimentsSetup',
+ SETUP_SHOW_DVC = 'dvc.showDvcSetup',
SELECT_FOCUSED_PROJECTS = 'dvc.selectFocusedProjects',
ADD_STUDIO_ACCESS_TOKEN = 'dvc.addStudioAccessToken',
diff --git a/extension/src/commands/util.ts b/extension/src/commands/util.ts
index 5fff197667..08c97dfa0a 100644
--- a/extension/src/commands/util.ts
+++ b/extension/src/commands/util.ts
@@ -7,5 +7,5 @@ export const showSetupOrExecuteCommand =
(setup: Setup, callback: (context: Context) => Promise) =>
(context: Context) =>
setup.shouldBeShown()
- ? commands.executeCommand(RegisteredCommands.SETUP_SHOW_EXPERIMENTS)
+ ? commands.executeCommand(RegisteredCommands.SETUP_SHOW_DVC)
: callback(context)
diff --git a/extension/src/setup/register.ts b/extension/src/setup/register.ts
index 7e65fbf393..f0e8468abc 100644
--- a/extension/src/setup/register.ts
+++ b/extension/src/setup/register.ts
@@ -44,6 +44,13 @@ const registerSetupShowCommands = (
}
)
+ internalCommands.registerExternalCommand(
+ RegisteredCommands.SETUP_SHOW_DVC,
+ async () => {
+ await setup.showSetup(SetupSection.DVC)
+ }
+ )
+
internalCommands.registerExternalCommand(
RegisteredCommands.SETUP_SHOW_STUDIO_CONNECT,
async () => {
diff --git a/extension/src/setup/webview/contract.ts b/extension/src/setup/webview/contract.ts
index 6866baaff5..a3aa5df5ea 100644
--- a/extension/src/setup/webview/contract.ts
+++ b/extension/src/setup/webview/contract.ts
@@ -14,12 +14,14 @@ export type SetupData = {
export enum SetupSection {
EXPERIMENTS = 'experiments',
- STUDIO = 'studio'
+ STUDIO = 'studio',
+ DVC = 'dvc'
}
export const DEFAULT_SECTION_COLLAPSED = {
[SetupSection.EXPERIMENTS]: false,
- [SetupSection.STUDIO]: false
+ [SetupSection.STUDIO]: false,
+ [SetupSection.DVC]: false
}
export type SectionCollapsed = typeof DEFAULT_SECTION_COLLAPSED
diff --git a/extension/src/telemetry/constants.ts b/extension/src/telemetry/constants.ts
index e365288daf..8b63d74e2d 100644
--- a/extension/src/telemetry/constants.ts
+++ b/extension/src/telemetry/constants.ts
@@ -275,6 +275,7 @@ export interface IEventNamePropertyMapping {
[EventName.SETUP_SHOW]: undefined
[EventName.SETUP_SHOW_EXPERIMENTS]: undefined
+ [EventName.SETUP_SHOW_DVC]: undefined
[EventName.SELECT_FOCUSED_PROJECTS]: undefined
[EventName.SETUP_SHOW_STUDIO_SETTINGS]: undefined
[EventName.SETUP_SHOW_STUDIO_CONNECT]: undefined
diff --git a/extension/src/test/suite/experiments/workspace.test.ts b/extension/src/test/suite/experiments/workspace.test.ts
index c8505896fe..01551b29d6 100644
--- a/extension/src/test/suite/experiments/workspace.test.ts
+++ b/extension/src/test/suite/experiments/workspace.test.ts
@@ -975,9 +975,7 @@ suite('Workspace Experiments Test Suite', () => {
await commands.executeCommand(RegisteredCommands.EXPERIMENT_SHOW)
- expect(executeCommandSpy).to.have.been.calledWithMatch(
- 'dvc.showExperimentsSetup'
- )
+ expect(executeCommandSpy).to.have.been.calledWithMatch('dvc.showDvcSetup')
})
it('should not show the experiments webview if the setup should be shown', async () => {
@@ -1001,7 +999,7 @@ suite('Workspace Experiments Test Suite', () => {
await commands.executeCommand(RegisteredCommands.EXPERIMENT_SHOW)
- expect(executeCommandSpy).not.to.be.calledWith('dvc.showExperimentsSetup')
+ expect(executeCommandSpy).not.to.be.calledWith('dvc.showDvcSetup')
})
it('should show the experiments webview if the setup should not be shown', async () => {
diff --git a/webview/src/setup/components/App.test.tsx b/webview/src/setup/components/App.test.tsx
index 38ea22bbc1..3784e95214 100644
--- a/webview/src/setup/components/App.test.tsx
+++ b/webview/src/setup/components/App.test.tsx
@@ -1,4 +1,4 @@
-import { fireEvent, render, screen } from '@testing-library/react'
+import { fireEvent, render, screen, within } from '@testing-library/react'
import {
MessageFromWebviewType,
MessageToWebviewType
@@ -22,6 +22,7 @@ const renderApp = ({
isPythonExtensionInstalled,
isStudioConnected,
needsGitInitialized,
+ needsGitCommit,
projectInitialized,
pythonBinPath,
sectionCollapsed,
@@ -38,6 +39,7 @@ const renderApp = ({
hasData,
isPythonExtensionInstalled,
isStudioConnected,
+ needsGitCommit,
needsGitInitialized,
projectInitialized,
pythonBinPath,
@@ -51,15 +53,15 @@ const renderApp = ({
}
describe('App', () => {
- describe('Experiments', () => {
- it('should send the initialized message on first render', () => {
- render()
- expect(mockPostMessage).toHaveBeenCalledWith({
- type: MessageFromWebviewType.INITIALIZED
- })
- expect(mockPostMessage).toHaveBeenCalledTimes(1)
+ it('should send the initialized message on first render', () => {
+ render()
+ expect(mockPostMessage).toHaveBeenCalledWith({
+ type: MessageFromWebviewType.INITIALIZED
})
+ expect(mockPostMessage).toHaveBeenCalledTimes(1)
+ })
+ describe('DVC', () => {
it('should show a screen saying that DVC is incompatible if the cli version is unexpected', () => {
renderApp({
canGitInitialize: false,
@@ -337,7 +339,7 @@ describe('App', () => {
).not.toBeInTheDocument()
})
- it('should send a message to initialize the project when clicking the Initialize Project buttons when the project is not initialized', () => {
+ it('should send a message to initialize the project when clicking the Initialize Project button when the project is not initialized', () => {
renderApp({
canGitInitialize: false,
cliCompatible: true,
@@ -360,7 +362,7 @@ describe('App', () => {
})
})
- it('should show a screen saying that the project contains no data if dvc is installed, the project is initialized but has no data', () => {
+ it('should open the experiments section when clicking the Open Experiments button when the project is initialized but has no data', () => {
renderApp({
canGitInitialize: false,
cliCompatible: true,
@@ -375,9 +377,98 @@ describe('App', () => {
shareLiveToStudio: false
})
- expect(
- screen.getByText('Your project contains no data')
- ).toBeInTheDocument()
+ mockPostMessage.mockClear()
+ const button = screen.getAllByText('Show Experiments')[0]
+ fireEvent.click(button)
+
+ expect(screen.getByText('Your project contains no data')).toBeVisible()
+ expect(screen.getByText('Setup Complete')).not.toBeVisible()
+ })
+
+ it('should enable the user to open the experiments webview when they have completed onboarding', () => {
+ renderApp({
+ canGitInitialize: false,
+ cliCompatible: true,
+ hasData: true,
+ isPythonExtensionInstalled: true,
+ isStudioConnected: true,
+ needsGitCommit: false,
+ needsGitInitialized: false,
+ projectInitialized: true,
+ pythonBinPath: 'python',
+ sectionCollapsed: undefined,
+ shareLiveToStudio: false
+ })
+ mockPostMessage.mockClear()
+ const button = screen.getAllByText('Show Experiments')[0]
+ fireEvent.click(button)
+ expect(mockPostMessage).toHaveBeenCalledTimes(1)
+ expect(mockPostMessage).toHaveBeenCalledWith({
+ type: MessageFromWebviewType.OPEN_EXPERIMENTS_WEBVIEW
+ })
+ })
+ })
+
+ describe('Experiments', () => {
+ it('should show a screen saying that dvc is not setup if the project is not initalized', () => {
+ renderApp({
+ canGitInitialize: false,
+ cliCompatible: true,
+ hasData: false,
+ isPythonExtensionInstalled: false,
+ isStudioConnected: false,
+ needsGitCommit: true,
+ needsGitInitialized: true,
+ projectInitialized: false,
+ pythonBinPath: undefined,
+ sectionCollapsed: undefined,
+ shareLiveToStudio: false
+ })
+
+ expect(screen.getByText('DVC is not setup')).toBeInTheDocument()
+ })
+
+ it('should open the dvc section when clicking the Setup DVC button on the dvc is not setup screen', () => {
+ renderApp({
+ canGitInitialize: false,
+ cliCompatible: true,
+ hasData: false,
+ isPythonExtensionInstalled: false,
+ isStudioConnected: false,
+ needsGitCommit: true,
+ needsGitInitialized: true,
+ projectInitialized: false,
+ pythonBinPath: undefined,
+ sectionCollapsed: undefined,
+ shareLiveToStudio: false
+ })
+
+ const experimentsText = screen.getByText('DVC is not setup')
+ expect(experimentsText).toBeInTheDocument()
+
+ mockPostMessage.mockClear()
+ const button = screen.getByText('Setup DVC')
+ fireEvent.click(button)
+ expect(screen.getByText('DVC is not initialized')).toBeVisible()
+ expect(experimentsText).not.toBeVisible()
+ })
+
+ it('should show a screen saying that dvc is not setup if the project is initalized but dvc is not installed', () => {
+ renderApp({
+ canGitInitialize: false,
+ cliCompatible: true,
+ hasData: false,
+ isPythonExtensionInstalled: false,
+ isStudioConnected: false,
+ needsGitCommit: false,
+ needsGitInitialized: undefined,
+ projectInitialized: false,
+ pythonBinPath: undefined,
+ sectionCollapsed: undefined,
+ shareLiveToStudio: false
+ })
+
+ expect(screen.getByText('DVC is not setup')).toBeInTheDocument()
})
it('should not show a screen saying that the project contains no data if dvc is installed, the project is initialized and has data', () => {
@@ -400,6 +491,62 @@ describe('App', () => {
).not.toBeInTheDocument()
})
+ it('should show a screen saying there needs to be a git commit if the project is initialized, dvc is installed, but has not git commit', () => {
+ renderApp({
+ canGitInitialize: false,
+ cliCompatible: true,
+ hasData: false,
+ isPythonExtensionInstalled: false,
+ isStudioConnected: false,
+ needsGitCommit: true,
+ needsGitInitialized: false,
+ projectInitialized: true,
+ pythonBinPath: undefined,
+ sectionCollapsed: undefined,
+ shareLiveToStudio: false
+ })
+
+ expect(screen.getByText('No Git commits detected')).toBeInTheDocument()
+ })
+
+ it('should show a loading screen if the project is loading in data', () => {
+ renderApp({
+ canGitInitialize: false,
+ cliCompatible: true,
+ hasData: undefined,
+ isPythonExtensionInstalled: false,
+ isStudioConnected: false,
+ needsGitCommit: false,
+ needsGitInitialized: undefined,
+ projectInitialized: true,
+ pythonBinPath: undefined,
+ sectionCollapsed: undefined,
+ shareLiveToStudio: false
+ })
+
+ expect(screen.getByText('Loading Project...')).toBeInTheDocument()
+ })
+
+ it('should show a screen saying that the project contains no data if dvc is installed, the project is initialized but has no data', () => {
+ renderApp({
+ canGitInitialize: false,
+ cliCompatible: true,
+ hasData: false,
+ isPythonExtensionInstalled: false,
+ isStudioConnected: false,
+ needsGitCommit: false,
+ needsGitInitialized: undefined,
+ projectInitialized: true,
+ pythonBinPath: undefined,
+ sectionCollapsed: undefined,
+ shareLiveToStudio: false
+ })
+
+ expect(
+ screen.getByText('Your project contains no data')
+ ).toBeInTheDocument()
+ })
+
it('should enable the user to open the experiments webview when they have completed onboarding', () => {
renderApp({
canGitInitialize: false,
@@ -415,7 +562,7 @@ describe('App', () => {
shareLiveToStudio: false
})
mockPostMessage.mockClear()
- const button = screen.getByText('Show Experiments')
+ const button = screen.getAllByText('Show Experiments')[1]
fireEvent.click(button)
expect(mockPostMessage).toHaveBeenCalledTimes(1)
expect(mockPostMessage).toHaveBeenCalledWith({
@@ -439,7 +586,9 @@ describe('App', () => {
sectionCollapsed: undefined,
shareLiveToStudio: false
})
- const buttons = await screen.findAllByRole('button')
+ const buttons = await within(
+ await screen.findByTestId('setup-studio-content')
+ ).findAllByRole('button')
expect(buttons).toHaveLength(3)
})
@@ -580,36 +729,66 @@ describe('App', () => {
}
const experimentsText = 'Your project contains no data'
const studioButtonText = 'Update Token'
+ const dvcText = 'Setup Complete'
+
+ it('should render the app with other sections collapsed if the DVC section is focused', () => {
+ renderApp({
+ ...testData,
+ sectionCollapsed: {
+ [SetupSection.EXPERIMENTS]: true,
+ [SetupSection.STUDIO]: true,
+ [SetupSection.DVC]: false
+ }
+ })
+ mockPostMessage.mockClear()
+ const dvc = screen.getByText('DVC')
+ expect(dvc).toBeVisible()
+ expect(screen.queryByText(dvcText)).toBeVisible()
+ const experiments = screen.getByText('Experiments')
+ expect(experiments).toBeVisible()
+ expect(screen.getByText(experimentsText)).not.toBeVisible()
+ const studio = screen.getByText('Studio')
+ expect(studio).toBeVisible()
+ expect(screen.queryByText(studioButtonText)).not.toBeVisible()
+ })
- it('should render the app with the Studio section collapsed if the Experiments section is focused', () => {
+ it('should render the app with other sections collapsed if the Experiments section is focused', () => {
renderApp({
...testData,
sectionCollapsed: {
[SetupSection.EXPERIMENTS]: false,
- [SetupSection.STUDIO]: true
+ [SetupSection.STUDIO]: true,
+ [SetupSection.DVC]: true
}
})
mockPostMessage.mockClear()
const studio = screen.getByText('Studio')
expect(studio).toBeVisible()
expect(screen.queryByText(studioButtonText)).not.toBeVisible()
+ const dvc = screen.getByText('DVC')
+ expect(dvc).toBeVisible()
+ expect(screen.queryByText(dvcText)).not.toBeVisible()
const experiments = screen.getByText('Experiments')
expect(experiments).toBeVisible()
expect(screen.getByText(experimentsText)).toBeVisible()
})
- it('should render the app with the Experiments section collapsed if the Studio section is focused', () => {
+ it('should render the app with other sections collapsed if the Studio section is focused', () => {
renderApp({
...testData,
sectionCollapsed: {
[SetupSection.EXPERIMENTS]: true,
- [SetupSection.STUDIO]: false
+ [SetupSection.STUDIO]: false,
+ [SetupSection.DVC]: true
}
})
mockPostMessage.mockClear()
const studio = screen.getByText('Studio')
expect(studio).toBeVisible()
expect(screen.queryByText(studioButtonText)).toBeVisible()
+ const dvc = screen.getByText('DVC')
+ expect(dvc).toBeVisible()
+ expect(screen.queryByText(dvcText)).not.toBeVisible()
const experiments = screen.getByText('Experiments')
expect(experiments).toBeVisible()
expect(screen.getByText(experimentsText)).not.toBeVisible()
diff --git a/webview/src/setup/components/App.tsx b/webview/src/setup/components/App.tsx
index 91d2020d44..460b7bf079 100644
--- a/webview/src/setup/components/App.tsx
+++ b/webview/src/setup/components/App.tsx
@@ -8,6 +8,7 @@ import {
MessageToWebview
} from 'dvc/src/webview/contract'
import React, { useCallback, useState } from 'react'
+import { Dvc } from './Dvc'
import { Experiments } from './Experiments'
import { Studio } from './Studio'
import { SetupContainer } from './SetupContainer'
@@ -84,20 +85,33 @@ export const App: React.FC = () => {
return (
<>
-
+
+
+ void
+}
+
+export const Dvc: React.FC = ({
+ canGitInitialize,
+ cliCompatible,
+ isPythonExtensionInstalled,
+ needsGitInitialized,
+ projectInitialized,
+ pythonBinPath,
+ setSectionCollapsed,
+ isExperimentsAvailable
+}) => {
+ if (cliCompatible === false) {
+ return
+ }
+
+ if (cliCompatible === undefined) {
+ return (
+
+ )
+ }
+
+ if (!projectInitialized) {
+ return (
+
+ )
+ }
+
+ return (
+
+