Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dataset field level tags to UI #2729

Merged
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 59 additions & 114 deletions web/src/components/datasets/DatasetInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,29 @@
// Copyright 2018-2023 contributors to the Marquez project
// Copyright 2018-2024 contributors to the Marquez project
// SPDX-License-Identifier: Apache-2.0

import * as Redux from 'redux'
import {
Accordion,
Box,
Card,
CardContent,
Divider,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
} from '@mui/material'
import { Chip, Drawer } from '@mui/material'
import { Field, Run, Tag } from '../../types/api'
import { Field, Run } from '../../types/api'
import { IState } from '../../store/reducers'
import { connect, useSelector } from 'react-redux'
import { createTheme } from '@mui/material/styles'
import { fetchJobFacets, fetchTags, resetFacets } from '../../store/actionCreators'
import { fetchJobFacets, resetFacets } from '../../store/actionCreators'
import { stopWatchDuration } from '../../helpers/time'
import { useTheme } from '@emotion/react'
import AccordionDetails from '@mui/material/AccordionDetails'
import AccordionSummary from '@mui/material/AccordionSummary'
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
import MQTooltip from '../core/tooltip/MQTooltip'
import Collapse from '@mui/material/Collapse'
import DatasetTags from './DatasetTags'
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'
import MqCode from '../core/code/MqCode'
import MqEmpty from '../core/empty/MqEmpty'
import MqJsonView from '../core/json-view/MqJsonView'
import MqText from '../core/text/MqText'
import React, { FunctionComponent, useEffect, useState } from 'react'
import ReadMoreIcon from '@mui/icons-material/ReadMore'
import RunStatus from '../jobs/RunStatus'

export interface DispatchProps {
Expand All @@ -58,61 +50,50 @@ type DatasetInfoProps = {
} & JobFacetsProps &
DispatchProps

const formatColumnTags = (tags: string[], tag_desc: Tag[]) => {
const theme = createTheme(useTheme())
return (
<>
{tags.map((tag, index) => {
const tagDescription = tag_desc.find((tagItem) => tagItem.name === tag)
const tooltipTitle = tagDescription?.description || 'No Tag Description'
return (
<MQTooltip title={tooltipTitle} key={tag}>
<Chip
label={tag}
size='small'
style={{
display: 'row',
marginRight: index < tags.length - 1 ? theme.spacing(1) : 0,
marginTop: 3,
}}
/>
</MQTooltip>
)
})}
</>
)
}

const DatasetInfo: FunctionComponent<DatasetInfoProps> = (props) => {
const { datasetFields, facets, run, jobFacets, fetchJobFacets, resetFacets } = props
const i18next = require('i18next')
const dsNamespace = useSelector(
(state: IState) => state.datasetVersions.result.versions[0].namespace
)
const dsName = useSelector((state: IState) => state.datasetVersions.result.versions[0].name)

const [open, setOpen] = useState(false)
const [selectedKey, setSelectedKey] = useState<string | undefined>(undefined)
const theme = createTheme(useTheme())
const loadCollapsedState = () => {
const storedState = localStorage.getItem(`dsi_${dsNamespace}_${dsName}`)
return storedState ? JSON.parse(storedState) : []
}

useEffect(() => {
run && fetchJobFacets(run.id)
run && fetchTags()
}, [run])

// unmounting
useEffect(
() => () => {
resetFacets()
},
[]
)
const [expandedRows, setExpandedRows] = useState<number[]>(loadCollapsedState)

const toggleRow = (index: number) => {
setExpandedRows((prevExpandedRows) => {
const newExpandedRows = prevExpandedRows.includes(index)
? prevExpandedRows.filter((rowIndex) => rowIndex !== index)
: [...prevExpandedRows, index]

const tagData = useSelector((state: IState) => state.tags.tags)
const handleOpen = (key: string) => {
setOpen(true)
setSelectedKey(key)
localStorage.setItem(`dsi_${dsNamespace}_${dsName}`, JSON.stringify(newExpandedRows))

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kind of want to get away from doing this because it doesn't move across urls. Could we possibly use use parameters for functionality like this?

return newExpandedRows
})
}

const selectedField = datasetFields.find((field) => field.name === selectedKey)
const selectedFieldTags = selectedField?.tags || []
const selectedFieldDesc = selectedField?.description || 'No Description'
useEffect(() => {
for (const key in localStorage) {
if (key !== `dsi_${dsNamespace}_${dsName}`) {
localStorage.removeItem(key)
}
}
}, [dsNamespace, dsName])

return (
<Box>
Expand Down Expand Up @@ -146,73 +127,38 @@ const DatasetInfo: FunctionComponent<DatasetInfoProps> = (props) => {
</TableRow>
</TableHead>
<TableBody>
{datasetFields.map((field) => {
{datasetFields.map((field, index) => {
return (
<TableRow key={field.name}>
<TableCell align='left'>{field.name}</TableCell>
<TableCell align='left'>{field.type}</TableCell>
<TableCell align='left'>{field.description || 'no description'}</TableCell>
<TableCell>
<ReadMoreIcon
onClick={() => handleOpen(field.name)}
sx={{ align: 'Right' }}
></ReadMoreIcon>
</TableCell>
</TableRow>
<React.Fragment key={field.name}>
<TableRow onClick={() => toggleRow(index)} className='expandable-row'>
<TableCell align='left'>{field.name}</TableCell>
<TableCell align='left'>{field.type}</TableCell>
<TableCell align='left'>{field.description || 'no description'}</TableCell>
<TableCell align='right'>
<KeyboardArrowDownIcon />
</TableCell>
</TableRow>
<TableRow>
<TableCell colSpan={4} style={{ padding: 0, border: 'none' }}>
<Collapse in={expandedRows.includes(index)} timeout='auto'>
<Card>
<CardContent>
<DatasetTags
namespace={dsNamespace}
datasetName={dsName}
datasetTags={field.tags}
datasetField={field.name}
/>
</CardContent>
</Card>
</Collapse>
</TableCell>
</TableRow>
</React.Fragment>
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From a UI perspective, I think it may be perhaps better to have tags that are already part of a dataset listed.

For new tags, I think a dialog would be pretty nice for this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool - thanks for the feedback. Yeah I agree it's not as slick as I would like. I have refactored and DatasetTags now looks like this:

Screenshot 2024-02-01 at 18 20 19 Screenshot 2024-02-01 at 18 20 48

})}
</TableBody>
</Table>
<Drawer
elevation={0}
anchor='right'
open={open}
onClose={() => setOpen(false)}
sx={{ zIndex: theme.zIndex.drawer + 1 }}
PaperProps={{
sx: {
width: 400,
backgroundColor: theme.palette.background.paper,
border: `2px dashed ${theme.palette.secondary.main}`,
p: 1,
},
}}
>
<Card>
<CardContent sx={{ backgroundColor: theme.palette.background.paper }}>
<MqText heading bottomMargin>
{selectedKey}
</MqText>
</CardContent>
</Card>
<Divider />
<Card>
<CardContent sx={{ backgroundColor: theme.palette.background.paper }}>
<MqText bottomMargin>{selectedFieldDesc}</MqText>
</CardContent>
</Card>
<Accordion elevation={0}>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
sx={{
backgroundColor: theme.palette.background.paper,
}}
>
<MqText bold bottomMargin>
Tags
</MqText>
</AccordionSummary>
<AccordionDetails
sx={{
backgroundColor: theme.palette.background.paper,
}}
>
{selectedFieldTags.length > 0
? formatColumnTags(selectedFieldTags, tagData)
: 'No Tags'}
</AccordionDetails>
</Accordion>
</Drawer>
</>
)}
{facets && (
Expand Down Expand Up @@ -260,7 +206,6 @@ const mapDispatchToProps = (dispatch: Redux.Dispatch) =>
{
fetchJobFacets: fetchJobFacets,
resetFacets: resetFacets,
fetchTags: fetchTags,
},
dispatch
)
Expand Down
43 changes: 33 additions & 10 deletions web/src/components/datasets/DatasetTags.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
// Copyright 2018-2023 contributors to the Marquez project
// Copyright 2018-2024 contributors to the Marquez project
// SPDX-License-Identifier: Apache-2.0
import * as Redux from 'redux'
import { IState } from '../../store/reducers'
import { Tag } from '../../types/api'
import { addDatasetTag, deleteDatasetTag, fetchTags } from '../../store/actionCreators'
import {
addDatasetFieldTag,
addDatasetTag,
deleteDatasetFieldTag,
deleteDatasetTag,
fetchTags,
} from '../../store/actionCreators'
import { bindActionCreators } from 'redux'
import { connect, useSelector } from 'react-redux'
import { createTheme } from '@mui/material'
Expand All @@ -21,27 +27,36 @@ interface DatasetTagsProps {
namespace: string
datasetName: string
datasetTags: string[]
datasetField?: string
}

interface DispatchProps {
deleteDatasetTag: typeof deleteDatasetTag
addDatasetTag: typeof addDatasetTag
deleteDatasetFieldTag: typeof deleteDatasetFieldTag
addDatasetFieldTag: typeof addDatasetFieldTag
fetchTags: typeof fetchTags
}

type IProps = DatasetTagsProps & DispatchProps

const DatasetTags: React.FC<IProps> = (props) => {
const { namespace, datasetName, datasetTags, deleteDatasetTag, addDatasetTag, fetchTags } = props
const {
namespace,
datasetName,
datasetTags,
deleteDatasetTag,
addDatasetTag,
deleteDatasetFieldTag,
addDatasetFieldTag,
fetchTags,
datasetField,
} = props

useEffect(() => {
fetchTags()
}, [])

useEffect(() => {
fetchTags()
}, [deleteDatasetTag, addDatasetTag])

const tagData = useSelector((state: IState) => state.tags.tags)

const handleTagChange = (
Expand All @@ -51,12 +66,16 @@ const DatasetTags: React.FC<IProps> = (props) => {
details?: AutocompleteChangeDetails<string> | undefined
) => {
if (reason === 'selectOption' && details) {
addDatasetTag(namespace, datasetName, details.option)
datasetField
? addDatasetFieldTag(namespace, datasetName, details.option, datasetField)
: addDatasetTag(namespace, datasetName, details.option)
}
}

const handleDelete = (deletedTag: string) => {
deleteDatasetTag(namespace, datasetName, deletedTag)
datasetField
? deleteDatasetFieldTag(namespace, datasetName, deletedTag, datasetField)
: deleteDatasetTag(namespace, datasetName, deletedTag)
}

const formatTags = (tags: string[], tag_desc: Tag[]) => {
Expand All @@ -79,13 +98,15 @@ const DatasetTags: React.FC<IProps> = (props) => {
)
})
}

return (
<Autocomplete
multiple
id='dataset-tags'
size='small'
disableClearable
options={tagData.map((option) => option.name)}
getOptionLabel={(option) => `${option} - ${tagData.find((tagItem) => tagItem.name === option)?.description || 'No Tag Description'}`}
defaultValue={datasetTags}
onChange={handleTagChange}
renderTags={(value: string[]) => formatTags(value, tagData)}
Expand All @@ -107,6 +128,8 @@ const mapDispatchToProps = (dispatch: Redux.Dispatch) =>
fetchTags: fetchTags,
deleteDatasetTag: deleteDatasetTag,
addDatasetTag: addDatasetTag,
deleteDatasetFieldTag: deleteDatasetFieldTag,
addDatasetFieldTag: addDatasetFieldTag,
},
dispatch
)
Expand Down
6 changes: 5 additions & 1 deletion web/src/store/actionCreators/actionTypes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2018-2023 contributors to the Marquez project
// Copyright 2018-2024 contributors to the Marquez project
// SPDX-License-Identifier: Apache-2.0

export const APPLICATION_ERROR = 'APPLICATION_ERROR'
Expand Down Expand Up @@ -41,6 +41,10 @@ export const DELETE_DATASET_TAG = 'DELETE_DATASET_TAG'
export const DELETE_DATASET_TAG_SUCCESS = 'DELETE_DATASET_TAG_SUCCESS'
export const ADD_DATASET_TAG = 'ADD_DATASET_TAG'
export const ADD_DATASET_TAG_SUCCESS = 'ADD_DATASET_TAG_SUCCESS'
export const DELETE_DATASET_FIELD_TAG = 'DELETE_DATASET_FIELD_TAG'
export const DELETE_DATASET_FIELD_TAG_SUCCESS = 'DELETE_DATASET_FIELD_TAG_SUCCESS'
export const ADD_DATASET_FIELD_TAG = 'ADD_DATASET_FIELD_TAG'
export const ADD_DATASET_FIELD_TAG_SUCCESS = 'ADD_DATASET_FIELD_TAG_SUCCESS'

// events
export const FETCH_EVENTS = 'FETCH_EVENTS'
Expand Down
Loading
Loading