This is an automated email from the ASF dual-hosted git repository.
jshao pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/main by this push:
new 0c9c0d036 [#5447][#5720][#5727] create topic, disable create table for
hudi, enable schema properties and comment for partial paimon (#5771)
0c9c0d036 is described below
commit 0c9c0d03646575cb1b420b4045e007397d5e7ce3
Author: Qian Xia <[email protected]>
AuthorDate: Fri Dec 6 19:32:33 2024 +0800
[#5447][#5720][#5727] create topic, disable create table for hudi, enable
schema properties and comment for partial paimon (#5771)
### What changes were proposed in this pull request?
1. Support creating topic/editing topic/deleting topic/viewing topic
<img width="1417" alt="image"
src="https://github.com/user-attachments/assets/24e0ae59-7ec5-4115-becc-2d8a83dcd8d6">
2. Fix issue with creating a table for hudi
<img width="1481" alt="image"
src="https://github.com/user-attachments/assets/bc64eaa9-1b8e-48b2-98b4-899ebfd6eb02">
3. [Improvement] Paimon catalog, schema properties can be set when
backend is jdbc and hive
<img width="1441" alt="image"
src="https://github.com/user-attachments/assets/9bb2746f-d3b0-4f05-84bc-d5baadb8999d">
<img width="1434" alt="image"
src="https://github.com/user-attachments/assets/537ec2fc-a4fa-48ff-bbd1-f606751a2a46">
### Why are the changes needed?
N/A
Fix: #5447, #5720, #5727
### Does this PR introduce _any_ user-facing change?
N/A
### How was this patch tested?
manually
---------
Co-authored-by: Qiming Teng <[email protected]>
---
web/web/src/app/metalakes/CreateMetalakeDialog.js | 4 +-
.../metalake/rightContent/CreateCatalogDialog.js | 6 +-
.../metalake/rightContent/CreateFilesetDialog.js | 6 +-
.../metalake/rightContent/CreateSchemaDialog.js | 40 ++++---
.../metalake/rightContent/CreateTableDialog.js | 6 +-
...CreateFilesetDialog.js => CreateTopicDialog.js} | 133 +++++----------------
.../metalake/rightContent/RightContent.js | 50 ++++++--
.../tabsContent/tableView/TableView.js | 58 ++++++++-
web/web/src/lib/api/topics/index.js | 20 +++-
web/web/src/lib/store/metalakes/index.js | 108 ++++++++++++++++-
10 files changed, 281 insertions(+), 150 deletions(-)
diff --git a/web/web/src/app/metalakes/CreateMetalakeDialog.js
b/web/web/src/app/metalakes/CreateMetalakeDialog.js
index 8b8daaadb..2fb70ff38 100644
--- a/web/web/src/app/metalakes/CreateMetalakeDialog.js
+++ b/web/web/src/app/metalakes/CreateMetalakeDialog.js
@@ -316,8 +316,8 @@ const CreateMetalakeDialog = props => {
)}
{item.invalid && (
<FormHelperText className={'twc-text-error-main'}>
- Invalid key, matches strings starting with a
letter/underscore, followed by alphanumeric
- characters, underscores, hyphens, or dots.
+ Valid key must starts with a letter/underscore,
followed by alphanumeric characters,
+ underscores, hyphens, or dots.
</FormHelperText>
)}
</FormControl>
diff --git
a/web/web/src/app/metalakes/metalake/rightContent/CreateCatalogDialog.js
b/web/web/src/app/metalakes/metalake/rightContent/CreateCatalogDialog.js
index a4cf85623..cd9c101f7 100644
--- a/web/web/src/app/metalakes/metalake/rightContent/CreateCatalogDialog.js
+++ b/web/web/src/app/metalakes/metalake/rightContent/CreateCatalogDialog.js
@@ -680,12 +680,12 @@ const CreateCatalogDialog = props => {
)}
{item.key && item.invalid && (
<FormHelperText className={'twc-text-error-main'}>
- Invalid key, matches strings starting with a
letter/underscore, followed by alphanumeric
- characters, underscores, hyphens, or dots.
+ Valid key must starts with a letter/underscore,
followed by alphanumeric characters,
+ underscores, hyphens, or dots.
</FormHelperText>
)}
{!item.key.trim() && (
- <FormHelperText
className={'twc-text-error-main'}>Key is required field</FormHelperText>
+ <FormHelperText
className={'twc-text-error-main'}>Key is required</FormHelperText>
)}
</FormControl>
</Grid>
diff --git
a/web/web/src/app/metalakes/metalake/rightContent/CreateFilesetDialog.js
b/web/web/src/app/metalakes/metalake/rightContent/CreateFilesetDialog.js
index cb1901545..6a69d82f8 100644
--- a/web/web/src/app/metalakes/metalake/rightContent/CreateFilesetDialog.js
+++ b/web/web/src/app/metalakes/metalake/rightContent/CreateFilesetDialog.js
@@ -461,12 +461,12 @@ const CreateFilesetDialog = props => {
)}
{item.key && item.invalid && (
<FormHelperText className={'twc-text-error-main'}>
- Invalid key, matches strings starting with a
letter/underscore, followed by alphanumeric
- characters, underscores, hyphens, or dots.
+ Valid key must starts with a letter/underscore,
followed by alphanumeric characters,
+ underscores, hyphens, or dots.
</FormHelperText>
)}
{!item.key.trim() && (
- <FormHelperText
className={'twc-text-error-main'}>Key is required field</FormHelperText>
+ <FormHelperText
className={'twc-text-error-main'}>Key is required</FormHelperText>
)}
</FormControl>
</Grid>
diff --git
a/web/web/src/app/metalakes/metalake/rightContent/CreateSchemaDialog.js
b/web/web/src/app/metalakes/metalake/rightContent/CreateSchemaDialog.js
index 08efd4036..ce893802a 100644
--- a/web/web/src/app/metalakes/metalake/rightContent/CreateSchemaDialog.js
+++ b/web/web/src/app/metalakes/metalake/rightContent/CreateSchemaDialog.js
@@ -23,20 +23,18 @@ import { useState, forwardRef, useEffect, Fragment } from
'react'
import {
Box,
- Grid,
Button,
Dialog,
- TextField,
- Typography,
- DialogContent,
DialogActions,
- IconButton,
+ DialogContent,
Fade,
- Select,
- MenuItem,
- InputLabel,
FormControl,
- FormHelperText
+ FormHelperText,
+ Grid,
+ IconButton,
+ InputLabel,
+ TextField,
+ Typography
} from '@mui/material'
import Icon from '@/components/Icon'
@@ -90,6 +88,10 @@ const CreateSchemaDialog = props => {
const activatedCatalogDetail = store.activatedDetails
const [cacheData, setCacheData] = useState()
+ const paimonCatalogBackend =
+ activatedCatalogDetail?.provider === 'lakehouse-paimon' &&
+ ['hive',
'jdbc'].includes(activatedCatalogDetail?.properties['catalog-backend'])
+
const {
control,
reset,
@@ -293,7 +295,8 @@ const CreateSchemaDialog = props => {
</FormControl>
</Grid>
- {!['jdbc-mysql', 'lakehouse-paimon',
'jdbc-oceanbase'].includes(activatedCatalogDetail?.provider) && (
+ {(!['jdbc-mysql', 'lakehouse-paimon',
'jdbc-oceanbase'].includes(activatedCatalogDetail?.provider) ||
+ paimonCatalogBackend) && (
<Grid item xs={12}>
<FormControl fullWidth>
<Controller
@@ -315,12 +318,14 @@ const CreateSchemaDialog = props => {
)}
/>
</FormControl>
+ {activatedCatalogDetail?.properties['catalog-backend']}
</Grid>
)}
- {!['jdbc-postgresql', 'lakehouse-paimon', 'kafka', 'jdbc-mysql',
'jdbc-oceanbase'].includes(
+ {(!['jdbc-postgresql', 'lakehouse-paimon', 'kafka', 'jdbc-mysql',
'jdbc-oceanbase'].includes(
activatedCatalogDetail?.provider
- ) && (
+ ) ||
+ paimonCatalogBackend) && (
<Grid item xs={12} data-refer='schema-props-layout'>
<Typography sx={{ mb: 2 }} variant='body2'>
Properties
@@ -385,12 +390,12 @@ const CreateSchemaDialog = props => {
)}
{item.key && item.invalid && (
<FormHelperText className={'twc-text-error-main'}>
- Invalid key, matches strings starting with a
letter/underscore, followed by alphanumeric
- characters, underscores, hyphens, or dots.
+ Valid key must starts with a letter/underscore,
followed by alphanumeric characters,
+ underscores, hyphens, or dots.
</FormHelperText>
)}
{!item.key.trim() && (
- <FormHelperText
className={'twc-text-error-main'}>Key is required field</FormHelperText>
+ <FormHelperText
className={'twc-text-error-main'}>Key is required</FormHelperText>
)}
</FormControl>
</Grid>
@@ -400,9 +405,10 @@ const CreateSchemaDialog = props => {
</Grid>
)}
- {!['jdbc-postgresql', 'lakehouse-paimon', 'kafka', 'jdbc-mysql',
'jdbc-oceanbase'].includes(
+ {(!['jdbc-postgresql', 'lakehouse-paimon', 'kafka', 'jdbc-mysql',
'jdbc-oceanbase'].includes(
activatedCatalogDetail?.provider
- ) && (
+ ) ||
+ paimonCatalogBackend) && (
<Grid item xs={12}>
<Button
size='small'
diff --git
a/web/web/src/app/metalakes/metalake/rightContent/CreateTableDialog.js
b/web/web/src/app/metalakes/metalake/rightContent/CreateTableDialog.js
index b1a8a06f7..a1c337725 100644
--- a/web/web/src/app/metalakes/metalake/rightContent/CreateTableDialog.js
+++ b/web/web/src/app/metalakes/metalake/rightContent/CreateTableDialog.js
@@ -784,12 +784,12 @@ const CreateTableDialog = props => {
)}
{item.key && item.invalid && (
<FormHelperText className={'twc-text-error-main'}>
- Invalid key, matches strings starting with a
letter/underscore, followed by alphanumeric
- characters, underscores, hyphens, or dots.
+ Valid key must starts with a letter/underscore,
followed by alphanumeric characters,
+ underscores, hyphens, or dots.
</FormHelperText>
)}
{!item.key.trim() && (
- <FormHelperText
className={'twc-text-error-main'}>Key is required field</FormHelperText>
+ <FormHelperText
className={'twc-text-error-main'}>Key is required</FormHelperText>
)}
</FormControl>
</Grid>
diff --git
a/web/web/src/app/metalakes/metalake/rightContent/CreateFilesetDialog.js
b/web/web/src/app/metalakes/metalake/rightContent/CreateTopicDialog.js
similarity index 75%
copy from web/web/src/app/metalakes/metalake/rightContent/CreateFilesetDialog.js
copy to web/web/src/app/metalakes/metalake/rightContent/CreateTopicDialog.js
index cb1901545..4f671cc87 100644
--- a/web/web/src/app/metalakes/metalake/rightContent/CreateFilesetDialog.js
+++ b/web/web/src/app/metalakes/metalake/rightContent/CreateTopicDialog.js
@@ -23,26 +23,24 @@ import { useState, forwardRef, useEffect, Fragment } from
'react'
import {
Box,
- Grid,
Button,
Dialog,
- TextField,
- Typography,
- DialogContent,
DialogActions,
- IconButton,
+ DialogContent,
Fade,
- Select,
- MenuItem,
- InputLabel,
FormControl,
- FormHelperText
+ FormHelperText,
+ Grid,
+ IconButton,
+ InputLabel,
+ TextField,
+ Typography
} from '@mui/material'
import Icon from '@/components/Icon'
import { useAppDispatch } from '@/lib/hooks/useStore'
-import { createFileset, updateFileset } from '@/lib/store/metalakes'
+import { createTopic, updateTopic } from '@/lib/store/metalakes'
import * as yup from 'yup'
import { useForm, Controller } from 'react-hook-form'
@@ -52,23 +50,16 @@ import { groupBy } from 'lodash-es'
import { genUpdates } from '@/lib/utils'
import { nameRegex, nameRegexDesc, keyRegex } from '@/lib/utils/regex'
import { useSearchParams } from 'next/navigation'
+import { useAppSelector } from '@/lib/hooks/useStore'
const defaultValues = {
name: '',
- type: 'managed',
- storageLocation: '',
comment: '',
propItems: []
}
const schema = yup.object().shape({
name: yup.string().required().matches(nameRegex, nameRegexDesc),
- type: yup.mixed().oneOf(['managed', 'external']).required(),
- storageLocation: yup.string().when('type', {
- is: 'external',
- then: schema => schema.required(),
- otherwise: schema => schema
- }),
propItems: yup.array().of(
yup.object().shape({
required: yup.boolean(),
@@ -85,15 +76,16 @@ const Transition = forwardRef(function Transition(props,
ref) {
return <Fade ref={ref} {...props} />
})
-const CreateFilesetDialog = props => {
+const CreateTopicDialog = props => {
const { open, setOpen, type = 'create', data = {} } = props
const searchParams = useSearchParams()
const metalake = searchParams.get('metalake')
const catalog = searchParams.get('catalog')
- const catalogType = searchParams.get('type')
const schemaName = searchParams.get('schema')
+ const catalogType = searchParams.get('type')
const [innerProps, setInnerProps] = useState([])
const dispatch = useAppDispatch()
+ const store = useAppSelector(state => state.metalakes)
const [cacheData, setCacheData] = useState()
const {
@@ -197,16 +189,14 @@ const CreateFilesetDialog = props => {
return acc
}, {})
- const filesetData = {
+ const schemaData = {
name: data.name,
- type: data.type,
- storageLocation: data.storageLocation,
comment: data.comment,
properties
}
if (type === 'create') {
- dispatch(createFileset({ data: filesetData, metalake, catalog, type:
catalogType, schema: schemaName })).then(
+ dispatch(createTopic({ data: schemaData, metalake, catalog, schema:
schemaName, type: catalogType })).then(
res => {
if (!res.payload?.err) {
handleClose()
@@ -214,16 +204,16 @@ const CreateFilesetDialog = props => {
}
)
} else {
- const reqData = { updates: genUpdates(cacheData, filesetData) }
+ const reqData = { updates: genUpdates(cacheData, schemaData) }
if (reqData.updates.length !== 0) {
dispatch(
- updateFileset({
+ updateTopic({
metalake,
catalog,
type: catalogType,
schema: schemaName,
- fileset: cacheData.name,
+ topic: cacheData.name,
data: reqData
})
).then(res => {
@@ -249,8 +239,6 @@ const CreateFilesetDialog = props => {
setCacheData(data)
setValue('name', data.name)
- setValue('type', data.type)
- setValue('storageLocation', data.storageLocation)
setValue('comment', data.comment)
const propsItems = Object.entries(properties).map(([key, value]) => {
@@ -285,7 +273,7 @@ const CreateFilesetDialog = props => {
</IconButton>
<Box sx={{ mb: 8, textAlign: 'center' }}>
<Typography variant='h5' sx={{ mb: 3 }}>
- {type === 'create' ? 'Create' : 'Edit'} Fileset
+ {type === 'create' ? 'Create' : 'Edit'} Topic
</Typography>
</Box>
@@ -302,8 +290,9 @@ const CreateFilesetDialog = props => {
label='Name'
onChange={onChange}
placeholder=''
+ disabled={type === 'update'}
error={Boolean(errors.name)}
- data-refer='fileset-name-field'
+ data-refer='topic-name-field'
/>
)}
/>
@@ -311,70 +300,6 @@ const CreateFilesetDialog = props => {
</FormControl>
</Grid>
- <Grid item xs={12}>
- <FormControl fullWidth>
- <InputLabel id='select-fileset-type'
error={Boolean(errors.type)}>
- Type
- </InputLabel>
- <Controller
- name='type'
- control={control}
- rules={{ required: true }}
- render={({ field: { value, onChange } }) => (
- <Select
- value={value}
- label='Type'
- defaultValue='managed'
- onChange={onChange}
- disabled={type === 'update'}
- error={Boolean(errors.type)}
- labelId='select-fileset-type'
- data-refer='fileset-type-selector'
- >
- <MenuItem value={'managed'}>Managed</MenuItem>
- <MenuItem value={'external'}>External</MenuItem>
- </Select>
- )}
- />
- {errors.type && <FormHelperText sx={{ color: 'error.main'
}}>{errors.type.message}</FormHelperText>}
- </FormControl>
- </Grid>
-
- <Grid item xs={12}>
- <FormControl fullWidth>
- <Controller
- name='storageLocation'
- control={control}
- rules={{ required: true }}
- render={({ field: { value, onChange } }) => (
- <TextField
- value={value}
- label='Storage Location'
- onChange={onChange}
- disabled={type === 'update'}
- placeholder=''
- error={Boolean(errors.storageLocation)}
- data-refer='fileset-storageLocation-field'
- />
- )}
- />
- {errors.storageLocation ? (
- <FormHelperText sx={{ color: 'error.main'
}}>{errors.storageLocation.message}</FormHelperText>
- ) : (
- <>
- <FormHelperText sx={{ color: 'text.main' }}>
- It is optional if the fileset is 'Managed' type and a
storage location is already specified at the
- parent catalog or schema level.
- </FormHelperText>
- <FormHelperText sx={{ color: 'text.main' }}>
- It becomes mandatory if the fileset type is 'External'
or no storage location is defined at the
- parent level.
- </FormHelperText>
- </>
- )}
- </FormControl>
- </Grid>
-
<Grid item xs={12}>
<FormControl fullWidth>
<Controller
@@ -390,14 +315,14 @@ const CreateFilesetDialog = props => {
onChange={onChange}
placeholder=''
error={Boolean(errors.comment)}
- data-refer='fileset-comment-field'
+ data-refer='topic-comment-field'
/>
)}
/>
</FormControl>
</Grid>
- <Grid item xs={12} data-refer='fileset-props-layout'>
+ <Grid item xs={12} data-refer='topic-props-layout'>
<Typography sx={{ mb: 2 }} variant='body2'>
Properties
</Typography>
@@ -409,7 +334,7 @@ const CreateFilesetDialog = props => {
<Box>
<Box
sx={{ display: 'flex', alignItems: 'center',
justifyContent: 'space-between' }}
- data-refer={`fileset-props-${index}`}
+ data-refer={`topic-props-${index}`}
>
<Box>
<TextField
@@ -461,12 +386,12 @@ const CreateFilesetDialog = props => {
)}
{item.key && item.invalid && (
<FormHelperText className={'twc-text-error-main'}>
- Invalid key, matches strings starting with a
letter/underscore, followed by alphanumeric
- characters, underscores, hyphens, or dots.
+ Valid key must starts with a letter/underscore,
followed by alphanumeric characters,
+ underscores, hyphens, or dots.
</FormHelperText>
)}
{!item.key.trim() && (
- <FormHelperText
className={'twc-text-error-main'}>Key is required field</FormHelperText>
+ <FormHelperText
className={'twc-text-error-main'}>Key is required</FormHelperText>
)}
</FormControl>
</Grid>
@@ -481,7 +406,7 @@ const CreateFilesetDialog = props => {
onClick={addFields}
variant='outlined'
startIcon={<Icon icon='mdi:plus-circle-outline' />}
- data-refer='add-fileset-props'
+ data-refer='add-topic-props'
>
Add Property
</Button>
@@ -495,7 +420,7 @@ const CreateFilesetDialog = props => {
pb: theme => [`${theme.spacing(5)} !important`,
`${theme.spacing(12.5)} !important`]
}}
>
- <Button variant='contained' sx={{ mr: 1 }} type='submit'
data-refer='handle-submit-fileset'>
+ <Button variant='contained' sx={{ mr: 1 }} type='submit'
data-refer='handle-submit-topic'>
{type === 'create' ? 'Create' : 'Update'}
</Button>
<Button variant='outlined' onClick={handleClose}>
@@ -507,4 +432,4 @@ const CreateFilesetDialog = props => {
)
}
-export default CreateFilesetDialog
+export default CreateTopicDialog
diff --git a/web/web/src/app/metalakes/metalake/rightContent/RightContent.js
b/web/web/src/app/metalakes/metalake/rightContent/RightContent.js
index a4a59099f..8e061f97a 100644
--- a/web/web/src/app/metalakes/metalake/rightContent/RightContent.js
+++ b/web/web/src/app/metalakes/metalake/rightContent/RightContent.js
@@ -27,6 +27,7 @@ import MetalakePath from './MetalakePath'
import CreateCatalogDialog from './CreateCatalogDialog'
import CreateSchemaDialog from './CreateSchemaDialog'
import CreateFilesetDialog from './CreateFilesetDialog'
+import CreateTopicDialog from './CreateTopicDialog'
import CreateTableDialog from './CreateTableDialog'
import TabsContent from './tabsContent/TabsContent'
import { useSearchParams } from 'next/navigation'
@@ -36,11 +37,13 @@ const RightContent = () => {
const [open, setOpen] = useState(false)
const [openSchema, setOpenSchema] = useState(false)
const [openFileset, setOpenFileset] = useState(false)
+ const [openTopic, setOpenTopic] = useState(false)
const [openTable, setOpenTable] = useState(false)
const searchParams = useSearchParams()
const [isShowBtn, setBtnVisible] = useState(true)
const [isShowSchemaBtn, setSchemaBtnVisible] = useState(false)
const [isShowFilesetBtn, setFilesetBtnVisible] = useState(false)
+ const [isShowTopicBtn, setTopicBtnVisible] = useState(false)
const [isShowTableBtn, setTableBtnVisible] = useState(false)
const store = useAppSelector(state => state.metalakes)
@@ -56,6 +59,10 @@ const RightContent = () => {
setOpenFileset(true)
}
+ const handleCreateTopic = () => {
+ setOpenTopic(true)
+ }
+
const handleCreateTable = () => {
setOpenTable(true)
}
@@ -69,10 +76,18 @@ const RightContent = () => {
paramsSize == 4 &&
searchParams.has('metalake') &&
searchParams.has('catalog') &&
- searchParams.get('type') === 'fileset'
- searchParams.has('schema')
+ searchParams.get('type') === 'fileset' &&
+ searchParams.has('schema')
setFilesetBtnVisible(isFilesetList)
+ const isTopicList =
+ paramsSize == 4 &&
+ searchParams.has('metalake') &&
+ searchParams.has('catalog') &&
+ searchParams.get('type') === 'messaging' &&
+ searchParams.has('schema')
+ setTopicBtnVisible(isTopicList)
+
if (store.catalogs.length) {
const currentCatalog = store.catalogs.filter(ca => ca.name ===
searchParams.get('catalog'))[0]
@@ -83,15 +98,16 @@ const RightContent = () => {
searchParams.has('type') &&
!['lakehouse-hudi', 'kafka'].includes(currentCatalog?.provider)
setSchemaBtnVisible(isSchemaList)
- }
- const isTableList =
- paramsSize == 4 &&
- searchParams.has('metalake') &&
- searchParams.has('catalog') &&
- searchParams.get('type') === 'relational' &&
- searchParams.has('schema')
- setTableBtnVisible(isTableList)
+ const isTableList =
+ paramsSize == 4 &&
+ searchParams.has('metalake') &&
+ searchParams.has('catalog') &&
+ searchParams.get('type') === 'relational' &&
+ searchParams.has('schema') &&
+ 'lakehouse-hudi' !== currentCatalog?.provider
+ setTableBtnVisible(isTableList)
+ }
}, [searchParams, store.catalogs, store.catalogs.length])
return (
@@ -155,6 +171,20 @@ const RightContent = () => {
<CreateFilesetDialog open={openFileset} setOpen={setOpenFileset} />
</Box>
)}
+ {isShowTopicBtn && (
+ <Box className={`twc-flex twc-items-center`}>
+ <Button
+ variant='contained'
+ startIcon={<Icon icon='mdi:plus-box' />}
+ onClick={handleCreateTopic}
+ sx={{ width: 200 }}
+ data-refer='create-topic-btn'
+ >
+ Create Topic
+ </Button>
+ <CreateTopicDialog open={openTopic} setOpen={setOpenTopic} />
+ </Box>
+ )}
{isShowTableBtn && (
<Box className={`twc-flex twc-items-center`}>
<Button
diff --git
a/web/web/src/app/metalakes/metalake/rightContent/tabsContent/tableView/TableView.js
b/web/web/src/app/metalakes/metalake/rightContent/tabsContent/tableView/TableView.js
index f414ccfec..a2a73c1ec 100644
---
a/web/web/src/app/metalakes/metalake/rightContent/tabsContent/tableView/TableView.js
+++
b/web/web/src/app/metalakes/metalake/rightContent/tabsContent/tableView/TableView.js
@@ -42,16 +42,25 @@ import ConfirmDeleteDialog from
'@/components/ConfirmDeleteDialog'
import CreateCatalogDialog from '../../CreateCatalogDialog'
import CreateSchemaDialog from '../../CreateSchemaDialog'
import CreateFilesetDialog from '../../CreateFilesetDialog'
+import CreateTopicDialog from '../../CreateTopicDialog'
import CreateTableDialog from '../../CreateTableDialog'
import { useAppSelector, useAppDispatch } from '@/lib/hooks/useStore'
-import { deleteCatalog, deleteFileset, deleteSchema, deleteTable,
setCatalogInUse } from '@/lib/store/metalakes'
+import {
+ deleteCatalog,
+ deleteFileset,
+ deleteTopic,
+ deleteSchema,
+ deleteTable,
+ setCatalogInUse
+} from '@/lib/store/metalakes'
import { to } from '@/lib/utils'
import { getCatalogDetailsApi, switchInUseApi } from '@/lib/api/catalogs'
import { getSchemaDetailsApi } from '@/lib/api/schemas'
import { useSearchParams } from 'next/navigation'
import { getFilesetDetailsApi } from '@/lib/api/filesets'
+import { getTopicDetailsApi } from '@/lib/api/topics'
import { getTableDetailsApi } from '@/lib/api/tables'
const fonts = Inconsolata({ subsets: ['latin'] })
@@ -105,16 +114,20 @@ const TableView = () => {
const [openDialog, setOpenDialog] = useState(false)
const [openSchemaDialog, setOpenSchemaDialog] = useState(false)
const [openFilesetDialog, setOpenFilesetDialog] = useState(false)
+ const [openTopicDialog, setOpenTopicDialog] = useState(false)
const [openTableDialog, setOpenTableDialog] = useState(false)
const [dialogData, setDialogData] = useState({})
const [dialogType, setDialogType] = useState('create')
- const [isHideSchemaEdit, setIsHideSchemaEdit] = useState(true)
+ const [isHideEdit, setIsHideEdit] = useState(true)
useEffect(() => {
if (store.catalogs.length) {
const currentCatalog = store.catalogs.filter(ca => ca.name ===
catalog)[0]
- const isHideSchemaAction = ['lakehouse-hudi',
'kafka'].includes(currentCatalog?.provider) && paramsSize == 3
- setIsHideSchemaEdit(isHideSchemaAction)
+
+ const isHideAction =
+ (['lakehouse-hudi', 'kafka'].includes(currentCatalog?.provider) &&
paramsSize == 3) ||
+ (currentCatalog?.provider === 'lakehouse-hudi' && paramsSize == 4)
+ setIsHideEdit(isHideAction)
}
}, [store.catalogs, store.catalogs.length, paramsSize, catalog])
@@ -300,7 +313,7 @@ const TableView = () => {
<ViewIcon viewBox='0 0 24 22' />
</IconButton>
- {!isHideSchemaEdit && (
+ {!isHideEdit && (
<IconButton
title='Edit'
size='small'
@@ -312,7 +325,7 @@ const TableView = () => {
</IconButton>
)}
- {!isHideSchemaEdit && (
+ {!isHideEdit && (
<IconButton
title='Delete'
size='small'
@@ -502,6 +515,16 @@ const TableView = () => {
setOpenDrawer(true)
break
}
+ case 'topic': {
+ const [err, res] = await to(getTopicDetailsApi({ metalake, catalog,
schema, topic: row.name }))
+ if (err || !res) {
+ throw new Error(err)
+ }
+
+ setDrawerData(res.topic)
+ setOpenDrawer(true)
+ break
+ }
case 'table': {
const [err, res] = await to(getTableDetailsApi({ metalake, catalog,
schema, table: row.name }))
if (err || !res) {
@@ -560,6 +583,19 @@ const TableView = () => {
}
break
}
+ case 'topic': {
+ if (metalake && catalog && schema) {
+ const [err, res] = await to(getTopicDetailsApi({ metalake, catalog,
schema, topic: data.row?.name }))
+ if (err || !res) {
+ throw new Error(err)
+ }
+
+ setDialogType('update')
+ setDialogData(res.topic)
+ setOpenTopicDialog(true)
+ }
+ break
+ }
case 'table': {
if (metalake && catalog && schema) {
const [err, res] = await to(getTableDetailsApi({ metalake, catalog,
schema, table: data.row?.name }))
@@ -600,6 +636,9 @@ const TableView = () => {
case 'fileset':
dispatch(deleteFileset({ metalake, catalog, type, schema, fileset:
confirmCacheData.name }))
break
+ case 'topic':
+ dispatch(deleteTopic({ metalake, catalog, type, schema, topic:
confirmCacheData.name }))
+ break
case 'table':
dispatch(deleteTable({ metalake, catalog, type, schema, table:
confirmCacheData.name }))
break
@@ -628,6 +667,11 @@ const TableView = () => {
searchParams.has('catalog') &&
searchParams.get('type') === 'fileset' &&
searchParams.has('schema')) ||
+ (paramsSize == 4 &&
+ searchParams.has('metalake') &&
+ searchParams.has('catalog') &&
+ searchParams.get('type') === 'messaging' &&
+ searchParams.has('schema')) ||
(paramsSize == 4 &&
searchParams.has('metalake') &&
searchParams.has('catalog') &&
@@ -687,6 +731,8 @@ const TableView = () => {
type={dialogType}
/>
+ <CreateTopicDialog open={openTopicDialog} setOpen={setOpenTopicDialog}
data={dialogData} type={dialogType} />
+
<CreateTableDialog open={openTableDialog} setOpen={setOpenTableDialog}
data={dialogData} type={dialogType} />
</Box>
)
diff --git a/web/web/src/lib/api/topics/index.js
b/web/web/src/lib/api/topics/index.js
index 2ba86ae18..24f8afd81 100644
--- a/web/web/src/lib/api/topics/index.js
+++ b/web/web/src/lib/api/topics/index.js
@@ -27,7 +27,13 @@ const Apis = {
GET_DETAIL: ({ metalake, catalog, schema, topic }) =>
`/api/metalakes/${encodeURIComponent(metalake)}/catalogs/${encodeURIComponent(
catalog
-
)}/schemas/${encodeURIComponent(schema)}/topics/${encodeURIComponent(topic)}`
+
)}/schemas/${encodeURIComponent(schema)}/topics/${encodeURIComponent(topic)}`,
+ CREATE: ({ metalake, catalog, schema }) =>
+
`/api/metalakes/${encodeURIComponent(metalake)}/catalogs/${encodeURIComponent(catalog)}/schemas/${encodeURIComponent(schema)}/topics`,
+ UPDATE: ({ metalake, catalog, schema, topic }) =>
+
`/api/metalakes/${encodeURIComponent(metalake)}/catalogs/${encodeURIComponent(catalog)}/schemas/${encodeURIComponent(schema)}/topics/${encodeURIComponent(topic)}`,
+ DELETE: ({ metalake, catalog, schema, topic }) =>
+
`/api/metalakes/${encodeURIComponent(metalake)}/catalogs/${encodeURIComponent(catalog)}/schemas/${encodeURIComponent(schema)}/topics/${encodeURIComponent(topic)}`
}
export const getTopicsApi = params => {
@@ -41,3 +47,15 @@ export const getTopicDetailsApi = ({ metalake, catalog,
schema, topic }) => {
url: `${Apis.GET_DETAIL({ metalake, catalog, schema, topic })}`
})
}
+
+export const createTopicApi = ({ metalake, catalog, schema, data }) => {
+ return defHttp.post({ url: `${Apis.CREATE({ metalake, catalog, schema })}`,
data })
+}
+
+export const updateTopicApi = ({ metalake, catalog, schema, topic, data }) => {
+ return defHttp.put({ url: `${Apis.UPDATE({ metalake, catalog, schema, topic
})}`, data })
+}
+
+export const deleteTopicApi = ({ metalake, catalog, schema, topic }) => {
+ return defHttp.delete({ url: `${Apis.DELETE({ metalake, catalog, schema,
topic })}` })
+}
diff --git a/web/web/src/lib/store/metalakes/index.js
b/web/web/src/lib/store/metalakes/index.js
index 6b8fedf32..3d4ad454d 100644
--- a/web/web/src/lib/store/metalakes/index.js
+++ b/web/web/src/lib/store/metalakes/index.js
@@ -54,7 +54,7 @@ import {
updateFilesetApi,
deleteFilesetApi
} from '@/lib/api/filesets'
-import { getTopicsApi, getTopicDetailsApi } from '@/lib/api/topics'
+import { getTopicsApi, getTopicDetailsApi, createTopicApi, updateTopicApi,
deleteTopicApi } from '@/lib/api/topics'
export const fetchMetalakes = createAsyncThunk('appMetalakes/fetchMetalakes',
async (params, { getState }) => {
const [err, res] = await to(getMetalakesApi())
@@ -1104,6 +1104,67 @@ export const getTopicDetails = createAsyncThunk(
}
)
+export const createTopic = createAsyncThunk(
+ 'appMetalakes/createTopic',
+ async ({ data, metalake, catalog, type, schema }, { dispatch }) => {
+ dispatch(setTableLoading(true))
+ const [err, res] = await to(createTopicApi({ data, metalake, catalog,
schema }))
+ dispatch(setTableLoading(false))
+
+ if (err || !res) {
+ return { err: true }
+ }
+
+ const { topic: topicItem } = res
+
+ const topicData = {
+ ...topicItem,
+ node: 'topic',
+ id:
`{{${metalake}}}{{${catalog}}}{{${type}}}{{${schema}}}{{${topicItem.name}}}`,
+ key:
`{{${metalake}}}{{${catalog}}}{{${type}}}{{${schema}}}{{${topicItem.name}}}`,
+ path: `?${new URLSearchParams({ metalake, catalog, type, schema, topic:
topicItem.name }).toString()}`,
+ name: topicItem.name,
+ title: topicItem.name,
+ tables: [],
+ children: []
+ }
+
+ dispatch(fetchTopics({ metalake, catalog, schema, type, init: true }))
+
+ return topicData
+ }
+)
+
+export const updateTopic = createAsyncThunk(
+ 'appMetalakes/updateTopic',
+ async ({ metalake, catalog, type, schema, topic, data }, { dispatch }) => {
+ const [err, res] = await to(updateTopicApi({ metalake, catalog, schema,
topic, data }))
+ if (err || !res) {
+ return { err: true }
+ }
+ dispatch(fetchTopics({ metalake, catalog, type, schema, init: true }))
+
+ return res.catalog
+ }
+)
+
+export const deleteTopic = createAsyncThunk(
+ 'appMetalakes/deleteTopic',
+ async ({ metalake, catalog, type, schema, topic }, { dispatch }) => {
+ dispatch(setTableLoading(true))
+ const [err, res] = await to(deleteTopicApi({ metalake, catalog, schema,
topic }))
+ dispatch(setTableLoading(false))
+
+ if (err || !res) {
+ throw new Error(err)
+ }
+
+ dispatch(fetchTopics({ metalake, catalog, type, schema, page: 'topics',
init: true }))
+
+ return res
+ }
+)
+
export const appMetalakesSlice = createSlice({
name: 'appMetalakes',
initialState: {
@@ -1346,6 +1407,21 @@ export const appMetalakesSlice = createSlice({
toast.error(action.error.message)
}
})
+ builder.addCase(createTable.rejected, (state, action) => {
+ if (!action.error.message.includes('CanceledError')) {
+ toast.error(action.error.message)
+ }
+ })
+ builder.addCase(updateTable.rejected, (state, action) => {
+ if (!action.error.message.includes('CanceledError')) {
+ toast.error(action.error.message)
+ }
+ })
+ builder.addCase(deleteTable.rejected, (state, action) => {
+ if (!action.error.message.includes('CanceledError')) {
+ toast.error(action.error.message)
+ }
+ })
builder.addCase(fetchFilesets.fulfilled, (state, action) => {
state.filesets = action.payload.filesets
if (action.payload.init) {
@@ -1366,6 +1442,21 @@ export const appMetalakesSlice = createSlice({
toast.error(action.error.message)
}
})
+ builder.addCase(createFileset.rejected, (state, action) => {
+ if (!action.error.message.includes('CanceledError')) {
+ toast.error(action.error.message)
+ }
+ })
+ builder.addCase(updateFileset.rejected, (state, action) => {
+ if (!action.error.message.includes('CanceledError')) {
+ toast.error(action.error.message)
+ }
+ })
+ builder.addCase(deleteFileset.rejected, (state, action) => {
+ if (!action.error.message.includes('CanceledError')) {
+ toast.error(action.error.message)
+ }
+ })
builder.addCase(fetchTopics.fulfilled, (state, action) => {
state.topics = action.payload.topics
if (action.payload.init) {
@@ -1386,6 +1477,21 @@ export const appMetalakesSlice = createSlice({
toast.error(action.error.message)
}
})
+ builder.addCase(createTopic.rejected, (state, action) => {
+ if (!action.error.message.includes('CanceledError')) {
+ toast.error(action.error.message)
+ }
+ })
+ builder.addCase(updateTopic.rejected, (state, action) => {
+ if (!action.error.message.includes('CanceledError')) {
+ toast.error(action.error.message)
+ }
+ })
+ builder.addCase(deleteTopic.rejected, (state, action) => {
+ if (!action.error.message.includes('CanceledError')) {
+ toast.error(action.error.message)
+ }
+ })
}
})