Skip to content

Commit df3a7f1

Browse files
phixMephix
andauthored
Web: Data Quality (#2810)
* Quality and node display * Fixing display on column lineage title. * Fixing bad status icon and adding divider to tooltip. * Fixing minor theme details. * Minor code review updates. * UX tweaks * Adding updated at --------- Co-authored-by: phix <peter.hicks@astronomer.io>
1 parent 00b6d35 commit df3a7f1

14 files changed

Lines changed: 329 additions & 55 deletions

File tree

web/src/components/core/copy/MqCopy.tsx

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

4-
import { Snackbar, Tooltip } from '@mui/material'
4+
import { Snackbar } from '@mui/material'
55
import ContentCopyIcon from '@mui/icons-material/ContentCopy'
66
import IconButton from '@mui/material/IconButton'
7+
import MQTooltip from '../tooltip/MQTooltip'
78
import React from 'react'
89

910
interface MqCopyProps {
@@ -21,7 +22,7 @@ const MqEmpty: React.FC<MqCopyProps> = ({ string }) => {
2122
}
2223
return (
2324
<>
24-
<Tooltip title='Copy'>
25+
<MQTooltip title='Copy'>
2526
<IconButton
2627
onClick={(event) => {
2728
event.stopPropagation()
@@ -34,7 +35,7 @@ const MqEmpty: React.FC<MqCopyProps> = ({ string }) => {
3435
>
3536
<ContentCopyIcon fontSize={'small'} />
3637
</IconButton>
37-
</Tooltip>
38+
</MQTooltip>
3839
<Snackbar
3940
open={open}
4041
autoHideDuration={2000}

web/src/components/core/date-picker/MqDatePicker.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ const MqDatePicker: React.FC<DatePickerProps> = ({
2727
return (
2828
<DateTimePicker
2929
label={label}
30+
slotProps={{
31+
desktopPaper: {
32+
sx: {
33+
backgroundImage: 'none',
34+
},
35+
},
36+
}}
3037
sx={{
3138
label: {
3239
left: theme.spacing(2),

web/src/components/core/tooltip/MQTooltip.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import Tooltip from '@mui/material/Tooltip'
1010
interface MqToolTipProps {
1111
title: string | ReactElement
1212
children: ReactElement
13+
onOpen?: (event: React.SyntheticEvent) => void
14+
onClose?: (event: React.SyntheticEvent) => void
1315
placement?:
1416
| 'left'
1517
| 'right'
@@ -25,10 +27,12 @@ interface MqToolTipProps {
2527
| 'right-start'
2628
}
2729

28-
const MQTooltip: React.FC<MqToolTipProps> = ({ title, children, placement }) => {
30+
const MQTooltip: React.FC<MqToolTipProps> = ({ title, onOpen, onClose, children, placement }) => {
2931
const theme = createTheme(useTheme())
3032
return (
3133
<Tooltip
34+
onOpen={onOpen}
35+
onClose={onClose}
3236
title={title}
3337
placement={placement || 'bottom'}
3438
componentsProps={{
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
import { Assertion } from '../../types/api'
4+
import { Table, TableBody, TableCell, TableHead, TableRow } from '@mui/material'
5+
import { theme } from '../../helpers/theme'
6+
import MqStatus from '../core/status/MqStatus'
7+
import MqText from '../core/text/MqText'
8+
import React from 'react'
9+
10+
interface OwnProps {
11+
assertions: Assertion[]
12+
hasHeader?: boolean
13+
}
14+
15+
const Assertions: React.FC<OwnProps> = ({ assertions, hasHeader }) => {
16+
if (assertions.length === 0) {
17+
return null
18+
}
19+
return (
20+
<Table size={'small'}>
21+
{hasHeader && (
22+
<TableHead>
23+
<TableRow>
24+
<TableCell align={'left'}>
25+
<MqText bold>COLUMN</MqText>
26+
</TableCell>
27+
<TableCell align={'left'}>
28+
<MqText bold>ASSERTION</MqText>
29+
</TableCell>
30+
<TableCell align={'left'}>
31+
<MqText bold>STATUS</MqText>
32+
</TableCell>
33+
</TableRow>
34+
</TableHead>
35+
)}
36+
<TableBody>
37+
{assertions.map((assertion) => {
38+
const sx = { borderBottom: 'none' }
39+
return (
40+
<TableRow key={`${assertion.column}-${assertion.assertion}`}>
41+
<TableCell align={'left'} sx={sx}>
42+
<MqText font={'mono'}>{assertion.column}</MqText>
43+
</TableCell>
44+
<TableCell align={'left'} sx={sx}>
45+
<MqText subdued>{assertion.assertion}</MqText>
46+
</TableCell>
47+
<TableCell align={'left'} sx={sx}>
48+
{
49+
<MqStatus
50+
label={assertion.success ? 'pass'.toUpperCase() : 'fail'.toUpperCase()}
51+
color={
52+
assertion.success ? theme.palette.primary.main : theme.palette.error.main
53+
}
54+
></MqStatus>
55+
}
56+
</TableCell>
57+
</TableRow>
58+
)
59+
})}
60+
</TableBody>
61+
</Table>
62+
)
63+
}
64+
65+
export default Assertions

web/src/components/datasets/DatasetDetailPage.tsx

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { MqInfo } from '../core/info/MqInfo'
2323
import { alpha } from '@mui/material/styles'
2424
import { bindActionCreators } from 'redux'
2525
import { connect } from 'react-redux'
26-
import { datasetFacetsStatus } from '../../helpers/nodes'
26+
import { datasetFacetsQualityAssertions, datasetFacetsStatus } from '../../helpers/nodes'
2727
import {
2828
deleteDataset,
2929
dialogToggle,
@@ -38,16 +38,19 @@ import { formatUpdatedAt } from '../../helpers'
3838
import { truncateText } from '../../helpers/text'
3939
import { useNavigate, useSearchParams } from 'react-router-dom'
4040
import { useTheme } from '@emotion/react'
41+
import Assertions from './Assertions'
4142
import CloseIcon from '@mui/icons-material/Close'
4243
import DatasetInfo from './DatasetInfo'
4344
import DatasetTags from './DatasetTags'
4445
import DatasetVersions from './DatasetVersions'
4546
import Dialog from '../Dialog'
4647
import IconButton from '@mui/material/IconButton'
4748
import ListIcon from '@mui/icons-material/List'
49+
import MQTooltip from '../core/tooltip/MQTooltip'
4850
import MqStatus from '../core/status/MqStatus'
4951
import MqText from '../core/text/MqText'
5052
import React, { ChangeEvent, FunctionComponent, useEffect, useState } from 'react'
53+
import RuleIcon from '@mui/icons-material/Rule'
5154
import StorageIcon from '@mui/icons-material/Storage'
5255

5356
interface StateProps {
@@ -143,6 +146,8 @@ const DatasetDetailPage: FunctionComponent<IProps> = (props) => {
143146
const { name, tags, description } = firstVersion
144147
const facetsStatus = datasetFacetsStatus(firstVersion.facets)
145148

149+
const assertions = datasetFacetsQualityAssertions(firstVersion.facets)
150+
146151
return (
147152
<Box px={2}>
148153
<Box
@@ -155,11 +160,6 @@ const DatasetDetailPage: FunctionComponent<IProps> = (props) => {
155160
mb={2}
156161
>
157162
<Box display={'flex'} alignItems={'center'} justifyContent={'space-between'} pb={2}>
158-
{facetsStatus && (
159-
<Box mr={1}>
160-
<MqStatus label={'Quality'} color={facetsStatus} />
161-
</Box>
162-
)}
163163
<Box display={'flex'} alignItems={'center'}>
164164
<Box>
165165
<Box display={'flex'} alignItems={'center'}>
@@ -224,27 +224,74 @@ const DatasetDetailPage: FunctionComponent<IProps> = (props) => {
224224
</Box>
225225
</Box>
226226
<Grid container spacing={2}>
227-
<Grid item xs={4}>
227+
<Grid item xs={6}>
228228
<MqInfo
229229
icon={<CalendarIcon color={'disabled'} />}
230230
label={'Updated at'.toUpperCase()}
231231
value={formatUpdatedAt(firstVersion.createdAt)}
232232
/>
233233
</Grid>
234-
<Grid item xs={4}>
234+
<Grid item xs={6}>
235235
<MqInfo
236236
icon={<StorageIcon color={'disabled'} />}
237237
label={'Dataset Type'.toUpperCase()}
238238
value={<MqText font={'mono'}>{firstVersion.type}</MqText>}
239239
/>
240240
</Grid>
241-
<Grid item xs={4}>
241+
<Grid item xs={6}>
242242
<MqInfo
243243
icon={<ListIcon color={'disabled'} />}
244244
label={'Fields'.toUpperCase()}
245245
value={`${firstVersion.fields.length} columns`}
246246
/>
247247
</Grid>
248+
<Grid item xs={6}>
249+
<MqInfo
250+
icon={<RuleIcon color={'disabled'} />}
251+
label={'Quality'.toUpperCase()}
252+
value={
253+
facetsStatus ? (
254+
<Box display={'flex'}>
255+
<MQTooltip
256+
title={
257+
<Assertions
258+
assertions={assertions.filter((assertion) => assertion.success)}
259+
/>
260+
}
261+
>
262+
<Box>
263+
<MqStatus
264+
label={`${
265+
assertions.filter((assertion) => assertion.success).length
266+
} Passing`.toUpperCase()}
267+
color={theme.palette.primary.main}
268+
/>
269+
</Box>
270+
</MQTooltip>
271+
<Divider sx={{ mx: 1 }} orientation={'vertical'} />
272+
<MQTooltip
273+
title={
274+
<Assertions
275+
assertions={assertions.filter((assertion) => !assertion.success)}
276+
/>
277+
}
278+
>
279+
<Box>
280+
<MqStatus
281+
label={`${
282+
assertions.filter((assertion) => !assertion.success).length
283+
} Failing`.toUpperCase()}
284+
color={theme.palette.error.main}
285+
/>
286+
</Box>
287+
</MQTooltip>
288+
</Box>
289+
) : (
290+
<MqStatus label={'N/A'} color={theme.palette.secondary.main} />
291+
)
292+
}
293+
/>
294+
</Grid>
248295
</Grid>
249296
<Divider sx={{ my: 2 }} />
250297
<Box display={'flex'} justifyContent={'space-between'} alignItems={'center'}>

web/src/components/jobs/Runs.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import MqStatus from '../core/status/MqStatus'
2525
import MqText from '../core/text/MqText'
2626
import React, { FunctionComponent, SetStateAction } from 'react'
2727
import RunInfo from './RunInfo'
28-
import RunStatus from './RunStatus'
2928

3029
interface RunsProps {
3130
runs: Run[]
@@ -139,10 +138,7 @@ const Runs: FunctionComponent<RunsProps> = (props) => {
139138
>
140139
<TableCell align='left'>{run.id}</TableCell>
141140
<TableCell align='left'>
142-
<Box display={'flex'} alignItems={'center'}>
143-
<RunStatus run={run} />
144-
<MqText>{run.state}</MqText>
145-
</Box>
141+
<MqStatus color={runStateColor(run.state)} label={run.state} />
146142
</TableCell>
147143
<TableCell align='left'>{formatUpdatedAt(run.createdAt)}</TableCell>
148144
<TableCell align='left'>{formatUpdatedAt(run.startedAt)}</TableCell>

web/src/components/namespace-select/NamespaceSelect.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,15 @@ const NamespaceSelect: React.FC<NamespaceSelectProps> = ({
5959
</MqText>
6060
</Box>
6161
<Select
62+
inputProps={{
63+
MenuProps: {
64+
PaperProps: {
65+
sx: {
66+
backgroundImage: 'none',
67+
},
68+
},
69+
},
70+
}}
6271
labelId='namespace-label'
6372
id='namespace-select'
6473
value={selectedNamespace}

web/src/helpers/nodes.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,12 @@ export function jobRunsStatus(runs: Run[], limit = 14) {
107107
return theme.palette.primary.main as string
108108
}
109109
}
110+
export function datasetFacetsQualityAssertions(facets: DataQualityFacets) {
111+
const assertions = facets?.dataQualityAssertions?.assertions
112+
if (!assertions) {
113+
return []
114+
} else return assertions
115+
}
110116

111117
export function datasetFacetsStatus(facets: DataQualityFacets, limit = 14) {
112118
const assertions = facets?.dataQualityAssertions?.assertions?.slice(-limit)
@@ -121,7 +127,7 @@ export function datasetFacetsStatus(facets: DataQualityFacets, limit = 14) {
121127
if (isAllFalse) {
122128
return theme.palette.error.main as string
123129
} else if (isSomeFalse) {
124-
return theme.palette.info.main as string
130+
return theme.palette.error.main as string
125131
} else {
126132
return theme.palette.primary.main as string
127133
}

web/src/i18n/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ i18next
103103
namespace_col: 'NAMESPACE',
104104
source_col: 'SOURCE',
105105
updated_col: 'UPDATED AT',
106-
status_col: 'STATUS',
106+
quality: 'QUALITY',
107107
},
108108
datasets_column_lineage: {
109109
empty_title: 'No column lineage',

0 commit comments

Comments
 (0)