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 tags tabs for add/delete of tags. #2714

Merged
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 2 additions & 1 deletion web/src/__tests__/reducers/datasets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ describe('datasets reducer', () => {
isLoading: false,
result: datasets,
totalCount: 16,
deletedDatasetName: ''
deletedDatasetName: '',
refreshTags: false
})
})

Expand Down
56 changes: 19 additions & 37 deletions web/src/components/datasets/DatasetDetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,19 @@
// SPDX-License-Identifier: Apache-2.0

import * as Redux from 'redux'
import { Box, Button, Chip, Tab, Tabs, createTheme } from '@mui/material'
import { Box, Button, Tab, Tabs, createTheme } from '@mui/material'
import { CircularProgress } from '@mui/material'
import { DatasetVersion, Tag } from '../../types/api'
import { DatasetVersion } from '../../types/api'
import { IState } from '../../store/reducers'
import { LineageDataset } from '../lineage/types'
import { alpha } from '@mui/material/styles'
import { bindActionCreators } from 'redux'
import { connect, useSelector } from 'react-redux'
import { connect } from 'react-redux'
import { datasetFacetsStatus } from '../../helpers/nodes'
import {
deleteDataset,
dialogToggle,
fetchDatasetVersions,
fetchTags,
resetDataset,
resetDatasetVersions,
setTabIndex,
Expand All @@ -25,11 +24,11 @@ import { useTheme } from '@emotion/react'
import CloseIcon from '@mui/icons-material/Close'
import DatasetColumnLineage from './DatasetColumnLineage'
import DatasetInfo from './DatasetInfo'
import DatasetTags from './DatasetTags'
import DatasetVersions from './DatasetVersions'
import Dialog from '../Dialog'
import IconButton from '@mui/material/IconButton'
import Io from '../io/Io'
import MQTooltip from '../core/tooltip/MQTooltip'
import MqStatus from '../core/status/MqStatus'
import MqText from '../core/text/MqText'
import React, { ChangeEvent, FunctionComponent, useEffect } from 'react'
Expand All @@ -50,7 +49,6 @@ interface DispatchProps {
deleteDataset: typeof deleteDataset
dialogToggle: typeof dialogToggle
setTabIndex: typeof setTabIndex
fetchTags: typeof fetchTags
}

type IProps = StateProps & DispatchProps
Expand All @@ -76,18 +74,16 @@ const DatasetDetailPage: FunctionComponent<IProps> = (props) => {
lineageDataset,
tabIndex,
setTabIndex,
fetchTags,
} = props
const navigate = useNavigate()
const i18next = require('i18next')
const theme = createTheme(useTheme())
const tagData = useSelector((state: IState) => state.tags.tags)

useEffect(() => {
fetchDatasetVersions(props.lineageDataset.namespace, props.lineageDataset.name)
fetchTags()
}, [props.lineageDataset.name])
}, [props.lineageDataset.name, datasets.refreshTags])

// if the dataset is deleted then redirect to datasets end point
useEffect(() => {
if (datasets.deletedDatasetName) {
navigate('/datasets')
Expand Down Expand Up @@ -120,33 +116,9 @@ const DatasetDetailPage: FunctionComponent<IProps> = (props) => {
}

const firstVersion = versions[0]
const { name, tags, description } = firstVersion
const { name, description } = firstVersion
const facetsStatus = datasetFacetsStatus(firstVersion.facets)

const formatTags = (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,
}}
/>
</MQTooltip>
)
})}
</>
)
}

return (
<Box
my={2}
Expand All @@ -155,7 +127,6 @@ const DatasetDetailPage: FunctionComponent<IProps> = (props) => {
}}
>
<Box>
{formatTags(tags, tagData)}
<Box display={'flex'} justifyContent={'space-between'} mb={2}>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<Tabs
Expand All @@ -180,6 +151,11 @@ const DatasetDetailPage: FunctionComponent<IProps> = (props) => {
{...a11yProps(3)}
disableRipple={true}
/>
<Tab
label={i18next.t('datasets.dataset_tags_tab')}
{...a11yProps(4)}
disableRipple={true}
/>
</Tabs>
</Box>
<Box display={'flex'} alignItems={'center'}>
Expand Down Expand Up @@ -239,6 +215,13 @@ const DatasetDetailPage: FunctionComponent<IProps> = (props) => {
{tabIndex === 1 && <Io />}
{tabIndex === 2 && <DatasetVersions versions={props.versions} />}
{tabIndex === 3 && <DatasetColumnLineage lineageDataset={props.lineageDataset} />}
{tabIndex === 4 && (
<DatasetTags
namespace={props.lineageDataset.namespace}
datasetName={props.lineageDataset.name}
datasetTags={firstVersion.tags}
/>
)}
</Box>
)
}
Expand All @@ -260,7 +243,6 @@ const mapDispatchToProps = (dispatch: Redux.Dispatch) =>
deleteDataset: deleteDataset,
dialogToggle: dialogToggle,
setTabIndex: setTabIndex,
fetchTags: fetchTags,
},
dispatch
)
Expand Down
114 changes: 114 additions & 0 deletions web/src/components/datasets/DatasetTags.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Copyright 2018-2023 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 { bindActionCreators } from 'redux'
import { connect, useSelector } from 'react-redux'
import { createTheme } from '@mui/material'
import { useTheme } from '@emotion/react'
import Autocomplete, {
AutocompleteChangeDetails,
AutocompleteChangeReason,
} from '@mui/material/Autocomplete'
import Chip from '@mui/material/Chip'
import MQTooltip from '../core/tooltip/MQTooltip'
import React, { useEffect } from 'react'
import TextField from '@mui/material/TextField'

interface DatasetTagsProps {
namespace: string
datasetName: string
datasetTags: string[]
}

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

type IProps = DatasetTagsProps & DispatchProps

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

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

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

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

const handleTagChange = (
_event: React.SyntheticEvent,
_value: string[],
reason: AutocompleteChangeReason,
details?: AutocompleteChangeDetails<string> | undefined
) => {
if (reason === 'selectOption' && details) {
addDatasetTag(namespace, datasetName, details.option)
}
}

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

const formatTags = (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'
onDelete={() => handleDelete(tag)}
style={{
display: 'row',
marginRight: index < tags.length - 1 ? theme.spacing(1) : 0,
}}
/>
</MQTooltip>
)
})
}

return (
<Autocomplete
multiple
id='dataset-tags'
disableClearable
options={tagData.map((option) => option.name)}
defaultValue={datasetTags}
onChange={handleTagChange}
renderTags={(value: string[]) => formatTags(value, tagData)}
renderInput={(params) => (
<TextField
{...params}
InputLabelProps={{
shrink: true,
}}
/>
)}
/>
)
}

const mapDispatchToProps = (dispatch: Redux.Dispatch) =>
bindActionCreators(
{
fetchTags: fetchTags,
deleteDatasetTag: deleteDatasetTag,
addDatasetTag: addDatasetTag,
},
dispatch
)

export default connect(null, mapDispatchToProps)(DatasetTags)
1 change: 1 addition & 0 deletions web/src/i18n/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ i18next
datasets: {
latest_tab: 'LATEST SCHEMA',
history_tab: 'VERSION HISTORY',
dataset_tags_tab: 'TAGS',
column_lineage_tab: 'COLUMN LINEAGE',
dialog_delete: 'DELETE',
dialog_confirmation_title: 'Are you sure?',
Expand Down
4 changes: 4 additions & 0 deletions web/src/store/actionCreators/actionTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ export const FETCH_DATASET_VERSIONS_SUCCESS = 'FETCH_DATASET_VERSIONS_SUCCESS'
export const RESET_DATASET_VERSIONS = 'RESET_DATASET_VERSIONS'
export const DELETE_DATASET = 'DELETE_DATASET'
export const DELETE_DATASET_SUCCESS = 'DELETE_DATASET_SUCCESS'
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'

// events
export const FETCH_EVENTS = 'FETCH_EVENTS'
Expand Down
32 changes: 32 additions & 0 deletions web/src/store/actionCreators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,38 @@ export const deleteDatasetSuccess = (datasetName: string) => ({
},
})

export const deleteDatasetTag = (namespace: string, datasetName: string, tag: string) => ({
type: actionTypes.DELETE_DATASET_TAG,
payload: {
namespace,
datasetName,
tag,
},
})

export const deleteDatasetTagSuccess = (datasetName: string) => ({
type: actionTypes.DELETE_DATASET_TAG_SUCCESS,
payload: {
datasetName,
},
})

export const addDatasetTag = (namespace: string, datasetName: string, tag: string) => ({
type: actionTypes.ADD_DATASET_TAG,
payload: {
namespace,
datasetName,
tag,
},
})

export const addDatasetTagSuccess = (datasetName: string) => ({
type: actionTypes.ADD_DATASET_TAG_SUCCESS,
payload: {
datasetName,
},
})

export const resetDatasets = () => ({
type: actionTypes.RESET_DATASETS,
})
Expand Down
25 changes: 23 additions & 2 deletions web/src/store/reducers/datasets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,31 @@
// SPDX-License-Identifier: Apache-2.0

import {
ADD_DATASET_TAG,
ADD_DATASET_TAG_SUCCESS,
DELETE_DATASET,
DELETE_DATASET_SUCCESS,
DELETE_DATASET_TAG,
DELETE_DATASET_TAG_SUCCESS,
FETCH_DATASETS,
FETCH_DATASETS_SUCCESS,
RESET_DATASETS,
} from '../actionCreators/actionTypes'
import { Dataset } from '../../types/api'
import { deleteDataset, fetchDatasetsSuccess } from '../actionCreators'
import {
addDatasetTag,
deleteDataset,
deleteDatasetTag,
fetchDatasetsSuccess,
} from '../actionCreators'

export type IDatasetsState = {
isLoading: boolean
result: Dataset[]
totalCount: number
init: boolean
deletedDatasetName: string
refreshTags: boolean
}

export const initialState: IDatasetsState = {
Expand All @@ -25,10 +35,13 @@ export const initialState: IDatasetsState = {
result: [],
totalCount: 0,
deletedDatasetName: '',
refreshTags: false,
}

export type IDatasetsAction = ReturnType<typeof fetchDatasetsSuccess> &
ReturnType<typeof deleteDataset>
ReturnType<typeof deleteDataset> &
ReturnType<typeof deleteDatasetTag> &
ReturnType<typeof addDatasetTag>

export default (state: IDatasetsState = initialState, action: IDatasetsAction): IDatasetsState => {
const { type, payload } = action
Expand All @@ -50,6 +63,14 @@ export default (state: IDatasetsState = initialState, action: IDatasetsAction):
return { ...state, result: state.result.filter((e) => e.name !== payload.datasetName) }
case DELETE_DATASET_SUCCESS:
return { ...state, deletedDatasetName: payload.datasetName }
case DELETE_DATASET_TAG:
return { ...state, refreshTags: true }
case DELETE_DATASET_TAG_SUCCESS:
return { ...state, result: payload.datasets, refreshTags: false }
case ADD_DATASET_TAG:
return { ...state, refreshTags: true }
case ADD_DATASET_TAG_SUCCESS:
return { ...state, result: payload.datasets, refreshTags: false }
default:
return state
}
Expand Down
Loading
Loading