Skip to content

Commit fa54bc2

Browse files
authored
Apply table filters to custom plot data (#4657)
1 parent ab8a162 commit fa54bc2

File tree

10 files changed

+168
-36
lines changed

10 files changed

+168
-36
lines changed

extension/src/experiments/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,10 @@ export class Experiments extends BaseRepository<TableData> {
382382
return this.experiments.getWorkspaceCommitsAndExperiments()
383383
}
384384

385+
public getUnfilteredCommitsAndExperiments() {
386+
return this.experiments.getUnfilteredCommitsAndExperiments()
387+
}
388+
385389
public async selectExperimentsToPlot() {
386390
const experiments = this.experiments.getWorkspaceCommitsAndExperiments()
387391

extension/src/experiments/model/index.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Memento } from 'vscode'
22
import { SortDefinition, sortExperiments } from './sortBy'
3-
import { FilterDefinition, getFilterId } from './filterBy'
3+
import { FilterDefinition, filterExperiment, getFilterId } from './filterBy'
44
import { collectFiltered, collectUnfiltered } from './filterBy/collect'
55
import {
66
collectAddRemoveCommitsDetails,
@@ -365,6 +365,14 @@ export class ExperimentsModel extends ModelWithPersistence {
365365
return [...this.getWorkspaceAndCommits(), ...this.getExperiments()]
366366
}
367367

368+
public getUnfilteredCommitsAndExperiments() {
369+
const filters = this.getFilters()
370+
return this.getWorkspaceCommitsAndExperiments().filter(
371+
exp =>
372+
exp.id !== EXPERIMENT_WORKSPACE_ID && !!filterExperiment(filters, exp)
373+
)
374+
}
375+
368376
public getErrors() {
369377
const errors = new Set<string>()
370378
for (const { error, label } of this.getCombinedList()) {

extension/src/plots/model/index.ts

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import {
2727
DEFAULT_SECTION_NB_ITEMS_PER_ROW_OR_WIDTH,
2828
PlotsSection,
2929
SectionCollapsed,
30-
CustomPlotData,
3130
CustomPlotsData,
3231
DEFAULT_HEIGHT,
3332
DEFAULT_NB_ITEMS_PER_ROW,
@@ -154,44 +153,40 @@ export class PlotsModel extends ModelWithPersistence {
154153
}
155154

156155
public getCustomPlots(): CustomPlotsData | undefined {
157-
const experiments = this.experiments
158-
.getWorkspaceCommitsAndExperiments()
159-
.filter(({ id }) => id !== EXPERIMENT_WORKSPACE_ID)
160-
161-
if (experiments.length === 0) {
162-
return
163-
}
164-
156+
const experiments = this.experiments.getUnfilteredCommitsAndExperiments()
157+
const hasUnfilteredExperiments = experiments.length > 0
158+
const plotsOrderValues = this.getCustomPlotsOrder()
159+
const enablePlotCreation = checkForCustomPlotOptions(
160+
this.experiments.getColumnTerminalNodes(),
161+
plotsOrderValues
162+
)
165163
const height = this.getHeight(PlotsSection.CUSTOM_PLOTS)
166164
const nbItemsPerRow = this.getNbItemsPerRowOrWidth(
167165
PlotsSection.CUSTOM_PLOTS
168166
)
167+
const hasAddedPlots = plotsOrderValues.length > 0
168+
169169
const colorScale = getColorScale(
170170
this.getSelectedRevisionDetails().filter(
171171
({ id }) => id !== EXPERIMENT_WORKSPACE_ID
172172
)
173173
)
174-
const plotsOrderValues = this.getCustomPlotsOrder()
175-
const plots: CustomPlotData[] = collectCustomPlots({
176-
colorScale,
177-
experiments,
178-
height,
179-
nbItemsPerRow,
180-
plotsOrderValues
181-
})
182-
183-
if (plots.length === 0 && plotsOrderValues.length > 0) {
184-
return
185-
}
186174

187175
return {
188-
enablePlotCreation: checkForCustomPlotOptions(
189-
this.experiments.getColumnTerminalNodes(),
190-
plotsOrderValues
191-
),
176+
enablePlotCreation,
177+
hasAddedPlots,
178+
hasUnfilteredExperiments,
192179
height,
193180
nbItemsPerRow,
194-
plots
181+
plots: hasUnfilteredExperiments
182+
? collectCustomPlots({
183+
colorScale,
184+
experiments,
185+
height,
186+
nbItemsPerRow,
187+
plotsOrderValues
188+
})
189+
: []
195190
}
196191
}
197192

extension/src/plots/webview/contract.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ export type CustomPlotsData = {
102102
nbItemsPerRow: number
103103
enablePlotCreation: boolean
104104
height: PlotHeight
105+
hasUnfilteredExperiments: boolean
106+
hasAddedPlots: boolean
105107
}
106108

107109
export enum PlotsType {

extension/src/test/fixtures/expShow/base/customPlots.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,9 @@ const data: CustomPlotsData = {
313313
}
314314
],
315315
nbItemsPerRow: DEFAULT_NB_ITEMS_PER_ROW,
316-
height: DEFAULT_PLOT_HEIGHT
316+
height: DEFAULT_PLOT_HEIGHT,
317+
hasAddedPlots: true,
318+
hasUnfilteredExperiments: true
317319
}
318320

319321
export default data

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

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,16 @@ import { isErrorItem } from '../../../tree'
4444
import { RegisteredCommands } from '../../../commands/external'
4545
import { REVISIONS } from '../../fixtures/plotsDiff'
4646
import * as FileSystem from '../../../fileSystem'
47-
import { ExpShowOutput, experimentHasError } from '../../../cli/dvc/contract'
47+
import {
48+
EXPERIMENT_WORKSPACE_ID,
49+
ExpShowOutput,
50+
experimentHasError
51+
} from '../../../cli/dvc/contract'
52+
import { Experiment } from '../../../experiments/webview/contract'
4853
import { COMMITS_SEPARATOR } from '../../../cli/git/constants'
4954
import { BaseWebview } from '../../../webview'
55+
import * as PlotsCollectUtils from '../../../plots/model/collect'
56+
import { Operator } from '../../../experiments/model/filterBy'
5057

5158
suite('Plots Test Suite', () => {
5259
const disposable = Disposable.fn()
@@ -172,6 +179,84 @@ suite('Plots Test Suite', () => {
172179
)
173180
})
174181

182+
describe('Custom Plots Creation', () => {
183+
it('should only use unfiltered experiments and commits in custom plots', async () => {
184+
const { plots, plotsModel, experimentsModel } = await buildPlots({
185+
disposer: disposable,
186+
plotsDiff: plotsDiffFixture
187+
})
188+
189+
const plotsCustomPlotsSpy = spy(PlotsCollectUtils, 'collectCustomPlots')
190+
191+
await plots.isReady()
192+
193+
stub(experimentsModel, 'getFilters')
194+
.onFirstCall()
195+
.returns([
196+
{
197+
operator: Operator.EQUAL,
198+
path: 'params:params.yaml:epochs',
199+
value: 2
200+
}
201+
])
202+
203+
plotsModel.getCustomPlots()
204+
205+
const allExperiments: Experiment[] =
206+
experimentsModel.getWorkspaceCommitsAndExperiments()
207+
208+
const { experiments } = plotsCustomPlotsSpy.firstCall.args[0]
209+
210+
expect(experiments).to.deep.equal(
211+
allExperiments.filter(
212+
({ id, params }) =>
213+
id !== EXPERIMENT_WORKSPACE_ID &&
214+
params?.['params.yaml']?.epochs === 2
215+
)
216+
)
217+
})
218+
219+
it('should handle all experiments/commits being filtered', async () => {
220+
const { plots, plotsModel, experimentsModel } = await buildPlots({
221+
disposer: disposable,
222+
plotsDiff: plotsDiffFixture
223+
})
224+
225+
await plots.isReady()
226+
227+
stub(experimentsModel, 'getUnfilteredCommitsAndExperiments')
228+
.onFirstCall()
229+
.returns([])
230+
231+
const customPlots = plotsModel.getCustomPlots()
232+
233+
expect(customPlots).to.deep.equal({
234+
...customPlotsFixture,
235+
hasUnfilteredExperiments: false,
236+
plots: []
237+
})
238+
})
239+
240+
it('should handle no plots being added yet', async () => {
241+
const { plots, plotsModel } = await buildPlots({
242+
disposer: disposable,
243+
plotsDiff: plotsDiffFixture
244+
})
245+
246+
await plots.isReady()
247+
248+
stub(plotsModel, 'getCustomPlotsOrder').onFirstCall().returns([])
249+
250+
const customPlots = plotsModel.getCustomPlots()
251+
252+
expect(customPlots).to.deep.equal({
253+
...customPlotsFixture,
254+
hasAddedPlots: false,
255+
plots: []
256+
})
257+
})
258+
})
259+
175260
it('should handle a section resized message from the webview', async () => {
176261
const { mockMessageReceived, plotsModel } = await buildPlotsWebview({
177262
disposer: disposable

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,7 @@ describe('App', () => {
533533
renderAppWithOptionalData({
534534
custom: {
535535
...customPlotsFixture,
536+
hasAddedPlots: false,
536537
plots: []
537538
},
538539
template: templatePlotsFixture
@@ -544,6 +545,22 @@ describe('App', () => {
544545
expect(screen.getByText('No Plots Added')).toBeInTheDocument()
545546
})
546547

548+
it('should render custom with "No Data to Plot" message when there are added plots but no unfiltered experiments', () => {
549+
renderAppWithOptionalData({
550+
custom: {
551+
...customPlotsFixture,
552+
hasUnfilteredExperiments: false,
553+
plots: []
554+
},
555+
template: templatePlotsFixture
556+
})
557+
558+
expect(screen.queryByText('Loading Plots...')).not.toBeInTheDocument()
559+
expect(screen.queryByText('No Plots to Display')).not.toBeInTheDocument()
560+
expect(screen.getByText('Custom')).toBeInTheDocument()
561+
expect(screen.getByText('No Data to Plot')).toBeInTheDocument()
562+
})
563+
547564
it('should render the comparison table when given a message with comparison plots data', () => {
548565
const expectedSectionName = 'Images'
549566

webview/src/plots/components/customPlots/CustomPlots.tsx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,14 @@ interface CustomPlotsProps {
2222

2323
export const CustomPlots: React.FC<CustomPlotsProps> = ({ plotsIds }) => {
2424
const [order, setOrder] = useState(plotsIds)
25-
const { nbItemsPerRow, hasData, hasItems, disabledDragPlotIds } = useSelector(
26-
(state: PlotsState) => state.custom
27-
)
25+
const {
26+
nbItemsPerRow,
27+
hasData,
28+
hasItems,
29+
hasAddedPlots,
30+
disabledDragPlotIds,
31+
hasUnfilteredExperiments
32+
} = useSelector((state: PlotsState) => state.custom)
2833
const [onSection, setOnSection] = useState(false)
2934
const draggedRef = useSelector(
3035
(state: PlotsState) => state.dragAndDrop.draggedRef
@@ -50,10 +55,14 @@ export const CustomPlots: React.FC<CustomPlotsProps> = ({ plotsIds }) => {
5055
return <EmptyState isFullScreen={false}>No Plots to Display</EmptyState>
5156
}
5257

53-
if (order.length === 0) {
58+
if (!hasAddedPlots) {
5459
return <EmptyState isFullScreen={false}>No Plots Added</EmptyState>
5560
}
5661

62+
if (!hasUnfilteredExperiments) {
63+
return <EmptyState isFullScreen={false}>No Data to Plot</EmptyState>
64+
}
65+
5766
const items = order.map(plot => (
5867
<div key={plot} id={plot}>
5968
<CustomPlot id={plot} />

webview/src/plots/components/customPlots/CustomPlotsWrapper.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,14 @@ import { PlotsState } from '../../store'
88
import { addCustomPlot, removeCustomPlots } from '../../util/messages'
99

1010
export const CustomPlotsWrapper: React.FC = () => {
11-
const { plotsIds, nbItemsPerRow, isCollapsed, height, enablePlotCreation } =
12-
useSelector((state: PlotsState) => state.custom)
11+
const {
12+
plotsIds,
13+
nbItemsPerRow,
14+
isCollapsed,
15+
height,
16+
enablePlotCreation,
17+
hasAddedPlots
18+
} = useSelector((state: PlotsState) => state.custom)
1319
const [selectedPlots, setSelectedPlots] = useState<string[]>([])
1420
useEffect(() => {
1521
setSelectedPlots(plotsIds)
@@ -26,7 +32,9 @@ export const CustomPlotsWrapper: React.FC = () => {
2632
addPlotsButton={
2733
enablePlotCreation ? { onClick: addCustomPlot } : undefined
2834
}
29-
removePlotsButton={hasItems ? { onClick: removeCustomPlots } : undefined}
35+
removePlotsButton={
36+
hasAddedPlots ? { onClick: removeCustomPlots } : undefined
37+
}
3038
changeSize={changeSize}
3139
hasItems={hasItems}
3240
height={height}

webview/src/plots/components/customPlots/customPlotsSlice.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ export interface CustomPlotsState extends Omit<CustomPlotsData, 'plots'> {
2121
export const customPlotsInitialState: CustomPlotsState = {
2222
disabledDragPlotIds: [],
2323
enablePlotCreation: true,
24+
hasAddedPlots: false,
2425
hasData: false,
2526
hasItems: false,
27+
hasUnfilteredExperiments: false,
2628
height: DEFAULT_HEIGHT[PlotsSection.CUSTOM_PLOTS],
2729
isCollapsed: DEFAULT_SECTION_COLLAPSED[PlotsSection.CUSTOM_PLOTS],
2830
nbItemsPerRow:

0 commit comments

Comments
 (0)