Skip to content

Commit da7597c

Browse files
authored
Apply experiment table filters to commits (#4363)
* implement working prototype for new filter logic * refactor logic * fix with no experiments story
1 parent 610c27d commit da7597c

File tree

9 files changed

+135
-69
lines changed

9 files changed

+135
-69
lines changed

extension/src/experiments/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,10 @@ export class Experiments extends BaseRepository<TableData> {
341341
return this.experiments.getFilteredCount()
342342
}
343343

344+
public getRecordCount() {
345+
return this.experiments.getRecordCount()
346+
}
347+
344348
public getExperimentCount() {
345349
if (!this.columns.hasNonDefaultColumns()) {
346350
return 0
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { FilterDefinition, filterExperiment } from '.'
2+
import { definedAndNonEmpty } from '../../../util/array'
3+
import { Commit, Experiment } from '../../webview/contract'
4+
5+
export const collectFiltered = (
6+
acc: Experiment[],
7+
commit: Commit,
8+
experiments: Experiment[] | undefined,
9+
filters: FilterDefinition[]
10+
) => {
11+
let hasUnfilteredExperiment = false
12+
for (const experiment of experiments || []) {
13+
if (!filterExperiment(filters, experiment)) {
14+
acc.push(experiment)
15+
continue
16+
}
17+
hasUnfilteredExperiment = true
18+
}
19+
if (!hasUnfilteredExperiment && !filterExperiment(filters, commit)) {
20+
acc.push(commit)
21+
}
22+
}
23+
24+
export const collectUnfiltered = (
25+
commit: Commit,
26+
experiments: Experiment[] | undefined,
27+
filters: FilterDefinition[]
28+
): Commit | undefined => {
29+
const unfilteredExperiments = experiments?.filter(
30+
(experiment: Experiment) => !!filterExperiment(filters, experiment)
31+
)
32+
33+
const hasUnfilteredExperiments = definedAndNonEmpty(unfilteredExperiments)
34+
const filtered =
35+
!hasUnfilteredExperiments && !filterExperiment(filters, commit)
36+
37+
if (filtered) {
38+
return
39+
}
40+
41+
if (hasUnfilteredExperiments) {
42+
commit.subRows = unfilteredExperiments
43+
}
44+
45+
return commit
46+
}

extension/src/experiments/model/filterBy/tree.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,7 @@ import { RegisteredCommands } from '../../../commands/external'
1212
import { InternalCommands } from '../../../commands/internal'
1313
import { sendViewOpenedTelemetryEvent } from '../../../telemetry'
1414
import { EventName } from '../../../telemetry/constants'
15-
import {
16-
definedAndNonEmpty,
17-
joinTruthyItems,
18-
sortCollectedArray
19-
} from '../../../util/array'
15+
import { definedAndNonEmpty, sortCollectedArray } from '../../../util/array'
2016
import { createTreeView, getRootItem, isRoot } from '../../../tree'
2117
import { Disposable } from '../../../class/dispose'
2218
import { sum } from '../../../util/math'
@@ -171,11 +167,12 @@ export class ExperimentsFilterByTree
171167
this.experiments.getRepository(dvcRoot).getFilteredCount()
172168
)
173169
)
170+
const total = sum(
171+
dvcRoots.flatMap(dvcRoot =>
172+
this.experiments.getRepository(dvcRoot).getRecordCount()
173+
)
174+
)
174175

175-
return joinTruthyItems([
176-
`${filteredCount || 'No'}`,
177-
'Experiment' + (filteredCount === 1 ? '' : 's'),
178-
'Filtered'
179-
])
176+
return [filteredCount, 'of', total, 'Filtered'].join(' ')
180177
}
181178
}

extension/src/experiments/model/index.ts

Lines changed: 56 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Memento } from 'vscode'
22
import { SortDefinition, sortExperiments } from './sortBy'
3-
import { FilterDefinition, filterExperiment, getFilterId } from './filterBy'
3+
import { FilterDefinition, getFilterId } from './filterBy'
4+
import { collectFiltered, collectUnfiltered } from './filterBy/collect'
45
import {
56
collectAddRemoveCommitsDetails,
67
collectBranches,
@@ -27,7 +28,7 @@ import {
2728
GitRemoteStatus,
2829
RunningExperiment
2930
} from '../webview/contract'
30-
import { definedAndNonEmpty, reorderListSubset } from '../../util/array'
31+
import { reorderListSubset } from '../../util/array'
3132
import {
3233
Executor,
3334
ExpShowOutput,
@@ -409,31 +410,27 @@ export class ExperimentsModel extends ModelWithPersistence {
409410
}
410411

411412
public getRowData() {
412-
const commitsBySha: { [sha: string]: Commit } = {}
413-
for (const commit of this.commits) {
414-
commitsBySha[commit.sha as string] = commit
415-
}
413+
const commitsBySha = this.applyFiltersToCommits()
416414

417-
return [
418-
{ branch: undefined, ...this.addDetails(this.workspace) },
419-
...this.rowOrder.map(({ branch, sha }) => {
420-
const commit = { ...commitsBySha[sha], branch }
421-
const experiments = this.getExperimentsByCommit(commit)
422-
const commitWithSelectedAndStarred = this.addDetails(commit)
423-
424-
if (!definedAndNonEmpty(experiments)) {
425-
return commitWithSelectedAndStarred
426-
}
427-
428-
return {
429-
...commitWithSelectedAndStarred,
430-
subRows: experiments.filter(
431-
(experiment: Experiment) =>
432-
!!filterExperiment(this.getFilters(), experiment)
433-
)
434-
}
435-
})
415+
const rows: Commit[] = [
416+
{ branch: undefined, ...this.addDetails(this.workspace) }
436417
]
418+
for (const { branch, sha } of this.rowOrder) {
419+
const commit = commitsBySha[sha]
420+
if (!commit) {
421+
continue
422+
}
423+
if (commit.subRows) {
424+
commit.subRows = commit.subRows.map(experiment => ({
425+
...experiment,
426+
branch
427+
}))
428+
}
429+
430+
rows.push({ ...commit, branch })
431+
}
432+
433+
return rows
437434
}
438435

439436
public getHasMoreCommits() {
@@ -461,6 +458,10 @@ export class ExperimentsModel extends ModelWithPersistence {
461458
return filtered.length
462459
}
463460

461+
public getRecordCount() {
462+
return this.getCombinedList().length
463+
}
464+
464465
public getCombinedList() {
465466
return [this.workspace, ...this.commits, ...this.getExperimentsAndQueued()]
466467
}
@@ -530,10 +531,14 @@ export class ExperimentsModel extends ModelWithPersistence {
530531
private getFilteredExperiments() {
531532
const acc: Experiment[] = []
532533

533-
for (const experiment of this.getExperiments()) {
534-
if (!filterExperiment(this.getFilters(), experiment)) {
535-
acc.push(experiment)
536-
}
534+
for (const commit of this.commits) {
535+
const experiments = this.getExperimentsByCommit(commit)
536+
collectFiltered(
537+
acc,
538+
this.addDetails(commit),
539+
experiments,
540+
this.getFilters()
541+
)
537542
}
538543

539544
return acc
@@ -543,10 +548,7 @@ export class ExperimentsModel extends ModelWithPersistence {
543548
const experiments = this.experimentsByCommit
544549
.get(commit.id)
545550
?.map(originalExperiment => {
546-
const experiment = {
547-
...this.addDetails(originalExperiment),
548-
branch: commit.branch
549-
}
551+
const experiment = this.addDetails(originalExperiment)
550552

551553
this.addRemoteStatus(experiment)
552554

@@ -697,4 +699,25 @@ export class ExperimentsModel extends ModelWithPersistence {
697699
}
698700
return uniqueStatus
699701
}
702+
703+
private applyFiltersToCommits() {
704+
const commitsBySha: { [sha: string]: Commit } = {}
705+
for (const commit of this.commits) {
706+
const commitWithSelectedAndStarred = this.addDetails(commit)
707+
const experiments = this.getExperimentsByCommit(
708+
commitWithSelectedAndStarred
709+
)
710+
const unfilteredCommit = collectUnfiltered(
711+
commitWithSelectedAndStarred,
712+
experiments,
713+
this.getFilters()
714+
)
715+
if (!unfilteredCommit) {
716+
continue
717+
}
718+
719+
commitsBySha[commit.sha as string] = unfilteredCommit
720+
}
721+
return commitsBySha
722+
}
700723
}

extension/src/test/suite/experiments/model/filterBy/tree.test.ts

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,7 @@ suite('Experiments Filter By Tree Test Suite', () => {
9292
const accuracy = experiment.metrics?.['summary.json']?.accuracy
9393
return !!(accuracy === undefined || gte45(accuracy))
9494
})
95-
},
96-
fe2919b,
97-
_7df876c
95+
}
9896
]
9997

10098
const filteredTableData: Partial<TableData> = {
@@ -305,7 +303,7 @@ suite('Experiments Filter By Tree Test Suite', () => {
305303
void experiments.addFilter()
306304
await tableFilterAdded
307305

308-
expect(mockTreeView.description).to.equal('3 Experiments Filtered')
306+
expect(mockTreeView.description).to.equal('5 of 11 Filtered')
309307

310308
stubPrivateMethod(experimentsModel, 'getFilteredExperiments')
311309
.onFirstCall()
@@ -319,13 +317,13 @@ suite('Experiments Filter By Tree Test Suite', () => {
319317
workspaceExperiments.experimentsChanged.fire()
320318
await allButOneExperimentFilteredEvent
321319

322-
expect(mockTreeView.description).to.equal('1 Experiment Filtered')
320+
expect(mockTreeView.description).to.equal('1 of 11 Filtered')
323321

324322
const threeExperimentsFilteredEvent = getUpdateEvent()
325323
workspaceExperiments.experimentsChanged.fire()
326324
await threeExperimentsFilteredEvent
327325

328-
expect(mockTreeView.description).to.equal('3 Experiments Filtered')
326+
expect(mockTreeView.description).to.equal('3 of 11 Filtered')
329327

330328
const tableFilterRemoved = getUpdateEvent()
331329
experiments.removeFilter(getFilterId(filter))
@@ -336,24 +334,22 @@ suite('Experiments Filter By Tree Test Suite', () => {
336334
})
337335

338336
it('should be able to filter to starred experiments', async () => {
339-
const { experiments, messageSpy } =
337+
const { experiments, experimentsModel, messageSpy } =
340338
stubWorkspaceExperimentsGetters(disposable)
341339
await experiments.isReady()
342340
await experiments.showWebview()
343341

342+
experimentsModel.toggleStars(['main'])
343+
344344
await addFilterViaQuickInput(experiments, starredFilter)
345345

346-
const [workspace, main, fe2919b, _7df876c] = rowsFixture
346+
const [workspace, main] = rowsFixture
347347

348-
const filteredRows = [
349-
workspace,
350-
{
351-
...main,
352-
subRows: []
353-
},
354-
fe2919b,
355-
_7df876c
356-
]
348+
const mainNoSubRows = { ...main }
349+
delete mainNoSubRows.subRows
350+
mainNoSubRows.starred = true
351+
352+
const filteredRows = [workspace, mainNoSubRows]
357353

358354
const filteredTableData: Partial<TableData> = {
359355
changes: workspaceChangesFixture,

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ import {
4747
renderTableWithoutRunningExperiments,
4848
renderTableWithPlaceholder,
4949
renderTableWithSortingData,
50-
renderTableWithWorkspaceRowOnly,
50+
renderTableWithNoRows,
5151
selectedRows,
5252
setTableData
5353
} from '../../test/experimentsTable'
@@ -109,8 +109,8 @@ describe('App', () => {
109109
expect(noColumnsState).not.toBeInTheDocument()
110110
})
111111

112-
it('should show the no experiments empty state when only the workspace is provided', () => {
113-
renderTableWithWorkspaceRowOnly()
112+
it('should show the no experiments empty state when no rows are provided', () => {
113+
renderTableWithNoRows()
114114

115115
const noExperimentsState = screen.queryByText('No Experiments to Display.')
116116
expect(noExperimentsState).toBeInTheDocument()

webview/src/experiments/components/Experiments.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,9 +182,9 @@ export const ExperimentsTable: React.FC = () => {
182182
}, [toggleAllRowsExpanded])
183183

184184
const hasOnlyDefaultColumns = columns.length <= 1
185-
const hasOnlyWorkspace = data.length <= 1
186-
if (hasOnlyDefaultColumns || hasOnlyWorkspace) {
187-
return <GetStarted showWelcome={!hasColumns || hasOnlyWorkspace} />
185+
const hasNoRows = data.length === 0
186+
if (hasOnlyDefaultColumns || hasNoRows) {
187+
return <GetStarted showWelcome={!hasColumns || hasNoRows} />
188188
}
189189
return (
190190
<RowSelectionProvider>

webview/src/stories/Table.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ LoadingData.args = { tableData: undefined }
297297

298298
export const WithNoExperiments = Template.bind({})
299299
WithNoExperiments.args = {
300-
tableData: { ...tableData, rows: [rowsFixture[0]] }
300+
tableData: { ...tableData, rows: [] }
301301
}
302302

303303
export const WithNoColumns = Template.bind({})

webview/src/test/experimentsTable.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ export const renderTableWithNoColumns = () => {
5252
renderTable({ ...tableDataFixture, columns: [] })
5353
}
5454

55-
export const renderTableWithWorkspaceRowOnly = () => {
56-
renderTable({ ...tableDataFixture, rows: [tableDataFixture.rows[0]] })
55+
export const renderTableWithNoRows = () => {
56+
renderTable({ ...tableDataFixture, rows: [] })
5757
}
5858

5959
export const renderTableWithSortingData = () => {

0 commit comments

Comments
 (0)