Skip to content

Commit b7988ba

Browse files
authored
Add plotting actions to experiments table context menu (#2388)
* add plotting actions to experiments table context menu * fix view title command registration * fix brackets so that tests run and do what they are meant to * update context menu labels
1 parent b0342d0 commit b7988ba

File tree

9 files changed

+159
-11
lines changed

9 files changed

+159
-11
lines changed

extension/src/experiments/commands/register.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,8 @@ const registerExperimentRunCommands = (
295295

296296
internalCommands.registerExternalCommand(
297297
RegisteredCommands.EXPERIMENT_SHOW,
298-
() => experiments.showWebview()
298+
(context: { dvcRoot?: string } | undefined) =>
299+
experiments.showWebview(context?.dvcRoot)
299300
)
300301
}
301302

extension/src/experiments/webview/messages.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ export class WebviewMessages {
114114
return this.focusSortsTree()
115115

116116
case MessageFromWebviewType.OPEN_PLOTS_WEBVIEW:
117-
return commands.executeCommand(RegisteredCommands.PLOTS_SHOW)
117+
return this.showPlots()
118118

119119
case MessageFromWebviewType.SHARE_EXPERIMENT_AS_BRANCH:
120120
return commands.executeCommand(
@@ -127,6 +127,15 @@ export class WebviewMessages {
127127
{ dvcRoot: this.dvcRoot, id: message.payload }
128128
)
129129

130+
case MessageFromWebviewType.SET_EXPERIMENTS_FOR_PLOTS:
131+
return this.setSelectedExperiments(message.payload)
132+
133+
case MessageFromWebviewType.SET_EXPERIMENTS_AND_OPEN_PLOTS:
134+
return Promise.all([
135+
this.setSelectedExperiments(message.payload),
136+
this.showPlots()
137+
])
138+
130139
default:
131140
Logger.error(`Unexpected message: ${JSON.stringify(message)}`)
132141
}
@@ -254,4 +263,27 @@ export class WebviewMessages {
254263
undefined
255264
)
256265
}
266+
267+
private setSelectedExperiments(ids: string[]) {
268+
const experiments = this.experiments
269+
.getCombinedList()
270+
.filter(({ id }) => ids.includes(id))
271+
272+
this.experiments.setSelectionMode(false)
273+
this.experiments.setSelected(experiments)
274+
275+
this.notifyChanged()
276+
277+
sendTelemetryEvent(
278+
EventName.VIEWS_EXPERIMENTS_TABLE_SELECT_EXPERIMENTS_FOR_PLOTS,
279+
{ experimentCount: ids.length },
280+
undefined
281+
)
282+
}
283+
284+
private showPlots() {
285+
return commands.executeCommand(RegisteredCommands.PLOTS_SHOW, {
286+
dvcRoot: this.dvcRoot
287+
})
288+
}
257289
}

extension/src/plots/commands/register.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@ export const registerPlotsCommands = (
88
) => {
99
internalCommands.registerExternalCommand(
1010
RegisteredCommands.PLOTS_SHOW,
11-
() => {
12-
plots.showWebview()
13-
}
11+
(context: { dvcRoot?: string } | undefined) =>
12+
plots.showWebview(context?.dvcRoot)
1413
)
1514

1615
internalCommands.registerExternalCommand(

extension/src/telemetry/constants.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ export const EventName = Object.assign(
5151
'views.experimentsTable.columnResized',
5252
VIEWS_EXPERIMENTS_TABLE_SELECT_COLUMNS:
5353
'views.experimentsTable.selectColumns',
54+
VIEWS_EXPERIMENTS_TABLE_SELECT_EXPERIMENTS_FOR_PLOTS:
55+
'views.experimentsTable.selectExperimentsForPlots',
5456
VIEWS_EXPERIMENTS_TABLE_SORT_COLUMN:
5557
'views.experimentsTable.columnSortAdded',
5658

@@ -217,6 +219,9 @@ export interface IEventNamePropertyMapping {
217219
path: string
218220
}
219221
[EventName.VIEWS_EXPERIMENTS_TABLE_SELECT_COLUMNS]: undefined
222+
[EventName.VIEWS_EXPERIMENTS_TABLE_SELECT_EXPERIMENTS_FOR_PLOTS]: {
223+
experimentCount: number
224+
}
220225
[EventName.VIEWS_EXPERIMENTS_TABLE_OPEN_PARAMS_FILE]: {
221226
path: string
222227
}

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

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ import { ExperimentFlag } from '../../../cli/dvc/constants'
6464
import { DvcExecutor } from '../../../cli/dvc/executor'
6565
import { shortenForLabel } from '../../../util/string'
6666
import { GitExecutor } from '../../../cli/git/executor'
67+
import { WorkspacePlots } from '../../../plots/workspace'
6768

6869
suite('Experiments Test Suite', () => {
6970
const disposable = Disposable.fn()
@@ -917,8 +918,76 @@ suite('Experiments Test Suite', () => {
917918
areExperimentsStarred(experimentsToToggle),
918919
'experiments have been starred'
919920
).to.be.true
920-
})
921-
}).timeout(WEBVIEW_TEST_TIMEOUT)
921+
}).timeout(WEBVIEW_TEST_TIMEOUT)
922+
923+
it('should be able to handle a message to select experiments for plotting', async () => {
924+
const { experiments, experimentsModel } = buildExperiments(disposable)
925+
await experiments.isReady()
926+
927+
const webview = await experiments.showWebview()
928+
const mockMessageReceived = getMessageReceivedEmitter(webview)
929+
const mockExperimentIds = [
930+
'exp-e7a67',
931+
'd1343a87c6ee4a2e82d19525964d2fb2cb6756c9',
932+
'test-branch'
933+
]
934+
935+
stubWorkspaceExperimentsGetters(dvcDemoPath, experiments)
936+
937+
const tableChangePromise = experimentsUpdatedEvent(experiments)
938+
939+
mockMessageReceived.fire({
940+
payload: mockExperimentIds,
941+
type: MessageFromWebviewType.SET_EXPERIMENTS_FOR_PLOTS
942+
})
943+
944+
await tableChangePromise
945+
946+
const selectExperimentIds = experimentsModel
947+
.getSelectedRevisions()
948+
.map(({ id }) => id)
949+
.sort()
950+
expect(selectExperimentIds).to.deep.equal(mockExperimentIds.sort())
951+
expect(selectExperimentIds).to.deep.equal(mockExperimentIds)
952+
}).timeout(WEBVIEW_TEST_TIMEOUT)
953+
954+
it('should be able to handle a message to compare experiments plots', async () => {
955+
const { experiments, experimentsModel } = buildExperiments(disposable)
956+
const mockShowPlots = stub(
957+
WorkspacePlots.prototype,
958+
'showWebview'
959+
).resolves(undefined)
960+
961+
await experiments.isReady()
962+
963+
const webview = await experiments.showWebview()
964+
const mockMessageReceived = getMessageReceivedEmitter(webview)
965+
const mockExperimentIds = [
966+
'exp-e7a67',
967+
'd1343a87c6ee4a2e82d19525964d2fb2cb6756c9',
968+
'test-branch'
969+
]
970+
971+
stubWorkspaceExperimentsGetters(dvcDemoPath, experiments)
972+
973+
const tableChangePromise = experimentsUpdatedEvent(experiments)
974+
975+
mockMessageReceived.fire({
976+
payload: mockExperimentIds,
977+
type: MessageFromWebviewType.SET_EXPERIMENTS_AND_OPEN_PLOTS
978+
})
979+
980+
await tableChangePromise
981+
982+
const selectExperimentIds = experimentsModel
983+
.getSelectedRevisions()
984+
.map(({ id }) => id)
985+
.sort()
986+
expect(selectExperimentIds).to.deep.equal(mockExperimentIds.sort())
987+
expect(mockShowPlots).to.be.calledOnce
988+
expect(mockShowPlots).to.be.calledWith(dvcDemoPath)
989+
}).timeout(WEBVIEW_TEST_TIMEOUT)
990+
})
922991

923992
describe('Sorting', () => {
924993
it('should be able to sort', async () => {

extension/src/webview/contract.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ export enum MessageFromWebviewType {
3636
SELECT_EXPERIMENTS = 'select-experiments',
3737
SELECT_COLUMNS = 'select-columns',
3838
SELECT_PLOTS = 'select-plots',
39+
SET_EXPERIMENTS_FOR_PLOTS = 'set-experiments-for-plots',
40+
SET_EXPERIMENTS_AND_OPEN_PLOTS = 'set-experiments-and-open-plots',
3941
SHARE_EXPERIMENT_AS_BRANCH = 'share-experiment-as-branch',
4042
SHARE_EXPERIMENT_AS_COMMIT = 'share-experiment-as-commit',
4143
TOGGLE_METRIC = 'toggle-metric',
@@ -153,6 +155,14 @@ export type MessageFromWebview =
153155
| { type: MessageFromWebviewType.FOCUS_FILTERS_TREE }
154156
| { type: MessageFromWebviewType.FOCUS_SORTS_TREE }
155157
| { type: MessageFromWebviewType.OPEN_PLOTS_WEBVIEW }
158+
| {
159+
type: MessageFromWebviewType.SET_EXPERIMENTS_FOR_PLOTS
160+
payload: string[]
161+
}
162+
| {
163+
type: MessageFromWebviewType.SET_EXPERIMENTS_AND_OPEN_PLOTS
164+
payload: string[]
165+
}
156166
| {
157167
type: MessageFromWebviewType.SHARE_EXPERIMENT_AS_BRANCH
158168
payload: string

extension/src/webview/workspace.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ export abstract class BaseWorkspaceWebviews<
2727
}
2828
}
2929

30-
public async showWebview() {
31-
const dvcRoot = await this.getOnlyOrPickProject()
30+
public async showWebview(overrideRoot?: string) {
31+
const dvcRoot = overrideRoot || (await this.getOnlyOrPickProject())
3232
if (!dvcRoot) {
3333
return
3434
}

webview/src/experiments/components/App.test.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -868,6 +868,22 @@ describe('App', () => {
868868
expect(itemLabels).toContain('Remove Selected Rows')
869869
})
870870

871+
it('should always present the Plots options if multiple rows are selected', () => {
872+
renderTableWithoutRunningExperiments()
873+
874+
clickRowCheckbox('4fb124a')
875+
clickRowCheckbox('42b8736')
876+
877+
const target = screen.getByText('4fb124a')
878+
fireEvent.contextMenu(target, { bubbles: true })
879+
880+
jest.advanceTimersByTime(100)
881+
const menuitems = screen.getAllByRole('menuitem')
882+
const itemLabels = menuitems.map(item => item.textContent)
883+
expect(itemLabels).toContain('Plot and Show')
884+
expect(itemLabels).toContain('Plot')
885+
})
886+
871887
it('should allow batch selection of rows by shift-clicking a range of them', () => {
872888
renderTableWithoutRunningExperiments()
873889

webview/src/experiments/components/table/RowContextMenu.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { MessagesMenuOptionProps } from '../../../shared/components/messagesMenu
77
import { cond } from '../../../util/helpers'
88

99
const experimentMenuOption = (
10-
id: string | string[],
10+
payload: string | string[],
1111
label: string,
1212
type: MessageFromWebviewType,
1313
hidden?: boolean,
@@ -19,7 +19,7 @@ const experimentMenuOption = (
1919
id: type,
2020
label,
2121
message: {
22-
payload: id,
22+
payload,
2323
type
2424
}
2525
} as MessagesMenuOptionProps
@@ -45,6 +45,8 @@ const getMultiSelectMenuOptions = (
4545
}) => starred
4646
)
4747

48+
const selectedIds = selectedRowsList.map(value => value.row.values.id)
49+
4850
const removableRowIds = selectedRowsList
4951
.filter(value => value.row.depth === 1)
5052
.map(value => value.row.values.id)
@@ -69,6 +71,20 @@ const getMultiSelectMenuOptions = (
6971
starredExperiments.map(value => value.row.values.id),
7072
'Unstar'
7173
),
74+
experimentMenuOption(
75+
selectedIds,
76+
'Plot',
77+
MessageFromWebviewType.SET_EXPERIMENTS_FOR_PLOTS,
78+
false,
79+
true
80+
),
81+
experimentMenuOption(
82+
selectedIds,
83+
'Plot and Show',
84+
MessageFromWebviewType.SET_EXPERIMENTS_AND_OPEN_PLOTS,
85+
false,
86+
false
87+
),
7288
experimentMenuOption(
7389
removableRowIds,
7490
'Remove Selected Rows',

0 commit comments

Comments
 (0)