Skip to content

Commit 37687d7

Browse files
authored
UI: add full graph toggle (#2569)
* lint Signed-off-by: John Lukenoff <jlukenoff@gmail.com> * refactor Signed-off-by: John Lukenoff <jlukenoff@gmail.com> * fix test Signed-off-by: John Lukenoff <jlukenoff@gmail.com> * add tests Signed-off-by: John Lukenoff <johnlukenoff@asana.com> * convert depthconfig to formcontrol label Signed-off-by: John Lukenoff <johnlukenoff@asana.com> * unused import Signed-off-by: John Lukenoff <johnlukenoff@asana.com> --------- Signed-off-by: John Lukenoff <jlukenoff@gmail.com> Signed-off-by: John Lukenoff <johnlukenoff@asana.com>
1 parent df49db5 commit 37687d7

8 files changed

Lines changed: 258 additions & 143 deletions

File tree

Lines changed: 70 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,64 @@
11
// Copyright 2018-2023 contributors to the Marquez project
22
// SPDX-License-Identifier: Apache-2.0
33

4-
import React from 'react'
5-
import Lineage, { LineageProps, getSelectedPaths, initGraph, buildGraphAll } from '../../components/lineage/Lineage'
6-
import { LineageNode } from '../../components/lineage/types'
7-
import { render } from '@testing-library/react'
8-
import { createBrowserHistory } from 'history'
9-
import createSagaMiddleware from 'redux-saga'
10-
import { createRouterMiddleware } from '@lagunovsky/redux-react-router'
11-
import createRootReducer from '../../store/reducers'
12-
import { composeWithDevTools } from '@redux-devtools/extension'
13-
import { applyMiddleware, createStore } from 'redux'
14-
import { Provider } from 'react-redux'
15-
import { MqNode } from '../../components/lineage/types'
4+
import { getSelectedPaths, initGraph, buildGraphAll } from '../../components/lineage/Lineage'
5+
import { LineageNode, MqNode } from '../../components/lineage/types'
166
import { graphlib } from 'dagre'
17-
import rootSaga from '../../store/sagas'
187

19-
const mockGraphWithCycle = [
20-
{
21-
id: 'job_foo',
22-
inEdges: [
23-
{
24-
origin: 'dataset_foo',
25-
destination: 'job_foo'
26-
}
27-
],
28-
outEdges: [
29-
{
30-
origin: 'job_foo',
31-
destination: 'dataset_bar'
32-
}
33-
]
34-
},
35-
{
36-
id: 'dataset_bar',
37-
inEdges: [
38-
{
39-
origin: 'job_foo',
40-
destination: 'dataset_bar'
41-
}
42-
],
43-
outEdges: [
44-
{
45-
origin: 'dataset_bar',
46-
destination: 'job_bar'
47-
}
48-
]
49-
},
50-
{
51-
id: 'job_bar',
52-
inEdges: [
53-
{
54-
origin: 'dataset_bar',
55-
destination: 'job_bar'
56-
}
57-
],
58-
outEdges: [
59-
{
60-
origin: 'job_bar',
61-
destination: 'dataset_foo'
62-
}
63-
]
64-
},
65-
{
66-
id: 'dataset_foo',
67-
inEdges: [
68-
{
69-
origin: 'job_bar',
70-
destination: 'dataset_foo'
71-
}
72-
],
73-
outEdges: [
74-
{
75-
origin: 'dataset_foo',
76-
destination: 'job_foo'
77-
}
78-
]
8+
class MockEdge {
9+
origin: string
10+
destination: string
11+
12+
constructor(origin, destination) {
13+
this.origin = origin
14+
this.destination = destination
7915
}
80-
]
16+
}
17+
18+
class MockNode implements Partial<LineageNode> {
19+
id: string
20+
inEdges: MockEdge[]
21+
outEdges: MockEdge[]
22+
23+
constructor(id, prev: string[], next: string[]) {
24+
this.id = id
25+
this.inEdges = prev ? prev.map(p => new MockEdge(p, id)) : ([] as MockEdge[])
26+
this.outEdges = next ? next.map(n => new MockEdge(id, n)) : ([] as MockEdge[])
27+
}
28+
}
29+
30+
const mockGraphWithCycle = [
31+
new MockNode('1', ['3'], ['2']),
32+
new MockNode('2', ['1'], ['3']),
33+
new MockNode('3', ['2'], ['1'])
34+
] as LineageNode[]
35+
36+
const mockGraphWithoutCycle = [
37+
new MockNode('1', [], ['2', '4']),
38+
new MockNode('2', ['1'], ['3']),
39+
new MockNode('3', ['2'], []),
40+
new MockNode('4', ['1'], [])
41+
] as LineageNode[]
8142

8243
describe('Lineage Component', () => {
83-
const selectedNode = 'job_foo'
84-
let g: graphlib.Graph<MqNode>
44+
const selectedNode = '1'
45+
let graphWithCycle: graphlib.Graph<MqNode>
8546

8647
beforeEach(() => {
87-
g = initGraph()
88-
buildGraphAll(g, mockGraphWithCycle, (gResult: graphlib.Graph<MqNode>) => {
89-
g = gResult
90-
})
48+
graphWithCycle = initGraph()
49+
buildGraphAll(
50+
graphWithCycle,
51+
mockGraphWithCycle,
52+
true,
53+
selectedNode,
54+
(gResult: graphlib.Graph<MqNode>) => {
55+
graphWithCycle = gResult
56+
}
57+
)
9158
})
9259

9360
it("doesn't follow cycles in the lineage graph", () => {
94-
const paths = getSelectedPaths(g, selectedNode)
61+
const paths = getSelectedPaths(graphWithCycle, selectedNode)
9562

9663
const pathCounts = paths.reduce((acc, p) => {
9764
const pathId = p.join(':')
@@ -103,19 +70,32 @@ describe('Lineage Component', () => {
10370
})
10471

10572
it('renders a valid cycle', () => {
106-
const actualPaths = getSelectedPaths(g, selectedNode)
73+
const actualPaths = getSelectedPaths(graphWithCycle, selectedNode)
10774

10875
const expectedPaths = [
109-
['job_foo', 'dataset_bar'],
110-
['dataset_bar', 'job_bar'],
111-
['job_bar', 'dataset_foo'],
112-
['dataset_foo', 'job_foo'],
113-
['dataset_foo', 'job_foo'],
114-
['job_bar', 'dataset_foo'],
115-
['dataset_bar', 'job_bar'],
116-
['job_foo', 'dataset_bar']
76+
['1', '2'],
77+
['2', '3'],
78+
['3', '1'],
79+
['3', '1'],
80+
['2', '3'],
81+
['1', '2']
11782
]
11883

11984
expect(actualPaths).toEqual(expectedPaths)
12085
})
86+
87+
it('includes nodes in selected path when fullGraph is true', () => {
88+
const g = initGraph()
89+
buildGraphAll(g, mockGraphWithoutCycle, true, '3', () => null)
90+
91+
expect(g.node('4')).toBeDefined()
92+
})
93+
94+
it('exclude nodes not in selected path when fullGraph is false', () => {
95+
const g = initGraph()
96+
97+
buildGraphAll(g, mockGraphWithoutCycle, false, '3', () => null)
98+
99+
expect(g.node('4')).toBeUndefined()
100+
})
121101
})
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright 2018-2023 contributors to the Marquez project
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import * as Redux from 'redux'
5+
import { Box, FormControlLabel, Switch } from '@mui/material'
6+
import { IState } from '../../store/reducers'
7+
import { bindActionCreators } from 'redux'
8+
import { connect } from 'react-redux'
9+
import { setShowFullGraph } from '../../store/actionCreators'
10+
import React from 'react'
11+
12+
interface FullGraphSwitch {
13+
showFullGraph: boolean
14+
setShowFullGraph: (showFullGraph: boolean) => void
15+
}
16+
17+
const FullGraphSwitch: React.FC<FullGraphSwitch> = ({ setShowFullGraph, showFullGraph }) => {
18+
const i18next = require('i18next')
19+
const FULL_GRAPH_LABEL = i18next.t('lineage.full_graph_label')
20+
return (
21+
<Box
22+
sx={theme => ({
23+
display: 'flex',
24+
justifyContent: 'space-evenly',
25+
alignItems: 'center',
26+
zIndex: theme.zIndex.appBar
27+
})}
28+
>
29+
<FormControlLabel
30+
sx={{
31+
marginLeft: 0
32+
}}
33+
labelPlacement='start'
34+
control={
35+
<Switch
36+
checked={showFullGraph}
37+
onChange={(_, checked) => setShowFullGraph(checked)}
38+
color='primary'
39+
/>
40+
}
41+
label={FULL_GRAPH_LABEL}
42+
/>
43+
</Box>
44+
)
45+
}
46+
47+
const mapStateToProps = (state: IState) => ({
48+
showFullGraph: state.lineage.showFullGraph
49+
})
50+
51+
const mapDispatchToProps = (dispatch: Redux.Dispatch) =>
52+
bindActionCreators(
53+
{
54+
setShowFullGraph: setShowFullGraph
55+
},
56+
dispatch
57+
)
58+
59+
export default connect(mapStateToProps, mapDispatchToProps)(FullGraphSwitch)

0 commit comments

Comments
 (0)