diff --git a/frontend/src/pages/AllRunsAndArchive.test.jsx.tsx b/frontend/src/pages/AllRunsAndArchive.test.tsx similarity index 100% rename from frontend/src/pages/AllRunsAndArchive.test.jsx.tsx rename to frontend/src/pages/AllRunsAndArchive.test.tsx diff --git a/frontend/src/pages/ExperimentDetails.test.tsx b/frontend/src/pages/ExperimentDetails.test.tsx index 71ca9ed9977..d868b61384f 100644 --- a/frontend/src/pages/ExperimentDetails.test.tsx +++ b/frontend/src/pages/ExperimentDetails.test.tsx @@ -17,16 +17,15 @@ import * as React from 'react'; import EnhancedExperimentDetails, { ExperimentDetails } from './ExperimentDetails'; import TestUtils from '../TestUtils'; -import { ApiExperiment } from '../apis/experiment'; +import { ApiExperiment, ExperimentStorageState } from '../apis/experiment'; import { Apis } from '../lib/Apis'; import { PageProps } from './Page'; import { ReactWrapper, ShallowWrapper, shallow } from 'enzyme'; import { RoutePage, RouteParams, QUERY_PARAMS } from '../components/Router'; -import { RunStorageState } from '../apis/run'; import { ToolbarProps } from '../components/Toolbar'; import { range } from 'lodash'; import { ButtonKeys } from '../lib/Buttons'; -import { render } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import { NamespaceContext } from 'src/lib/KubeflowClient'; import { Router } from 'react-router-dom'; import { createMemoryHistory } from 'history'; @@ -234,7 +233,22 @@ describe('ExperimentDetails', () => { tree = shallow(); await TestUtils.flushPromises(); - expect(tree.find('RunList').prop('storageState')).toBe(RunStorageState.AVAILABLE.toString()); + expect(tree.find('RunListsRouter').prop('storageState')).toBe(ExperimentStorageState.AVAILABLE); + }); + + it('shows a list of archived runs', async () => { + await mockNJobs(1); + + getExperimentSpy.mockImplementation(() => { + let apiExperiment = newMockExperiment(); + apiExperiment['storage_state'] = ExperimentStorageState.ARCHIVED; + return apiExperiment; + }); + + tree = shallow(); + await TestUtils.flushPromises(); + + expect(tree.find('RunListsRouter').prop('storageState')).toBe(ExperimentStorageState.ARCHIVED); }); it("fetches this experiment's recurring runs", async () => { @@ -303,7 +317,7 @@ describe('ExperimentDetails', () => { .at(0) .simulate('click'); await TestUtils.flushPromises(); - expect(tree.state('recurringRunsManagerOpen')).toBe(true); + expect(tree.state('recurringRunsManagerOpen')).toBeTruthy(); }); it('closes the recurring run manager modal', async () => { @@ -318,14 +332,14 @@ describe('ExperimentDetails', () => { .at(0) .simulate('click'); await TestUtils.flushPromises(); - expect(tree.state('recurringRunsManagerOpen')).toBe(true); + expect(tree.state('recurringRunsManagerOpen')).toBeTruthy(); tree .find('#closeExperimentRecurringRunManagerBtn') .at(0) .simulate('click'); await TestUtils.flushPromises(); - expect(tree.state('recurringRunsManagerOpen')).toBe(false); + expect(tree.state('recurringRunsManagerOpen')).toBeFalsy(); }); it('refreshes the number of active recurring runs when the recurring run manager is closed', async () => { @@ -343,7 +357,7 @@ describe('ExperimentDetails', () => { .at(0) .simulate('click'); await TestUtils.flushPromises(); - expect(tree.state('recurringRunsManagerOpen')).toBe(true); + expect(tree.state('recurringRunsManagerOpen')).toBeTruthy(); // Called in the recurring run manager to list the recurring runs expect(listJobsSpy).toHaveBeenCalledTimes(2); @@ -353,7 +367,7 @@ describe('ExperimentDetails', () => { .at(0) .simulate('click'); await TestUtils.flushPromises(); - expect(tree.state('recurringRunsManagerOpen')).toBe(false); + expect(tree.state('recurringRunsManagerOpen')).toBeFalsy(); // Called a third time when the manager is closed to update the number of active recurring runs expect(listJobsSpy).toHaveBeenCalledTimes(3); @@ -466,15 +480,14 @@ describe('ExperimentDetails', () => { await TestUtils.flushPromises(); tree.update(); - const compareBtn = (tree.state('runListToolbarProps') as ToolbarProps).actions[ - ButtonKeys.COMPARE - ]; - for (let i = 0; i < 12; i++) { + const compareBtn = (tree.state('runListToolbarProps') as ToolbarProps).actions[ + ButtonKeys.COMPARE + ]; if (i < 2 || i > 10) { - expect(compareBtn!.disabled).toBe(true); + expect(compareBtn!.disabled).toBeTruthy(); } else { - expect(compareBtn!.disabled).toBe(false); + expect(compareBtn!.disabled).toBeFalsy(); } tree .find('.tableRow') @@ -490,15 +503,67 @@ describe('ExperimentDetails', () => { await TestUtils.flushPromises(); tree.update(); - const cloneBtn = (tree.state('runListToolbarProps') as ToolbarProps).actions[ - ButtonKeys.CLONE_RUN - ]; - for (let i = 0; i < 4; i++) { + const cloneBtn = (tree.state('runListToolbarProps') as ToolbarProps).actions[ + ButtonKeys.CLONE_RUN + ]; if (i === 1) { - expect(cloneBtn!.disabled).toBe(false); + expect(cloneBtn!.disabled).toBeFalsy(); + } else { + expect(cloneBtn!.disabled).toBeTruthy(); + } + tree + .find('.tableRow') + .at(i) + .simulate('click'); + } + }); + + it('enables Archive button when at least one run is selected', async () => { + await mockNRuns(4); + + tree = TestUtils.mountWithRouter(); + await TestUtils.flushPromises(); + tree.update(); + + for (let i = 0; i < 4; i++) { + const archiveButton = (tree.state('runListToolbarProps') as ToolbarProps).actions[ + ButtonKeys.ARCHIVE + ]; + if (i === 0) { + expect(archiveButton!.disabled).toBeTruthy(); + } else { + expect(archiveButton!.disabled).toBeFalsy(); + } + tree + .find('.tableRow') + .at(i) + .simulate('click'); + } + }); + + it('enables Restore button when at least one run is selected', async () => { + await mockNRuns(4); + + tree = TestUtils.mountWithRouter(); + await TestUtils.flushPromises(); + tree.update(); + + tree + .find('MD2Tabs') + .find('Button') + .at(1) // `Archived` tab button + .simulate('click'); + await TestUtils.flushPromises(); + + for (let i = 0; i < 4; i++) { + const restoreButton = (tree.state('runListToolbarProps') as ToolbarProps).actions[ + ButtonKeys.RESTORE + ]; + if (i === 0) { + expect(restoreButton!.disabled).toBeTruthy(); } else { - expect(cloneBtn!.disabled).toBe(true); + expect(restoreButton!.disabled).toBeFalsy(); } tree .find('.tableRow') @@ -507,6 +572,92 @@ describe('ExperimentDetails', () => { } }); + it('switches to another tab will change Archive/Restore button', async () => { + await mockNRuns(4); + + tree = TestUtils.mountWithRouter(); + await TestUtils.flushPromises(); + tree.update(); + + tree + .find('MD2Tabs') + .find('Button') + .at(1) // `Archived` tab button + .simulate('click'); + await TestUtils.flushPromises(); + expect( + (tree.state('runListToolbarProps') as ToolbarProps).actions[ButtonKeys.ARCHIVE], + ).toBeUndefined(); + expect( + (tree.state('runListToolbarProps') as ToolbarProps).actions[ButtonKeys.RESTORE], + ).toBeDefined(); + + tree + .find('MD2Tabs') + .find('Button') + .at(0) // `Active` tab button + .simulate('click'); + await TestUtils.flushPromises(); + expect( + (tree.state('runListToolbarProps') as ToolbarProps).actions[ButtonKeys.ARCHIVE], + ).toBeDefined(); + expect( + (tree.state('runListToolbarProps') as ToolbarProps).actions[ButtonKeys.RESTORE], + ).toBeUndefined(); + }); + + it('switches to active/archive tab will show active/archive runs', async () => { + await mockNRuns(4); + tree = TestUtils.mountWithRouter(); + await TestUtils.flushPromises(); + tree.update(); + expect(tree.find('.tableRow').length).toEqual(4); + + await mockNRuns(2); + tree + .find('MD2Tabs') + .find('Button') + .at(1) // `Archived` tab button + .simulate('click'); + await TestUtils.flushPromises(); + tree.update(); + expect(tree.find('.tableRow').length).toEqual(2); + }); + + it('switches to another tab will change Archive/Restore button', async () => { + await mockNRuns(4); + + tree = TestUtils.mountWithRouter(); + await TestUtils.flushPromises(); + tree.update(); + + tree + .find('MD2Tabs') + .find('Button') + .at(1) // `Archived` tab button + .simulate('click'); + await TestUtils.flushPromises(); + expect( + (tree.state('runListToolbarProps') as ToolbarProps).actions[ButtonKeys.ARCHIVE], + ).toBeUndefined(); + expect( + (tree.state('runListToolbarProps') as ToolbarProps).actions[ButtonKeys.RESTORE], + ).toBeDefined(); + + tree + .find('MD2Tabs') + .find('Button') + .at(0) // `Active` tab button + .simulate('click'); + await TestUtils.flushPromises(); + expect( + (tree.state('runListToolbarProps') as ToolbarProps).actions[ButtonKeys.ARCHIVE], + ).toBeDefined(); + expect( + (tree.state('runListToolbarProps') as ToolbarProps).actions[ButtonKeys.RESTORE], + ).toBeUndefined(); + }); + describe('EnhancedExperimentDetails', () => { it('renders ExperimentDetails initially', () => { render(); diff --git a/frontend/src/pages/ExperimentDetails.tsx b/frontend/src/pages/ExperimentDetails.tsx index 66af136ffe6..9a17117b999 100644 --- a/frontend/src/pages/ExperimentDetails.tsx +++ b/frontend/src/pages/ExperimentDetails.tsx @@ -23,19 +23,19 @@ import DialogContent from '@material-ui/core/DialogContent'; import Paper from '@material-ui/core/Paper'; import PopOutIcon from '@material-ui/icons/Launch'; import RecurringRunsManager from './RecurringRunsManager'; -import RunList from '../pages/RunList'; +import RunListsRouter, { RunListsGroupTab } from './RunListsRouter'; import Toolbar, { ToolbarProps } from '../components/Toolbar'; import Tooltip from '@material-ui/core/Tooltip'; import { ApiExperiment, ExperimentStorageState } from '../apis/experiment'; import { Apis } from '../lib/Apis'; import { Page, PageProps } from './Page'; import { RoutePage, RouteParams } from '../components/Router'; -import { RunStorageState } from '../apis/run'; import { classes, stylesheet } from 'typestyle'; import { color, commonCss, padding } from '../Css'; import { logger } from '../lib/Utils'; import { useNamespaceChangeEvent } from 'src/lib/KubeflowClient'; import { Redirect } from 'react-router-dom'; +import { RunStorageState } from 'src/apis/run'; const css = stylesheet({ card: { @@ -104,44 +104,42 @@ interface ExperimentDetailsState { experiment: ApiExperiment | null; recurringRunsManagerOpen: boolean; selectedIds: string[]; - selectedTab: number; + runStorageState: RunStorageState; runListToolbarProps: ToolbarProps; + runlistRefreshCount: number; } export class ExperimentDetails extends Page<{}, ExperimentDetailsState> { - private _runlistRef = React.createRef(); - constructor(props: any) { super(props); - const buttons = new Buttons(this.props, this.refresh.bind(this)); this.state = { activeRecurringRunsCount: 0, experiment: null, recurringRunsManagerOpen: false, runListToolbarProps: { - actions: buttons - .newRun(() => this.props.match.params[RouteParams.experimentId]) - .newRecurringRun(this.props.match.params[RouteParams.experimentId]) - .compareRuns(() => this.state.selectedIds) - .cloneRun(() => this.state.selectedIds, false) - .archive( - 'run', - () => this.state.selectedIds, - false, - ids => this._selectionChanged(ids), - ) - .getToolbarActionMap(), + actions: this._getRunInitialToolBarButtons().getToolbarActionMap(), breadcrumbs: [], pageTitle: 'Runs', topLevelToolbar: false, }, // TODO: remove selectedIds: [], - selectedTab: 0, + runStorageState: RunStorageState.AVAILABLE, + runlistRefreshCount: 0, }; } + private _getRunInitialToolBarButtons(): Buttons { + const buttons = new Buttons(this.props, this.refresh.bind(this)); + buttons + .newRun(() => this.props.match.params[RouteParams.experimentId]) + .newRecurringRun(this.props.match.params[RouteParams.experimentId]) + .compareRuns(() => this.state.selectedIds) + .cloneRun(() => this.state.selectedIds, false); + return buttons; + } + public getInitialToolbarState(): ToolbarProps { const buttons = new Buttons(this.props, this.refresh.bind(this)); return { @@ -227,14 +225,15 @@ export class ExperimentDetails extends Page<{}, ExperimentDetailsState> { - @@ -267,17 +266,14 @@ export class ExperimentDetails extends Page<{}, ExperimentDetailsState> { public async refresh(): Promise { await this.load(); - if (this._runlistRef.current) { - await this._runlistRef.current.refresh(); - } return; } public async componentDidMount(): Promise { - return this.load(); + return this.load(true); } - public async load(): Promise { + public async load(isFirstTimeLoad: boolean = false): Promise { this.clearBanner(); const experimentId = this.props.match.params[RouteParams.experimentId]; @@ -296,6 +292,19 @@ export class ExperimentDetails extends Page<{}, ExperimentDetailsState> { experiment.storage_state === ExperimentStorageState.ARCHIVED ? buttons.restore('experiment', idGetter, true, () => this.refresh()) : buttons.archive('experiment', idGetter, true, () => this.refresh()); + // If experiment is archived, shows archived runs list by default. + // If experiment is active, shows active runs list by default. + let runStorageState = this.state.runStorageState; + // Determine the default Active/Archive run list tab based on experiment status. + // After component is mounted, it is up to user to decide the run storage state they + // want to view. + if (isFirstTimeLoad) { + runStorageState = + experiment.storage_state === ExperimentStorageState.ARCHIVED + ? RunStorageState.ARCHIVED + : RunStorageState.AVAILABLE; + } + const actions = buttons.getToolbarActionMap(); this.props.updateToolbar({ actions, @@ -326,19 +335,73 @@ export class ExperimentDetails extends Page<{}, ExperimentDetailsState> { logger.error(`Error fetching recurring runs for experiment: ${experimentId}`, err); } - this.setStateSafe({ activeRecurringRunsCount, experiment }); + let runlistRefreshCount = this.state.runlistRefreshCount + 1; + this.setStateSafe({ + activeRecurringRunsCount, + experiment, + runStorageState, + runlistRefreshCount, + }); + this._selectionChanged([]); } catch (err) { await this.showPageError(`Error: failed to retrieve experiment: ${experimentId}.`, err); logger.error(`Error loading experiment: ${experimentId}`, err); } } - private _selectionChanged(selectedIds: string[]): void { - const toolbarActions = this.state.runListToolbarProps.actions; + /** + * Users can choose to show runs list in different run storage states. + * + * @param tab selected by user for run storage state + */ + _onRunTabSwitch = (tab: RunListsGroupTab) => { + let runStorageState = RunStorageState.AVAILABLE; + if (tab === RunListsGroupTab.ARCHIVE) { + runStorageState = RunStorageState.ARCHIVED; + } + let runlistRefreshCount = this.state.runlistRefreshCount + 1; + this.setStateSafe( + { + runStorageState, + runlistRefreshCount, + }, + () => { + this._selectionChanged([]); + }, + ); + + return; + }; + + _selectionChanged = (selectedIds: string[]) => { + const toolbarButtons = this._getRunInitialToolBarButtons(); + // If user selects to show Active runs list, shows `Archive` button for selected runs. + // If user selects to show Archive runs list, shows `Restore` button for selected runs. + if (this.state.runStorageState === RunStorageState.AVAILABLE) { + toolbarButtons.archive( + 'run', + () => this.state.selectedIds, + false, + ids => this._selectionChanged(ids), + ); + } else { + toolbarButtons.restore( + 'run', + () => this.state.selectedIds, + false, + ids => this._selectionChanged(ids), + ); + } + const toolbarActions = toolbarButtons.getToolbarActionMap(); toolbarActions[ButtonKeys.COMPARE].disabled = selectedIds.length <= 1 || selectedIds.length > 10; toolbarActions[ButtonKeys.CLONE_RUN].disabled = selectedIds.length !== 1; - toolbarActions[ButtonKeys.ARCHIVE].disabled = !selectedIds.length; + if (toolbarActions[ButtonKeys.ARCHIVE]) { + toolbarActions[ButtonKeys.ARCHIVE].disabled = !selectedIds.length; + } + if (toolbarActions[ButtonKeys.RESTORE]) { + toolbarActions[ButtonKeys.RESTORE].disabled = !selectedIds.length; + } this.setState({ runListToolbarProps: { actions: toolbarActions, @@ -348,7 +411,7 @@ export class ExperimentDetails extends Page<{}, ExperimentDetailsState> { }, selectedIds, }); - } + }; private _recurringRunsManagerClosed(): void { this.setState({ recurringRunsManagerOpen: false }); diff --git a/frontend/src/pages/RunListsRouter.test.tsx b/frontend/src/pages/RunListsRouter.test.tsx new file mode 100644 index 00000000000..de121578ae5 --- /dev/null +++ b/frontend/src/pages/RunListsRouter.test.tsx @@ -0,0 +1,144 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { render, screen } from '@testing-library/react'; +import produce from 'immer'; +import RunListsRouter, { RunListsRouterProps } from './RunListsRouter'; +import React from 'react'; +import { RouteParams } from 'src/components/Router'; +import { ApiRunDetail, RunStorageState } from 'src/apis/run'; +import { ApiExperiment } from 'src/apis/experiment'; +import { Apis } from 'src/lib/Apis'; +import * as Utils from '../lib/Utils'; +import { BrowserRouter } from 'react-router-dom'; +import { PredicateOp } from 'src/apis/filter'; + +describe('RunListsRouter', () => { + let historyPushSpy: any; + let runStorageState = RunStorageState.AVAILABLE; + + const onSelectionChangeMock = jest.fn(); + const listRunsSpy = jest.spyOn(Apis.runServiceApi, 'listRuns'); + const getRunSpy = jest.spyOn(Apis.runServiceApi, 'getRun'); + const getPipelineSpy = jest.spyOn(Apis.pipelineServiceApi, 'getPipeline'); + const getExperimentSpy = jest.spyOn(Apis.experimentServiceApi, 'getExperiment'); + const formatDateStringSpy = jest.spyOn(Utils, 'formatDateString'); + const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => null); + + const MOCK_EXPERIMENT = newMockExperiment(); + const archiveRunDisplayName = 'run with id: achiverunid'; + const activeRunDisplayName = 'run with id: activerunid'; + + function newMockExperiment(): ApiExperiment { + return { + description: 'mock experiment description', + id: 'some-mock-experiment-id', + name: 'some mock experiment name', + }; + } + + function generateProps(): RunListsRouterProps { + const runListsRouterProps: RunListsRouterProps = { + onTabSwitch: jest.fn((newTab: number) => { + // this.refresh(); + if (newTab === 1) { + runStorageState = RunStorageState.ARCHIVED; + } else { + runStorageState = RunStorageState.AVAILABLE; + } + }), + hideExperimentColumn: true, + history: { push: historyPushSpy } as any, + location: '' as any, + match: { params: { [RouteParams.experimentId]: MOCK_EXPERIMENT.id } } as any, + onSelectionChange: onSelectionChangeMock, + selectedIds: [], + storageState: runStorageState, + refreshCount: 0, + noFilterBox: false, + disablePaging: false, + disableSorting: true, + disableSelection: false, + hideMetricMetadata: false, + onError: consoleErrorSpy, + }; + return runListsRouterProps; + } + + beforeEach(() => { + getRunSpy.mockImplementation(id => + Promise.resolve( + produce({} as Partial, draft => { + draft.run = draft.run || {}; + draft.run.id = id; + draft.run.name = 'run with id: ' + id; + }), + ), + ); + listRunsSpy.mockImplementation((pageToken, pageSize, sortBy, keyType, keyId, filter) => { + let filterForArchive = JSON.parse(decodeURIComponent('{"predicates": []}')); + filterForArchive = encodeURIComponent( + JSON.stringify({ + predicates: [ + { + key: 'storage_state', + op: PredicateOp.EQUALS, + string_value: RunStorageState.ARCHIVED.toString(), + }, + ], + }), + ); + if (filter === filterForArchive) { + return Promise.resolve({ + runs: [ + { + id: 'achiverunid', + name: archiveRunDisplayName, + }, + ], + }); + } + return Promise.resolve({ + runs: [ + { + id: 'activerunid', + name: activeRunDisplayName, + }, + ], + }); + }); + getPipelineSpy.mockImplementation(() => ({ name: 'some pipeline' })); + getExperimentSpy.mockImplementation(() => ({ name: 'some experiment' })); + formatDateStringSpy.mockImplementation((date?: Date) => { + return date ? '1/2/2019, 12:34:56 PM' : '-'; + }); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it('shows Active and Archive tabs', () => { + render( + + + , + ); + + screen.getByText('Active'); + screen.getByText('Archived'); + }); +}); diff --git a/frontend/src/pages/RunListsRouter.tsx b/frontend/src/pages/RunListsRouter.tsx new file mode 100644 index 00000000000..bbd37668e08 --- /dev/null +++ b/frontend/src/pages/RunListsRouter.tsx @@ -0,0 +1,97 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as React from 'react'; +import { RunStorageState } from 'src/apis/run'; +import MD2Tabs from 'src/atoms/MD2Tabs'; +import { commonCss, padding } from 'src/Css'; +import { classes } from 'typestyle'; +import RunList, { RunListProps } from './RunList'; + +export enum RunListsGroupTab { + ACTIVE = 0, + ARCHIVE = 1, +} + +export type RunListsRouterProps = RunListProps & { + storageState: RunStorageState; + refreshCount: number; + onTabSwitch?: (tab: RunListsGroupTab) => void; +}; + +/** + * Contains two tab buttons which allows user to see Active or Archived runs list. + */ +class RunListsRouter extends React.PureComponent { + private _runlistRef = React.createRef(); + + switchTab = (newTab: number) => { + if (this._getSelectedTab() === newTab) { + return; + } + if (this.props.onTabSwitch) { + this.props.onTabSwitch(newTab); + } + }; + + componentDidUpdate(prevProps: { refreshCount: number }) { + if (prevProps.refreshCount === this.props.refreshCount) { + return; + } + this.refresh(); + } + + public async refresh(): Promise { + if (this._runlistRef.current) { + await this._runlistRef.current.refresh(); + } + return; + } + + public render(): JSX.Element { + return ( +
+ + + { + + } +
+ ); + } + + private _getSelectedTab() { + return this.props.storageState === RunStorageState.ARCHIVED + ? RunListsGroupTab.ARCHIVE + : RunListsGroupTab.ACTIVE; + } +} + +export default RunListsRouter; diff --git a/frontend/src/pages/__snapshots__/AllRunsAndArchive.test.tsx.snap b/frontend/src/pages/__snapshots__/AllRunsAndArchive.test.tsx.snap new file mode 100644 index 00000000000..27cd4e21b58 --- /dev/null +++ b/frontend/src/pages/__snapshots__/AllRunsAndArchive.test.tsx.snap @@ -0,0 +1,57 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`RunsAndArchive renders archive page 1`] = ` +
+ + +
+`; + +exports[`RunsAndArchive renders runs page 1`] = ` +
+ + +
+`; diff --git a/frontend/src/pages/__snapshots__/ExperimentDetails.test.tsx.snap b/frontend/src/pages/__snapshots__/ExperimentDetails.test.tsx.snap index fd4724ec16b..05dc4676236 100644 --- a/frontend/src/pages/__snapshots__/ExperimentDetails.test.tsx.snap +++ b/frontend/src/pages/__snapshots__/ExperimentDetails.test.tsx.snap @@ -141,7 +141,7 @@ exports[`ExperimentDetails fetches this experiment's recurring runs 1`] = ` pageTitle="Runs" topLevelToolbar={false} /> - - - -