Skip to content

Commit ddcd7f7

Browse files
authored
Patch Studio API update timinig issue (#4619)
1 parent 58cff44 commit ddcd7f7

File tree

7 files changed

+144
-13
lines changed

7 files changed

+144
-13
lines changed

extension/src/experiments/index.ts

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import omit from 'lodash.omit'
1010
import { ColumnLike, addStarredToColumns } from './columns/like'
1111
import { setContextForEditorTitleIcons } from './context'
1212
import { ExperimentsModel } from './model'
13+
import { collectRemoteExpShas } from './model/collect'
1314
import {
1415
pickExperiment,
1516
pickExperiments,
@@ -229,9 +230,10 @@ export class Experiments extends BaseRepository<TableData> {
229230
}
230231

231232
public async unsetPushing(ids: string[]) {
232-
await this.update()
233+
await Promise.all([this.update(), this.patchStudioApiTimingIssue(ids)])
234+
233235
this.experiments.unsetPushing(ids)
234-
return this.notifyChanged()
236+
return this.webviewMessages.sendWebviewMessage()
235237
}
236238

237239
public hasCheckpoints() {
@@ -759,4 +761,56 @@ export class Experiments extends BaseRepository<TableData> {
759761
const columnLikes = addStarredToColumns(columns)
760762
return pickColumnToFilter(columnLikes)
761763
}
764+
765+
private async patchStudioApiTimingIssue(ids: string[]) {
766+
if (!this.studio.isConnected()) {
767+
return
768+
}
769+
770+
const [, { lsRemoteOutput }] = await Promise.all([
771+
this.waitForStudioUpdate(),
772+
this.waitForRemoteUpdate()
773+
])
774+
775+
const remoteExpShas = collectRemoteExpShas(lsRemoteOutput)
776+
777+
const shas = []
778+
for (const { id, sha } of this.experiments.getExperiments()) {
779+
if (ids.includes(id) && sha && remoteExpShas.has(sha)) {
780+
shas.push(sha)
781+
}
782+
}
783+
784+
this.experiments.assumePushed(shas)
785+
}
786+
787+
private waitForStudioUpdate() {
788+
return new Promise(resolve => {
789+
const listener = this.dispose.track(
790+
this.data.onDidUpdate(data => {
791+
if (!isStudioExperimentsOutput(data)) {
792+
return
793+
}
794+
this.dispose.untrack(listener)
795+
listener.dispose()
796+
resolve(undefined)
797+
})
798+
)
799+
})
800+
}
801+
802+
private waitForRemoteUpdate(): Promise<{ lsRemoteOutput: string }> {
803+
return new Promise(resolve => {
804+
const listener = this.dispose.track(
805+
this.data.onDidUpdate(data => {
806+
if (!isRemoteExperimentsOutput(data)) {
807+
return
808+
}
809+
this.dispose.untrack(listener)
810+
listener.dispose()
811+
resolve(data)
812+
})
813+
)
814+
})
815+
}
762816
}

extension/src/experiments/model/collect.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { collectExperiments, collectRemoteExpRefs } from './collect'
1+
import { collectExperiments, collectRemoteExpShas } from './collect'
22
import { generateTestExpShowOutput } from '../../test/util/experiments'
33
import { ExpShowOutput } from '../../cli/dvc/contract'
44

@@ -102,7 +102,7 @@ describe('collectExperiments', () => {
102102
})
103103
})
104104

105-
describe('collectRemoteExpRefs', () => {
105+
describe('collectRemoteExpShas', () => {
106106
it('should parse the git ls-remote output', () => {
107107
const output = `263e4408e42a0e215b0f70b36b2ab7b65a160d7e refs/exps/a9/b32d14966b9be1396f2211d9eb743359708a07/vital-taal
108108
d4f2a35773ead55b7ce4b596f600e98360e49372 refs/exps/a9/b32d14966b9be1396f2211d9eb743359708a07/whole-bout
@@ -111,7 +111,7 @@ describe('collectRemoteExpRefs', () => {
111111
390aef747f45fc49ec8928b24771f8950d057393 refs/exps/a9/d8057e088d46842f15c3b6d1bb2e4befd5f677/known-flus
112112
142a803b83ff784ba1106cc4ad0ba03310da6186 refs/exps/a9/d8057e088d46842f15c3b6d1bb2e4befd5f677/tight-lira
113113
21ce298cd1743405a0d73f5cb4cf52289ffa3276 refs/exps/bf/6ca8a35911bc6e62fb9bcaa506d4f4e185450c/crumb-orcs`
114-
const remoteExpShas = collectRemoteExpRefs(output)
114+
const remoteExpShas = collectRemoteExpShas(output)
115115
expect(remoteExpShas).toStrictEqual(
116116
new Set([
117117
'263e4408e42a0e215b0f70b36b2ab7b65a160d7e',

extension/src/experiments/model/collect.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,7 @@ export const collectRunningInWorkspace = (
485485
}
486486
}
487487

488-
export const collectRemoteExpRefs = (lsRemoteOutput: string): Set<string> => {
488+
export const collectRemoteExpShas = (lsRemoteOutput: string): Set<string> => {
489489
const acc = new Set<string>()
490490
for (const shaAndRef of trimAndSplit(lsRemoteOutput)) {
491491
const [sha] = shaAndRef.split(/\s+/)

extension/src/experiments/model/index.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
collectAddRemoveCommitsDetails,
77
collectExperiments,
88
collectOrderedCommitsAndExperiments,
9-
collectRemoteExpRefs,
9+
collectRemoteExpShas,
1010
collectRunningInQueue,
1111
collectRunningInWorkspace
1212
} from './collect'
@@ -181,7 +181,7 @@ export class ExperimentsModel extends ModelWithPersistence {
181181
}
182182

183183
public transformAndSetRemote(lsRemoteOutput: string) {
184-
const remoteExpShas = collectRemoteExpRefs(lsRemoteOutput)
184+
const remoteExpShas = collectRemoteExpShas(lsRemoteOutput)
185185
this.remoteExpShas = remoteExpShas
186186
}
187187

@@ -563,6 +563,15 @@ export class ExperimentsModel extends ModelWithPersistence {
563563
this.studioPushedExperiments = pushed
564564
}
565565

566+
public assumePushed(shas: string[]) {
567+
for (const sha of shas) {
568+
if (this.studioPushedExperiments.includes(sha)) {
569+
continue
570+
}
571+
this.studioPushedExperiments.push(sha)
572+
}
573+
}
574+
566575
public hasDvcLiveOnlyRunning() {
567576
return !!this.dvcLiveOnlyExpName
568577
}

extension/src/experiments/studio.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ export class Studio extends DeferredDisposable {
3636
return this.accessTokenSet
3737
}
3838

39+
public isConnected() {
40+
return !!this.baseUrl
41+
}
42+
3943
public getAccessToken() {
4044
return this.studioAccessToken
4145
}

extension/src/test/suite/experiments/index.test.ts

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ import {
1313
CancellationToken,
1414
WorkspaceConfiguration,
1515
MessageItem,
16-
ConfigurationTarget
16+
ConfigurationTarget,
17+
EventEmitter
1718
} from 'vscode'
1819
import {
1920
DEFAULT_EXPERIMENTS_OUTPUT,
@@ -94,6 +95,7 @@ import { MAX_SELECTED_EXPERIMENTS } from '../../../experiments/model/status'
9495
import { Pipeline } from '../../../pipeline'
9596
import { ColumnLike } from '../../../experiments/columns/like'
9697
import * as Clipboard from '../../../vscode/clipboard'
98+
import { ExperimentsOutput } from '../../../data'
9799

98100
const { openFileInEditor } = FileSystem
99101

@@ -593,8 +595,18 @@ suite('Experiments Test Suite', () => {
593595
}).timeout(WEBVIEW_TEST_TIMEOUT)
594596

595597
it('should handle a message to push an experiment', async () => {
596-
const { experiments, mockMessageReceived } =
597-
await stubWorkspaceGettersWebview(disposable)
598+
const {
599+
experiments,
600+
experimentsModel,
601+
messageSpy,
602+
mockMessageReceived,
603+
webview
604+
} = await stubWorkspaceGettersWebview(disposable)
605+
606+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
607+
const studio = (experiments as any).studio
608+
609+
const mockIsConnected = stub(studio, 'isConnected').returns(false)
598610

599611
const mockExpId = 'exp-e7a67'
600612

@@ -634,6 +646,15 @@ suite('Experiments Test Suite', () => {
634646
RegisteredCommands.SETUP_SHOW_STUDIO_CONNECT
635647
)
636648

649+
const experimentWithoutLink = experimentsModel
650+
.getRowData()[1]
651+
.subRows?.find(({ id }) => id === mockExpId)
652+
653+
expect(experimentWithoutLink?.studioLinkType).not.to.equal(
654+
StudioLinkType.PUSHED
655+
)
656+
657+
mockIsConnected.restore()
637658
mockGetStudioAccessToken.resetBehavior()
638659

639660
const tokenFound = new Promise(resolve =>
@@ -664,6 +685,25 @@ suite('Experiments Test Suite', () => {
664685
})
665686
)
666687

688+
const dataUpdated = disposable.track(
689+
new EventEmitter<ExperimentsOutput>()
690+
)
691+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
692+
;(experiments as any).data.onDidUpdate = dataUpdated.event
693+
694+
let calls = 0
695+
696+
const remoteUpdated = new Promise(resolve =>
697+
disposable.track(
698+
dataUpdated.event(() => {
699+
calls = calls + 1
700+
if (calls === 2) {
701+
resolve(undefined)
702+
}
703+
})
704+
)
705+
)
706+
667707
mockMessageReceived.fire({
668708
payload: [mockExpId],
669709
type: MessageFromWebviewType.PUSH_EXPERIMENT
@@ -677,6 +717,28 @@ suite('Experiments Test Suite', () => {
677717
message:
678718
"Experiment major-lamb is up to date on Git remote 'origin'.\nView your experiments in [Studio](https://studio.iterative.ai/user/mattseddon/projects/vscode-dvc-demo-ynm6t3jxdx)"
679719
})
720+
721+
messageSpy.restore()
722+
const mockShow = stub(webview, 'show')
723+
724+
const messageSent = new Promise(resolve =>
725+
mockShow.callsFake(() => {
726+
resolve(undefined)
727+
return Promise.resolve(true)
728+
})
729+
)
730+
dataUpdated.fire({ live: [], pushed: [], baseUrl: '' })
731+
dataUpdated.fire({
732+
lsRemoteOutput: `42b8736b08170529903cd203a1f40382a4b4a8cd refs/exps/a9/b32d14966b9be1396f2211d9eb743359708a07/test-branch
733+
4fb124aebddb2adf1545030907687fa9a4c80e70 refs/exps/a9/53c3851f46955fa3e2b8f6e1c52999acc8c9ea77/${mockExpId}`
734+
})
735+
await Promise.all([remoteUpdated, messageSent])
736+
737+
const experimentWithLink = experimentsModel
738+
.getRowData()[1]
739+
.subRows?.find(({ id }) => id === mockExpId)
740+
741+
expect(experimentWithLink?.studioLinkType).to.equal(StudioLinkType.PUSHED)
680742
}).timeout(WEBVIEW_TEST_TIMEOUT)
681743

682744
it("should handle a message to copy an experiment's Studio link", async () => {

extension/src/test/suite/experiments/util.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,8 @@ export const stubWorkspaceGettersWebview = async (
384384
experiments,
385385
experimentsModel,
386386
messageSpy,
387-
mockMessageReceived
387+
mockMessageReceived,
388+
webview
388389
} = await buildExperimentsWebview({ disposer })
389390

390391
return {
@@ -395,6 +396,7 @@ export const stubWorkspaceGettersWebview = async (
395396
experimentsModel,
396397
messageSpy,
397398
...stubWorkspaceExperiments(dvcRoot, experiments),
398-
mockMessageReceived
399+
mockMessageReceived,
400+
webview
399401
}
400402
}

0 commit comments

Comments
 (0)