diff --git a/CHANGELOG.md b/CHANGELOG.md index 7729fb125e..bbd1a701dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ All notable changes to this project will be documented in this file. +## [0.8.4] - 2023-05-02 + +### 🐛 Bug Fixes + +- Fix missing tracked decorations [#3801](https://github.com/iterative/vscode-dvc/pull/3801) by [@mattseddon](https://github.com/mattseddon) +- Hide push option when an experiment is running [#3808](https://github.com/iterative/vscode-dvc/pull/3808) by [@mattseddon](https://github.com/mattseddon) + +### 🔨 Maintenance + +- Update demo project and latest tested CLI version (2.56.0) [#3805](https://github.com/iterative/vscode-dvc/pull/3805) by [@sroy3](https://github.com/sroy3) +- WorkspaceExperiments housekeeping [#3797](https://github.com/iterative/vscode-dvc/pull/3797) by [@mattseddon](https://github.com/mattseddon) +- Wait for config to be ready before accessing values [#3799](https://github.com/iterative/vscode-dvc/pull/3799) by [@mattseddon](https://github.com/mattseddon) + ## [0.8.3] - 2023-05-01 ### 🚀 New Features and Enhancements diff --git a/demo b/demo index a9b32d1496..b950796124 160000 --- a/demo +++ b/demo @@ -1 +1 @@ -Subproject commit a9b32d14966b9be1396f2211d9eb743359708a07 +Subproject commit b950796124a63f7b6a0481a7692d02ca8905d183 diff --git a/extension/package.json b/extension/package.json index 89f2319528..301f4d7e10 100644 --- a/extension/package.json +++ b/extension/package.json @@ -9,7 +9,7 @@ "extensionDependencies": [ "vscode.git" ], - "version": "0.8.3", + "version": "0.8.4", "license": "Apache-2.0", "readme": "./README.md", "repository": { diff --git a/extension/src/cli/dvc/contract.ts b/extension/src/cli/dvc/contract.ts index 0d99e06f56..f0d0e0bb0c 100644 --- a/extension/src/cli/dvc/contract.ts +++ b/extension/src/cli/dvc/contract.ts @@ -1,7 +1,7 @@ import { Plot } from '../../plots/webview/contract' export const MIN_CLI_VERSION = '2.55.0' -export const LATEST_TESTED_CLI_VERSION = '2.55.0' +export const LATEST_TESTED_CLI_VERSION = '2.56.0' export const MAX_CLI_VERSION = '3' type ErrorContents = { type: string; msg: string } diff --git a/extension/src/config.ts b/extension/src/config.ts index 049c4fd04c..869fe3ef96 100644 --- a/extension/src/config.ts +++ b/extension/src/config.ts @@ -57,13 +57,14 @@ export class Config extends DeferredDisposable { } public async setPythonBinPath() { - this.pythonBinPath = await this.getConfigOrExtensionPythonBinPath() - return this.deferred.resolve() + const pythonBinPath = await this.getConfigOrExtensionPythonBinPath() + this.pythonBinPath = pythonBinPath + this.deferred.resolve() } public async setPythonAndNotifyIfChanged() { const oldPath = this.pythonBinPath - this.pythonBinPath = await this.getConfigOrExtensionPythonBinPath() + await this.setPythonBinPath() this.notifyIfChanged(oldPath, this.pythonBinPath) } diff --git a/extension/src/experiments/workspace.ts b/extension/src/experiments/workspace.ts index 945fce327b..f5d2b836c3 100644 --- a/extension/src/experiments/workspace.ts +++ b/extension/src/experiments/workspace.ts @@ -95,83 +95,43 @@ export class WorkspaceExperiments extends BaseWorkspaceWebviews< ) } - public async addFilter(overrideRoot?: string) { - const dvcRoot = await this.getDvcRoot(overrideRoot) - if (!dvcRoot) { - return - } - return this.getRepository(dvcRoot).addFilter() + public addFilter(overrideRoot?: string) { + return this.getRepositoryThenUpdate('addFilter', overrideRoot) } - public async addStarredFilter(overrideRoot?: string) { - const dvcRoot = await this.getDvcRoot(overrideRoot) - if (!dvcRoot) { - return - } - return this.getRepository(dvcRoot).addStarredFilter() + public addStarredFilter(overrideRoot?: string) { + return this.getRepositoryThenUpdate('addStarredFilter', overrideRoot) } - public async removeFilters() { - const dvcRoot = await this.getFocusedOrOnlyOrPickProject() - if (!dvcRoot) { - return - } - return this.getRepository(dvcRoot).removeFilters() + public removeFilters() { + return this.getRepositoryThenUpdate('removeFilters') } - public async addSort(overrideRoot?: string) { - const dvcRoot = await this.getDvcRoot(overrideRoot) - if (!dvcRoot) { - return - } - return this.getRepository(dvcRoot).addSort() + public addSort(overrideRoot?: string) { + return this.getRepositoryThenUpdate('addSort', overrideRoot) } - public async addStarredSort(overrideRoot?: string) { - const dvcRoot = await this.getDvcRoot(overrideRoot) - if (!dvcRoot) { - return - } - return this.getRepository(dvcRoot).addStarredSort() + public addStarredSort(overrideRoot?: string) { + return this.getRepositoryThenUpdate('addStarredSort', overrideRoot) } - public async removeSorts() { - const dvcRoot = await this.getFocusedOrOnlyOrPickProject() - if (!dvcRoot) { - return - } - - return this.getRepository(dvcRoot).removeSorts() + public removeSorts() { + return this.getRepositoryThenUpdate('removeSorts') } - public async selectExperimentsToPlot(overrideRoot?: string) { - const dvcRoot = await this.getDvcRoot(overrideRoot) - if (!dvcRoot) { - return - } - return this.getRepository(dvcRoot).selectExperimentsToPlot() + public selectExperimentsToPlot(overrideRoot?: string) { + return this.getRepositoryThenUpdate('selectExperimentsToPlot', overrideRoot) } - public async selectColumns(overrideRoot?: string) { - const dvcRoot = await this.getDvcRoot(overrideRoot) - if (!dvcRoot) { - return - } - return this.getRepository(dvcRoot).selectColumns() + public selectColumns(overrideRoot?: string) { + return this.getRepositoryThenUpdate('selectColumns', overrideRoot) } - public async selectQueueTasksToKill() { - const cwd = await this.getFocusedOrOnlyOrPickProject() - if (!cwd) { - return - } - - const taskIds = await this.getRepository(cwd).pickQueueTasksToKill() - - if (!taskIds || isEmpty(taskIds)) { - return - } - return this.runCommand(AvailableCommands.QUEUE_KILL, cwd, ...taskIds) + public selectQueueTasksToKill() { + return this.pickIdsThenRun( + 'pickQueueTasksToKill', + AvailableCommands.QUEUE_KILL + ) } public async selectExperimentsToPush(setup: Setup) { @@ -190,20 +150,11 @@ export class WorkspaceExperiments extends BaseWorkspaceWebviews< return pushCommand({ dvcRoot, ids }) } - public async selectExperimentsToRemove() { - const cwd = await this.getFocusedOrOnlyOrPickProject() - if (!cwd) { - return - } - - const experimentIds = await this.getRepository( - cwd - ).pickExperimentsToRemove() - if (!experimentIds || isEmpty(experimentIds)) { - return - } - - return this.runCommand(AvailableCommands.EXP_REMOVE, cwd, ...experimentIds) + public selectExperimentsToRemove() { + return this.pickIdsThenRun( + 'pickExperimentsToRemove', + AvailableCommands.EXP_REMOVE + ) } public async modifyExperimentParamsAndRun( @@ -453,6 +404,25 @@ export class WorkspaceExperiments extends BaseWorkspaceWebviews< ) } + private async getRepositoryThenUpdate( + method: + | 'addFilter' + | 'addStarredFilter' + | 'removeFilters' + | 'addSort' + | 'addStarredSort' + | 'removeSorts' + | 'selectExperimentsToPlot' + | 'selectColumns', + overrideRoot?: string + ) { + const dvcRoot = await this.getDvcRoot(overrideRoot) + if (!dvcRoot) { + return + } + return this.getRepository(dvcRoot)[method]() + } + private async shouldRun() { const cwd = await this.getFocusedOrOnlyOrPickProject() if (!cwd) { @@ -555,6 +525,25 @@ export class WorkspaceExperiments extends BaseWorkspaceWebviews< return { command, enteredManually, trainingScript } } + private async pickIdsThenRun( + pickMethod: 'pickQueueTasksToKill' | 'pickExperimentsToRemove', + commandId: + | typeof AvailableCommands.QUEUE_KILL + | typeof AvailableCommands.EXP_REMOVE + ) { + const cwd = await this.getFocusedOrOnlyOrPickProject() + if (!cwd) { + return + } + + const ids = await this.getRepository(cwd)[pickMethod]() + + if (!ids || isEmpty(ids)) { + return + } + return this.runCommand(commandId, cwd, ...ids) + } + private async pickExpThenRun( commandId: CommandId, pickFunc: (cwd: string) => Thenable | undefined diff --git a/extension/src/repository/model/collect.test.ts b/extension/src/repository/model/collect.test.ts index d89437b4df..26679d9f07 100644 --- a/extension/src/repository/model/collect.test.ts +++ b/extension/src/repository/model/collect.test.ts @@ -251,6 +251,55 @@ describe('collectDataStatus', () => { makeAbsPathSet(dvcDemoPath, rawDataDir) ) }) + + it('should fill in the gaps for tracked decorations', () => { + const data = { + not_in_remote: [ + join('static', 'uploads', 'images', '2019-12-14', 'devsprints.png') + ], + unchanged: [ + join('static', 'uploads', 'images', '2019-09-26', 'dvc-org.png') + ], + uncommitted: { + added: [ + join( + 'static', + 'uploads', + 'images', + '2017-05-15', + '20190925_181739.jpg' + ) + ], + modified: [join('static', 'uploads') + sep] + } + } + const collected = collectDataStatus(dvcDemoPath, data) + + expect( + collected.trackedDecorations.has( + join( + dvcDemoPath, + 'static', + 'uploads', + 'images', + '2019-09-26', + 'dvc-org.png' + ) + ) + ).toBe(true) + + expect( + collected.trackedDecorations.has( + join(dvcDemoPath, 'static', 'uploads', 'images') + ) + ).toBe(true) + + expect( + collected.trackedDecorations.has( + join(dvcDemoPath, 'static', 'uploads', 'images', '2019-09-26') + ) + ).toBe(true) + }) }) const makeUri = (...paths: string[]): Uri => diff --git a/extension/src/repository/model/collect.ts b/extension/src/repository/model/collect.ts index 4b74cb445e..a172a0d4f6 100644 --- a/extension/src/repository/model/collect.ts +++ b/extension/src/repository/model/collect.ts @@ -1,4 +1,4 @@ -import { join, relative, resolve } from 'path' +import { join, relative, resolve, sep } from 'path' import { Uri } from 'vscode' import { Resource } from '../commands' import { addToMapSet } from '../../util/map' @@ -195,6 +195,33 @@ const uncommitNotInCache = ( return ('un' + status) as ExtendedStatus } +const collectMissingTracked = ( + trackedDecorations: Set, + path: string, + add: boolean +) => { + if (add) { + trackedDecorations.add(path) + } +} + +const fillGapsInTrackedDecorations = ( + rootDepth: number, + trackedDecorations: Set +) => { + for (const path of trackedDecorations) { + const pathArray = getPathArray(path) + let add = false + for (let idx = rootDepth; idx < pathArray.length; idx++) { + const currPath = getPath(pathArray, idx) + if (trackedDecorations.has(currPath)) { + add = true + } + collectMissingTracked(trackedDecorations, currPath, add) + } + } +} + const collectGroupWithMissingAncestors = ( acc: DataStatusAccumulator, dvcRoot: string, @@ -214,6 +241,11 @@ const collectGroupWithMissingAncestors = ( addToTracked(acc.trackedDecorations, absPath, originalStatus) } + + fillGapsInTrackedDecorations( + dvcRoot.split(sep).length + 1, + acc.trackedDecorations + ) } export const collectDataStatus = ( diff --git a/extension/src/setup/index.ts b/extension/src/setup/index.ts index db2f622502..78462ca607 100644 --- a/extension/src/setup/index.ts +++ b/extension/src/setup/index.ts @@ -374,7 +374,8 @@ export class Setup return this.sendDataToWebview() } - public getDvcCliDetails(): DvcCliDetails { + private async getDvcCliDetails(): Promise { + await this.config.isReady() const dvcPath = this.config.getCliPath() const pythonBinPath = this.config.getPythonBinPath() const cwd = getFirstWorkspaceFolder() @@ -389,18 +390,14 @@ export class Setup } } - private isDVCBeingUsedGlobally() { - const dvcPath = this.config.getCliPath() - const pythonBinPath = this.config.getPythonBinPath() - - return dvcPath || !pythonBinPath - } - private async sendDataToWebview() { const projectInitialized = this.hasRoots() const hasData = this.getHasData() - const isPythonExtensionUsed = await this.isPythonExtensionUsed() + const [isPythonExtensionUsed, dvcCliDetails] = await Promise.all([ + this.isPythonExtensionUsed(), + this.getDvcCliDetails() + ]) const needsGitInitialized = !projectInitialized && !!(await this.needsGitInit()) @@ -415,10 +412,9 @@ export class Setup this.webviewMessages.sendWebviewMessage({ canGitInitialize, cliCompatible: this.getCliCompatible(), - dvcCliDetails: this.getDvcCliDetails(), + dvcCliDetails, hasData, - isPythonExtensionUsed: - !this.isDVCBeingUsedGlobally() && isPythonExtensionUsed, + isPythonExtensionUsed, isStudioConnected: this.studioIsConnected, needsGitCommit, needsGitInitialized, diff --git a/extension/src/test/suite/setup/index.test.ts b/extension/src/test/suite/setup/index.test.ts index fd213975bc..df1692577f 100644 --- a/extension/src/test/suite/setup/index.test.ts +++ b/extension/src/test/suite/setup/index.test.ts @@ -577,7 +577,7 @@ suite('Setup Test Suite', () => { mockExecuteCommand.restore() mockRunSetup.restore() stub(config, 'isPythonExtensionUsed').returns(false) - stub(config, 'getPythonBinPath').resolves(join('python')) + stub(config, 'getPythonBinPath').returns(join('python')) mockVersion.resetBehavior() mockVersion diff --git a/webview/src/experiments/components/App.test.tsx b/webview/src/experiments/components/App.test.tsx index cd2f6d14c2..1caf3c14ce 100644 --- a/webview/src/experiments/components/App.test.tsx +++ b/webview/src/experiments/components/App.test.tsx @@ -1068,6 +1068,18 @@ describe('App', () => { }) }) + it('should not enable the user to share an experiment whilst an experiment is running', () => { + renderTable() + + const target = screen.getByText('4fb124a') + fireEvent.contextMenu(target, { bubbles: true }) + + advanceTimersByTime(100) + const menuitems = screen.getAllByRole('menuitem') + const itemLabels = menuitems.map(item => item.textContent) + expect(itemLabels).not.toContain('Push') + }) + it('should always present the Plots options if multiple rows are selected', () => { renderTableWithoutRunningExperiments() diff --git a/webview/src/experiments/components/table/body/RowContextMenu.tsx b/webview/src/experiments/components/table/body/RowContextMenu.tsx index f2000e5fcd..ae58915f79 100644 --- a/webview/src/experiments/components/table/body/RowContextMenu.tsx +++ b/webview/src/experiments/components/table/body/RowContextMenu.tsx @@ -211,7 +211,7 @@ const getSingleSelectMenuOptions = ( [id], 'Push', MessageFromWebviewType.PUSH_EXPERIMENT, - isNotExperiment, + isNotExperiment || hasRunningExperiment, true ), ...getRunResumeOptions(