diff --git a/README.md b/README.md index 2b7c136934..b27ca30a2f 100644 --- a/README.md +++ b/README.md @@ -146,16 +146,16 @@ These are the VS Code [settings] available for the Extension: [settings]: https://code.visualstudio.com/docs/getstarted/settings -| **Option** | **Description** | -| -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `dvc.dvcPath` | Path or shell command to the DVC binary. Required unless Microsoft's [Python extension] is installed and the `dvc` package found in its environment. | -| `dvc.pythonPath` | Path to the desired Python interpreter to use with DVC. Should only be utilized when using a virtual environment without Microsoft's [Python extension]. | -| `dvc.experimentsTableHeadMaxHeight` | Maximum height of experiment table head rows. | -| `dvc.focusedProjects` | A subset of paths to the workspace's available DVC projects. Using this option will override project auto-discovery. | -| `dvc.doNotShowWalkthroughAfterInstall` | Do not prompt to show the Get Started page after installing. Useful for pre-configured development environments | -| `dvc.doNotRecommendAddStudioToken` | Do not prompt to add a [studio.token] to the global DVC config, which enables automatic sharing of experiments to [Studio]. | -| `dvc.doNotRecommendRedHatExtension` | Do not prompt to install the Red Hat YAML extension, which helps with DVC YAML schema validation (`dvc.yaml` and `.dvc` files). | -| `dvc.doNotShowCliUnavailable` | Do not warn when the workspace contains a DVC project but the DVC binary is unavailable. | +| **Option** | **Description** | +| ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `dvc.dvcPath` | Path or shell command to the DVC binary. Required unless Microsoft's [Python extension] is installed and the `dvc` package found in its environment. | +| `dvc.pythonPath` | Path to the desired Python interpreter to use with DVC. Should only be utilized when using a virtual environment without Microsoft's [Python extension]. | +| `dvc.experimentsTableHeadMaxHeight` | Maximum height of experiment table head rows. | +| `dvc.focusedProjects` | A subset of paths to the workspace's available DVC projects. Using this option will override project auto-discovery. | +| `dvc.doNotShowSetupAfterInstall` | Do not prompt to show the setup page after installing. Useful for pre-configured development environments | +| `dvc.doNotRecommendAddStudioToken` | Do not prompt to add a [studio.token] to the global DVC config, which enables automatic sharing of experiments to [Studio]. | +| `dvc.doNotRecommendRedHatExtension` | Do not prompt to install the Red Hat YAML extension, which helps with DVC YAML schema validation (`dvc.yaml` and `.dvc` files). | +| `dvc.doNotShowCliUnavailable` | Do not warn when the workspace contains a DVC project but the DVC binary is unavailable. | > **Note** that the `Setup The Workspace` command helps you set up the basic > ones at the [Workspace level] (saved to `.vscode/setting.json`). diff --git a/extension/package.json b/extension/package.json index 87fa760224..2073c1b1d7 100644 --- a/extension/package.json +++ b/extension/package.json @@ -615,8 +615,8 @@ "type": "boolean", "default": null }, - "dvc.doNotShowWalkthroughAfterInstall": { - "description": "Do not prompt to show the Get Started page after installing. Useful for pre-configured development environments.", + "dvc.doNotShowSetupAfterInstall": { + "description": "Do not prompt to show the setup page after installing. Useful for pre-configured development environments.", "type": "boolean", "default": null }, @@ -1512,32 +1512,6 @@ "title": "Get Started", "description": "Your first steps to build better models faster with experiment tracking and dataset versioning", "steps": [ - { - "id": "dvc.installDVC", - "title": "Install DVC", - "description": "This extension requires DVC to be installed.\n\n[Show Setup](command:dvc.showDvcSetup)\n", - "media": { - "markdown": "resources/walkthrough/install-dvc.md" - }, - "completionEvents": [ - "onLink:https://dvc.org", - "onLink:https://dvc.org/doc/install", - "onCommand:dvc.setupWorkspace" - ] - }, - { - "id": "dvc.setupProject", - "title": "Setup a DVC Project", - "description": "Try an example DVC project or set one up quickly, and start tracking datasets, models, metrics, and plots\n\n[Example Project](https://github.com/iterative/example-get-started)\n", - "media": { - "markdown": "resources/walkthrough/setup-project.md" - }, - "completionEvents": [ - "onLink:https://github.com/iterative/example-get-started", - "onLink:https://github.com/iterative/vscode-dvc-demo", - "onLink:https://dvc.org/doc/dvclive" - ] - }, { "id": "dvc.showCommands", "title": "Available Commands", diff --git a/extension/resources/walkthrough/command-palette.md b/extension/resources/walkthrough/command-palette.md index f739f9ecfb..2a00b53e1f 100644 --- a/extension/resources/walkthrough/command-palette.md +++ b/extension/resources/walkthrough/command-palette.md @@ -1,3 +1,7 @@ +> ℹ️ The extension's features cannot be accessed until DVC is installed and a +> DVC project is available in the workspace. Please refer to the +> [setup page](command:dvc.dvc.showDvcSetup) if you have not setup DVC yet. + # Command Palette This extension makes extensive use of the diff --git a/extension/resources/walkthrough/experiments-table.md b/extension/resources/walkthrough/experiments-table.md index b66a1f7553..c3114543f5 100644 --- a/extension/resources/walkthrough/experiments-table.md +++ b/extension/resources/walkthrough/experiments-table.md @@ -1,9 +1,5 @@ # Experiments Table -> ❗ DVC should be installed and DVC project available in the workspace for this -> and the next steps to work properly. Please refer to first two steps of this -> guide. - As you change code, parameters, data and run new experiments this table will keep all the details about your iterations. It can be manipulated and configured using the [DVC side panel](command:workbench.view.extension.dvc-views). This is diff --git a/extension/resources/walkthrough/images/install-dvc-status-bar-detected.png b/extension/resources/walkthrough/images/install-dvc-status-bar-detected.png deleted file mode 100644 index 41982b3ee8..0000000000 Binary files a/extension/resources/walkthrough/images/install-dvc-status-bar-detected.png and /dev/null differ diff --git a/extension/resources/walkthrough/install-dvc.md b/extension/resources/walkthrough/install-dvc.md deleted file mode 100644 index 58d78bddb5..0000000000 --- a/extension/resources/walkthrough/install-dvc.md +++ /dev/null @@ -1,22 +0,0 @@ -# Install DVC - -There are a few [options](https://dvc.org/doc/install) for installing `DVC`. -This extension supports all installation types. - -To verify the installation run `dvc -h` in a -[Terminal](command:workbench.action.terminal.new). - -If DVC is installed as a global binary or in a Python virtual environment, the -extension would attempt to detect it automatically. If successful, you'll see a -DVC icon like this in the status bar: - -

- DVC icon in the status bar -

- -If you see instead the crossed circle icon, click on the icon or follow the -[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/resources/walkthrough/setup-project.md b/extension/resources/walkthrough/setup-project.md deleted file mode 100644 index 73f6a0572e..0000000000 --- a/extension/resources/walkthrough/setup-project.md +++ /dev/null @@ -1,60 +0,0 @@ -# Setup a DVC Project - -> Skip this step if you already have a DVC project with metrics, plots, and -> params. - -💡 Check out the -[DVC Get Started](https://github.com/iterative/example-get-started) or -[Extension Demo](https://github.com/iterative/vscode-dvc-demo) projects to -quickly try the extension. - -To quickly setup a new DVC project run -[`dvc exp init -i`](https://dvc.org/doc/command-reference/exp/init#example-interactive-mode) -in a [Terminal](command:workbench.action.terminal.new). It will generate a -config file `dvc.yaml` that describes the project, and will look something like -this: - -```yaml -train: - cmd: python src/train.py - deps: - - data/features - - src/train.py - params: - - epochs - outs: - - models/predict.h5 - metrics: - - metrics.json: - cache: false -``` - -💡 Names, values in this file are project dependent and can be customized. - -DVC and this extension read experiments data from these files (e.g -`metrics.json`, `params.yaml`, etc). Your code needs to write and read to them -(the example below is Python, but it can be done in any language): - -```python -with open('metrics.json', 'w') as fd: - json.dump({'avg_prec': avg_prec, 'roc_auc': roc_auc}, fd) -``` - -To DVC-ify an existing machine learning project use the -[`DVCLive`](https://dvc.org/doc/dvclive) Python library, which can read and -write a lot of different common metrics and plots: - -```python -from dvclive import Live - -live = Live("evaluation") - -live.log("avg_prec", metrics.average_precision_score(labels, predictions)) -live.log("roc_auc", metrics.roc_auc_score(labels, predictions)) -``` - -💡 View -[Instant Experiment Tracking: Just Add DVC!](https://iterative.ai/blog/exp-tracking-dvc-python) -for a quick-start guide on migrating an existing project. Use -[Setup](command:dvc.showExperimentsSetup) to be guided through the onboarding -process. diff --git a/extension/src/extension.ts b/extension/src/extension.ts index cb4fdee044..e2a7560c72 100644 --- a/extension/src/extension.ts +++ b/extension/src/extension.ts @@ -29,10 +29,8 @@ import { } from './telemetry' import { RegisteredCommands } from './commands/external' import { StopWatch } from './util/time' -import { - registerWalkthroughCommands, - showWalkthroughOnFirstUse -} from './vscode/walkthrough' +import { registerWalkthroughCommands } from './vscode/walkthrough' +import { showSetupOnFirstUse } from './setup/util' import { WorkspaceRepositories } from './repository/workspace' import { recommendRedHatExtensionOnce } from './vscode/recommend' import { WorkspacePlots } from './plots/workspace' @@ -258,7 +256,7 @@ class Extension extends Disposable { registerPersistenceCommands(context.workspaceState, this.internalCommands) - void showWalkthroughOnFirstUse(env.isNewAppInstall) + void showSetupOnFirstUse(env.isNewAppInstall) this.dispose.track(recommendRedHatExtensionOnce()) this.dispose.track(new LanguageClient()) diff --git a/extension/src/vscode/walkthrough.test.ts b/extension/src/setup/util.test.ts similarity index 66% rename from extension/src/vscode/walkthrough.test.ts rename to extension/src/setup/util.test.ts index 03019e9635..12fc9bb2f6 100644 --- a/extension/src/vscode/walkthrough.test.ts +++ b/extension/src/setup/util.test.ts @@ -1,13 +1,13 @@ import { commands } from 'vscode' -import { showWalkthroughOnFirstUse } from './walkthrough' -import { ConfigKey, getConfigValue, setUserConfigValue } from './config' -import { Toast } from './toast' -import { Response } from './response' +import { showSetupOnFirstUse } from './util' +import { ConfigKey, getConfigValue, setUserConfigValue } from '../vscode/config' +import { Toast } from '../vscode/toast' +import { Response } from '../vscode/response' import { RegisteredCommands } from '../commands/external' jest.mock('vscode') -jest.mock('./toast') -jest.mock('./config') +jest.mock('../vscode/toast') +jest.mock('../vscode/config') const mockedCommands = jest.mocked(commands) const mockedExecuteCommand = jest.fn() @@ -24,51 +24,51 @@ beforeEach(() => { jest.resetAllMocks() }) -describe('showWalkthroughOnFirstUse', () => { - it('should ask to show the walkthrough after a new install', async () => { - await showWalkthroughOnFirstUse(true) +describe('showSetupOnFirstUse', () => { + it('should ask to show the setup page after a new install', async () => { + await showSetupOnFirstUse(true) expect(mockedAskShowOrCloseOrNever).toHaveBeenCalledTimes(1) }) - it('should not ask to show the walkthrough when the install is not new', async () => { - await showWalkthroughOnFirstUse(false) + it('should not ask to show the setup page when the install is not new', async () => { + await showSetupOnFirstUse(false) expect(mockedAskShowOrCloseOrNever).not.toHaveBeenCalled() }) - it('should not ask to show the walkthrough when the user has set a config option', async () => { + it('should not ask to show the setup page when the user has set a config option', async () => { mockedGetConfigValue.mockReturnValueOnce(true) - await showWalkthroughOnFirstUse(true) + await showSetupOnFirstUse(true) expect(mockedAskShowOrCloseOrNever).not.toHaveBeenCalled() expect(mockedGetConfigValue).toHaveBeenCalledTimes(1) expect(mockedGetConfigValue).toHaveBeenCalledWith( - ConfigKey.DO_NOT_SHOW_WALKTHROUGH_AFTER_INSTALL + ConfigKey.DO_NOT_SHOW_SETUP_AFTER_INSTALL ) }) it('should set the config option if the user responds with never', async () => { mockedAskShowOrCloseOrNever.mockResolvedValueOnce(Response.NEVER) - await showWalkthroughOnFirstUse(true) + await showSetupOnFirstUse(true) expect(mockedSetConfigValue).toHaveBeenCalledTimes(1) expect(mockedSetConfigValue).toHaveBeenCalledWith( - ConfigKey.DO_NOT_SHOW_WALKTHROUGH_AFTER_INSTALL, + ConfigKey.DO_NOT_SHOW_SETUP_AFTER_INSTALL, true ) }) - it('should show the walkthrough if the user responds with show', async () => { + it('should show the setup page if the user responds with show', async () => { mockedAskShowOrCloseOrNever.mockResolvedValueOnce(Response.SHOW) - await showWalkthroughOnFirstUse(true) + await showSetupOnFirstUse(true) expect(mockedSetConfigValue).not.toHaveBeenCalled() expect(mockedExecuteCommand).toHaveBeenCalledWith( - RegisteredCommands.EXTENSION_GET_STARTED + RegisteredCommands.SETUP_SHOW ) }) it('should take no action if the user closes the dialog', async () => { mockedAskShowOrCloseOrNever.mockResolvedValueOnce(undefined) - await showWalkthroughOnFirstUse(true) + await showSetupOnFirstUse(true) expect(mockedSetConfigValue).not.toHaveBeenCalled() expect(mockedExecuteCommand).not.toHaveBeenCalled() @@ -76,7 +76,7 @@ describe('showWalkthroughOnFirstUse', () => { it('should take no action if the user respond with close', async () => { mockedAskShowOrCloseOrNever.mockResolvedValueOnce(Response.CLOSE) - await showWalkthroughOnFirstUse(true) + await showSetupOnFirstUse(true) expect(mockedSetConfigValue).not.toHaveBeenCalled() expect(mockedExecuteCommand).not.toHaveBeenCalled() diff --git a/extension/src/setup/util.ts b/extension/src/setup/util.ts new file mode 100644 index 0000000000..91c2603d84 --- /dev/null +++ b/extension/src/setup/util.ts @@ -0,0 +1,27 @@ +import { commands } from 'vscode' +import { RegisteredCommands } from '../commands/external' +import { ConfigKey, getConfigValue, setUserConfigValue } from '../vscode/config' +import { Response } from '../vscode/response' +import { Toast } from '../vscode/toast' + +export const showSetupOnFirstUse = async ( + isNewAppInstall: boolean +): Promise => { + if ( + !isNewAppInstall || + getConfigValue(ConfigKey.DO_NOT_SHOW_SETUP_AFTER_INSTALL) + ) { + return + } + + const response = await Toast.askShowOrCloseOrNever( + 'Need help? Go to our setup view.' + ) + + if (response === Response.SHOW) { + void commands.executeCommand(RegisteredCommands.SETUP_SHOW) + } + if (response === Response.NEVER) { + void setUserConfigValue(ConfigKey.DO_NOT_SHOW_SETUP_AFTER_INSTALL, true) + } +} diff --git a/extension/src/vscode/config.ts b/extension/src/vscode/config.ts index ccb29e1f96..fd1c73ce62 100644 --- a/extension/src/vscode/config.ts +++ b/extension/src/vscode/config.ts @@ -4,7 +4,7 @@ export enum ConfigKey { DO_NOT_RECOMMEND_ADD_STUDIO_TOKEN = 'dvc.doNotRecommendAddStudioToken', DO_NOT_RECOMMEND_RED_HAT = 'dvc.doNotRecommendRedHatExtension', DO_NOT_SHOW_CLI_UNAVAILABLE = 'dvc.doNotShowCliUnavailable', - DO_NOT_SHOW_WALKTHROUGH_AFTER_INSTALL = 'dvc.doNotShowWalkthroughAfterInstall', + DO_NOT_SHOW_SETUP_AFTER_INSTALL = 'dvc.doNotShowSetupAfterInstall', DVC_PATH = 'dvc.dvcPath', EXP_TABLE_HEAD_MAX_HEIGHT = 'dvc.experimentsTableHeadMaxHeight', FOCUSED_PROJECTS = 'dvc.focusedProjects', diff --git a/extension/src/vscode/walkthrough.ts b/extension/src/vscode/walkthrough.ts index 50e52353c6..da62c114bd 100644 --- a/extension/src/vscode/walkthrough.ts +++ b/extension/src/vscode/walkthrough.ts @@ -1,7 +1,4 @@ import { commands } from 'vscode' -import { ConfigKey, getConfigValue, setUserConfigValue } from './config' -import { Response } from './response' -import { Toast } from './toast' import { RegisteredCommands } from '../commands/external' import { InternalCommands } from '../commands/internal' import { joinTruthyItems } from '../util/array' @@ -25,28 +22,3 @@ export const registerWalkthroughCommands = ( () => commands.executeCommand('workbench.action.quickOpen', '> DVC') ) } - -export const showWalkthroughOnFirstUse = async ( - isNewAppInstall: boolean -): Promise => { - if ( - !isNewAppInstall || - getConfigValue(ConfigKey.DO_NOT_SHOW_WALKTHROUGH_AFTER_INSTALL) - ) { - return - } - - const response = await Toast.askShowOrCloseOrNever( - 'Need help? There is a walkthrough.' - ) - - if (response === Response.SHOW) { - void commands.executeCommand(RegisteredCommands.EXTENSION_GET_STARTED) - } - if (response === Response.NEVER) { - void setUserConfigValue( - ConfigKey.DO_NOT_SHOW_WALKTHROUGH_AFTER_INSTALL, - true - ) - } -} diff --git a/webview/src/setup/components/App.test.tsx b/webview/src/setup/components/App.test.tsx index 736977890d..a0b87202e0 100644 --- a/webview/src/setup/components/App.test.tsx +++ b/webview/src/setup/components/App.test.tsx @@ -143,7 +143,7 @@ describe('App', () => { expect( screen.getByText( - 'DVC & DVCLive cannot be auto-installed as Python was not located.' + /DVC & DVCLive cannot be auto-installed as Python was not located./ ) ).toBeInTheDocument() expect(screen.queryByText('Install')).not.toBeInTheDocument() @@ -160,11 +160,11 @@ describe('App', () => { pythonBinPath: defaultInterpreter }) - expect( - screen.getByText( - `DVC & DVCLive can be auto-installed as packages with ${defaultInterpreter}` - ) - ).toBeInTheDocument() + const sentenceReg = new RegExp( + `DVC & DVCLive can be auto-installed with ${defaultInterpreter}.` + ) + + expect(screen.getByText(sentenceReg)).toBeInTheDocument() expect(screen.getByText('Install (pip)')).toBeInTheDocument() }) @@ -453,6 +453,37 @@ describe('App', () => { }) describe('Get Started', () => { + it('should show a screen saying that dvc is not setup if DVC is not found or initialized', () => { + renderApp({ + cliCompatible: undefined + }) + + const details = screen.getByTestId('get-started-section-details') + + expect(within(details).getByText('DVC is not setup')).toBeInTheDocument() + + sendSetDataMessage({ ...DEFAULT_DATA, projectInitialized: false }) + + expect(within(details).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({ + projectInitialized: false, + remoteList: { mockRoot: undefined } + }) + + const details = screen.getByTestId('get-started-section-details') + const getStartedText = within(details).getByText('DVC is not setup') + expect(getStartedText).toBeInTheDocument() + + mockPostMessage.mockClear() + const button = within(details).getByText('Setup DVC') + fireEvent.click(button) + expect(screen.getByText('DVC is not initialized')).toBeVisible() + expect(getStartedText).not.toBeVisible() + }) + it('should show a button that takes the user to the "Get Started" walkthrough', () => { renderApp() @@ -478,7 +509,9 @@ describe('App', () => { projectInitialized: false }) - expect(screen.getByText('DVC is not setup')).toBeInTheDocument() + const details = screen.getByTestId('experiments-section-details') + + expect(within(details).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', () => { @@ -487,11 +520,12 @@ describe('App', () => { remoteList: { mockRoot: undefined } }) - const experimentsText = screen.getByText('DVC is not setup') + const details = screen.getByTestId('experiments-section-details') + const experimentsText = within(details).getByText('DVC is not setup') expect(experimentsText).toBeInTheDocument() mockPostMessage.mockClear() - const button = screen.getByText('Setup DVC') + const button = within(details).getByText('Setup DVC') fireEvent.click(button) expect(screen.getByText('DVC is not initialized')).toBeVisible() expect(experimentsText).not.toBeVisible() @@ -506,7 +540,8 @@ describe('App', () => { } }) - expect(screen.getByText('DVC is not setup')).toBeInTheDocument() + const details = screen.getByTestId('experiments-section-details') + expect(within(details).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', () => { diff --git a/webview/src/setup/components/App.tsx b/webview/src/setup/components/App.tsx index 980f004bdc..873d032a53 100644 --- a/webview/src/setup/components/App.tsx +++ b/webview/src/setup/components/App.tsx @@ -59,6 +59,10 @@ const getStudioStatusIcon = (cliCompatible: boolean, isConnected: boolean) => { return isConnected ? TooltipIconType.PASSED : TooltipIconType.INFO } + +const getGetStartedIcon = (isDvcSetup: boolean) => + isDvcSetup ? TooltipIconType.INFO : TooltipIconType.ERROR + export const feedStore = ( data: MessageToWebview, dispatch: SetupDispatch @@ -175,9 +179,9 @@ export const App: React.FC = () => { - + { +export const GetStarted: React.FC<{ isDvcSetup: boolean }> = ({ + isDvcSetup +}) => { + if (!isDvcSetup) { + return ( + +

+ This extension's features cannot be accessed without DVC being + setup. +

+
+ ) + } + return (

Get Started

diff --git a/webview/src/setup/components/dvc/CliUnavailable.tsx b/webview/src/setup/components/dvc/CliUnavailable.tsx index 6692faeb37..33544c769d 100644 --- a/webview/src/setup/components/dvc/CliUnavailable.tsx +++ b/webview/src/setup/components/dvc/CliUnavailable.tsx @@ -11,11 +11,21 @@ export const CliUnavailable: React.FC = ({ children }) => { (state: SetupState) => state.dvc.pythonBinPath ) const canInstall = !!pythonBinPath + const installationSentence = ( + <> + The extension supports all{' '} + installation types and can + auto-install recommended packages via{' '} + pip + . + + ) - const contents = canInstall ? ( + const conditionalContents = canInstall ? ( <>

- DVC & DVCLive can be auto-installed as packages with {pythonBinPath} + {installationSentence} DVC & DVCLive can be auto-installed with{' '} + {pythonBinPath}.

diff --git a/webview/src/setup/components/experiments/Experiments.tsx b/webview/src/setup/components/experiments/Experiments.tsx index cc3fb5255b..a9378a0f67 100644 --- a/webview/src/setup/components/experiments/Experiments.tsx +++ b/webview/src/setup/components/experiments/Experiments.tsx @@ -8,7 +8,7 @@ import { EmptyState } from '../../../shared/components/emptyState/EmptyState' import { IconButton } from '../../../shared/components/button/IconButton' import { Beaker } from '../../../shared/components/icons' import { SetupState } from '../../store' -import { FocusDvcSection } from '../shared/FocusDvcSection' +import { DvcNotSetup } from '../shared/DvcNotSetup' type ExperimentsProps = { isDvcSetup: boolean @@ -21,11 +21,9 @@ export const Experiments: React.FC = ({ isDvcSetup }) => { if (!isDvcSetup) { return ( - -

DVC is not setup

+

DVC needs to be setup before you can access experiments.

- -
+ ) } diff --git a/webview/src/setup/components/shared/DvcNotSetup.tsx b/webview/src/setup/components/shared/DvcNotSetup.tsx new file mode 100644 index 0000000000..a4a4d70340 --- /dev/null +++ b/webview/src/setup/components/shared/DvcNotSetup.tsx @@ -0,0 +1,11 @@ +import React, { PropsWithChildren } from 'react' +import { FocusDvcSection } from './FocusDvcSection' +import { EmptyState } from '../../../shared/components/emptyState/EmptyState' + +export const DvcNotSetup: React.FC = ({ children }) => ( + +

DVC is not setup

+ {children} + +
+)