diff --git a/extension/package.json b/extension/package.json index 496995ae53..1831c18362 100644 --- a/extension/package.json +++ b/extension/package.json @@ -520,6 +520,11 @@ "category": "DVC", "icon": "$(repo-push)" }, + { + "title": "%command.views.experiments.showLogs%", + "command": "dvc.views.experiments.showLogs", + "category": "DVC" + }, { "title": "%command.views.experimentsTree.selectExperiments%", "command": "dvc.views.experimentsTree.selectExperiments", @@ -885,6 +890,10 @@ "command": "dvc.views.experiments.shareExperimentToStudio", "when": "false" }, + { + "command": "dvc.views.experiments.showLogs", + "when": "false" + }, { "command": "dvc.views.experimentsFilterByTree.removeAllFilters", "when": "false" @@ -1611,7 +1620,7 @@ "@types/node": "16.x", "@types/react-vega": "7.0.0", "@types/sinon-chai": "3.2.9", - "@types/uuid": "9.0.0", + "@types/uuid": "9.0.1", "@types/vscode": "1.64.0", "@vscode/test-electron": "2.2.3", "@vscode/vsce": "2.17.0", diff --git a/extension/package.nls.json b/extension/package.nls.json index 0b23181227..e9b1bc1381 100644 --- a/extension/package.nls.json +++ b/extension/package.nls.json @@ -76,6 +76,7 @@ "command.views.experiments.shareExperimentAsBranch": "Share as Branch", "command.views.experiments.shareExperimentAsCommit": "Commit and Share", "command.views.experiments.shareExperimentToStudio": "Share to Studio", + "command.views.experiments.showLogs": "Show Logs", "command.views.experimentsTree.selectExperiments": "Select Experiments to Display in Plots", "command.views.plotsPathsTree.selectPlots": "Select Plots to Display", "command.views.plotsPathsTree.refreshPlots": "Refresh Plots for Selected Experiments", diff --git a/extension/src/cli/dvc/viewer.ts b/extension/src/cli/dvc/viewer.ts index b475241911..5781f468d6 100644 --- a/extension/src/cli/dvc/viewer.ts +++ b/extension/src/cli/dvc/viewer.ts @@ -53,7 +53,7 @@ export class DvcViewer extends Disposable implements ICli { public queueLogs(cwd: string, expName: string) { return this.run( - expName, + `${expName} logs`, cwd, Command.QUEUE, QueueSubCommand.LOGS, diff --git a/extension/src/cli/viewable.ts b/extension/src/cli/viewable.ts index fc3d47291c..9e7dcecb02 100644 --- a/extension/src/cli/viewable.ts +++ b/extension/src/cli/viewable.ts @@ -82,6 +82,7 @@ export class ViewableCliProcess extends DeferredDisposable { }, processCompleted ) + this.pseudoTerminal.setBlocked(false) processOutput.fire('\r\nPress any key to close\r\n\n') }) } diff --git a/extension/src/commands/external.ts b/extension/src/commands/external.ts index df6134eae9..b3ec900ffd 100644 --- a/extension/src/commands/external.ts +++ b/extension/src/commands/external.ts @@ -19,6 +19,7 @@ export enum RegisteredCliCommands { EXPERIMENT_VIEW_REMOVE = 'dvc.views.experiments.removeExperiment', EXPERIMENT_VIEW_SHARE_AS_BRANCH = 'dvc.views.experiments.shareExperimentAsBranch', EXPERIMENT_VIEW_SHARE_AS_COMMIT = 'dvc.views.experiments.shareExperimentAsCommit', + EXPERIMENT_VIEW_SHOW_LOGS = 'dvc.views.experiments.showLogs', EXPERIMENT_VIEW_STOP = 'dvc.views.experiments.stopQueueExperiment', EXPERIMENT_VIEW_QUEUE = 'dvc.views.experiments.queueExperiment', diff --git a/extension/src/experiments/commands/register.ts b/extension/src/experiments/commands/register.ts index cbd690b048..0973e87206 100644 --- a/extension/src/experiments/commands/register.ts +++ b/extension/src/experiments/commands/register.ts @@ -350,4 +350,10 @@ export const registerExperimentCommands = ( RegisteredCommands.EXPERIMENT_VIEW_SHARE_TO_STUDIO, getShareExperimentToStudioCommand(internalCommands, connect) ) + + internalCommands.registerExternalCliCommand( + RegisteredCliCommands.EXPERIMENT_VIEW_SHOW_LOGS, + ({ dvcRoot, id }: ExperimentDetails) => + internalCommands.executeCommand(AvailableCommands.QUEUE_LOGS, dvcRoot, id) + ) } diff --git a/extension/src/experiments/webview/messages.ts b/extension/src/experiments/webview/messages.ts index cb87402439..e5de97c1af 100644 --- a/extension/src/experiments/webview/messages.ts +++ b/extension/src/experiments/webview/messages.ts @@ -190,6 +190,15 @@ export class WebviewMessages { { dvcRoot: this.dvcRoot, id: message.payload } ) + case MessageFromWebviewType.SHOW_EXPERIMENT_LOGS: + return commands.executeCommand( + RegisteredCliCommands.EXPERIMENT_VIEW_SHOW_LOGS, + { + dvcRoot: this.dvcRoot, + id: message.payload + } + ) + default: Logger.error(`Unexpected message: ${JSON.stringify(message)}`) } diff --git a/extension/src/telemetry/constants.ts b/extension/src/telemetry/constants.ts index cf346e051f..e7c28a8f75 100644 --- a/extension/src/telemetry/constants.ts +++ b/extension/src/telemetry/constants.ts @@ -148,6 +148,7 @@ export interface IEventNamePropertyMapping { [EventName.EXPERIMENT_VIEW_SHARE_AS_BRANCH]: undefined [EventName.EXPERIMENT_VIEW_SHARE_AS_COMMIT]: undefined [EventName.EXPERIMENT_VIEW_SHARE_TO_STUDIO]: undefined + [EventName.EXPERIMENT_VIEW_SHOW_LOGS]: undefined [EventName.EXPERIMENT_VIEW_STOP]: undefined [EventName.QUEUE_EXPERIMENT]: undefined [EventName.QUEUE_KILL]: undefined diff --git a/extension/src/test/suite/experiments/index.test.ts b/extension/src/test/suite/experiments/index.test.ts index 878f5ce276..ece954f630 100644 --- a/extension/src/test/suite/experiments/index.test.ts +++ b/extension/src/test/suite/experiments/index.test.ts @@ -68,7 +68,10 @@ import { shortenForLabel } from '../../../util/string' import { GitExecutor } from '../../../cli/git/executor' import { WorkspacePlots } from '../../../plots/workspace' import { PlotSizeNumber } from '../../../plots/webview/contract' -import { RegisteredCommands } from '../../../commands/external' +import { + RegisteredCliCommands, + RegisteredCommands +} from '../../../commands/external' import { ConfigKey } from '../../../vscode/config' import { EXPERIMENT_WORKSPACE_ID } from '../../../cli/dvc/contract' import * as Time from '../../../util/time' @@ -78,6 +81,7 @@ import * as FileSystem from '../../../fileSystem' import * as ProcessExecution from '../../../process/execution' import { DvcReader } from '../../../cli/dvc/reader' import { Connect } from '../../../connect' +import { DvcViewer } from '../../../cli/dvc/viewer' const { openFileInEditor } = FileSystem @@ -549,6 +553,39 @@ suite('Experiments Test Suite', () => { ) }).timeout(WEBVIEW_TEST_TIMEOUT) + it('should handle a message to show the logs of an experiment', async () => { + const { experiments } = buildExperiments(disposable) + await experiments.isReady() + + const mockExpId = 'exp-e7a67' + + const webview = await experiments.showWebview() + const mockMessageReceived = getMessageReceivedEmitter(webview) + + const executeCommandSpy = spy(commands, 'executeCommand') + + const mockShowLogs = stub(DvcViewer.prototype, 'queueLogs') + + const logsShown = new Promise(resolve => + mockShowLogs.callsFake(() => { + resolve(undefined) + return Promise.resolve(undefined) + }) + ) + + mockMessageReceived.fire({ + payload: mockExpId, + type: MessageFromWebviewType.SHOW_EXPERIMENT_LOGS + }) + + await logsShown + + expect(executeCommandSpy).to.be.calledWithExactly( + RegisteredCliCommands.EXPERIMENT_VIEW_SHOW_LOGS, + { dvcRoot: dvcDemoPath, id: mockExpId } + ) + }).timeout(WEBVIEW_TEST_TIMEOUT) + it('should handle a message to share an experiment to Studio', async () => { const { experiments } = buildExperiments(disposable) await experiments.isReady() diff --git a/extension/src/test/suite/experiments/util.ts b/extension/src/test/suite/experiments/util.ts index 183807845c..e7634392c0 100644 --- a/extension/src/test/suite/experiments/util.ts +++ b/extension/src/test/suite/experiments/util.ts @@ -51,6 +51,7 @@ export const buildExperiments = ( dvcExecutor, dvcReader, dvcRunner, + dvcViewer, gitReader, internalCommands, messageSpy, @@ -90,6 +91,7 @@ export const buildExperiments = ( dvcExecutor, dvcReader, dvcRunner, + dvcViewer, experiments, // eslint-disable-next-line @typescript-eslint/no-explicit-any experimentsModel: (experiments as any).experiments as ExperimentsModel, diff --git a/extension/src/test/suite/util.ts b/extension/src/test/suite/util.ts index f93ef57993..d20fbe15b5 100644 --- a/extension/src/test/suite/util.ts +++ b/extension/src/test/suite/util.ts @@ -34,6 +34,7 @@ import { TableData } from '../../experiments/webview/contract' import { DvcExecutor } from '../../cli/dvc/executor' import { GitReader } from '../../cli/git/reader' import { SetupData } from '../../setup/webview/contract' +import { DvcViewer } from '../../cli/dvc/viewer' export const mockDisposable = { dispose: stub() @@ -141,6 +142,7 @@ export const buildInternalCommands = (disposer: Disposer) => { const dvcReader = disposer.track(new DvcReader(config)) const dvcRunner = disposer.track(new DvcRunner(config)) const dvcExecutor = disposer.track(new DvcExecutor(config)) + const dvcViewer = disposer.track(new DvcViewer(config)) const gitReader = disposer.track(new GitReader()) const outputChannel = disposer.track( @@ -153,6 +155,7 @@ export const buildInternalCommands = (disposer: Disposer) => { dvcExecutor, dvcReader, dvcRunner, + dvcViewer, gitReader ) ) @@ -162,6 +165,7 @@ export const buildInternalCommands = (disposer: Disposer) => { dvcExecutor, dvcReader, dvcRunner, + dvcViewer, gitReader, internalCommands } @@ -189,6 +193,7 @@ export const buildDependencies = ( dvcExecutor, dvcReader, dvcRunner, + dvcViewer, gitReader, internalCommands } = buildInternalCommands(disposer) @@ -223,6 +228,7 @@ export const buildDependencies = ( dvcExecutor, dvcReader, dvcRunner, + dvcViewer, gitReader, internalCommands, messageSpy, diff --git a/extension/src/webview/contract.ts b/extension/src/webview/contract.ts index ccb4f574f1..00d071112b 100644 --- a/extension/src/webview/contract.ts +++ b/extension/src/webview/contract.ts @@ -35,6 +35,7 @@ export enum MessageFromWebviewType { RESIZE_PLOTS = 'resize-plots', SAVE_STUDIO_TOKEN = 'save-studio-token', SHARE_EXPERIMENT_TO_STUDIO = 'share-experiment-to-studio', + SHOW_EXPERIMENT_LOGS = 'show-experiment-logs', STOP_EXPERIMENT = 'stop-experiment', SORT_COLUMN = 'sort-column', TOGGLE_EXPERIMENT = 'toggle-experiment', @@ -136,6 +137,7 @@ export type MessageFromWebview = type: MessageFromWebviewType.STOP_EXPERIMENT payload: { id: string; executor?: string | null }[] } + | { type: MessageFromWebviewType.SHOW_EXPERIMENT_LOGS; payload: string } | { type: MessageFromWebviewType.SHARE_EXPERIMENT_TO_STUDIO payload: string diff --git a/webview/src/experiments/components/App.test.tsx b/webview/src/experiments/components/App.test.tsx index bbed320f13..edd4d4a208 100644 --- a/webview/src/experiments/components/App.test.tsx +++ b/webview/src/experiments/components/App.test.tsx @@ -864,6 +864,7 @@ describe('App', () => { const menuitems = screen.getAllByRole('menuitem') const itemLabels = menuitems.map(item => item.textContent) expect(itemLabels).toStrictEqual([ + 'Show Logs', 'Apply to Workspace', 'Create new Branch', 'Share to Studio', @@ -888,7 +889,7 @@ describe('App', () => { fireEvent.contextMenu(row, { bubbles: true }) advanceTimersByTime(100) - expect(screen.getAllByRole('menuitem')).toHaveLength(11) + expect(screen.getAllByRole('menuitem')).toHaveLength(12) fireEvent.click(window, { bubbles: true }) advanceTimersByTime(100) @@ -902,7 +903,7 @@ describe('App', () => { fireEvent.contextMenu(row, { bubbles: true }) advanceTimersByTime(100) - expect(screen.getAllByRole('menuitem')).toHaveLength(11) + expect(screen.getAllByRole('menuitem')).toHaveLength(12) const commit = getRow('main') fireEvent.click(commit, { bubbles: true }) @@ -917,13 +918,13 @@ describe('App', () => { fireEvent.contextMenu(row, { bubbles: true }) advanceTimersByTime(100) - expect(screen.queryAllByRole('menuitem')).toHaveLength(11) + expect(screen.queryAllByRole('menuitem')).toHaveLength(12) fireEvent.contextMenu(within(row).getByText('[exp-e7a67]'), { bubbles: true }) advanceTimersByTime(200) - expect(screen.queryAllByRole('menuitem')).toHaveLength(11) + expect(screen.queryAllByRole('menuitem')).toHaveLength(12) }) it('should present the Remove experiment option for the checkpoint tips', () => { diff --git a/webview/src/experiments/components/table/RowContextMenu.tsx b/webview/src/experiments/components/table/RowContextMenu.tsx index 105ba077b3..11c8513362 100644 --- a/webview/src/experiments/components/table/RowContextMenu.tsx +++ b/webview/src/experiments/components/table/RowContextMenu.tsx @@ -3,7 +3,8 @@ import { MessageFromWebviewType } from 'dvc/src/webview/contract' import { ExperimentStatus, isQueued, - isRunning + isRunning, + isRunningInQueue } from 'dvc/src/experiments/webview/contract' import { EXPERIMENT_WORKSPACE_ID } from 'dvc/src/cli/dvc/contract' import { RowProp } from './interfaces' @@ -190,6 +191,12 @@ const getSingleSelectMenuOptions = ( ) return [ + experimentMenuOption( + id, + 'Show Logs', + MessageFromWebviewType.SHOW_EXPERIMENT_LOGS, + !isRunningInQueue({ executor, status }) + ), notRunningWithId( 'Apply to Workspace', MessageFromWebviewType.APPLY_EXPERIMENT_TO_WORKSPACE, diff --git a/webview/src/plots/components/PlotsContainer.tsx b/webview/src/plots/components/PlotsContainer.tsx index 3f65989b92..c157d23d3f 100644 --- a/webview/src/plots/components/PlotsContainer.tsx +++ b/webview/src/plots/components/PlotsContainer.tsx @@ -23,11 +23,7 @@ import { import { isSelecting } from '../../util/strings' import { isTooltip } from '../../util/helpers' -export interface CommonPlotsContainerProps { - onResize: (size: number) => void -} - -export interface PlotsContainerProps extends CommonPlotsContainerProps { +export interface PlotsContainerProps { sectionCollapsed: boolean sectionKey: Section title: string diff --git a/webview/src/plots/components/checkpointPlots/CheckpointPlotsWrapper.tsx b/webview/src/plots/components/checkpointPlots/CheckpointPlotsWrapper.tsx index 708e5194b3..a1a8b03ef3 100644 --- a/webview/src/plots/components/checkpointPlots/CheckpointPlotsWrapper.tsx +++ b/webview/src/plots/components/checkpointPlots/CheckpointPlotsWrapper.tsx @@ -1,15 +1,13 @@ import { Section } from 'dvc/src/plots/webview/contract' import { MessageFromWebviewType } from 'dvc/src/webview/contract' import React, { useEffect, useState } from 'react' -import { useSelector, useDispatch } from 'react-redux' +import { useSelector } from 'react-redux' import { CheckpointPlots } from './CheckpointPlots' -import { changeSize } from './checkpointPlotsSlice' import { PlotsContainer } from '../PlotsContainer' import { sendMessage } from '../../../shared/vscode' import { PlotsState } from '../../store' export const CheckpointPlotsWrapper: React.FC = () => { - const dispatch = useDispatch() const { plotsIds, size, selectedMetrics, isCollapsed, colors } = useSelector( (state: PlotsState) => state.checkpoint ) @@ -29,10 +27,6 @@ export const CheckpointPlotsWrapper: React.FC = () => { }) } - const handleResize = (size: number) => { - dispatch(changeSize(size)) - } - const menu = plotsIds.length > 0 ? { @@ -49,7 +43,6 @@ export const CheckpointPlotsWrapper: React.FC = () => { menu={menu} currentSize={size} sectionCollapsed={isCollapsed} - onResize={handleResize} > diff --git a/webview/src/plots/components/comparisonTable/ComparisonTableWrapper.tsx b/webview/src/plots/components/comparisonTable/ComparisonTableWrapper.tsx index e7d07f960f..476c4cf9fb 100644 --- a/webview/src/plots/components/comparisonTable/ComparisonTableWrapper.tsx +++ b/webview/src/plots/components/comparisonTable/ComparisonTableWrapper.tsx @@ -1,19 +1,14 @@ import { Section } from 'dvc/src/plots/webview/contract' import React from 'react' -import { useSelector, useDispatch } from 'react-redux' +import { useSelector } from 'react-redux' import { ComparisonTable } from './ComparisonTable' -import { changeSize } from './comparisonTableSlice' import { PlotsContainer } from '../PlotsContainer' import { PlotsState } from '../../store' export const ComparisonTableWrapper: React.FC = () => { - const dispatch = useDispatch() const { size, isCollapsed } = useSelector( (state: PlotsState) => state.comparison ) - const handleResize = (size: number) => { - dispatch(changeSize(size)) - } return ( { sectionKey={Section.COMPARISON_TABLE} currentSize={size} sectionCollapsed={isCollapsed} - onResize={handleResize} > diff --git a/webview/src/plots/components/templatePlots/TemplatePlotsWrapper.tsx b/webview/src/plots/components/templatePlots/TemplatePlotsWrapper.tsx index 2b45f58120..1f0ed0b4fc 100644 --- a/webview/src/plots/components/templatePlots/TemplatePlotsWrapper.tsx +++ b/webview/src/plots/components/templatePlots/TemplatePlotsWrapper.tsx @@ -1,28 +1,20 @@ import { Section } from 'dvc/src/plots/webview/contract' import React from 'react' -import { useSelector, useDispatch } from 'react-redux' +import { useSelector } from 'react-redux' import { TemplatePlots } from './TemplatePlots' -import { changeSize } from './templatePlotsSlice' import { PlotsContainer } from '../PlotsContainer' import { PlotsState } from '../../store' export const TemplatePlotsWrapper: React.FC = () => { - const dispatch = useDispatch() const { size, isCollapsed } = useSelector( (state: PlotsState) => state.template ) - - const handleResize = (size: number) => { - dispatch(changeSize(size)) - } - return ( diff --git a/webview/src/shared/components/emptyState/styles.module.scss b/webview/src/shared/components/emptyState/styles.module.scss index 751e59cd36..0613e16a2b 100644 --- a/webview/src/shared/components/emptyState/styles.module.scss +++ b/webview/src/shared/components/emptyState/styles.module.scss @@ -6,7 +6,7 @@ } .emptySection { - width: 100vw; + width: 100%; height: 33vh; } diff --git a/yarn.lock b/yarn.lock index 1a9006a209..8f44cb560e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4442,10 +4442,10 @@ resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43" integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA== -"@types/uuid@9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.0.tgz#53ef263e5239728b56096b0a869595135b7952d2" - integrity sha512-kr90f+ERiQtKWMz5rP32ltJ/BtULDI5RVO0uavn1HQUOwjx0R1h0rnDYNL0CepF1zL5bSY6FISAfd9tOdDhU5Q== +"@types/uuid@9.0.1": + version "9.0.1" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.1.tgz#98586dc36aee8dacc98cc396dbca8d0429647aa6" + integrity sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA== "@types/vscode@1.64.0": version "1.64.0"