Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add action buttons to empty plot sections #4694

Merged
merged 3 commits into from
Sep 21, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 1 addition & 81 deletions extension/src/plots/model/custom.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { checkForCustomPlotOptions, cleanupOldOrderValue } from './custom'
import { customPlotsOrderFixture } from '../../test/fixtures/expShow/base/customPlots'
import { ColumnType } from '../../experiments/webview/contract'
import { cleanupOldOrderValue } from './custom'

describe('cleanupOlderValue', () => {
it('should update value if contents are outdated', () => {
Expand All @@ -23,81 +21,3 @@ describe('cleanupOlderValue', () => {
expect(output).toStrictEqual(value)
})
})

describe('checkForCustomPlotOptions', () => {
it('should return true if there are plots available', () => {
const output = checkForCustomPlotOptions(
[
{
hasChildren: false,
label: 'log_file',
path: 'params:params.yaml:log_file',
type: ColumnType.PARAMS
},
{
hasChildren: false,
label: 'log_file',
path: 'params:params.yaml:epochs',
type: ColumnType.PARAMS
},
{
hasChildren: false,
label: 'accuracy',
path: 'metrics:summary.json:loss',
type: ColumnType.METRICS
},
{
hasChildren: false,
label: 'accuracy',
path: 'metrics:summary.json:accuracy',
type: ColumnType.METRICS
}
],
customPlotsOrderFixture
)
expect(output).toStrictEqual(true)
})

it('should return false if there are no plots available', () => {
const output = checkForCustomPlotOptions(
[
{
hasChildren: false,
label: 'log_file',
path: 'params:params.yaml:log_file',
type: ColumnType.PARAMS
},
{
hasChildren: false,
label: 'log_file',
path: 'params:params.yaml:epochs',
type: ColumnType.PARAMS
},
{
hasChildren: false,
label: 'accuracy',
path: 'metrics:summary.json:loss',
type: ColumnType.METRICS
},
{
hasChildren: false,
label: 'accuracy',
path: 'metrics:summary.json:accuracy',
type: ColumnType.METRICS
}
],
[
...customPlotsOrderFixture,
{
metric: 'summary.json:accuracy',
param: 'params.yaml:log_file'
},
{
metric: 'summary.json:loss',
param: 'params.yaml:epochs'
}
]
)
expect(output).toStrictEqual(false)
})
})
18 changes: 0 additions & 18 deletions extension/src/plots/model/custom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,24 +52,6 @@ export const getCustomPlotPathsFromColumns = (
return { metrics, params }
}

export const checkForCustomPlotOptions = (
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently our "Add Plot" shows the custom plot option even if you can't create any more custom plots. We could technically add an extra check but then our choose plot quick pick would have only one item.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the option fails under these conditions with a clear message then that is ok.

I think the chances of real users hitting this is low.

Copy link
Contributor Author

@julieg18 julieg18 Sep 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the option fails under these conditions with a clear message then that is ok.

Yes, the option fails with an error toast message.

columns: Column[],
customPlotOrder: CustomPlotsOrderValue[]
): boolean => {
const { metrics, params } = getCustomPlotPathsFromColumns(columns)
const plotIds = getCustomPlotIds(customPlotOrder)

for (const metric of metrics) {
for (const param of params) {
if (!plotIds.has(getCustomPlotId(metric, param))) {
return true
}
}
}

return false
}

const getSpecDataType = (type: string) =>
type === 'number' ? 'quantitative' : 'nominal'

Expand Down
11 changes: 1 addition & 10 deletions extension/src/plots/model/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,7 @@ import {
collectSelectedComparisonPlots
} from './collect'
import { getRevisionSummaryColumns } from './util'
import {
checkForCustomPlotOptions,
cleanupOldOrderValue,
CustomPlotsOrderValue
} from './custom'
import { cleanupOldOrderValue, CustomPlotsOrderValue } from './custom'
import {
Revision,
DEFAULT_SECTION_COLLAPSED,
Expand Down Expand Up @@ -156,10 +152,6 @@ export class PlotsModel extends ModelWithPersistence {
const experiments = this.experiments.getUnfilteredCommitsAndExperiments()
const hasUnfilteredExperiments = experiments.length > 0
const plotsOrderValues = this.getCustomPlotsOrder()
const enablePlotCreation = checkForCustomPlotOptions(
this.experiments.getColumnTerminalNodes(),
plotsOrderValues
)
const height = this.getHeight(PlotsSection.CUSTOM_PLOTS)
const nbItemsPerRow = this.getNbItemsPerRowOrWidth(
PlotsSection.CUSTOM_PLOTS
Expand All @@ -173,7 +165,6 @@ export class PlotsModel extends ModelWithPersistence {
)

return {
enablePlotCreation,
hasAddedPlots,
hasUnfilteredExperiments,
height,
Expand Down
1 change: 0 additions & 1 deletion extension/src/plots/webview/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ export type CustomPlotData = CustomPlot & {
export type CustomPlotsData = {
plots: CustomPlotData[]
nbItemsPerRow: number
enablePlotCreation: boolean
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer needed since our "Add Custom Plot" action buttons will only appear in the case of there being no custom plots at all.

height: PlotHeight
hasUnfilteredExperiments: boolean
hasAddedPlots: boolean
Expand Down
1 change: 0 additions & 1 deletion extension/src/test/fixtures/expShow/base/customPlots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ export const experimentsWithCommits: Experiment[] = [
]

const data: CustomPlotsData = {
enablePlotCreation: true,
plots: [
{
id: 'custom-summary.json:loss-params.yaml:log_file',
Expand Down
87 changes: 67 additions & 20 deletions webview/src/plots/components/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,7 @@ describe('App', () => {
expect(screen.getByText('No Plots to Display')).toBeInTheDocument()
})

it('should render custom with "No Plots Added" message when there are no plots added', () => {
it('should render custom with "No Plots Added" message and "Add Plot" button when there are no plots added', () => {
renderAppWithOptionalData({
custom: {
...customPlotsFixture,
Expand All @@ -552,6 +552,16 @@ describe('App', () => {
expect(screen.queryByText('No Plots to Display')).not.toBeInTheDocument()
expect(screen.getByText('Custom')).toBeInTheDocument()
expect(screen.getByText('No Plots Added')).toBeInTheDocument()
const customSection = screen.getByTestId('custom-plots-section-details')

const addPlotButton = within(customSection).getByText('Add Plot')

expect(addPlotButton).toBeInTheDocument()

fireEvent.click(addPlotButton)
expect(mockPostMessage).toHaveBeenCalledWith({
type: MessageFromWebviewType.ADD_CUSTOM_PLOT
})
})

it('should render custom with "No Data to Plot" message when there are added plots but no unfiltered experiments', () => {
Expand All @@ -570,6 +580,62 @@ describe('App', () => {
expect(screen.getByText('No Data to Plot')).toBeInTheDocument()
})

it('should render template with "No Plots to Display" message and "Add Plot" button if there is no template data and no unselected plots', () => {
renderAppWithOptionalData({
comparison: comparisonTableFixture,
custom: customPlotsFixture,
hasUnselectedPlots: false,
template: null
})

expect(screen.queryByText('Loading Plots...')).not.toBeInTheDocument()
expect(screen.getByText('No Plots to Display')).toBeInTheDocument()

const templateSection = screen.getByTestId('template-plots-section-details')

const addPlotButton = within(templateSection).getByText('Add Plot')
const selectPlotsButton =
within(templateSection).queryByText('Select Plots')

expect(selectPlotsButton).not.toBeInTheDocument()
expect(addPlotButton).toBeInTheDocument()

fireEvent.click(addPlotButton)
expect(mockPostMessage).toHaveBeenCalledWith({
type: MessageFromWebviewType.ADD_PLOT
})
})

it('should render template with "No Plots to Display" message and action buttons ("Select Plots" and "Add Plot") if there is no template data and unselected plots', () => {
renderAppWithOptionalData({
comparison: comparisonTableFixture,
custom: customPlotsFixture,
hasUnselectedPlots: true,
template: null
})

expect(screen.queryByText('Loading Plots...')).not.toBeInTheDocument()
expect(screen.getByText('No Plots to Display')).toBeInTheDocument()

const templateSection = screen.getByTestId('template-plots-section-details')
const selectPlotsButton = within(templateSection).getByText('Select Plots')
const addPlotButton = within(templateSection).getByText('Add Plot')

expect(selectPlotsButton).toBeInTheDocument()
fireEvent.click(selectPlotsButton)
expect(mockPostMessage).toHaveBeenCalledWith({
type: MessageFromWebviewType.SELECT_PLOTS
})

mockPostMessage.mockReset()

expect(addPlotButton).toBeInTheDocument()
fireEvent.click(addPlotButton)
expect(mockPostMessage).toHaveBeenCalledWith({
type: MessageFromWebviewType.ADD_PLOT
})
})

it('should render the comparison table when given a message with comparison plots data', () => {
const expectedSectionName = 'Images'

Expand Down Expand Up @@ -766,25 +832,6 @@ describe('App', () => {
})
})

it('should hide the custom plots add button if there are no more plots to create', () => {
renderAppWithOptionalData({
comparison: comparisonTableFixture,
custom: customPlotsFixture
})

const customSection = screen.getAllByTestId('section-container')[2]

expect(within(customSection).getByLabelText('Add Plot')).toBeInTheDocument()

sendSetDataMessage({
custom: { ...customPlotsFixture, enablePlotCreation: false }
})

expect(
within(customSection).queryByLabelText('Add Plot')
).not.toBeInTheDocument()
})

it('should display a slider to pick the number of items per row if there are items and the action is available', () => {
const store = renderAppWithOptionalData({
custom: customPlotsFixture
Expand Down
3 changes: 2 additions & 1 deletion webview/src/plots/components/customPlots/CustomPlots.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { DragEvent, useEffect, useState } from 'react'
import { useSelector } from 'react-redux'
import cx from 'classnames'
import { CustomPlot } from './CustomPlot'
import { NoPlotsAdded } from './NoPlotsAdded'
import styles from '../styles.module.scss'
import { EmptyState } from '../../../shared/components/emptyState/EmptyState'
import {
Expand Down Expand Up @@ -56,7 +57,7 @@ export const CustomPlots: React.FC<CustomPlotsProps> = ({ plotsIds }) => {
}

if (!hasAddedPlots) {
return <EmptyState isFullScreen={false}>No Plots Added</EmptyState>
return <NoPlotsAdded />
}

if (!hasUnfilteredExperiments) {
Expand Down
15 changes: 3 additions & 12 deletions webview/src/plots/components/customPlots/CustomPlotsWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,11 @@ import { CustomPlots } from './CustomPlots'
import { changeSize } from './customPlotsSlice'
import { PlotsContainer } from '../PlotsContainer'
import { PlotsState } from '../../store'
import { addCustomPlot, removeCustomPlots } from '../../util/messages'
import { removeCustomPlots } from '../../util/messages'

export const CustomPlotsWrapper: React.FC = () => {
const {
plotsIds,
nbItemsPerRow,
isCollapsed,
height,
enablePlotCreation,
hasAddedPlots
} = useSelector((state: PlotsState) => state.custom)
const { plotsIds, nbItemsPerRow, isCollapsed, height, hasAddedPlots } =
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar blocks of code found in 3 locations. Consider refactoring.

useSelector((state: PlotsState) => state.custom)
const [selectedPlots, setSelectedPlots] = useState<string[]>([])
useEffect(() => {
setSelectedPlots(plotsIds)
Expand All @@ -29,9 +23,6 @@ export const CustomPlotsWrapper: React.FC = () => {
sectionKey={PlotsSection.CUSTOM_PLOTS}
nbItemsPerRowOrWidth={nbItemsPerRow}
sectionCollapsed={isCollapsed}
addPlotsButton={
enablePlotCreation ? { onClick: addCustomPlot } : undefined
}
removePlotsButton={
hasAddedPlots ? { onClick: removeCustomPlots } : undefined
}
Expand Down
13 changes: 13 additions & 0 deletions webview/src/plots/components/customPlots/NoPlotsAdded.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react'
import { StartButton } from '../../../shared/components/button/StartButton'
import { EmptyState } from '../../../shared/components/emptyState/EmptyState'
import { addCustomPlot } from '../../util/messages'

export const NoPlotsAdded: React.FC = () => {
return (
<EmptyState isFullScreen={false}>
<p>No Plots Added</p>
<StartButton onClick={addCustomPlot} text="Add Plot" />
</EmptyState>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ export interface CustomPlotsState extends Omit<CustomPlotsData, 'plots'> {

export const customPlotsInitialState: CustomPlotsState = {
disabledDragPlotIds: [],
enablePlotCreation: true,
hasAddedPlots: false,
hasData: false,
hasItems: false,
Expand Down
34 changes: 34 additions & 0 deletions webview/src/plots/components/templatePlots/NoPlotsToDisplay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react'
import { useSelector } from 'react-redux'
import { StartButton } from '../../../shared/components/button/StartButton'
import { EmptyState } from '../../../shared/components/emptyState/EmptyState'
import { addPlot, selectPlots } from '../../util/messages'
import { PlotsState } from '../../store'

export const NoPlotsToDisplay: React.FC = () => {
const { hasUnselectedPlots } = useSelector(
(state: PlotsState) => state.webview
)
return (
<EmptyState isFullScreen={false}>
<p>No Plots to Display</p>
{hasUnselectedPlots ? (
<div>
<StartButton
isNested={true}
onClick={selectPlots}
text="Select Plots"
/>
<StartButton
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The custom section "Add Plot" button is a bit different than the template one since it can take you to right to custom plot creation, unlike the data series which needs that extra description for more context.

We could keep as is, but if we wanted them to function the same we could:

  1. Have the custom plot section open the "Select Plot Type" quick pick as well. Would mean an extra click for the user but would reduce some code.
  2. Update our "Add Data Series Plot" action to open a quick pick first instead of the file selector, allowing the user to get context and type in a relative file if preferred. Would get rid of the extra click a user needs to make, but would make data series plot creation a bit more complex.

Copy link
Member

@mattseddon mattseddon Sep 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I vote for option 1. The only drawback that I see is that the plot may not necessarily appear in the section that the user is currently looking at. However, I think that is fine. "Add Data Series Plot" ends with the dvc.yaml being open anyway... right?

Copy link
Contributor Author

@julieg18 julieg18 Sep 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, "Add Data Series" ends with the dvc.yaml being opened. Makes sense! I'll update the code to use the add plot command instead.

text="Add Plot"
appearance="secondary"
isNested={true}
onClick={addPlot}
/>
</div>
) : (
<StartButton text="Add Plot" onClick={addPlot} />
)}
</EmptyState>
)
}
Loading
Loading