From 9b55012bc25da074d577fc22a1fb19cc2ae21d9d Mon Sep 17 00:00:00 2001 From: Matt Seddon <37993418+mattseddon@users.noreply.github.com> Date: Tue, 5 Sep 2023 09:03:09 +1000 Subject: [PATCH] Provide shareable links for all experiments found on Studio (#4600) * hack together new integration * fix webview tests * fix integration tests * refactor * add test for call to Studio --- extension/src/data/index.ts | 16 +++- extension/src/experiments/data/index.ts | 84 ++++++++++++++++-- extension/src/experiments/index.ts | 30 +++++-- extension/src/experiments/model/collect.ts | 4 +- extension/src/experiments/model/index.test.ts | 1 + extension/src/experiments/model/index.ts | 61 +++++++++++-- extension/src/experiments/studio.ts | 86 +++++++++---------- extension/src/experiments/webview/contract.ts | 8 +- extension/src/experiments/webview/messages.ts | 16 ++-- extension/src/experiments/workspace.ts | 4 +- .../src/test/fixtures/expShow/base/rows.ts | 9 ++ .../test/fixtures/expShow/base/tableData.ts | 1 - .../test/suite/experiments/data/index.test.ts | 69 ++++++++++++++- .../src/test/suite/experiments/index.test.ts | 48 ++--------- extension/src/test/suite/experiments/util.ts | 31 ++++++- extension/src/webview/contract.ts | 4 +- .../src/experiments/components/App.test.tsx | 61 ++++++++++--- webview/src/experiments/components/App.tsx | 6 +- .../components/table/body/Cell.tsx | 3 +- .../table/body/ExperimentStatusIndicator.tsx | 21 +++-- .../components/table/body/RowContextMenu.tsx | 36 ++++++-- .../src/experiments/state/tableDataSlice.ts | 8 -- webview/src/experiments/util/messages.ts | 5 +- webview/src/stories/Table.stories.tsx | 1 - 24 files changed, 440 insertions(+), 173 deletions(-) diff --git a/extension/src/data/index.ts b/extension/src/data/index.ts index 07bdb77a7f..a7f44856ed 100644 --- a/extension/src/data/index.ts +++ b/extension/src/data/index.ts @@ -18,13 +18,27 @@ type LocalExperimentsOutput = { type RemoteExperimentsOutput = { lsRemoteOutput: string } -export type ExperimentsOutput = LocalExperimentsOutput | RemoteExperimentsOutput +type StudioExperimentsOutput = { + baseUrl: string | undefined + live: { baselineSha: string; name: string }[] + pushed: string[] +} + +export type ExperimentsOutput = + | LocalExperimentsOutput + | RemoteExperimentsOutput + | StudioExperimentsOutput export const isRemoteExperimentsOutput = ( data: ExperimentsOutput ): data is RemoteExperimentsOutput => (data as RemoteExperimentsOutput).lsRemoteOutput !== undefined +export const isStudioExperimentsOutput = ( + data: ExperimentsOutput +): data is StudioExperimentsOutput => + (data as StudioExperimentsOutput).live !== undefined + export abstract class BaseData< T extends | { data: PlotsOutputOrError; revs: string[] } diff --git a/extension/src/experiments/data/index.ts b/extension/src/experiments/data/index.ts index fe1868805c..8d86552767 100644 --- a/extension/src/experiments/data/index.ts +++ b/extension/src/experiments/data/index.ts @@ -1,3 +1,5 @@ +import querystring from 'querystring' +import fetch from 'node-fetch' import { collectBranches, collectFiles } from './collect' import { EXPERIMENTS_GIT_LOGS_REFS, @@ -11,7 +13,8 @@ import { ExpShowOutput } from '../../cli/dvc/contract' import { BaseData, ExperimentsOutput, - isRemoteExperimentsOutput + isRemoteExperimentsOutput, + isStudioExperimentsOutput } from '../../data' import { Args, @@ -22,14 +25,18 @@ import { import { COMMITS_SEPARATOR, gitPath } from '../../cli/git/constants' import { getGitPath } from '../../fileSystem' import { ExperimentsModel } from '../model' +import { Studio } from '../studio' +import { STUDIO_URL } from '../../setup/webview/contract' export class ExperimentsData extends BaseData { private readonly experiments: ExperimentsModel + private readonly studio: Studio constructor( dvcRoot: string, internalCommands: InternalCommands, experiments: ExperimentsModel, + studio: Studio, subProjects: string[] ) { super( @@ -47,6 +54,7 @@ export class ExperimentsData extends BaseData { ) this.experiments = experiments + this.studio = studio void this.watchExpGitRefs() void this.managedUpdate() @@ -58,10 +66,13 @@ export class ExperimentsData extends BaseData { } public async update(): Promise { - await Promise.all([this.updateExpShow(), this.updateRemoteExpRefs()]) + await Promise.all([ + this.updateExpShowAndStudio(), + this.updateRemoteExpRefs() + ]) } - private async updateExpShow() { + private async updateExpShowAndStudio() { await this.updateBranches() const [currentBranch, ...branches] = this.experiments.getBranchesToShow() const availableNbCommits: { [branch: string]: number } = {} @@ -77,8 +88,21 @@ export class ExperimentsData extends BaseData { } const branchLogs = await Promise.all(promises) - const { args, gitLog, rowOrder } = this.collectGitLogAndOrder(branchLogs) + const { args, gitLog, rowOrder, shas } = + this.collectGitLogAndOrder(branchLogs) + + return Promise.all([ + this.updateExpShow(args, availableNbCommits, gitLog, rowOrder), + this.requestStudioData(shas) + ]) + } + private async updateExpShow( + args: Args, + availableNbCommits: { [branch: string]: number }, + gitLog: string, + rowOrder: { branch: string; sha: string }[] + ) { const expShow = await this.internalCommands.executeCommand( AvailableCommands.EXP_SHOW, this.dvcRoot, @@ -90,6 +114,49 @@ export class ExperimentsData extends BaseData { this.collectFiles({ expShow }) } + private async requestStudioData(shas: string[]) { + await this.studio.isReady() + + const defaultData = { baseUrl: undefined, live: [], pushed: [] } + + const studioAccessToken = this.studio.getAccessToken() + + if (!studioAccessToken || shas.length === 0) { + this.notifyChanged(defaultData) + return + } + + const params = querystring.stringify({ + commits: shas, + git_remote_url: this.studio.getGitRemoteUrl() + }) + + try { + const response = await fetch(`${STUDIO_URL}/api/view-links?${params}`, { + headers: { + Authorization: `token ${studioAccessToken}` + }, + method: 'GET' + }) + + const { live, pushed, view_url } = (await response.json()) as { + live: { baseline_sha: string; name: string }[] + pushed: string[] + view_url: string + } + this.notifyChanged({ + baseUrl: view_url, + live: live.map(({ baseline_sha, name }) => ({ + baselineSha: baseline_sha, + name + })), + pushed + }) + } catch { + this.notifyChanged(defaultData) + } + } + private async collectGitLogByBranch( branch: string, availableNbCommits: { [branch: string]: number }, @@ -122,6 +189,7 @@ export class ExperimentsData extends BaseData { ) { const rowOrder: { branch: string; sha: string }[] = [] const args: Args = [] + const shas = [] const gitLog: string[] = [] for (const { branch, branchLog } of branchLogs) { @@ -133,9 +201,10 @@ export class ExperimentsData extends BaseData { continue } args.push(ExperimentFlag.REV, sha) + shas.push(sha) } } - return { args, gitLog: gitLog.join(COMMITS_SEPARATOR), rowOrder } + return { args, gitLog: gitLog.join(COMMITS_SEPARATOR), rowOrder, shas } } private async updateBranches() { @@ -200,7 +269,10 @@ export class ExperimentsData extends BaseData { private waitForInitialLocalData() { const waitForInitialData = this.dispose.track( this.onDidUpdate(data => { - if (isRemoteExperimentsOutput(data)) { + if ( + isRemoteExperimentsOutput(data) || + isStudioExperimentsOutput(data) + ) { return } this.dispose.untrack(waitForInitialData) diff --git a/extension/src/experiments/index.ts b/extension/src/experiments/index.ts index da693b70fd..579d5725bd 100644 --- a/extension/src/experiments/index.ts +++ b/extension/src/experiments/index.ts @@ -35,7 +35,11 @@ import { DecorationProvider } from './model/decorationProvider' import { starredFilter } from './model/filterBy/constants' import { ResourceLocator } from '../resourceLocator' import { AvailableCommands, InternalCommands } from '../commands/internal' -import { ExperimentsOutput, isRemoteExperimentsOutput } from '../data' +import { + ExperimentsOutput, + isRemoteExperimentsOutput, + isStudioExperimentsOutput +} from '../data' import { ViewKey } from '../webview/constants' import { BaseRepository } from '../webview/repository' import { Title } from '../vscode/title' @@ -159,6 +163,7 @@ export class Experiments extends BaseRepository { dvcRoot, internalCommands, this.experiments, + this.studio, subProjects ) ) @@ -189,6 +194,13 @@ export class Experiments extends BaseRepository { return this.webviewMessages.sendWebviewMessage() } + if (isStudioExperimentsOutput(data)) { + const { live, pushed, baseUrl } = data + this.studio.setBaseUrl(baseUrl) + this.experiments.setStudioData(live, pushed) + return this.webviewMessages.sendWebviewMessage() + } + const { expShow, gitLog, rowOrder, availableNbCommits } = data const hadCheckpoints = this.hasCheckpoints() @@ -597,13 +609,15 @@ export class Experiments extends BaseRepository { return this.data.update() } - public async setStudioBaseUrl(studioToken: string | undefined) { - await Promise.all([this.isReady(), this.experiments.isReady()]) - await this.studio.setBaseUrl( - studioToken, - this.experiments.getRemoteExpRefs() - ) - return this.webviewMessages.sendWebviewMessage() + public setStudioAccessToken(studioAccessToken: string | undefined) { + const oldAccessToken = this.studio.getAccessToken() + const accessTokenInitialized = this.studio.isAccessTokenSet() + this.studio.setAccessToken(studioAccessToken) + + if (!accessTokenInitialized || oldAccessToken === studioAccessToken) { + return + } + return this.data.managedUpdate() } public hasDvcLiveOnlyRunning() { diff --git a/extension/src/experiments/model/collect.ts b/extension/src/experiments/model/collect.ts index 9d46a722fa..6fe57b299a 100644 --- a/extension/src/experiments/model/collect.ts +++ b/extension/src/experiments/model/collect.ts @@ -262,7 +262,7 @@ const collectExpRange = ( const expState = revs[0] const { name, rev } = expState - const { id: baselineId } = baseline + const { id: baselineId, sha: baselineSha } = baseline const label = rev === EXPERIMENT_WORKSPACE_ID @@ -292,6 +292,8 @@ const collectExpRange = ( experiment.description = `[${name}]` } + experiment.baselineSha = baselineSha + collectExecutorInfo(experiment, executor) collectRunningExperiment(acc, experiment) diff --git a/extension/src/experiments/model/index.test.ts b/extension/src/experiments/model/index.test.ts index 62cdd261b4..d476f87dec 100644 --- a/extension/src/experiments/model/index.test.ts +++ b/extension/src/experiments/model/index.test.ts @@ -57,6 +57,7 @@ describe('ExperimentsModel', () => { { main: 6 } ) model.transformAndSetRemote(remoteExpRefsFixture) + model.setStudioData([], ['42b8736b08170529903cd203a1f40382a4b4a8cd']) expect(model.getRowData()).toStrictEqual(rowsFixture) }) diff --git a/extension/src/experiments/model/index.ts b/extension/src/experiments/model/index.ts index 4c8002a885..e7b16903d2 100644 --- a/extension/src/experiments/model/index.ts +++ b/extension/src/experiments/model/index.ts @@ -26,7 +26,8 @@ import { isRunning, GitRemoteStatus, RunningExperiment, - WORKSPACE_BRANCH + WORKSPACE_BRANCH, + StudioLinkType } from '../webview/contract' import { reorderListSubset } from '../../util/array' import { @@ -80,9 +81,13 @@ export class ExperimentsModel extends ModelWithPersistence { private filters: Map = new Map() private remoteExpShas?: Set - private remoteExpRefs: string[] = [] private pushing = new Set() + private studioLiveOnlyExperiments: { baselineSha: string; name: string }[] = + [] + + private studioPushedExperiments: string[] = [] + private currentSorts: SortDefinition[] private running: RunningExperiment[] = [] private startedRunning: Set = new Set() @@ -176,10 +181,8 @@ export class ExperimentsModel extends ModelWithPersistence { } public transformAndSetRemote(lsRemoteOutput: string) { - const { remoteExpShas, remoteExpRefs } = - collectRemoteExpDetails(lsRemoteOutput) + const { remoteExpShas } = collectRemoteExpDetails(lsRemoteOutput) this.remoteExpShas = remoteExpShas - this.remoteExpRefs = remoteExpRefs this.deferred.resolve() } @@ -390,6 +393,16 @@ export class ExperimentsModel extends ModelWithPersistence { }) } + public getExperimentShas(id: string) { + for (const experiment of this.getExperiments()) { + if (experiment.id === id) { + const { baselineSha, sha } = experiment + return { baselineSha, sha } + } + } + return { baselineSha: undefined, sha: undefined } + } + public getCommitsAndExperiments() { return collectOrderedCommitsAndExperiments(this.commits, commit => this.getExperimentsByCommit(commit) @@ -543,8 +556,12 @@ export class ExperimentsModel extends ModelWithPersistence { return this.availableBranchesToSelect } - public getRemoteExpRefs() { - return this.remoteExpRefs + public setStudioData( + live: { baselineSha: string; name: string }[], + pushed: string[] + ) { + this.studioLiveOnlyExperiments = live + this.studioPushedExperiments = pushed } public hasDvcLiveOnlyRunning() { @@ -596,6 +613,7 @@ export class ExperimentsModel extends ModelWithPersistence { const experiment = this.addDetails(originalExperiment) this.addRemoteStatus(experiment) + this.addStudioLinkType(experiment, commit.sha) return experiment }) @@ -623,6 +641,35 @@ export class ExperimentsModel extends ModelWithPersistence { : GitRemoteStatus.NOT_ON_REMOTE } + private addStudioLinkType( + experiment: Experiment, + baselineSha: string | undefined + ) { + if ( + this.studioLiveOnlyExperiments.length === 0 && + this.studioPushedExperiments.length === 0 + ) { + return + } + + if ( + experiment.sha && + this.studioPushedExperiments.includes(experiment.sha) + ) { + experiment.studioLinkType = StudioLinkType.PUSHED + return + } + + if ( + this.studioLiveOnlyExperiments.some( + ({ baselineSha: expBaselineSha, name }) => + baselineSha === expBaselineSha && experiment.id === name + ) + ) { + experiment.studioLinkType = StudioLinkType.LIVE + } + } + private setColoredStatus(runningExperiments: RunningExperiment[]) { this.setRunning(runningExperiments) diff --git a/extension/src/experiments/studio.ts b/extension/src/experiments/studio.ts index 367d09759f..8a6da91675 100644 --- a/extension/src/experiments/studio.ts +++ b/extension/src/experiments/studio.ts @@ -1,69 +1,63 @@ -import fetch from 'node-fetch' -import { STUDIO_URL } from '../setup/webview/contract' -import { Disposable } from '../class/dispose' +import { StudioLinkType } from './webview/contract' import { AvailableCommands, InternalCommands } from '../commands/internal' +import { DeferredDisposable } from '../class/deferred' -export class Studio extends Disposable { +export class Studio extends DeferredDisposable { private readonly dvcRoot: string private readonly internalCommands: InternalCommands - private baseUrl?: string + private baseUrl: string | undefined = undefined + private studioAccessToken: string | undefined + private gitRemoteUrl?: string + + private accessTokenSet = false constructor(dvcRoot: string, internalCommands: InternalCommands) { super() this.dvcRoot = dvcRoot this.internalCommands = internalCommands + + void this.internalCommands + .executeCommand(AvailableCommands.GIT_GET_REMOTE_URL, this.dvcRoot) + .then(gitRemoteUrl => (this.gitRemoteUrl = gitRemoteUrl)) } - public isConnected() { - return !!this.baseUrl + public getGitRemoteUrl() { + return this.gitRemoteUrl } - public async setBaseUrl( - studioToken: string | undefined, - remoteExpRefs: string[] - ) { - if (!studioToken) { - this.baseUrl = undefined - return - } + public setAccessToken(studioAccessToken: string | undefined) { + this.studioAccessToken = studioAccessToken + this.accessTokenSet = true + this.deferred.resolve() + } - const gitRemote = await this.internalCommands.executeCommand( - AvailableCommands.GIT_GET_REMOTE_URL, - this.dvcRoot - ) + public isAccessTokenSet() { + return this.accessTokenSet + } - this.baseUrl = await this.getBaseUrl(studioToken, gitRemote, remoteExpRefs) + public getAccessToken() { + return this.studioAccessToken } - public getLink(sha: string) { - if (!this.baseUrl) { - return '' - } - return `${this.baseUrl}?showOnlySelected=1&experimentReferences=${sha}&activeExperimentReferences=${sha}%3Aprimary` + public setBaseUrl(baseUrl: string | undefined) { + this.baseUrl = baseUrl } - private async getBaseUrl( - studioToken: string, - gitRemoteUrl: string, - remoteExpRefs: string[] + public getLink( + studioLinkType: StudioLinkType, + sha: string, + name: string, + baselineSha: string ) { - try { - const response = await fetch(`${STUDIO_URL}/webhook/dvc`, { - body: JSON.stringify({ - client: 'vscode', - refs: { pushed: remoteExpRefs }, - repo_url: gitRemoteUrl - }), - headers: { - Authorization: `token ${studioToken}`, - 'Content-Type': 'application/json' - }, - method: 'POST' - }) - - const { url } = (await response.json()) as { url: string } - return url - } catch {} + if (!this.baseUrl) { + return '' + } + return ( + `${this.baseUrl}?showOnlySelected=1&` + + (studioLinkType === StudioLinkType.PUSHED + ? `experimentReferences=${sha}` + : `liveExperiments=${baselineSha}%3A${name}`) + ) } } diff --git a/extension/src/experiments/webview/contract.ts b/extension/src/experiments/webview/contract.ts index 7428f47f63..eeacbb1c3c 100644 --- a/extension/src/experiments/webview/contract.ts +++ b/extension/src/experiments/webview/contract.ts @@ -33,6 +33,11 @@ export enum GitRemoteStatus { ON_REMOTE = 'on-remote' } +export enum StudioLinkType { + LIVE = 'live', + PUSHED = 'pushed' +} + export type Experiment = { commit?: CommitData Created?: string @@ -53,6 +58,8 @@ export type Experiment = { executorStatus?: ExecutorStatus timestamp?: string | null branch?: string | typeof WORKSPACE_BRANCH + baselineSha?: string | undefined + studioLinkType?: StudioLinkType } export const isRunning = ( @@ -106,7 +113,6 @@ export type TableData = { hasConfig: boolean hasMoreCommits: Record hasRunningWorkspaceExperiment: boolean - isStudioConnected: boolean isShowingMoreCommits: Record rows: Commit[] showOnlyChanged: boolean diff --git a/extension/src/experiments/webview/messages.ts b/extension/src/experiments/webview/messages.ts index 02fe6fee2f..9d00f64d2c 100644 --- a/extension/src/experiments/webview/messages.ts +++ b/extension/src/experiments/webview/messages.ts @@ -1,5 +1,5 @@ import { commands, Uri, ViewColumn, window } from 'vscode' -import { TableData } from './contract' +import { StudioLinkType, TableData } from './contract' import { RegisteredCliCommands, RegisteredCommands @@ -236,7 +236,7 @@ export class WebviewMessages { return this.copyToClipboard(message.payload) case MessageFromWebviewType.COPY_STUDIO_LINK: - return this.copyStudioLink(message.payload) + return this.copyStudioLink(message.payload.id, message.payload.type) default: Logger.error(`Unexpected message: ${JSON.stringify(message)}`) @@ -321,7 +321,6 @@ export class WebviewMessages { hasMoreCommits, hasRunningWorkspaceExperiment, isShowingMoreCommits, - isStudioConnected, rows, selectedBranches, selectedForPlotsCount, @@ -340,7 +339,6 @@ export class WebviewMessages { this.experiments.getHasMoreCommits(), this.experiments.hasRunningWorkspaceExperiment(), this.experiments.getIsShowingMoreCommits(), - this.studio.isConnected(), this.experiments.getRowData(), this.experiments.getSelectedBranches(), this.experiments.getSelectedRevisions().length, @@ -365,7 +363,6 @@ export class WebviewMessages { hasMoreCommits, hasRunningWorkspaceExperiment, isShowingMoreCommits, - isStudioConnected, rows, selectedBranches, selectedForPlotsCount, @@ -595,15 +592,14 @@ export class WebviewMessages { ) } - private async copyStudioLink(id: string) { - const sha = this.experiments.getExperiments().find(exp => exp.id === id) - ?.sha + private async copyStudioLink(id: string, studioLinkType: StudioLinkType) { + const { baselineSha, sha } = this.experiments.getExperimentShas(id) - if (!sha) { + if (!(sha && baselineSha)) { return } - const link = this.studio.getLink(sha) + const link = this.studio.getLink(studioLinkType, sha, id, baselineSha) await writeToClipboard(link, `[Studio link](${link})`) diff --git a/extension/src/experiments/workspace.ts b/extension/src/experiments/workspace.ts index bb82f50a71..d1ebaf88f5 100644 --- a/extension/src/experiments/workspace.ts +++ b/extension/src/experiments/workspace.ts @@ -299,11 +299,11 @@ export class WorkspaceExperiments extends BaseWorkspaceWebviews< this.setRepository(dvcRoot, experiments) - void experiments.setStudioBaseUrl(setup.getStudioAccessToken()) + void experiments.setStudioAccessToken(setup.getStudioAccessToken()) experiments.dispose.track( setup.onDidChangeStudioConnection(() => { - void experiments.setStudioBaseUrl(setup.getStudioAccessToken()) + void experiments.setStudioAccessToken(setup.getStudioAccessToken()) }) ) diff --git a/extension/src/test/fixtures/expShow/base/rows.ts b/extension/src/test/fixtures/expShow/base/rows.ts index 9957ca09bc..b14cdcd401 100644 --- a/extension/src/test/fixtures/expShow/base/rows.ts +++ b/extension/src/test/fixtures/expShow/base/rows.ts @@ -2,6 +2,7 @@ import { join } from '../../../util/path' import { Commit, GitRemoteStatus, + StudioLinkType, WORKSPACE_BRANCH } from '../../../../experiments/webview/contract' import { copyOriginalColors } from '../../../../experiments/model/status/colors' @@ -143,6 +144,7 @@ const rowsFixture: Commit[] = [ subRows: [ { branch: 'main', + baselineSha: '53c3851f46955fa3e2b8f6e1c52999acc8c9ea77', deps: { [join('data', 'data.xml')]: valueWithNoChanges( '22a1a2931c8370d3aeedd7183606fd7f' @@ -202,6 +204,7 @@ const rowsFixture: Commit[] = [ }, { branch: 'main', + baselineSha: '53c3851f46955fa3e2b8f6e1c52999acc8c9ea77', deps: { [join('data', 'data.xml')]: valueWithNoChanges( '22a1a2931c8370d3aeedd7183606fd7f' @@ -253,6 +256,7 @@ const rowsFixture: Commit[] = [ } }, gitRemoteStatus: GitRemoteStatus.ON_REMOTE, + studioLinkType: StudioLinkType.PUSHED, selected: false, sha: '42b8736b08170529903cd203a1f40382a4b4a8cd', starred: false, @@ -260,6 +264,7 @@ const rowsFixture: Commit[] = [ }, { branch: 'main', + baselineSha: '53c3851f46955fa3e2b8f6e1c52999acc8c9ea77', deps: { [join('data', 'data.xml')]: valueWithNoChanges( '22a1a2931c8370d3aeedd7183606fd7f' @@ -318,6 +323,7 @@ const rowsFixture: Commit[] = [ }, { branch: 'main', + baselineSha: '53c3851f46955fa3e2b8f6e1c52999acc8c9ea77', displayColor: undefined, id: '489fd8b', sha: '489fd8bdaa709f7330aac342e051a9431c625481', @@ -330,6 +336,7 @@ const rowsFixture: Commit[] = [ }, { branch: 'main', + baselineSha: '53c3851f46955fa3e2b8f6e1c52999acc8c9ea77', deps: { [join('data', 'data.xml')]: valueWithNoChanges( '22a1a2931c8370d3aeedd7183606fd7f' @@ -383,6 +390,7 @@ const rowsFixture: Commit[] = [ }, { branch: 'main', + baselineSha: '53c3851f46955fa3e2b8f6e1c52999acc8c9ea77', displayColor: undefined, deps: { [join('data', 'data.xml')]: valueWithNoChanges( @@ -432,6 +440,7 @@ const rowsFixture: Commit[] = [ }, { branch: 'main', + baselineSha: '53c3851f46955fa3e2b8f6e1c52999acc8c9ea77', displayColor: undefined, deps: { [join('data', 'data.xml')]: valueWithNoChanges( diff --git a/extension/src/test/fixtures/expShow/base/tableData.ts b/extension/src/test/fixtures/expShow/base/tableData.ts index 98f5786837..b4b70e1629 100644 --- a/extension/src/test/fixtures/expShow/base/tableData.ts +++ b/extension/src/test/fixtures/expShow/base/tableData.ts @@ -15,7 +15,6 @@ const tableDataFixture: TableData = { hasMoreCommits: { main: true }, hasRunningWorkspaceExperiment: true, isShowingMoreCommits: { main: true }, - isStudioConnected: false, rows: rowsFixture, selectedBranches: [], selectedForPlotsCount: 2, diff --git a/extension/src/test/suite/experiments/data/index.test.ts b/extension/src/test/suite/experiments/data/index.test.ts index 18a2717b43..01a17895b8 100644 --- a/extension/src/test/suite/experiments/data/index.test.ts +++ b/extension/src/test/suite/experiments/data/index.test.ts @@ -18,7 +18,7 @@ import { CommandId, InternalCommands } from '../../../../commands/internal' -import { buildExperimentsData } from '../util' +import { buildExperimentsData, mockBaseStudioUrl } from '../util' import { DEFAULT_CURRENT_BRANCH_COMMITS_TO_SHOW, ExperimentFlag @@ -28,7 +28,13 @@ import * as FileSystem from '../../../../fileSystem' import { ExperimentsModel } from '../../../../experiments/model' import { EXPERIMENT_WORKSPACE_ID } from '../../../../cli/dvc/contract' import expShowFixture from '../../../fixtures/expShow/base/output' -import { isRemoteExperimentsOutput } from '../../../../data' +import gitLogFixture from '../../../fixtures/expShow/base/gitLog' +import { + isRemoteExperimentsOutput, + isStudioExperimentsOutput +} from '../../../../data' +import { Studio } from '../../../../experiments/studio' +import { STUDIO_URL } from '../../../../setup/webview/contract' const MOCK_WORKSPACE_GIT_FOLDER = join(dvcDemoPath, '.mock-git') @@ -52,7 +58,10 @@ suite('Experiments Data Test Suite', () => { const getDataUpdatedEvent = (data: ExperimentsData) => new Promise(resolve => data.onDidUpdate(data => { - if (isRemoteExperimentsOutput(data)) { + if ( + isRemoteExperimentsOutput(data) || + isStudioExperimentsOutput(data) + ) { return } @@ -126,6 +135,12 @@ suite('Experiments Data Test Suite', () => { }), setBranches: stub() } as unknown as ExperimentsModel, + { + getAccessToken: () => '', + getGitRemoteUrl: () => + 'git@github.com:iterative/vscode-dvc-demo.git', + isReady: () => Promise.resolve(undefined) + } as Studio, [] ) ) @@ -187,6 +202,12 @@ suite('Experiments Data Test Suite', () => { }), setBranches: stub() } as unknown as ExperimentsModel, + { + getAccessToken: () => '', + getGitRemoteUrl: () => + 'git@github.com:iterative/vscode-dvc-demo.git', + isReady: () => Promise.resolve(undefined) + } as Studio, [] ) ) @@ -302,5 +323,47 @@ suite('Experiments Data Test Suite', () => { 'other-branch' ) }) + + it('should send the expected request to Studio if the user has a token set', async () => { + const mockStudioToken = 'isat_BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB' + const { data, mockFetch } = buildExperimentsData( + disposable, + '* main', + gitLogFixture, + mockStudioToken + ) + const requestSent = new Promise(resolve => + data.onDidUpdate(data => { + if (isStudioExperimentsOutput(data)) { + resolve(undefined) + expect(data).to.deep.equal({ + live: [], + pushed: [], + view_url: mockBaseStudioUrl + }) + } + }) + ) + + await data.isReady() + + await requestSent + + expect(mockFetch).to.be.calledOnce + expect(mockFetch).to.be.calledWithExactly( + STUDIO_URL + + '/api/view-links?' + + 'commits=53c3851f46955fa3e2b8f6e1c52999acc8c9ea77' + + '&commits=fe2919bb4394b30494bea905c253e10077b9a1bd' + + '&commits=7df876cb5147800cd3e489d563bc6dcd67188621' + + '&git_remote_url=git%40github.com%3Aiterative%2Fvscode-dvc-demo.git', + { + headers: { + Authorization: `token ${mockStudioToken}` + }, + method: 'GET' + } + ) + }) }) }) diff --git a/extension/src/test/suite/experiments/index.test.ts b/extension/src/test/suite/experiments/index.test.ts index 7fedf9f9ab..1309677261 100644 --- a/extension/src/test/suite/experiments/index.test.ts +++ b/extension/src/test/suite/experiments/index.test.ts @@ -1,7 +1,6 @@ /* eslint-disable sort-keys-fix/sort-keys-fix */ import { join, resolve } from 'path' import { after, afterEach, beforeEach, describe, it, suite } from 'mocha' -import * as Fetch from 'node-fetch' import { expect } from 'chai' import { stub, spy, restore, SinonStub } from 'sinon' import { @@ -39,6 +38,7 @@ import { Column, ColumnType, Commit, + StudioLinkType, TableData } from '../../../experiments/webview/contract' import { @@ -94,7 +94,6 @@ import { MAX_SELECTED_EXPERIMENTS } from '../../../experiments/model/status' import { Pipeline } from '../../../pipeline' import { ColumnLike } from '../../../experiments/columns/like' import * as Clipboard from '../../../vscode/clipboard' -import { STUDIO_URL } from '../../../setup/webview/contract' const { openFileInEditor } = FileSystem @@ -681,23 +680,10 @@ suite('Experiments Test Suite', () => { }).timeout(WEBVIEW_TEST_TIMEOUT) it("should handle a message to copy an experiment's Studio link", async () => { - const { mockMessageReceived, experiments, gitReader } = - await buildExperimentsWebview({ disposer: disposable }) - - const viewUrl = - 'https://studio.iterative.ai/user/demo-user/projects/demo-ynm6t3jxdx' - - const mockGitRemoteUrl = 'git@github.com:iterative/vscode-dvc-demo.git' - stub(gitReader, 'getRemoteUrl').resolves(mockGitRemoteUrl) - const mockStudioToken = 'isat_BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB' - const mockFetch = stub(Fetch, 'default').resolves({ - json: () => - Promise.resolve({ - url: viewUrl - }) - } as unknown as Fetch.Response) + const { mockMessageReceived } = await buildExperimentsWebview({ + disposer: disposable + }) - await experiments.setStudioBaseUrl(mockStudioToken) const mockWriteToClipboard = stub(Clipboard, 'writeToClipboard') const writeToClipboardCalled = new Promise(resolve => mockWriteToClipboard.callsFake(() => { @@ -708,39 +694,21 @@ suite('Experiments Test Suite', () => { mockMessageReceived.fire({ type: MessageFromWebviewType.COPY_STUDIO_LINK, - payload: 'exp-e7a67' - }) - - expect(mockFetch).to.be.calledWith(`${STUDIO_URL}/webhook/dvc`, { - body: JSON.stringify({ - client: 'vscode', - refs: { - pushed: [ - 'refs/exps/a9/b32d14966b9be1396f2211d9eb743359708a07/test-branch' - ] - }, - repo_url: mockGitRemoteUrl - }), - headers: { - Authorization: `token ${mockStudioToken}`, - 'Content-Type': 'application/json' - }, - method: 'POST' + payload: { id: 'exp-e7a67', type: StudioLinkType.PUSHED } }) await writeToClipboardCalled const link = - viewUrl + + 'https://studio.iterative.ai/user/olivaw/projects/vscode-dvc-demo-ynm6t3jxdx' + '?showOnlySelected=1' + - '&experimentReferences=4fb124aebddb2adf1545030907687fa9a4c80e70' + - '&activeExperimentReferences=4fb124aebddb2adf1545030907687fa9a4c80e70%3Aprimary' + '&experimentReferences=4fb124aebddb2adf1545030907687fa9a4c80e70' expect(mockWriteToClipboard).to.be.calledOnce expect(mockWriteToClipboard).to.be.calledWithExactly( link, `[Studio link](${link})` ) - }) + }).timeout(WEBVIEW_TEST_TIMEOUT) it('should be able to handle a message to modify the workspace params and queue an experiment', async () => { const { experiments, dvcExecutor, mockMessageReceived } = diff --git a/extension/src/test/suite/experiments/util.ts b/extension/src/test/suite/experiments/util.ts index a27aff0e7b..db4b916d75 100644 --- a/extension/src/test/suite/experiments/util.ts +++ b/extension/src/test/suite/experiments/util.ts @@ -1,5 +1,6 @@ import { spy, stub } from 'sinon' import { EventEmitter } from 'vscode' +import * as Fetch from 'node-fetch' import { WorkspaceExperiments } from '../../../experiments/workspace' import { Experiments } from '../../../experiments' import { Disposer } from '../../../extension' @@ -24,6 +25,7 @@ import { PersistenceKey } from '../../../persistence/constants' import { ExpShowOutput } from '../../../cli/dvc/contract' import { buildExperimentsPipeline } from '../pipeline/util' import { Setup } from '../../../setup' +import { Studio } from '../../../experiments/studio' export const DEFAULT_EXPERIMENTS_OUTPUT = { availableNbCommits: { main: 5 }, @@ -32,22 +34,31 @@ export const DEFAULT_EXPERIMENTS_OUTPUT = { rowOrder: rowOrderFixture } +export const mockBaseStudioUrl = + 'https://studio.iterative.ai/user/olivaw/projects/vscode-dvc-demo-ynm6t3jxdx' + export const buildExperiments = ({ availableNbCommits = { main: 5 }, + baseUrl = mockBaseStudioUrl, disposer, dvcRoot = dvcDemoPath, expShow = expShowFixture, gitLog = gitLogFixture, + live = [], lsRemoteOutput = remoteExpRefsFixture, + pushed = ['42b8736b08170529903cd203a1f40382a4b4a8cd'], rowOrder = rowOrderFixture, stageList = 'train' }: { availableNbCommits?: { [branch: string]: number } disposer: Disposer + baseUrl?: string dvcRoot?: string expShow?: ExpShowOutput gitLog?: string + live?: { baselineSha: string; name: string }[] lsRemoteOutput?: string + pushed?: string[] rowOrder?: { branch: string; sha: string }[] stageList?: string | null }) => { @@ -103,7 +114,8 @@ export const buildExperiments = ({ gitLog, rowOrder }), - experiments.setState({ lsRemoteOutput }) + experiments.setState({ lsRemoteOutput }), + experiments.setState({ baseUrl, live, pushed }) ]) return { @@ -258,7 +270,8 @@ const buildExperimentsDataDependencies = (disposer: Disposer) => { export const buildExperimentsData = ( disposer: SafeWatcherDisposer, currentBranch = '* main', - commitOutput = gitLogFixture + commitOutput = gitLogFixture, + studioAccessToken = '' ) => { const { internalCommands, @@ -273,6 +286,14 @@ export const buildExperimentsData = ( commitOutput ) const mockGetNumCommits = stub(gitReader, 'getNumCommits').resolves(404) + const mockFetch = stub(Fetch, 'default').resolves({ + json: () => + Promise.resolve({ + live: [], + pushed: [], + view_url: mockBaseStudioUrl + }) + } as Fetch.Response) const mockGetBranchesToShow = stub().returns(['main']) const mockSetBranches = stub() @@ -285,6 +306,11 @@ export const buildExperimentsData = ( getNbOfCommitsToShow: () => DEFAULT_CURRENT_BRANCH_COMMITS_TO_SHOW, setBranches: mockSetBranches } as unknown as ExperimentsModel, + { + getAccessToken: () => studioAccessToken, + getGitRemoteUrl: () => 'git@github.com:iterative/vscode-dvc-demo.git', + isReady: () => Promise.resolve(undefined) + } as Studio, [] ) ) @@ -293,6 +319,7 @@ export const buildExperimentsData = ( data, mockCreateFileSystemWatcher, mockExpShow, + mockFetch, mockGetBranchesToShow, mockGetCommitMessages, mockGetNumCommits, diff --git a/extension/src/webview/contract.ts b/extension/src/webview/contract.ts index 02a9286a30..4d22b91e5d 100644 --- a/extension/src/webview/contract.ts +++ b/extension/src/webview/contract.ts @@ -1,5 +1,5 @@ import { SortDefinition } from '../experiments/model/sortBy' -import { TableData } from '../experiments/webview/contract' +import { StudioLinkType, TableData } from '../experiments/webview/contract' import { PlotHeight, PlotsData, @@ -116,7 +116,7 @@ export type MessageFromWebview = } | { type: MessageFromWebviewType.COPY_STUDIO_LINK - payload: string + payload: { id: string; type: StudioLinkType } } | { type: MessageFromWebviewType.EXPORT_PLOT_DATA_AS_JSON diff --git a/webview/src/experiments/components/App.test.tsx b/webview/src/experiments/components/App.test.tsx index 794ed022dc..5d0deeeaee 100644 --- a/webview/src/experiments/components/App.test.tsx +++ b/webview/src/experiments/components/App.test.tsx @@ -13,7 +13,8 @@ import { Column, ColumnType, Commit, - Experiment + Experiment, + StudioLinkType } from 'dvc/src/experiments/webview/contract' import { buildMetricOrParamPath } from 'dvc/src/experiments/columns/paths' import dataTypesTableFixture from 'dvc/src/test/fixtures/expShow/dataTypes/tableData' @@ -1070,7 +1071,7 @@ describe('App', () => { expect(disabledMenuItem).toBeDefined() disabledMenuItem && fireEvent.click(disabledMenuItem, { bubbles: true }) - expect(screen.queryAllByRole('menuitem')).toHaveLength(12) + expect(screen.queryAllByRole('menuitem')).toHaveLength(13) }) it('should be removed with a left click', () => { @@ -1080,7 +1081,7 @@ describe('App', () => { fireEvent.contextMenu(row, { bubbles: true }) advanceTimersByTime(100) - expect(screen.getAllByRole('menuitem')).toHaveLength(12) + expect(screen.getAllByRole('menuitem')).toHaveLength(13) fireEvent.click(row, { bubbles: true @@ -1097,7 +1098,7 @@ describe('App', () => { fireEvent.contextMenu(row, { bubbles: true }) advanceTimersByTime(100) - expect(screen.getAllByRole('menuitem')).toHaveLength(12) + expect(screen.getAllByRole('menuitem')).toHaveLength(13) const commit = getRow('main') fireEvent.click(commit, { bubbles: true }) @@ -1112,13 +1113,13 @@ describe('App', () => { fireEvent.contextMenu(row, { bubbles: true }) advanceTimersByTime(100) - expect(screen.queryAllByRole('menuitem')).toHaveLength(12) + expect(screen.queryAllByRole('menuitem')).toHaveLength(13) fireEvent.contextMenu(within(row).getByText('[exp-e7a67]'), { bubbles: true }) advanceTimersByTime(200) - expect(screen.queryAllByRole('menuitem')).toHaveLength(12) + expect(screen.queryAllByRole('menuitem')).toHaveLength(13) }) it('should enable the remove option for an experiment', () => { @@ -1483,6 +1484,31 @@ describe('App', () => { advanceTimersByTime(100) expect(selectedRows().length).toBe(0) }) + + it("should allow the user to copy an experiment's Studio link", () => { + renderTable() + + const target = screen.getByText('42b8736') + fireEvent.contextMenu(target, { bubbles: true }) + + advanceTimersByTime(100) + const menuitems = screen.getAllByRole('menuitem') + + const copyLinkOption = menuitems.find( + item => + item.textContent?.includes('Copy Studio Link') && + !item.className.includes('disabled') + ) + + expect(copyLinkOption).toBeDefined() + + copyLinkOption && fireEvent.click(copyLinkOption) + + expect(sendMessage).toHaveBeenCalledWith({ + payload: { id: 'test-branch', type: StudioLinkType.PUSHED }, + type: MessageFromWebviewType.COPY_STUDIO_LINK + }) + }) }) describe('Star', () => { @@ -2063,8 +2089,8 @@ describe('App', () => { }) }) - it('should allow copying a Studio link when an experiment exists on the remote and Studio is connected', () => { - renderTable({ ...tableStateFixture, isStudioConnected: true }) + it('should allow copying a Studio link when an experiment exists on the remote and Studio', () => { + renderTable() const copyLinkButton = within(getRow('42b8736')).getByLabelText( 'Copy Experiment Link' @@ -2072,21 +2098,30 @@ describe('App', () => { fireEvent.click(copyLinkButton) expect(mockPostMessage).toHaveBeenCalledWith({ - payload: 'test-branch', + payload: { id: 'test-branch', type: StudioLinkType.PUSHED }, type: MessageFromWebviewType.COPY_STUDIO_LINK }) }) - it('should not allow copying a Studio link when an experiment exists on the remote and Studio is not connected', () => { - renderTable({ ...tableStateFixture, isStudioConnected: false }) + it('should not allow copying a Studio link when an experiment exists on the remote but not on Studio', () => { + renderTable({ + ...tableStateFixture, + rows: tableDataFixture.rows.map(row => ({ + ...row, + subRows: row.subRows?.map(exp => ({ + ...exp, + studioLinkType: undefined + })) + })) + }) expect( within(getRow('42b8736')).queryByLabelText('Copy Experiment Link') ).not.toBeInTheDocument() }) - it('should not allow copying a Studio link when an experiment does not exist on the remote and Studio is connected', () => { - renderTable({ ...tableStateFixture, isStudioConnected: true }) + it('should not allow copying a Studio link when an experiment does not exist on the remote or Studio', () => { + renderTable() expect( within(getRow('f0f9186')).queryByLabelText('Copy Experiment Link') diff --git a/webview/src/experiments/components/App.tsx b/webview/src/experiments/components/App.tsx index 407f069f3f..c8296d0f8d 100644 --- a/webview/src/experiments/components/App.tsx +++ b/webview/src/experiments/components/App.tsx @@ -24,8 +24,7 @@ import { updateRows, updateSelectedForPlotsCount, updateSorts, - updateShowOnlyChanged, - updateIsStudioConnected + updateShowOnlyChanged } from '../state/tableDataSlice' import { useVsCodeMessaging } from '../../shared/hooks/useVsCodeMessaging' @@ -83,9 +82,6 @@ export const App: React.FC> = () => { updateIsShowingMoreCommits(data.data.isShowingMoreCommits) ) continue - case 'isStudioConnected': - dispatch(updateIsStudioConnected(data.data.isStudioConnected)) - continue case 'rows': dispatch(updateRows(data.data.rows)) continue diff --git a/webview/src/experiments/components/table/body/Cell.tsx b/webview/src/experiments/components/table/body/Cell.tsx index db7b5ff190..a738b65987 100644 --- a/webview/src/experiments/components/table/body/Cell.tsx +++ b/webview/src/experiments/components/table/body/Cell.tsx @@ -50,7 +50,7 @@ export const StubCell: React.FC< } } = cell const { - original: { error, executorStatus, gitRemoteStatus, id } + original: { error, executorStatus, gitRemoteStatus, id, studioLinkType } } = row return ( @@ -62,6 +62,7 @@ export const StubCell: React.FC< id={id} executorStatus={executorStatus} gitRemoteStatus={gitRemoteStatus} + studioLinkType={studioLinkType} /> {getIsPlaceholder() ? null : ( diff --git a/webview/src/experiments/components/table/body/ExperimentStatusIndicator.tsx b/webview/src/experiments/components/table/body/ExperimentStatusIndicator.tsx index 3b9dc2ddc3..14e9669d5e 100644 --- a/webview/src/experiments/components/table/body/ExperimentStatusIndicator.tsx +++ b/webview/src/experiments/components/table/body/ExperimentStatusIndicator.tsx @@ -5,6 +5,7 @@ import { useSelector } from 'react-redux' import { ExecutorStatus, GitRemoteStatus, + StudioLinkType, isRunning } from 'dvc/src/experiments/webview/contract' import { CellHintTooltip } from './CellHintTooltip' @@ -19,6 +20,7 @@ type ExperimentStatusIndicatorProps = { executorStatus: ExecutorStatus | undefined gitRemoteStatus: GitRemoteStatus | undefined id: string + studioLinkType: StudioLinkType | undefined } const Progress: React.FC = () => ( @@ -56,7 +58,7 @@ const CopyStudioLink: React.FC<{ id: string }> = ({ id }) => { timer.current = window.setTimeout(() => { setCopying(false) }, 1000) - return copyStudioLink(id) + return copyStudioLink(id, StudioLinkType.PUSHED) })} > = ({ id }) => { ) } -const OnRemote: React.FC<{ id: string; isStudioConnected: boolean }> = ({ +const OnRemote: React.FC<{ id: string; showLinkIcon: boolean }> = ({ id, - isStudioConnected + showLinkIcon }) => { - if (isStudioConnected) { + if (showLinkIcon) { return } return ( @@ -87,8 +89,8 @@ const OnRemote: React.FC<{ id: string; isStudioConnected: boolean }> = ({ export const ExperimentStatusIndicator: React.FC< ExperimentStatusIndicatorProps -> = ({ id, executorStatus: status, gitRemoteStatus }) => { - const { hasRunningWorkspaceExperiment, isStudioConnected } = useSelector( +> = ({ id, executorStatus: status, gitRemoteStatus, studioLinkType }) => { + const { hasRunningWorkspaceExperiment } = useSelector( (state: ExperimentsState) => state.tableData ) @@ -121,6 +123,11 @@ export const ExperimentStatusIndicator: React.FC< } if (gitRemoteStatus === GitRemoteStatus.ON_REMOTE) { - return + return ( + + ) } } diff --git a/webview/src/experiments/components/table/body/RowContextMenu.tsx b/webview/src/experiments/components/table/body/RowContextMenu.tsx index a8ed6b28e2..eda13cdb64 100644 --- a/webview/src/experiments/components/table/body/RowContextMenu.tsx +++ b/webview/src/experiments/components/table/body/RowContextMenu.tsx @@ -9,7 +9,8 @@ import { ExecutorStatus, isQueued, isRunning, - isRunningInQueue + isRunningInQueue, + StudioLinkType } from 'dvc/src/experiments/webview/contract' import { EXPERIMENT_WORKSPACE_ID } from 'dvc/src/cli/dvc/contract' import { RowProp } from '../../../util/interfaces' @@ -222,7 +223,8 @@ const getSingleSelectMenuOptions = ( depth: number, executorStatus?: ExecutorStatus, starred?: boolean, - executor?: string | null + executor?: string | null, + studioLinkType?: StudioLinkType ) => { const isNotExperiment = isQueued(executorStatus) || isWorkspace || depth <= 0 @@ -269,6 +271,7 @@ const getSingleSelectMenuOptions = ( ), { disabled: isWorkspace || !sha, + divider: true, id: MessageFromWebviewType.COPY_TO_CLIPBOARD, label: 'Copy Sha', message: { @@ -285,6 +288,15 @@ const getSingleSelectMenuOptions = ( type: MessageFromWebviewType.COPY_TO_CLIPBOARD } as MessageFromWebview }, + { + disabled: !studioLinkType, + id: MessageFromWebviewType.COPY_STUDIO_LINK, + label: 'Copy Studio Link', + message: { + payload: { id, type: studioLinkType }, + type: MessageFromWebviewType.COPY_STUDIO_LINK + } as MessageFromWebview + }, experimentMenuOption( [id], 'Push', @@ -333,7 +345,8 @@ const getContextMenuOptions = ( selectedRows: Record, executorStatus?: ExecutorStatus, starred?: boolean, - executor?: string | null + executor?: string | null, + studioLinkType?: StudioLinkType ) => { const isFromSelection = !!selectedRows[getCompositeId(id, branch)] const selectedRowsList = Object.values(selectedRows).filter( @@ -357,14 +370,23 @@ const getContextMenuOptions = ( depth, executorStatus, starred, - executor + executor, + studioLinkType ) ) } export const RowContextMenu: React.FC = ({ row: { - original: { branch, executorStatus, starred, id, executor, sha }, + original: { + branch, + executorStatus, + starred, + id, + executor, + sha, + studioLinkType + }, depth } }) => { @@ -391,7 +413,8 @@ export const RowContextMenu: React.FC = ({ selectedRows, executorStatus, starred, - executor + executor, + studioLinkType ) }, [ branch, @@ -404,6 +427,7 @@ export const RowContextMenu: React.FC = ({ sha, projectHasCheckpoints, selectedRows, + studioLinkType, hasRunningWorkspaceExperiment ]) diff --git a/webview/src/experiments/state/tableDataSlice.ts b/webview/src/experiments/state/tableDataSlice.ts index aef57a6b0c..2ee04c4943 100644 --- a/webview/src/experiments/state/tableDataSlice.ts +++ b/webview/src/experiments/state/tableDataSlice.ts @@ -42,7 +42,6 @@ export const tableDataInitialState: TableDataState = { hasMoreCommits: {}, hasRunningWorkspaceExperiment: false, isShowingMoreCommits: {}, - isStudioConnected: false, rows: [], selectedBranches: [], selectedForPlotsCount: 0, @@ -179,12 +178,6 @@ export const tableDataSlice = createSlice({ action.payload ) }, - updateIsStudioConnected: (state, action: PayloadAction) => { - state.isStudioConnected = keepReferenceIfEqual( - state.isStudioConnected, - action.payload - ) - }, updateRows: (state, action: PayloadAction) => { state.rows = keepEqualOldReferencesInArray(state.rows, action.payload) }, @@ -217,7 +210,6 @@ export const { updateHasMoreCommits, updateHasRunningWorkspaceExperiment, updateIsShowingMoreCommits, - updateIsStudioConnected, updateRows, updateSelectedBranches, updateSelectedForPlotsCount, diff --git a/webview/src/experiments/util/messages.ts b/webview/src/experiments/util/messages.ts index 3bb5c9fd36..7c9a5eec51 100644 --- a/webview/src/experiments/util/messages.ts +++ b/webview/src/experiments/util/messages.ts @@ -1,9 +1,10 @@ import { MessageFromWebviewType } from 'dvc/src/webview/contract' +import { StudioLinkType } from 'dvc/src/experiments/webview/contract' import { sendMessage } from '../../shared/vscode' -export const copyStudioLink = (id: string) => +export const copyStudioLink = (id: string, type: StudioLinkType) => sendMessage({ - payload: id, + payload: { id, type }, type: MessageFromWebviewType.COPY_STUDIO_LINK }) diff --git a/webview/src/stories/Table.stories.tsx b/webview/src/stories/Table.stories.tsx index 89af4d5cc4..a9367631b3 100644 --- a/webview/src/stories/Table.stories.tsx +++ b/webview/src/stories/Table.stories.tsx @@ -57,7 +57,6 @@ const tableData = getTableState({ hasMoreCommits: { main: true }, hasRunningWorkspaceExperiment: true, isShowingMoreCommits: { main: true }, - isStudioConnected: true, rows: rowsFixture, selectedBranches: [], selectedForPlotsCount: 2,