Skip to content

Commit 45b7281

Browse files
phixMephixwslulciuc
authored
Adding the IO Tab (#2613)
* Adding the IO Tab. * Removing logs. * Updates for code review. --------- Co-authored-by: phix <peter.hicks@astronomer.io> Co-authored-by: Willy Lulciuc <willy@datakin.com>
1 parent 6003af6 commit 45b7281

12 files changed

Lines changed: 353 additions & 142 deletions

File tree

web/src/components/core/text/MqText.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ interface OwnProps {
3030
small?: boolean
3131
bottomMargin?: boolean
3232
children: ReactElement | (string | ReactElement)[] | string | string[] | number | undefined | null
33+
onClick?: () => void
3334
}
3435

3536
type MqTextProps = OwnProps
@@ -53,6 +54,7 @@ const MqText: React.FC<MqTextProps> = ({
5354
highlight,
5455
color,
5556
small,
57+
onClick,
5658
}) => {
5759
const theme = createTheme(useTheme())
5860

@@ -143,6 +145,7 @@ const MqText: React.FC<MqTextProps> = ({
143145
if (heading) {
144146
return (
145147
<Typography
148+
onClick={onClick}
146149
variant='h4'
147150
sx={Object.assign(classesObject.root, classesObject.heading, conditionalClasses)}
148151
style={style}
@@ -152,7 +155,12 @@ const MqText: React.FC<MqTextProps> = ({
152155
)
153156
} else if (link && linkTo) {
154157
return (
155-
<LinkRouter to={linkTo} aria-disabled={disabled} style={{ textDecoration: 'none' }}>
158+
<LinkRouter
159+
to={linkTo}
160+
aria-disabled={disabled}
161+
style={{ textDecoration: 'none' }}
162+
onClick={onClick}
163+
>
156164
<Box
157165
component='span'
158166
sx={Object.assign(classesObject.root, classesObject.link, conditionalClasses)}
@@ -164,6 +172,7 @@ const MqText: React.FC<MqTextProps> = ({
164172
} else if (link && href) {
165173
return (
166174
<Link
175+
onClick={onClick}
167176
href={href}
168177
target={'_blank'}
169178
rel='noopener noreferrer'
@@ -174,7 +183,11 @@ const MqText: React.FC<MqTextProps> = ({
174183
)
175184
} else {
176185
return (
177-
<Box sx={Object.assign(classesObject.root, conditionalClasses)} style={style}>
186+
<Box
187+
onClick={onClick}
188+
sx={Object.assign(classesObject.root, conditionalClasses)}
189+
style={style}
190+
>
178191
{children}
179192
</Box>
180193
)

web/src/components/datasets/DatasetDetailPage.tsx

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
fetchDatasetVersions,
1818
resetDataset,
1919
resetDatasetVersions,
20+
setTabIndex,
2021
} from '../../store/actionCreators'
2122
import { useNavigate } from 'react-router-dom'
2223
import CloseIcon from '@mui/icons-material/Close'
@@ -29,14 +30,16 @@ import MqStatus from '../core/status/MqStatus'
2930
import MqText from '../core/text/MqText'
3031

3132
import { useTheme } from '@emotion/react'
32-
import React, { ChangeEvent, FunctionComponent, SetStateAction, useEffect } from 'react'
33+
import Io from '../io/Io'
34+
import React, { ChangeEvent, FunctionComponent, useEffect } from 'react'
3335

3436
interface StateProps {
3537
lineageDataset: LineageDataset
3638
versions: DatasetVersion[]
3739
versionsLoading: boolean
3840
datasets: IState['datasets']
3941
display: IState['display']
42+
tabIndex: IState['lineage']['tabIndex']
4043
}
4144

4245
interface DispatchProps {
@@ -45,6 +48,7 @@ interface DispatchProps {
4548
resetDataset: typeof resetDataset
4649
deleteDataset: typeof deleteDataset
4750
dialogToggle: typeof dialogToggle
51+
setTabIndex: typeof setTabIndex
4852
}
4953

5054
type IProps = StateProps & DispatchProps
@@ -68,6 +72,8 @@ const DatasetDetailPage: FunctionComponent<IProps> = (props) => {
6872
versions,
6973
versionsLoading,
7074
lineageDataset,
75+
tabIndex,
76+
setTabIndex,
7177
} = props
7278
const navigate = useNavigate()
7379
const i18next = require('i18next')
@@ -92,9 +98,8 @@ const DatasetDetailPage: FunctionComponent<IProps> = (props) => {
9298
[]
9399
)
94100

95-
const [tab, setTab] = React.useState(0)
96-
const handleChange = (event: ChangeEvent, newValue: SetStateAction<number>) => {
97-
setTab(newValue)
101+
const handleChange = (_: ChangeEvent, newValue: number) => {
102+
setTabIndex(newValue)
98103
}
99104

100105
if (versionsLoading) {
@@ -149,20 +154,26 @@ const DatasetDetailPage: FunctionComponent<IProps> = (props) => {
149154
)}
150155
<Box display={'flex'} justifyContent={'space-between'} mb={2}>
151156
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
152-
<Tabs value={tab} onChange={handleChange} textColor='primary' indicatorColor='primary'>
157+
<Tabs
158+
value={tabIndex}
159+
onChange={handleChange}
160+
textColor='primary'
161+
indicatorColor='primary'
162+
>
153163
<Tab
154164
label={i18next.t('datasets.latest_tab')}
155165
{...a11yProps(0)}
156166
disableRipple={true}
157167
/>
168+
<Tab label={'I/O'} {...a11yProps(1)} disableRipple={true} />
158169
<Tab
159170
label={i18next.t('datasets.history_tab')}
160-
{...a11yProps(1)}
171+
{...a11yProps(2)}
161172
disableRipple={true}
162173
/>
163174
<Tab
164175
label={i18next.t('datasets.column_lineage_tab')}
165-
{...a11yProps(1)}
176+
{...a11yProps(3)}
166177
disableRipple={true}
167178
/>
168179
</Tabs>
@@ -214,15 +225,16 @@ const DatasetDetailPage: FunctionComponent<IProps> = (props) => {
214225
<MqText subdued>{description}</MqText>
215226
</Box>
216227
</Box>
217-
{tab === 0 && (
228+
{tabIndex === 0 && (
218229
<DatasetInfo
219230
datasetFields={firstVersion.fields}
220231
facets={firstVersion.facets}
221232
run={firstVersion.createdByRun}
222233
/>
223234
)}
224-
{tab === 1 && <DatasetVersions versions={props.versions} />}
225-
{tab === 2 && <DatasetColumnLineage lineageDataset={props.lineageDataset} />}
235+
{tabIndex === 1 && <Io />}
236+
{tabIndex === 2 && <DatasetVersions versions={props.versions} />}
237+
{tabIndex === 3 && <DatasetColumnLineage lineageDataset={props.lineageDataset} />}
226238
</Box>
227239
)
228240
}
@@ -232,6 +244,7 @@ const mapStateToProps = (state: IState) => ({
232244
display: state.display,
233245
versions: state.datasetVersions.result.versions,
234246
versionsLoading: state.datasetVersions.isLoading,
247+
tabIndex: state.lineage.tabIndex,
235248
})
236249

237250
const mapDispatchToProps = (dispatch: Redux.Dispatch) =>
@@ -242,6 +255,7 @@ const mapDispatchToProps = (dispatch: Redux.Dispatch) =>
242255
resetDataset: resetDataset,
243256
deleteDataset: deleteDataset,
244257
dialogToggle: dialogToggle,
258+
setTabIndex: setTabIndex,
245259
},
246260
dispatch
247261
)

web/src/components/io/Io.tsx

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
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 } from '@mui/system'
6+
import { IState } from '../../store/reducers'
7+
import { LineageEdge, LineageNode } from '../lineage/types'
8+
9+
import { Table, TableBody, TableCell, TableHead, TableRow } from '@mui/material'
10+
import { Undefinable } from '../../types/util/Nullable'
11+
import { bindActionCreators } from 'redux'
12+
import { connect } from 'react-redux'
13+
import { encodeNode } from '../../helpers/nodes'
14+
import { setSelectedNode } from '../../store/actionCreators'
15+
import MqEmpty from '../core/empty/MqEmpty'
16+
import MqText from '../core/text/MqText'
17+
import React, { FunctionComponent } from 'react'
18+
19+
export interface DispatchProps {
20+
setSelectedNode: typeof setSelectedNode
21+
}
22+
23+
interface IOProps {
24+
node: Undefinable<LineageNode>
25+
inputs: Undefinable<LineageEdge[]>
26+
outputs: Undefinable<LineageEdge[]>
27+
}
28+
29+
function determineName(node: string) {
30+
const colonIndex1 = node.indexOf(':')
31+
if (colonIndex1 !== -1) {
32+
const colonIndex2 = node.indexOf(':', colonIndex1 + 1)
33+
if (colonIndex2 !== -1) {
34+
return node.substring(colonIndex2 + 1)
35+
}
36+
}
37+
return ''
38+
}
39+
40+
export const determineLink = (current: LineageNode, edge: string) => {
41+
return `/lineage/${encodeNode(
42+
current.type === 'JOB' ? 'DATASET' : 'JOB',
43+
edge.split(':')[1],
44+
determineName(edge)
45+
)}`
46+
}
47+
48+
const Io: FunctionComponent<IOProps & DispatchProps> = ({
49+
node,
50+
inputs,
51+
outputs,
52+
setSelectedNode,
53+
}) => {
54+
const i18next = require('i18next')
55+
if (!node) {
56+
return null
57+
}
58+
59+
return (
60+
<Box display={'flex'}>
61+
<Box width={'50%'}>
62+
<Table sx={{ p: 2, mr: 1 }}>
63+
<TableHead>
64+
<TableRow>
65+
<TableCell>
66+
<MqText bold>INPUTS</MqText>
67+
</TableCell>
68+
</TableRow>
69+
</TableHead>
70+
<TableBody>
71+
{inputs?.map((input) => (
72+
<TableRow key={input.origin}>
73+
<TableCell>
74+
<MqText
75+
link
76+
linkTo={determineLink(node, input.origin)}
77+
onClick={() => {
78+
setSelectedNode(input.origin)
79+
}}
80+
>
81+
{determineName(input.origin)}
82+
</MqText>
83+
</TableCell>
84+
</TableRow>
85+
))}
86+
</TableBody>
87+
</Table>
88+
{inputs && inputs.length === 0 && (
89+
<Box mt={2}>
90+
<MqEmpty title={i18next.t('lineage.empty')}>
91+
<MqText subdued>{i18next.t('lineage.no_inputs')}</MqText>
92+
</MqEmpty>
93+
</Box>
94+
)}
95+
</Box>
96+
<Box width={'50%'}>
97+
<Table sx={{ p: 2, ml: 1 }}>
98+
<TableHead>
99+
<TableRow>
100+
<TableCell>
101+
<MqText bold>OUTPUTS</MqText>
102+
</TableCell>
103+
</TableRow>
104+
</TableHead>
105+
<TableBody>
106+
{outputs?.map((output) => (
107+
<TableRow key={output.destination}>
108+
<TableCell>
109+
<MqText
110+
link
111+
linkTo={determineLink(node, output.destination)}
112+
onClick={() => setSelectedNode(output.destination)}
113+
>
114+
{determineName(output.destination)}
115+
</MqText>
116+
</TableCell>
117+
</TableRow>
118+
))}
119+
</TableBody>
120+
</Table>
121+
{outputs && outputs.length === 0 && (
122+
<Box mt={2}>
123+
<MqEmpty title={i18next.t('lineage.empty')}>
124+
<MqText subdued>{i18next.t('lineage.no_outputs')}</MqText>
125+
</MqEmpty>
126+
</Box>
127+
)}
128+
</Box>
129+
</Box>
130+
)
131+
}
132+
133+
const mapStateToProps = (state: IState) => {
134+
const node = state.lineage.lineage.graph.find((node) => node.id === state.lineage.selectedNode)
135+
return {
136+
node: node,
137+
inputs: node?.inEdges,
138+
outputs: node?.outEdges,
139+
}
140+
}
141+
142+
const mapDispatchToProps = (dispatch: Redux.Dispatch) =>
143+
bindActionCreators(
144+
{
145+
setSelectedNode: setSelectedNode,
146+
},
147+
dispatch
148+
)
149+
150+
export default connect(mapStateToProps, mapDispatchToProps)(Io)

0 commit comments

Comments
 (0)