Skip to content

Commit

Permalink
Add Remotes section to setup webview (#3901)
Browse files Browse the repository at this point in the history
* add Remote section to setup webview

* handle workspace with multiple dvc projects

* fix integration test

* refactor out duplication

* reinstate blatted test

* make sending data to webview more explicit for config watcher

* add stories and tests

* remove duplication

* add unit test for remote collection

* address review comment
  • Loading branch information
mattseddon authored May 17, 2023
1 parent e95cc0e commit d06c0e3
Show file tree
Hide file tree
Showing 30 changed files with 573 additions and 126 deletions.
1 change: 1 addition & 0 deletions extension/src/cli/dvc/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export enum Command {
PUSH = 'push',
QUEUE = 'queue',
REMOVE = 'remove',
REMOTE = 'remote',
ROOT = 'root',
PARAMS = 'params',
METRICS = 'metrics',
Expand Down
8 changes: 7 additions & 1 deletion extension/src/cli/dvc/executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
ExperimentSubCommand,
Flag,
GcPreserveFlag,
QueueSubCommand
QueueSubCommand,
SubCommand
} from './constants'
import { addStudioAccessToken } from './options'
import { CliResult, CliStarted, typeCheckCommands } from '..'
Expand Down Expand Up @@ -35,6 +36,7 @@ export const autoRegisteredCommands = {
QUEUE_KILL: 'queueKill',
QUEUE_START: 'queueStart',
QUEUE_STOP: 'queueStop',
REMOTE: 'remote',
REMOVE: 'remove'
} as const

Expand Down Expand Up @@ -196,6 +198,10 @@ export class DvcExecutor extends DvcCli {
return this.blockAndExecuteProcess(cwd, Command.REMOVE, ...args)
}

public remote(cwd: string, arg: typeof SubCommand.LIST) {
return this.executeDvcProcess(cwd, Command.REMOTE, arg)
}

private executeExperimentProcess(cwd: string, ...args: Args) {
return this.executeDvcProcess(cwd, Command.EXPERIMENT, ...args)
}
Expand Down
30 changes: 30 additions & 0 deletions extension/src/setup/collect.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { collectRemoteList } from './collect'
import { dvcDemoPath } from '../test/util'
import { join } from '../test/util/path'

describe('collectRemoteList', () => {
it('should return the expected data structure', async () => {
const mockedRoot = join('some', 'other', 'root')
const remoteList = await collectRemoteList(
[dvcDemoPath, 'mockedOtherRoot', mockedRoot],
root =>
Promise.resolve(
{
[dvcDemoPath]:
'storage s3://dvc-public/remote/mnist-vscode\nbackup gdrive://appDataDir\nurl https://remote.dvc.org/mnist-vscode',
mockedOtherRoot: '',
[mockedRoot]: undefined
}[root]
)
)
expect(remoteList).toStrictEqual({
[dvcDemoPath]: {
backup: 'gdrive://appDataDir',
storage: 's3://dvc-public/remote/mnist-vscode',
url: 'https://remote.dvc.org/mnist-vscode'
},
mockedOtherRoot: undefined,
[mockedRoot]: undefined
})
})
})
31 changes: 30 additions & 1 deletion extension/src/setup/collect.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { DEFAULT_SECTION_COLLAPSED, SetupSection } from './webview/contract'
import {
DEFAULT_SECTION_COLLAPSED,
RemoteList,
SetupSection
} from './webview/contract'
import { trimAndSplit } from '../util/stdout'

export const collectSectionCollapsed = (
focusedSection?: SetupSection
Expand All @@ -16,3 +21,27 @@ export const collectSectionCollapsed = (

return acc
}

export const collectRemoteList = async (
dvcRoots: string[],
getRemoteList: (cwd: string) => Promise<string | undefined>
): Promise<NonNullable<RemoteList>> => {
const acc: NonNullable<RemoteList> = {}

for (const dvcRoot of dvcRoots) {
const remoteList = await getRemoteList(dvcRoot)
if (!remoteList) {
acc[dvcRoot] = undefined
continue
}
const remotes = trimAndSplit(remoteList)
const dvcRootRemotes: { [alias: string]: string } = {}
for (const remote of remotes) {
const [alias, url] = remote.split(/\s+/)
dvcRootRemotes[alias] = url
}
acc[dvcRoot] = dvcRootRemotes
}

return acc
}
66 changes: 46 additions & 20 deletions extension/src/setup/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
SetupSection,
SetupData as TSetupData
} from './webview/contract'
import { collectSectionCollapsed } from './collect'
import { collectRemoteList, collectSectionCollapsed } from './collect'
import { WebviewMessages } from './webview/messages'
import { validateTokenInput } from './inputBox'
import { findPythonBinForInstall } from './autoInstall'
Expand Down Expand Up @@ -47,7 +47,8 @@ import {
Flag,
ConfigKey as DvcConfigKey,
DOT_DVC,
Args
Args,
SubCommand
} from '../cli/dvc/constants'
import { GLOBAL_WEBVIEW_DVCROOT } from '../webview/factory'
import {
Expand Down Expand Up @@ -229,7 +230,7 @@ export class Setup
) {
this.cliCompatible = compatible
this.cliVersion = version
void this.updateIsStudioConnected()
void this.updateStudioAndSend()
const incompatible = compatible === undefined ? undefined : !compatible
void setContextValue(ContextKey.CLI_INCOMPATIBLE, incompatible)
}
Expand Down Expand Up @@ -339,7 +340,7 @@ export class Setup
}

await this.accessConfig(cwd, Flag.GLOBAL, DvcConfigKey.STUDIO_TOKEN, token)
return this.updateIsStudioConnected()
return this.updateStudioAndSend()
}

public getStudioLiveShareToken() {
Expand Down Expand Up @@ -375,14 +376,28 @@ export class Setup
}
}

private async getRemoteList() {
await this.config.isReady()

if (!this.hasRoots()) {
return undefined
}

return collectRemoteList(this.dvcRoots, (cwd: string) =>
this.accessRemote(cwd, SubCommand.LIST)
)
}

private async sendDataToWebview() {
const projectInitialized = this.hasRoots()
const hasData = this.getHasData()

const [isPythonExtensionUsed, dvcCliDetails] = await Promise.all([
this.isPythonExtensionUsed(),
this.getDvcCliDetails()
])
const [isPythonExtensionUsed, dvcCliDetails, remoteList] =
await Promise.all([
this.isPythonExtensionUsed(),
this.getDvcCliDetails(),
this.getRemoteList()
])

const needsGitInitialized =
!projectInitialized && !!(await this.needsGitInit())
Expand All @@ -405,6 +420,7 @@ export class Setup
needsGitInitialized,
projectInitialized,
pythonBinPath: getBinDisplayText(pythonBinPath),
remoteList,
sectionCollapsed: collectSectionCollapsed(this.focusedSection),
shareLiveToStudio: getConfigValue(
ExtensionConfigKey.STUDIO_SHARE_EXPERIMENTS_LIVE
Expand Down Expand Up @@ -644,16 +660,16 @@ export class Setup
}
}

private async updateStudioAndSend() {
await this.updateIsStudioConnected()
return this.sendDataToWebview()
}

private async updateIsStudioConnected() {
await this.setStudioAccessToken()
const storedToken = this.getStudioAccessToken()
const isConnected = isStudioAccessToken(storedToken)
return this.setStudioIsConnected(isConnected)
}

private setStudioIsConnected(isConnected: boolean) {
this.studioIsConnected = isConnected
void this.sendDataToWebview()
return setContextValue(ContextKey.STUDIO_CONNECTED, isConnected)
}

Expand All @@ -667,7 +683,7 @@ export class Setup
path.endsWith(join('dvc', 'config')) ||
path.endsWith(join('dvc', 'config.local'))
) {
void this.updateIsStudioConnected()
void this.updateStudioAndSend()
}
}
)
Expand Down Expand Up @@ -705,13 +721,23 @@ export class Setup
)
}

private async accessConfig(cwd: string, ...args: Args) {
private accessConfig(cwd: string, ...args: Args) {
return this.accessDvc(AvailableCommands.CONFIG, cwd, ...args)
}

private accessRemote(cwd: string, ...args: Args) {
return this.accessDvc(AvailableCommands.REMOTE, cwd, ...args)
}

private async accessDvc(
commandId:
| typeof AvailableCommands.CONFIG
| typeof AvailableCommands.REMOTE,
cwd: string,
...args: Args
) {
try {
return await this.internalCommands.executeCommand(
AvailableCommands.CONFIG,
cwd,
...args
)
return await this.internalCommands.executeCommand(commandId, cwd, ...args)
} catch {}
}
}
15 changes: 11 additions & 4 deletions extension/src/setup/webview/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ export type DvcCliDetails = {
version: string | undefined
}

export type RemoteList =
| { [dvcRoot: string]: { [alias: string]: string } | undefined }
| undefined

export type SetupData = {
canGitInitialize: boolean
cliCompatible: boolean | undefined
Expand All @@ -14,20 +18,23 @@ export type SetupData = {
needsGitInitialized: boolean | undefined
projectInitialized: boolean
pythonBinPath: string | undefined
remoteList: RemoteList
sectionCollapsed: typeof DEFAULT_SECTION_COLLAPSED | undefined
shareLiveToStudio: boolean
}

export enum SetupSection {
DVC = 'dvc',
EXPERIMENTS = 'experiments',
STUDIO = 'studio',
DVC = 'dvc'
REMOTES = 'remotes',
STUDIO = 'studio'
}

export const DEFAULT_SECTION_COLLAPSED = {
[SetupSection.DVC]: false,
[SetupSection.EXPERIMENTS]: false,
[SetupSection.STUDIO]: false,
[SetupSection.DVC]: false
[SetupSection.REMOTES]: false,
[SetupSection.STUDIO]: false
}

export type SectionCollapsed = typeof DEFAULT_SECTION_COLLAPSED
Expand Down
2 changes: 2 additions & 0 deletions extension/src/setup/webview/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export class WebviewMessages {
needsGitInitialized,
projectInitialized,
pythonBinPath,
remoteList,
sectionCollapsed,
shareLiveToStudio
}: SetupData) {
Expand All @@ -54,6 +55,7 @@ export class WebviewMessages {
needsGitInitialized,
projectInitialized,
pythonBinPath,
remoteList,
sectionCollapsed,
shareLiveToStudio
})
Expand Down
4 changes: 4 additions & 0 deletions extension/src/test/suite/setup/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ suite('Setup Test Suite', () => {
needsGitInitialized: true,
projectInitialized: false,
pythonBinPath: undefined,
remoteList: undefined,
sectionCollapsed: undefined,
shareLiveToStudio: false
})
Expand Down Expand Up @@ -287,6 +288,7 @@ suite('Setup Test Suite', () => {
needsGitInitialized: true,
projectInitialized: false,
pythonBinPath: undefined,
remoteList: undefined,
sectionCollapsed: undefined,
shareLiveToStudio: false
})
Expand Down Expand Up @@ -337,6 +339,7 @@ suite('Setup Test Suite', () => {
needsGitInitialized: false,
projectInitialized: false,
pythonBinPath: undefined,
remoteList: undefined,
sectionCollapsed: undefined,
shareLiveToStudio: false
})
Expand Down Expand Up @@ -387,6 +390,7 @@ suite('Setup Test Suite', () => {
needsGitInitialized: false,
projectInitialized: true,
pythonBinPath: undefined,
remoteList: { [dvcDemoPath]: undefined },
sectionCollapsed: undefined,
shareLiveToStudio: false
})
Expand Down
1 change: 1 addition & 0 deletions extension/src/test/suite/setup/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export const buildSetup = (

const mockEmitter = disposer.track(new EventEmitter())
stub(dvcReader, 'root').resolves(mockDvcRoot)
stub(dvcExecutor, 'remote').resolves('')
const mockVersion = stub(dvcReader, 'version').resolves(MIN_CLI_VERSION)
const mockGlobalVersion = stub(dvcReader, 'globalVersion').resolves(
MIN_CLI_VERSION
Expand Down
Loading

0 comments on commit d06c0e3

Please sign in to comment.