Skip to content

Commit c471ec2

Browse files
authored
Merge pull request #1950 from iterative/star-experiments
Star experiments in table
2 parents eb31b02 + 6c8c561 commit c471ec2

File tree

17 files changed

+484
-59
lines changed

17 files changed

+484
-59
lines changed

extension/src/experiments/index.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,11 @@ export class Experiments extends BaseRepository<TableData> {
176176
return this.columns.getTerminalNodeStatuses()
177177
}
178178

179+
public toggleExperimentStars(ids: string[]) {
180+
this.experiments.toggleStars(ids)
181+
this.notifyChanged()
182+
}
183+
179184
public toggleExperimentStatus(id: string) {
180185
const selected = this.experiments.isSelected(id)
181186
if (!selected && !this.experiments.canSelect()) {
@@ -532,6 +537,8 @@ export class Experiments extends BaseRepository<TableData> {
532537
RegisteredCommands.EXPERIMENT_TOGGLE,
533538
{ dvcRoot: this.dvcRoot, id: message.payload }
534539
)
540+
case MessageFromWebviewType.TOGGLE_EXPERIMENT_STAR:
541+
return this.setExperimentStars(message.payload)
535542
case MessageFromWebviewType.HIDE_EXPERIMENTS_TABLE_COLUMN:
536543
return this.hideTableColumn(message.payload)
537544
case MessageFromWebviewType.OPEN_PARAMS_FILE_TO_THE_SIDE:
@@ -604,6 +611,15 @@ export class Experiments extends BaseRepository<TableData> {
604611
)
605612
}
606613

614+
private setExperimentStars(ids: string[]) {
615+
this.toggleExperimentStars(ids)
616+
sendTelemetryEvent(
617+
EventName.VIEWS_EXPERIMENTS_TABLE_EXPERIMENT_STARS_TOGGLE,
618+
undefined,
619+
undefined
620+
)
621+
}
622+
607623
private setColumnsStatus() {
608624
this.selectColumns()
609625
sendTelemetryEvent(

extension/src/experiments/model/index.ts

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import { ModelWithPersistence } from '../../persistence/model'
3232
import { PersistenceKey } from '../../persistence/constants'
3333
import { sum } from '../../util/math'
3434

35+
export type StarredExperiments = Record<string, boolean | undefined>
36+
3537
type SelectedExperimentWithColor = Experiment & {
3638
displayColor: Color
3739
selected: true
@@ -56,6 +58,7 @@ export class ExperimentsModel extends ModelWithPersistence {
5658
private checkpointsByTip: Map<string, Experiment[]> = new Map()
5759
private availableColors: Color[]
5860
private coloredStatus: ColoredStatus
61+
private starredExperiments: StarredExperiments
5962

6063
private filters: Map<string, FilterDefinition> = new Map()
6164
private useFiltersForSelection = false
@@ -81,6 +84,10 @@ export class ExperimentsModel extends ModelWithPersistence {
8184
PersistenceKey.EXPERIMENTS_STATUS,
8285
{}
8386
)
87+
this.starredExperiments = this.revive<StarredExperiments>(
88+
PersistenceKey.EXPERIMENTS_STARS,
89+
{}
90+
)
8491

8592
const assignedColors = new Set(
8693
Object.values(this.coloredStatus).filter(Boolean)
@@ -108,6 +115,13 @@ export class ExperimentsModel extends ModelWithPersistence {
108115
this.setColoredStatus()
109116
}
110117

118+
public toggleStars(ids: string[]) {
119+
for (const id of ids) {
120+
this.starredExperiments[id] = !this.starredExperiments[id]
121+
this.persistStars()
122+
}
123+
}
124+
111125
public toggleStatus(id: string) {
112126
if (
113127
this.flattenExperiments().find(({ id: queuedId }) => queuedId === id)
@@ -273,19 +287,19 @@ export class ExperimentsModel extends ModelWithPersistence {
273287
})[] {
274288
return [
275289
{
276-
...this.addSelected(this.workspace),
290+
...this.addDetails(this.workspace),
277291
hasChildren: false,
278292
type: ExperimentType.WORKSPACE
279293
},
280294
...this.branches.map(branch => {
281295
return {
282-
...this.addSelected(branch),
296+
...this.addDetails(branch),
283297
hasChildren: false,
284298
type: ExperimentType.BRANCH
285299
}
286300
}),
287301
...this.flattenExperiments().map(experiment => ({
288-
...this.addSelected(experiment),
302+
...this.addDetails(experiment),
289303
hasChildren: definedAndNonEmpty(
290304
this.checkpointsByTip.get(experiment.id)
291305
),
@@ -300,7 +314,7 @@ export class ExperimentsModel extends ModelWithPersistence {
300314
return this.getExperiments().map(experiment => {
301315
const checkpoints = this.checkpointsByTip
302316
.get(experiment.id)
303-
?.map(checkpoint => this.addSelected(checkpoint))
317+
?.map(checkpoint => this.addDetails(checkpoint))
304318
if (!definedAndNonEmpty(checkpoints)) {
305319
return experiment
306320
}
@@ -328,24 +342,24 @@ export class ExperimentsModel extends ModelWithPersistence {
328342
id: string
329343
): (Experiment & { type: ExperimentType })[] | undefined {
330344
return this.checkpointsByTip.get(id)?.map(checkpoint => ({
331-
...this.addSelected(checkpoint),
345+
...this.addDetails(checkpoint),
332346
type: ExperimentType.CHECKPOINT
333347
}))
334348
}
335349

336350
public getRowData() {
337351
return [
338-
this.addSelected(this.workspace),
352+
this.addDetails(this.workspace),
339353
...this.branches.map(branch => {
340354
const experiments = this.getExperimentsByBranch(branch)
341-
const branchWithSelected = this.addSelected(branch)
355+
const branchWithSelectedAndStarred = this.addDetails(branch)
342356

343357
if (!definedAndNonEmpty(experiments)) {
344-
return branchWithSelected
358+
return branchWithSelectedAndStarred
345359
}
346360

347361
return {
348-
...branchWithSelected,
362+
...branchWithSelectedAndStarred,
349363
subRows: this.getSubRows(experiments)
350364
}
351365
})
@@ -356,6 +370,10 @@ export class ExperimentsModel extends ModelWithPersistence {
356370
return !!this.coloredStatus[id]
357371
}
358372

373+
public isStarred(id: string) {
374+
return !!this.starredExperiments[id]
375+
}
376+
359377
public getExperimentCount() {
360378
return sum([
361379
this.flattenCheckpoints().length,
@@ -387,12 +405,12 @@ export class ExperimentsModel extends ModelWithPersistence {
387405
filters
388406
)
389407
if (!checkpoints) {
390-
return this.addSelected(experiment)
408+
return this.addDetails(experiment)
391409
}
392410
return {
393-
...this.addSelected(experiment),
411+
...this.addDetails(experiment),
394412
subRows: checkpoints.map(checkpoint => ({
395-
...this.addSelected(checkpoint)
413+
...this.addDetails(checkpoint)
396414
}))
397415
}
398416
})
@@ -505,10 +523,30 @@ export class ExperimentsModel extends ModelWithPersistence {
505523
return this.persist(PersistenceKey.EXPERIMENTS_FILTER_BY, [...this.filters])
506524
}
507525

526+
private persistStars() {
527+
return this.persist(
528+
PersistenceKey.EXPERIMENTS_STARS,
529+
this.starredExperiments
530+
)
531+
}
532+
508533
private persistStatus() {
509534
return this.persist(PersistenceKey.EXPERIMENTS_STATUS, this.coloredStatus)
510535
}
511536

537+
private addStarred(experiment: Experiment) {
538+
const { id } = experiment
539+
540+
if (!this.isStarred(id)) {
541+
return experiment
542+
}
543+
544+
return {
545+
...experiment,
546+
starred: true
547+
}
548+
}
549+
512550
private addSelected(experiment: Experiment) {
513551
const { id } = experiment
514552
if (!hasKey(this.coloredStatus, id)) {
@@ -524,6 +562,10 @@ export class ExperimentsModel extends ModelWithPersistence {
524562
}
525563
}
526564

565+
private addDetails(experiment: Experiment) {
566+
return this.addStarred(this.addSelected(experiment))
567+
}
568+
527569
private getDisplayColor(id: string) {
528570
const color = this.coloredStatus[id]
529571
if (!color) {

extension/src/experiments/webview/contract.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export interface Experiment extends BaseExperimentFields {
2121
outs?: MetricOrParamColumns
2222
displayColor?: string
2323
selected?: boolean
24+
starred?: boolean
2425
mutable?: boolean
2526
sha?: string
2627
}

extension/src/persistence/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export enum PersistenceKey {
22
EXPERIMENTS_FILTER_BY = 'experimentsFilterBy:',
33
EXPERIMENTS_SORT_BY = 'experimentsSortBy:',
44
EXPERIMENTS_STATUS = 'experimentsStatus:',
5+
EXPERIMENTS_STARS = 'experimentsStars:',
56
METRICS_AND_PARAMS_COLUMN_ORDER = 'columnsColumnOrder:',
67
METRICS_AND_PARAMS_COLUMN_WIDTHS = 'columnsColumnWidths:',
78
METRICS_AND_PARAMS_STATUS = 'columnsStatus:',

extension/src/telemetry/constants.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ export const EventName = Object.assign(
3232
VIEWS_EXPERIMENTS_TABLE_COLUMNS_REORDERED:
3333
'views.experimentsTable.columnsReordered',
3434
VIEWS_EXPERIMENTS_TABLE_CREATED: 'views.experimentsTable.created',
35+
VIEWS_EXPERIMENTS_TABLE_EXPERIMENT_STARS_TOGGLE:
36+
'views.experimentTable.toggleStars',
3537
VIEWS_EXPERIMENTS_TABLE_EXPERIMENT_TOGGLE:
3638
'views.experimentTable.toggleStatus',
3739
VIEWS_EXPERIMENTS_TABLE_FOCUS_CHANGED:
@@ -189,6 +191,7 @@ export interface IEventNamePropertyMapping {
189191
[EventName.VIEWS_EXPERIMENTS_SORT_BY_TREE_OPENED]: DvcRootCount
190192
[EventName.VIEWS_EXPERIMENTS_TREE_OPENED]: DvcRootCount
191193
[EventName.VIEWS_EXPERIMENTS_TABLE_EXPERIMENT_TOGGLE]: undefined
194+
[EventName.VIEWS_EXPERIMENTS_TABLE_EXPERIMENT_STARS_TOGGLE]: undefined
192195
[EventName.VIEWS_EXPERIMENTS_TABLE_CLOSED]: undefined
193196
[EventName.VIEWS_EXPERIMENTS_TABLE_COLUMNS_REORDERED]: undefined
194197
[EventName.VIEWS_EXPERIMENTS_TABLE_FOCUS_FILTERS_TREE]: undefined

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -788,6 +788,42 @@ suite('Experiments Test Suite', () => {
788788
undefined
789789
)
790790
})
791+
792+
it("should be able to handle a message to toggle an experiment's star status", async () => {
793+
const { experiments, experimentsModel } =
794+
setupExperimentsAndMockCommands()
795+
796+
const experimentsToToggle = ['exp-e7a67']
797+
798+
const areExperimentsStarred = (expIds: string[]): boolean =>
799+
expIds
800+
.map(expId =>
801+
experimentsModel.getExperiments().find(({ id }) => id === expId)
802+
)
803+
.every(exp => exp?.starred)
804+
805+
expect(
806+
areExperimentsStarred(experimentsToToggle),
807+
'experiments are starred'
808+
).to.be.false
809+
810+
const webview = await experiments.showWebview()
811+
const mockMessageReceived = getMessageReceivedEmitter(webview)
812+
const toggleSpy = spy(experimentsModel, 'toggleStars')
813+
814+
mockMessageReceived.fire({
815+
payload: experimentsToToggle,
816+
type: MessageFromWebviewType.TOGGLE_EXPERIMENT_STAR
817+
})
818+
819+
expect(toggleSpy).to.be.calledWith(experimentsToToggle)
820+
toggleSpy.resetHistory()
821+
822+
expect(
823+
areExperimentsStarred(experimentsToToggle),
824+
'experiments have been starred'
825+
).to.be.true
826+
})
791827
})
792828

793829
describe('Sorting', () => {

extension/src/webview/contract.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export enum MessageFromWebviewType {
2929
RESIZE_PLOTS = 'resize-plots',
3030
SORT_COLUMN = 'sort-column',
3131
TOGGLE_EXPERIMENT = 'toggle-experiment',
32+
TOGGLE_EXPERIMENT_STAR = 'toggle-experiment-star',
3233
HIDE_EXPERIMENTS_TABLE_COLUMN = 'hide-experiments-table-column',
3334
SELECT_EXPERIMENTS = 'select-experiments',
3435
SELECT_COLUMNS = 'select-columns',
@@ -67,6 +68,10 @@ export type MessageFromWebview =
6768
type: MessageFromWebviewType.TOGGLE_EXPERIMENT
6869
payload: string
6970
}
71+
| {
72+
type: MessageFromWebviewType.TOGGLE_EXPERIMENT_STAR
73+
payload: string[]
74+
}
7075
| {
7176
type: MessageFromWebviewType.HIDE_EXPERIMENTS_TABLE_COLUMN
7277
payload: string

0 commit comments

Comments
 (0)