PipelineDetails page tests (#380)

* wip pipeline details tests

* pipeline details tests

* safe load template

* add close panel test

* pr comments
This commit is contained in:
Yasser Elsayed 2018-11-27 23:31:19 -08:00 committed by k8s-ci-robot
parent 2a3ec15993
commit db772d51bc
5 changed files with 1193 additions and 46 deletions

View File

@ -119,11 +119,14 @@ interface GraphProps {
} }
export default class Graph extends React.Component<GraphProps> { export default class Graph extends React.Component<GraphProps> {
public render(): JSX.Element { public render(): JSX.Element | null {
const { graph } = this.props; const { graph } = this.props;
const displayEdges: Edge[] = []; const displayEdges: Edge[] = [];
const displayEdgeStartPoints: number[][] = []; const displayEdgeStartPoints: number[][] = [];
if (!graph.nodes().length) {
return null;
}
dagre.layout(graph); dagre.layout(graph);
// Creates the lines that constitute the edges connecting the graph. // Creates the lines that constitute the edges connecting the graph.

View File

@ -94,11 +94,7 @@ exports[`Graph gracefully renders a graph with a selected node id that does not
</div> </div>
`; `;
exports[`Graph handles an empty graph 1`] = ` exports[`Graph handles an empty graph 1`] = `""`;
<div
className="root"
/>
`;
exports[`Graph renders a complex graph with six nodes and seven edges 1`] = ` exports[`Graph renders a complex graph with six nodes and seven edges 1`] = `
<div <div

View File

@ -0,0 +1,332 @@
/*
* Copyright 2018 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 * as StaticGraphParser from '../lib/StaticGraphParser';
import PipelineDetails, { css } from './PipelineDetails';
import TestUtils from '../TestUtils';
import { ApiPipeline } from '../apis/pipeline';
import { Apis } from '../lib/Apis';
import { PageProps } from './Page';
import { QUERY_PARAMS } from '../lib/URLParser';
import { RouteParams, RoutePage } from '../components/Router';
import { graphlib } from 'dagre';
import { shallow, mount, ShallowWrapper, ReactWrapper } from 'enzyme';
describe('PipelineDetails', () => {
const updateBannerSpy = jest.fn();
const updateDialogSpy = jest.fn();
const updateSnackbarSpy = jest.fn();
const updateToolbarSpy = jest.fn();
const historyPushSpy = jest.fn();
const getPipelineSpy = jest.spyOn(Apis.pipelineServiceApi, 'getPipeline');
const deletePipelineSpy = jest.spyOn(Apis.pipelineServiceApi, 'deletePipeline');
const getTemplateSpy = jest.spyOn(Apis.pipelineServiceApi, 'getTemplate');
const createGraphSpy = jest.spyOn(StaticGraphParser, 'createGraph');
let tree: ShallowWrapper | ReactWrapper;
let testPipeline: ApiPipeline = {};
function generateProps(): PageProps {
// getInitialToolbarState relies on page props having been populated, so fill those first
const pageProps: PageProps = {
history: { push: historyPushSpy } as any,
location: '' as any,
match: { params: { [RouteParams.pipelineId]: testPipeline.id }, isExact: true, path: '', url: '' },
toolbarProps: { actions: [], breadcrumbs: [], pageTitle: '' },
updateBanner: updateBannerSpy,
updateDialog: updateDialogSpy,
updateSnackbar: updateSnackbarSpy,
updateToolbar: updateToolbarSpy,
};
return Object.assign(pageProps, {
toolbarProps: new PipelineDetails(pageProps).getInitialToolbarState(),
});
}
beforeAll(() => jest.spyOn(console, 'error').mockImplementation());
afterAll(() => jest.resetAllMocks());
beforeEach(() => {
testPipeline = {
created_at: new Date(2018, 8, 5, 4, 3, 2),
description: 'test job description',
enabled: true,
id: 'test-job-id',
max_concurrency: '50',
name: 'test job',
pipeline_spec: {
parameters: [{ name: 'param1', value: 'value1' }],
pipeline_id: 'some-pipeline-id',
},
trigger: {
periodic_schedule: {
end_time: new Date(2018, 10, 9, 8, 7, 6),
interval_second: '3600',
start_time: new Date(2018, 9, 8, 7, 6),
}
},
} as ApiPipeline;
historyPushSpy.mockClear();
updateBannerSpy.mockClear();
updateDialogSpy.mockClear();
updateSnackbarSpy.mockClear();
updateToolbarSpy.mockClear();
deletePipelineSpy.mockReset();
getPipelineSpy.mockImplementation(() => Promise.resolve(testPipeline));
getPipelineSpy.mockClear();
getTemplateSpy.mockImplementation(() => Promise.resolve('{}'));
getTemplateSpy.mockClear();
createGraphSpy.mockImplementation(() => new graphlib.Graph());
createGraphSpy.mockClear();
});
afterEach(() => tree.unmount());
it('shows empty pipeline details with no graph', async () => {
TestUtils.makeErrorResponseOnce(createGraphSpy, 'bad graph');
tree = shallow(<PipelineDetails {...generateProps()} />);
await getTemplateSpy;
await TestUtils.flushPromises();
expect(tree).toMatchSnapshot();
});
it('shows load error banner when failing to get pipeline', async () => {
TestUtils.makeErrorResponseOnce(getPipelineSpy, 'woops');
tree = shallow(<PipelineDetails {...generateProps()} />);
await getPipelineSpy;
await TestUtils.flushPromises();
expect(updateBannerSpy).toHaveBeenCalledTimes(2); // Once to clear banner, once to show error
expect(updateBannerSpy).toHaveBeenLastCalledWith(expect.objectContaining({
additionalInfo: 'woops',
message: 'Cannot retrieve pipeline details. Click Details for more information.',
mode: 'error',
}));
});
it('shows load error banner when failing to get pipeline template', async () => {
TestUtils.makeErrorResponseOnce(getTemplateSpy, 'woops');
tree = shallow(<PipelineDetails {...generateProps()} />);
await getPipelineSpy;
await TestUtils.flushPromises();
expect(updateBannerSpy).toHaveBeenCalledTimes(2); // Once to clear banner, once to show error
expect(updateBannerSpy).toHaveBeenLastCalledWith(expect.objectContaining({
additionalInfo: 'woops',
message: 'Cannot retrieve pipeline template. Click Details for more information.',
mode: 'error',
}));
});
it('shows no graph error banner when failing to parse graph', async () => {
TestUtils.makeErrorResponseOnce(createGraphSpy, 'bad graph');
tree = shallow(<PipelineDetails {...generateProps()} />);
await getTemplateSpy;
await TestUtils.flushPromises();
expect(updateBannerSpy).toHaveBeenCalledTimes(2); // Once to clear banner, once to show error
expect(updateBannerSpy).toHaveBeenLastCalledWith(expect.objectContaining({
additionalInfo: 'bad graph',
message: 'Error: failed to generate Pipeline graph. Click Details for more information.',
mode: 'error',
}));
});
it('shows empty pipeline details with empty graph', async () => {
tree = shallow(<PipelineDetails {...generateProps()} />);
await getTemplateSpy;
await TestUtils.flushPromises();
expect(tree).toMatchSnapshot();
});
it('sets summary shown state to false when clicking the Hide button', async () => {
tree = mount(<PipelineDetails {...generateProps()} />);
await getTemplateSpy;
await TestUtils.flushPromises();
tree.update();
expect(tree.state('summaryShown')).toBe(true);
tree.find('Paper Button').simulate('click');
expect(tree.state('summaryShown')).toBe(false);
});
it('collapses summary card when summary shown state is false', async () => {
tree = shallow(<PipelineDetails {...generateProps()} />);
await getTemplateSpy;
await TestUtils.flushPromises();
tree.setState({ summaryShown: false });
expect(tree).toMatchSnapshot();
});
it('shows the summary card when clicking Show button', async () => {
tree = mount(<PipelineDetails {...generateProps()} />);
await getTemplateSpy;
await TestUtils.flushPromises();
tree.setState({ summaryShown: false });
tree.find(`.${css.footer} Button`).simulate('click');
expect(tree.state('summaryShown')).toBe(true);
});
it('has a new experiment button', async () => {
tree = shallow(<PipelineDetails {...generateProps()} />);
await getTemplateSpy;
await TestUtils.flushPromises();
const instance = tree.instance() as PipelineDetails;
const newExperimentBtn = instance.getInitialToolbarState().actions.find(
b => b.title === 'Start an experiment');
expect(newExperimentBtn).toBeDefined();
});
it('clicking new experiment button navigates to new experiment page', async () => {
tree = shallow(<PipelineDetails {...generateProps()} />);
await getTemplateSpy;
await TestUtils.flushPromises();
const instance = tree.instance() as PipelineDetails;
const newExperimentBtn = instance.getInitialToolbarState().actions.find(
b => b.title === 'Start an experiment');
await newExperimentBtn!.action();
expect(historyPushSpy).toHaveBeenCalledTimes(1);
expect(historyPushSpy).toHaveBeenLastCalledWith(
RoutePage.NEW_EXPERIMENT + `?${QUERY_PARAMS.pipelineId}=${testPipeline.id}`);
});
it('has a delete button', async () => {
tree = shallow(<PipelineDetails {...generateProps()} />);
await getTemplateSpy;
await TestUtils.flushPromises();
const instance = tree.instance() as PipelineDetails;
const deleteBtn = instance.getInitialToolbarState().actions.find(
b => b.title === 'Delete');
expect(deleteBtn).toBeDefined();
});
it('shows delete confirmation dialog when delete buttin is clicked', async () => {
tree = shallow(<PipelineDetails {...generateProps()} />);
const deleteBtn = (tree.instance() as PipelineDetails)
.getInitialToolbarState().actions.find(b => b.title === 'Delete');
await deleteBtn!.action();
expect(updateDialogSpy).toHaveBeenCalledTimes(1);
expect(updateDialogSpy).toHaveBeenLastCalledWith(expect.objectContaining({
title: 'Delete this pipeline?',
}));
});
it('does not call delete API for selected pipeline when delete dialog is canceled', async () => {
tree = shallow(<PipelineDetails {...generateProps()} />);
const deleteBtn = (tree.instance() as PipelineDetails)
.getInitialToolbarState().actions.find(b => b.title === 'Delete');
await deleteBtn!.action();
const call = updateDialogSpy.mock.calls[0][0];
const cancelBtn = call.buttons.find((b: any) => b.text === 'Cancel');
await cancelBtn.onClick();
expect(deletePipelineSpy).not.toHaveBeenCalled();
});
it('calls delete API when delete dialog is confirmed', async () => {
tree = shallow(<PipelineDetails {...generateProps()} />);
await getTemplateSpy;
await TestUtils.flushPromises();
const deleteBtn = (tree.instance() as PipelineDetails)
.getInitialToolbarState().actions.find(b => b.title === 'Delete');
await deleteBtn!.action();
const call = updateDialogSpy.mock.calls[0][0];
const confirmBtn = call.buttons.find((b: any) => b.text === 'Delete');
await confirmBtn.onClick();
expect(deletePipelineSpy).toHaveBeenCalledTimes(1);
expect(deletePipelineSpy).toHaveBeenLastCalledWith(testPipeline.id);
});
it('shows error dialog if deletion fails', async () => {
tree = shallow(<PipelineDetails {...generateProps()} />);
TestUtils.makeErrorResponseOnce(deletePipelineSpy, 'woops');
await getTemplateSpy;
await TestUtils.flushPromises();
const deleteBtn = (tree.instance() as PipelineDetails)
.getInitialToolbarState().actions.find(b => b.title === 'Delete');
await deleteBtn!.action();
const call = updateDialogSpy.mock.calls[0][0];
const confirmBtn = call.buttons.find((b: any) => b.text === 'Delete');
await confirmBtn.onClick();
expect(updateDialogSpy).toHaveBeenCalledTimes(2); // Delete dialog + error dialog
expect(updateDialogSpy).toHaveBeenLastCalledWith(expect.objectContaining({
content: 'woops',
title: 'Failed to delete pipeline',
}));
});
it('shows success snackbar if deletion succeeds', async () => {
tree = shallow(<PipelineDetails {...generateProps()} />);
await getTemplateSpy;
await TestUtils.flushPromises();
const deleteBtn = (tree.instance() as PipelineDetails)
.getInitialToolbarState().actions.find(b => b.title === 'Delete');
await deleteBtn!.action();
const call = updateDialogSpy.mock.calls[0][0];
const confirmBtn = call.buttons.find((b: any) => b.text === 'Delete');
await confirmBtn.onClick();
expect(updateSnackbarSpy).toHaveBeenCalledTimes(1);
expect(updateSnackbarSpy).toHaveBeenLastCalledWith(expect.objectContaining({
message: 'Successfully deleted pipeline: ' + testPipeline.name,
open: true,
}));
});
it('opens side panel on clicked node, shows message when node is not found in graph', async () => {
tree = shallow(<PipelineDetails {...generateProps()} />);
await getTemplateSpy;
await TestUtils.flushPromises();
tree.find('Graph').simulate('click', 'some-node-id');
expect(tree.state('selectedNodeId')).toBe('some-node-id');
expect(tree).toMatchSnapshot();
});
it('shows clicked node info in the side panel if it is in the graph', async () => {
const g = new graphlib.Graph();
const info = new StaticGraphParser.SelectedNodeInfo();
info.args = ['test arg', 'test arg2'];
info.command = ['test command', 'test command 2'];
info.condition = 'test condition';
info.image = 'test image';
info.inputs = [['key1', 'val1'], ['key2', 'val2']];
info.outputs = [['key3', 'val3'], ['key4', 'val4']];
info.nodeType = 'container';
g.setNode('node1', { info, label: 'node1' });
createGraphSpy.mockImplementation(() => g);
tree = shallow(<PipelineDetails {...generateProps()} />);
await getTemplateSpy;
await TestUtils.flushPromises();
tree.find('Graph').simulate('click', 'node1');
expect(tree).toMatchSnapshot();
});
it('shows pipeline source code when config tab is clicked', async () => {
tree = shallow(<PipelineDetails {...generateProps()} />);
await getTemplateSpy;
await TestUtils.flushPromises();
tree.find('MD2Tabs').simulate('switch', 1);
expect(tree.state('selectedTab')).toBe(1);
expect(tree).toMatchSnapshot();
});
it('closes side panel when close button is clicked', async () => {
tree = shallow(<PipelineDetails {...generateProps()} />);
await getTemplateSpy;
await TestUtils.flushPromises();
tree.setState({ selectedNodeId: 'some-node-id' });
tree.find('SidePanel').simulate('close');
expect(tree.state('selectedNodeId')).toBe('');
expect(tree).toMatchSnapshot();
});
});

View File

@ -27,7 +27,7 @@ import MD2Tabs from '../atoms/MD2Tabs';
import Paper from '@material-ui/core/Paper'; import Paper from '@material-ui/core/Paper';
import SidePanel from '../components/SidePanel'; import SidePanel from '../components/SidePanel';
import StaticNodeDetails from '../components/StaticNodeDetails'; import StaticNodeDetails from '../components/StaticNodeDetails';
import { ApiPipeline } from '../apis/pipeline'; import { ApiPipeline, ApiGetTemplateResponse } from '../apis/pipeline';
import { Apis } from '../lib/Apis'; import { Apis } from '../lib/Apis';
import { Page } from './Page'; import { Page } from './Page';
import { RoutePage, RouteParams } from '../components/Router'; import { RoutePage, RouteParams } from '../components/Router';
@ -52,7 +52,7 @@ interface PipelineDetailsState {
const summaryCardWidth = 500; const summaryCardWidth = 500;
const css = stylesheet({ export const css = stylesheet({
containerCss: { containerCss: {
$nest: { $nest: {
'& .CodeMirror': { '& .CodeMirror': {
@ -126,7 +126,7 @@ class PipelineDetails extends Page<{}, PipelineDetailsState> {
{ onClick: () => this._deleteDialogClosed(false), text: 'Cancel' }, { onClick: () => this._deleteDialogClosed(false), text: 'Cancel' },
], ],
onClose: () => this._deleteDialogClosed(false), onClose: () => this._deleteDialogClosed(false),
title: 'Delete this Pipeline?', title: 'Delete this pipeline?',
}), }),
id: 'deleteBtn', id: 'deleteBtn',
title: 'Delete', title: 'Delete',
@ -155,7 +155,7 @@ class PipelineDetails extends Page<{}, PipelineDetailsState> {
<div className={commonCss.page}> <div className={commonCss.page}>
<MD2Tabs <MD2Tabs
selectedTab={selectedTab} selectedTab={selectedTab}
onSwitch={(tab: number) => this.setState({ selectedTab: tab })} onSwitch={(tab: number) => this.setStateSafe({ selectedTab: tab })}
tabs={['Graph', 'Source']} tabs={['Graph', 'Source']}
/> />
<div className={commonCss.page}> <div className={commonCss.page}>
@ -167,7 +167,7 @@ class PipelineDetails extends Page<{}, PipelineDetailsState> {
<div className={commonCss.header}> <div className={commonCss.header}>
Summary Summary
</div> </div>
<Button onClick={() => this.setState({ summaryShown: false })} color='secondary'> <Button onClick={() => this.setStateSafe({ summaryShown: false })} color='secondary'>
Hide Hide
</Button> </Button>
</div> </div>
@ -179,10 +179,10 @@ class PipelineDetails extends Page<{}, PipelineDetailsState> {
)} )}
<Graph graph={this.state.graph} selectedNodeId={selectedNodeId} <Graph graph={this.state.graph} selectedNodeId={selectedNodeId}
onClick={id => this.setState({ selectedNodeId: id })} /> onClick={id => this.setStateSafe({ selectedNodeId: id })} />
<SidePanel isOpen={!!selectedNodeId} <SidePanel isOpen={!!selectedNodeId}
title={selectedNodeId} onClose={() => this.setState({ selectedNodeId: '' })}> title={selectedNodeId} onClose={() => this.setStateSafe({ selectedNodeId: '' })}>
<div className={commonCss.page}> <div className={commonCss.page}>
{!selectedNodeInfo && <div>Unable to retrieve node info</div>} {!selectedNodeInfo && <div>Unable to retrieve node info</div>}
{!!selectedNodeInfo && <div className={padding(20, 'lr')}> {!!selectedNodeInfo && <div className={padding(20, 'lr')}>
@ -192,7 +192,7 @@ class PipelineDetails extends Page<{}, PipelineDetailsState> {
</SidePanel> </SidePanel>
<div className={css.footer}> <div className={css.footer}>
{!summaryShown && ( {!summaryShown && (
<Button onClick={() => this.setState({ summaryShown: !summaryShown })} color='secondary'> <Button onClick={() => this.setStateSafe({ summaryShown: !summaryShown })} color='secondary'>
Show summary Show summary
</Button> </Button>
)} )}
@ -238,16 +238,30 @@ class PipelineDetails extends Page<{}, PipelineDetailsState> {
this.clearBanner(); this.clearBanner();
const pipelineId = this.props.match.params[RouteParams.pipelineId]; const pipelineId = this.props.match.params[RouteParams.pipelineId];
try { let pipeline: ApiPipeline;
const [pipeline, templateResponse] = await Promise.all([ let templateResponse: ApiGetTemplateResponse;
Apis.pipelineServiceApi.getPipeline(pipelineId),
Apis.pipelineServiceApi.getTemplate(pipelineId)
]);
const template: Workflow = JsYaml.safeLoad(templateResponse.template!); try {
pipeline = await Apis.pipelineServiceApi.getPipeline(pipelineId);
} catch (err) {
await this.showPageError('Cannot retrieve pipeline details.', err);
logger.error('Cannot retrieve pipeline details.', err);
return;
}
try {
templateResponse = await Apis.pipelineServiceApi.getTemplate(pipelineId);
} catch (err) {
await this.showPageError('Cannot retrieve pipeline template.', err);
logger.error('Cannot retrieve pipeline details.', err);
return;
}
let template: Workflow | undefined;
let g: dagre.graphlib.Graph | undefined; let g: dagre.graphlib.Graph | undefined;
try { try {
g = StaticGraphParser.createGraph(template); template = JsYaml.safeLoad(templateResponse.template || '{}');
g = StaticGraphParser.createGraph(template!);
} catch (err) { } catch (err) {
await this.showPageError('Error: failed to generate Pipeline graph.', err); await this.showPageError('Error: failed to generate Pipeline graph.', err);
} }
@ -259,21 +273,13 @@ class PipelineDetails extends Page<{}, PipelineDetailsState> {
toolbarActions[0].disabled = false; toolbarActions[0].disabled = false;
this.props.updateToolbar({ breadcrumbs, actions: toolbarActions, pageTitle }); this.props.updateToolbar({ breadcrumbs, actions: toolbarActions, pageTitle });
this.setState({ this.setStateSafe({
graph: g, graph: g,
pipeline, pipeline,
template, template,
templateYaml: templateResponse.template, templateYaml: templateResponse.template,
}); });
} }
catch (err) {
await this.showPageError(
`Error: failed to retrieve pipeline or template for ID: ${pipelineId}.`,
err,
);
logger.error(`Error loading pipeline or template for ID: ${pipelineId}`, err);
}
}
private _createNewExperiment(): void { private _createNewExperiment(): void {
const searchString = new URLParser(this.props).build({ const searchString = new URLParser(this.props).build({

View File

@ -0,0 +1,810 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PipelineDetails closes side panel when close button is clicked 1`] = `
<div
className="page"
>
<div
className="page"
>
<MD2Tabs
onSwitch={[Function]}
selectedTab={0}
tabs={
Array [
"Graph",
"Source",
]
}
/>
<div
className="page"
>
<div
className="page"
>
<div
className="page"
style={
Object {
"overflow": "hidden",
"position": "relative",
}
}
>
<WithStyles(Paper)
className="summaryCard"
>
<div
style={
Object {
"alignItems": "baseline",
"display": "flex",
"justifyContent": "space-between",
}
}
>
<div
className="header"
>
Summary
</div>
<WithStyles(Button)
color="secondary"
onClick={[Function]}
>
Hide
</WithStyles(Button)>
</div>
<div
className="summaryKey"
>
Uploaded on
</div>
<div>
9/5/2018, 4:03:02 AM
</div>
<div
className="summaryKey"
>
Description
</div>
<div>
test job description
</div>
</WithStyles(Paper)>
<Graph
graph={
Graph {
"_defaultEdgeLabelFn": [Function],
"_defaultNodeLabelFn": [Function],
"_edgeLabels": Object {},
"_edgeObjs": Object {},
"_in": Object {},
"_isCompound": false,
"_isDirected": true,
"_isMultigraph": false,
"_label": undefined,
"_nodes": Object {},
"_out": Object {},
"_preds": Object {},
"_sucs": Object {},
}
}
onClick={[Function]}
selectedNodeId=""
/>
<SidePanel
isOpen={false}
onClose={[Function]}
title=""
>
<div
className="page"
>
<div>
Unable to retrieve node info
</div>
</div>
</SidePanel>
<div
className="footer"
>
<div
className="flex footerInfoOffset"
>
<pure(InfoOutlinedIcon)
style={
Object {
"color": "#80868b",
"height": 16,
"width": 16,
}
}
/>
<span
className="infoSpan"
>
Static pipeline graph
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;
exports[`PipelineDetails collapses summary card when summary shown state is false 1`] = `
<div
className="page"
>
<div
className="page"
>
<MD2Tabs
onSwitch={[Function]}
selectedTab={0}
tabs={
Array [
"Graph",
"Source",
]
}
/>
<div
className="page"
>
<div
className="page"
>
<div
className="page"
style={
Object {
"overflow": "hidden",
"position": "relative",
}
}
>
<Graph
graph={
Graph {
"_defaultEdgeLabelFn": [Function],
"_defaultNodeLabelFn": [Function],
"_edgeLabels": Object {},
"_edgeObjs": Object {},
"_in": Object {},
"_isCompound": false,
"_isDirected": true,
"_isMultigraph": false,
"_label": undefined,
"_nodes": Object {},
"_out": Object {},
"_preds": Object {},
"_sucs": Object {},
}
}
onClick={[Function]}
selectedNodeId=""
/>
<SidePanel
isOpen={false}
onClose={[Function]}
title=""
>
<div
className="page"
>
<div>
Unable to retrieve node info
</div>
</div>
</SidePanel>
<div
className="footer"
>
<WithStyles(Button)
color="secondary"
onClick={[Function]}
>
Show summary
</WithStyles(Button)>
<div
className="flex"
>
<pure(InfoOutlinedIcon)
style={
Object {
"color": "#80868b",
"height": 16,
"width": 16,
}
}
/>
<span
className="infoSpan"
>
Static pipeline graph
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;
exports[`PipelineDetails opens side panel on clicked node, shows message when node is not found in graph 1`] = `
<div
className="page"
>
<div
className="page"
>
<MD2Tabs
onSwitch={[Function]}
selectedTab={0}
tabs={
Array [
"Graph",
"Source",
]
}
/>
<div
className="page"
>
<div
className="page"
>
<div
className="page"
style={
Object {
"overflow": "hidden",
"position": "relative",
}
}
>
<WithStyles(Paper)
className="summaryCard"
>
<div
style={
Object {
"alignItems": "baseline",
"display": "flex",
"justifyContent": "space-between",
}
}
>
<div
className="header"
>
Summary
</div>
<WithStyles(Button)
color="secondary"
onClick={[Function]}
>
Hide
</WithStyles(Button)>
</div>
<div
className="summaryKey"
>
Uploaded on
</div>
<div>
9/5/2018, 4:03:02 AM
</div>
<div
className="summaryKey"
>
Description
</div>
<div>
test job description
</div>
</WithStyles(Paper)>
<Graph
graph={
Graph {
"_defaultEdgeLabelFn": [Function],
"_defaultNodeLabelFn": [Function],
"_edgeLabels": Object {},
"_edgeObjs": Object {},
"_in": Object {},
"_isCompound": false,
"_isDirected": true,
"_isMultigraph": false,
"_label": undefined,
"_nodes": Object {},
"_out": Object {},
"_preds": Object {},
"_sucs": Object {},
}
}
onClick={[Function]}
selectedNodeId="some-node-id"
/>
<SidePanel
isOpen={true}
onClose={[Function]}
title="some-node-id"
>
<div
className="page"
>
<div>
Unable to retrieve node info
</div>
</div>
</SidePanel>
<div
className="footer"
>
<div
className="flex footerInfoOffset"
>
<pure(InfoOutlinedIcon)
style={
Object {
"color": "#80868b",
"height": 16,
"width": 16,
}
}
/>
<span
className="infoSpan"
>
Static pipeline graph
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;
exports[`PipelineDetails shows clicked node info in the side panel if it is in the graph 1`] = `
<div
className="page"
>
<div
className="page"
>
<MD2Tabs
onSwitch={[Function]}
selectedTab={0}
tabs={
Array [
"Graph",
"Source",
]
}
/>
<div
className="page"
>
<div
className="page"
>
<div
className="page"
style={
Object {
"overflow": "hidden",
"position": "relative",
}
}
>
<WithStyles(Paper)
className="summaryCard"
>
<div
style={
Object {
"alignItems": "baseline",
"display": "flex",
"justifyContent": "space-between",
}
}
>
<div
className="header"
>
Summary
</div>
<WithStyles(Button)
color="secondary"
onClick={[Function]}
>
Hide
</WithStyles(Button)>
</div>
<div
className="summaryKey"
>
Uploaded on
</div>
<div>
9/5/2018, 4:03:02 AM
</div>
<div
className="summaryKey"
>
Description
</div>
<div>
test job description
</div>
</WithStyles(Paper)>
<Graph
graph={
Graph {
"_defaultEdgeLabelFn": [Function],
"_defaultNodeLabelFn": [Function],
"_edgeLabels": Object {},
"_edgeObjs": Object {},
"_in": Object {
"node1": Object {},
},
"_isCompound": false,
"_isDirected": true,
"_isMultigraph": false,
"_label": undefined,
"_nodeCount": 1,
"_nodes": Object {
"node1": Object {
"info": SelectedNodeInfo {
"args": Array [
"test arg",
"test arg2",
],
"command": Array [
"test command",
"test command 2",
],
"condition": "test condition",
"image": "test image",
"inputs": Array [
Array [
"key1",
"val1",
],
Array [
"key2",
"val2",
],
],
"nodeType": "container",
"outputs": Array [
Array [
"key3",
"val3",
],
Array [
"key4",
"val4",
],
],
},
"label": "node1",
},
},
"_out": Object {
"node1": Object {},
},
"_preds": Object {
"node1": Object {},
},
"_sucs": Object {
"node1": Object {},
},
}
}
onClick={[Function]}
selectedNodeId="node1"
/>
<SidePanel
isOpen={true}
onClose={[Function]}
title="node1"
>
<div
className="page"
>
<div
className=""
>
<StaticNodeDetails
nodeInfo={
SelectedNodeInfo {
"args": Array [
"test arg",
"test arg2",
],
"command": Array [
"test command",
"test command 2",
],
"condition": "test condition",
"image": "test image",
"inputs": Array [
Array [
"key1",
"val1",
],
Array [
"key2",
"val2",
],
],
"nodeType": "container",
"outputs": Array [
Array [
"key3",
"val3",
],
Array [
"key4",
"val4",
],
],
}
}
/>
</div>
</div>
</SidePanel>
<div
className="footer"
>
<div
className="flex footerInfoOffset"
>
<pure(InfoOutlinedIcon)
style={
Object {
"color": "#80868b",
"height": 16,
"width": 16,
}
}
/>
<span
className="infoSpan"
>
Static pipeline graph
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;
exports[`PipelineDetails shows empty pipeline details with empty graph 1`] = `
<div
className="page"
>
<div
className="page"
>
<MD2Tabs
onSwitch={[Function]}
selectedTab={0}
tabs={
Array [
"Graph",
"Source",
]
}
/>
<div
className="page"
>
<div
className="page"
>
<div
className="page"
style={
Object {
"overflow": "hidden",
"position": "relative",
}
}
>
<WithStyles(Paper)
className="summaryCard"
>
<div
style={
Object {
"alignItems": "baseline",
"display": "flex",
"justifyContent": "space-between",
}
}
>
<div
className="header"
>
Summary
</div>
<WithStyles(Button)
color="secondary"
onClick={[Function]}
>
Hide
</WithStyles(Button)>
</div>
<div
className="summaryKey"
>
Uploaded on
</div>
<div>
9/5/2018, 4:03:02 AM
</div>
<div
className="summaryKey"
>
Description
</div>
<div>
test job description
</div>
</WithStyles(Paper)>
<Graph
graph={
Graph {
"_defaultEdgeLabelFn": [Function],
"_defaultNodeLabelFn": [Function],
"_edgeLabels": Object {},
"_edgeObjs": Object {},
"_in": Object {},
"_isCompound": false,
"_isDirected": true,
"_isMultigraph": false,
"_label": undefined,
"_nodes": Object {},
"_out": Object {},
"_preds": Object {},
"_sucs": Object {},
}
}
onClick={[Function]}
selectedNodeId=""
/>
<SidePanel
isOpen={false}
onClose={[Function]}
title=""
>
<div
className="page"
>
<div>
Unable to retrieve node info
</div>
</div>
</SidePanel>
<div
className="footer"
>
<div
className="flex footerInfoOffset"
>
<pure(InfoOutlinedIcon)
style={
Object {
"color": "#80868b",
"height": 16,
"width": 16,
}
}
/>
<span
className="infoSpan"
>
Static pipeline graph
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;
exports[`PipelineDetails shows empty pipeline details with no graph 1`] = `
<div
className="page"
>
<div
className="page"
>
<MD2Tabs
onSwitch={[Function]}
selectedTab={0}
tabs={
Array [
"Graph",
"Source",
]
}
/>
<div
className="page"
>
<div
className="page"
>
<span
style={
Object {
"margin": "40px auto",
}
}
>
No graph to show
</span>
</div>
</div>
</div>
</div>
`;
exports[`PipelineDetails shows pipeline source code when config tab is clicked 1`] = `
<div
className="page"
>
<div
className="page"
>
<MD2Tabs
onSwitch={[Function]}
selectedTab={1}
tabs={
Array [
"Graph",
"Source",
]
}
/>
<div
className="page"
>
<div
className="containerCss"
>
<UnControlled
editorDidMount={[Function]}
options={
Object {
"lineNumbers": true,
"lineWrapping": true,
"mode": "text/yaml",
"readOnly": true,
"theme": "default",
}
}
value=""
/>
</div>
</div>
</div>
</div>
`;