This is an automated email from the ASF dual-hosted git repository.
mchades 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 dde3b633bf [#11342] web-v2(UI): support glue catalog UI (#11418)
dde3b633bf is described below
commit dde3b633bf87f68135df051b08f0633a283c1f7e
Author: Qian Xia <[email protected]>
AuthorDate: Sun Jun 7 17:02:00 2026 +0800
[#11342] web-v2(UI): support glue catalog UI (#11418)
### What changes were proposed in this pull request?
<img width="2212" height="1600" alt="image"
src="https://github.com/user-attachments/assets/41d9e086-df62-4f6f-9a62-04cb97d5a21d"
/>
<img width="2238" height="1648" alt="image"
src="https://github.com/user-attachments/assets/ef963fc1-47c9-42c6-b5c2-785958a2e9bc"
/>
### Why are the changes needed?
Fix: #11342
### Does this PR introduce _any_ user-facing change?
N/A
### How was this patch tested?
manually
---
web-v2/web/src/app/catalogs/TreeComponent.js | 16 +++
.../catalogs/rightContent/CreateCatalogDialog.js | 7 +-
.../catalogs/rightContent/CreateSchemaDialog.js | 109 ++++++++++++------
.../entitiesContent/CatalogDetailsPage.js | 2 +
web-v2/web/src/components/Icons.js | 16 +++
web-v2/web/src/config/catalog.js | 128 +++++++++++++++++++++
web-v2/web/src/config/index.js | 6 +
7 files changed, 244 insertions(+), 40 deletions(-)
diff --git a/web-v2/web/src/app/catalogs/TreeComponent.js
b/web-v2/web/src/app/catalogs/TreeComponent.js
index 1d826a3e18..9efa32a3ee 100644
--- a/web-v2/web/src/app/catalogs/TreeComponent.js
+++ b/web-v2/web/src/app/catalogs/TreeComponent.js
@@ -231,6 +231,22 @@ export const TreeComponent = forwardRef(function
TreeComponent(props, ref) {
)}
</span>
)
+ case 'custom-icons-glue':
+ return (
+ <span
+ role='img'
+ className='anticon'
+ onMouseEnter={e => onMouseEnter(e, catalog)}
+ onMouseLeave={e => onMouseLeave(e, catalog)}
+ onClick={e => handleClickIcon(e, catalog)}
+ >
+ {isHover !== key ? (
+ <Icons.glue className='size-4'></Icons.glue>
+ ) : (
+ <Icons.RotateCw className='h-4 w-3'></Icons.RotateCw>
+ )}
+ </span>
+ )
}
} else {
return (
diff --git a/web-v2/web/src/app/catalogs/rightContent/CreateCatalogDialog.js
b/web-v2/web/src/app/catalogs/rightContent/CreateCatalogDialog.js
index 9bf9289c5d..570cd1d45c 100644
--- a/web-v2/web/src/app/catalogs/rightContent/CreateCatalogDialog.js
+++ b/web-v2/web/src/app/catalogs/rightContent/CreateCatalogDialog.js
@@ -40,7 +40,8 @@ import {
providerBase,
rangerDefaultProps,
regionOptions,
- relationalProviders
+ relationalProviders,
+ sensitiveCatalogPropertyKeys
} from '@/config/catalog'
import { nameRegex } from '@/lib/utils/regex'
import { useResetFormOnCloseModal } from '@/lib/hooks/use-reset'
@@ -364,6 +365,8 @@ export default function CreateCatalogDialog({ ...props }) {
return <Icons.lakehouse className={small ? 'size-6' :
'size-12'}></Icons.lakehouse>
case 'custom-icons-clickhouse':
return <Icons.clickhouse className={small ? 'size-6' :
'size-12'}></Icons.clickhouse>
+ case 'custom-icons-glue':
+ return <Icons.glue className={small ? 'size-6' :
'size-12'}></Icons.glue>
}
} else {
return <Icons.iconify icon={calalogIcon} className={small ? 'size-6' :
'size-12'} />
@@ -546,7 +549,7 @@ export default function CreateCatalogDialog({ ...props }) {
data-refer={`catalog-props-${prop.key}`}
placeholder={prop.description ?
prop.description : ''}
disabled={prop.disabled}
- type={prop.key === 'jdbc-password' ?
'password' : 'text'}
+
type={sensitiveCatalogPropertyKeys.includes(prop.key) ? 'password' : 'text'}
/>
)}
</Form.Item>
diff --git a/web-v2/web/src/app/catalogs/rightContent/CreateSchemaDialog.js
b/web-v2/web/src/app/catalogs/rightContent/CreateSchemaDialog.js
index 2cfa8b3b47..2ddffa92cc 100644
--- a/web-v2/web/src/app/catalogs/rightContent/CreateSchemaDialog.js
+++ b/web-v2/web/src/app/catalogs/rightContent/CreateSchemaDialog.js
@@ -41,6 +41,7 @@ const { TextArea } = Input
const defaultValues = {
name: '',
comment: '',
+ glueLocation: '',
properties: []
}
@@ -68,6 +69,7 @@ export default function CreateSchemaDialog({ ...props }) {
const [topShadow, setTopShadow] = useState(false)
const currentSelectBefore = locationProviders?.map(p =>
filesetLocationProviders[p])
const selectBefore = [...new Set(['file:/', 'hdfs://',
...currentSelectBefore])]
+ const isGlueProvider = provider === 'glue'
const dispatch = useAppDispatch()
const paimonCatalogBackend = provider === 'lakehouse-paimon' && ['hive',
'jdbc'].includes(catalogBackend)
@@ -118,12 +120,17 @@ export default function CreateSchemaDialog({ ...props }) {
setCacheData(schema)
form.setFieldValue('name', schema.name)
form.setFieldValue('comment', schema.comment)
- let index = 0
- Object.entries(schema.properties || {}).forEach(([key, value]) => {
- form.setFieldValue(['properties', index, 'key'], key)
- form.setFieldValue(['properties', index, 'value'], value)
- index++
- })
+ if (isGlueProvider) {
+ form.setFieldValue('glueLocation', schema.properties?.location ||
'')
+ form.setFieldValue('properties', [])
+ } else {
+ let index = 0
+ Object.entries(schema.properties || {}).forEach(([key, value]) => {
+ form.setFieldValue(['properties', index, 'key'], key)
+ form.setFieldValue(['properties', index, 'value'], value)
+ index++
+ })
+ }
handScroll()
setIsLoading(false)
} catch (e) {
@@ -137,14 +144,24 @@ export default function CreateSchemaDialog({ ...props }) {
if (!open) {
loadedRef.current = false
}
- }, [open, editSchema, metalake, catalog])
+ }, [open, editSchema, metalake, catalog, provider])
useEffect(() => {
- if (open && !editSchema && locationProviders?.length) {
- form.setFieldValue(['properties', 0, 'key'], 'location')
- form.setFieldValue(['properties', 0, 'description'], 'Hadoop catalog
storage location')
- form.setFieldValue(['properties', 0, 'selectBefore'], selectBefore)
- form.setFieldValue(['properties', 0, 'prefix'], selectBefore?.[0])
+ if (!open || editSchema) {
+ return
+ }
+
+ if (locationProviders?.length || isGlueProvider) {
+ if (isGlueProvider) {
+ form.setFieldValue('glueLocation', '')
+ form.setFieldValue('properties', [])
+ } else {
+ const locationPrefix = selectBefore
+ form.setFieldValue(['properties', 0, 'key'], 'location')
+ form.setFieldValue(['properties', 0, 'description'], 'Schema storage
location')
+ form.setFieldValue(['properties', 0, 'selectBefore'], locationPrefix)
+ form.setFieldValue(['properties', 0, 'prefix'], locationPrefix?.[0])
+ }
}
}, [open, editSchema, provider, locationProviders])
@@ -158,19 +175,24 @@ export default function CreateSchemaDialog({ ...props }) {
const submitData = {
name: values.name.trim(),
comment: values.comment,
- properties:
- values.properties &&
- values.properties.reduce((acc, item) => {
- if (item.key === 'location' || item.key.startsWith('location-'))
{
- if (item.value) {
- acc[item.key] = item.prefix ? item.prefix + item.value :
item.value
+ properties: isGlueProvider
+ ? (() => {
+ const location = values.glueLocation?.trim()
+
+ return location ? { location } : {}
+ })()
+ : values.properties &&
+ values.properties.reduce((acc, item) => {
+ if (item.key === 'location' ||
item.key.startsWith('location-')) {
+ if (item.value) {
+ acc[item.key] = item.prefix ? item.prefix + item.value :
item.value
+ }
+ } else {
+ acc[item.key] = values[item.key] || item.value
}
- } else {
- acc[item.key] = values[item.key] || item.value
- }
- return acc
- }, {})
+ return acc
+ }, {})
}
if (editSchema) {
// update schema
@@ -254,22 +276,33 @@ export default function CreateSchemaDialog({ ...props }) {
<TextArea disabled={editSchema} />
</Form.Item>
)}
- {(!['jdbc-postgresql', 'lakehouse-paimon', 'kafka',
'jdbc-mysql'].includes(provider) ||
- paimonCatalogBackend) && (
- <Form.Item label='Properties'>
- <Form.List name='properties'>
- {(fields, subOpt) => (
- <RenderPropertiesFormItem
- fields={fields}
- subOpt={subOpt}
- form={form}
- isEdit={!!editSchema}
- isDisable={['jdbc-doris'].includes(provider) &&
!!editSchema}
- selectBefore={selectBefore}
- />
- )}
- </Form.List>
+ {isGlueProvider ? (
+ <Form.Item
+ name='glueLocation'
+ label='Location'
+ data-refer='schema-location-field'
+ extra='Only location is supported for Glue schema
properties.'
+ >
+ <Input placeholder='s3://my-bucket/path' />
</Form.Item>
+ ) : (
+ (!['jdbc-postgresql', 'lakehouse-paimon', 'kafka',
'jdbc-mysql'].includes(provider) ||
+ paimonCatalogBackend) && (
+ <Form.Item label='Properties'>
+ <Form.List name='properties'>
+ {(fields, subOpt) => (
+ <RenderPropertiesFormItem
+ fields={fields}
+ subOpt={subOpt}
+ form={form}
+ isEdit={!!editSchema}
+ isDisable={['jdbc-doris'].includes(provider) &&
!!editSchema}
+ selectBefore={selectBefore}
+ />
+ )}
+ </Form.List>
+ </Form.Item>
+ )
)}
</Form>
</Spin>
diff --git
a/web-v2/web/src/app/catalogs/rightContent/entitiesContent/CatalogDetailsPage.js
b/web-v2/web/src/app/catalogs/rightContent/entitiesContent/CatalogDetailsPage.js
index afbd387513..0f8f2651e6 100644
---
a/web-v2/web/src/app/catalogs/rightContent/entitiesContent/CatalogDetailsPage.js
+++
b/web-v2/web/src/app/catalogs/rightContent/entitiesContent/CatalogDetailsPage.js
@@ -92,6 +92,8 @@ const renderIcon = catalog => {
return <Icons.lakehouse className='size-8'></Icons.lakehouse>
case 'custom-icons-clickhouse':
return <Icons.clickhouse className='size-8'></Icons.clickhouse>
+ case 'custom-icons-glue':
+ return <Icons.glue className='size-8'></Icons.glue>
}
} else {
return <Icons.iconify icon={calalogIcon} className='size-8' />
diff --git a/web-v2/web/src/components/Icons.js
b/web-v2/web/src/components/Icons.js
index 0995f2e1b0..2c666fe8bc 100644
--- a/web-v2/web/src/components/Icons.js
+++ b/web-v2/web/src/components/Icons.js
@@ -153,6 +153,22 @@ const Icons = {
</g>
</svg>
),
+ glue: props => (
+ <svg {...props} xmlns='http://www.w3.org/2000/svg' width='1em'
height='1em' viewBox='0 0 256 256'>
+ <path d='M0 0h256v256H0z' fill='none' />
+ <defs>
+ <linearGradient id='SVGusebDeuy' x1='0%' x2='100%' y1='100%' y2='0%'>
+ <stop offset='0%' stopColor='#4d27a8' />
+ <stop offset='100%' stopColor='#a166ff' />
+ </linearGradient>
+ </defs>
+ <path fill='url(#SVGusebDeuy)' d='M0 0h256v256H0z' />
+ <path
+ fill='#fff'
+ d='M105.6 73.6h6.4v-6.4h-6.4zm-6.4 3.2V64a3.2 3.2 0 0 1
3.2-3.2h12.8a3.2 3.2 0 0 1 3.2 3.2v12.8a3.2 3.2 0 0 1-3.2 3.2h-12.8a3.2 3.2 0 0
1-3.2-3.2m32-22.4h6.4V48h-6.4zm-6.4 3.2V44.8a3.2 3.2 0 0 1 3.2-3.2h12.8a3.2 3.2
0 0 1 3.2 3.2v12.8a3.2 3.2 0 0 1-3.2 3.2H128a3.2 3.2 0 0 1-3.2-3.2m12.8
28.8h6.4V80h-6.4zm-3.2-12.8h12.8a3.2 3.2 0 0 1 3.2 3.2v12.8a3.2 3.2 0 0 1-3.2
3.2h-12.8a3.2 3.2 0 0 1-3.2-3.2V76.8a3.2 3.2 0 0 1 3.2-3.2m-6.4 33.6c-5.635
0-26.067-.24-39.357-3.507l32.464 51.398c.3 [...]
+ />
+ </svg>
+ ),
paimon: props => (
<svg id='_图层_2' data-name='图层 2' xmlns='http://www.w3.org/2000/svg'
viewBox='0 0 218.61 218.61' {...props}>
<g id='_图层_1-2' data-name='图层 1'>
diff --git a/web-v2/web/src/config/catalog.js b/web-v2/web/src/config/catalog.js
index b979578f8d..76d3d6b02c 100644
--- a/web-v2/web/src/config/catalog.js
+++ b/web-v2/web/src/config/catalog.js
@@ -45,6 +45,8 @@ export const checkCatalogIcon = ({ type, provider }) => {
return 'custom-icons-lakehouse'
case 'jdbc-clickhouse':
return 'custom-icons-clickhouse'
+ case 'glue':
+ return 'custom-icons-glue'
default:
return 'bx:book'
}
@@ -59,6 +61,8 @@ export const checkCatalogIcon = ({ type, provider }) => {
}
}
+export const sensitiveCatalogPropertyKeys = ['jdbc-password',
'aws-secret-access-key']
+
export const tableDefaultProps = {
hive: [
{
@@ -134,6 +138,69 @@ export const tableDefaultProps = {
description: 'autoIncrementOffsetDesc'
}
],
+ glue: [
+ {
+ key: 'table-format',
+ defaultValue: '',
+ select: ['ICEBERG', 'HIVE'],
+ description: 'Table format: ICEBERG or HIVE'
+ },
+ {
+ key: 'metadata_location',
+ defaultValue: '',
+ description: 'Iceberg metadata file path'
+ },
+ {
+ key: 'format',
+ defaultValue: '',
+ select: ['TEXTFILE', 'SEQUENCEFILE', 'RCFILE', 'ORC', 'PARQUET', 'AVRO',
'JSON', 'CSV', 'REGEX']
+ },
+ {
+ key: 'input-format',
+ defaultValue: 'org.apache.hadoop.mapred.TextInputFormat',
+ defaultValueOptions: {
+ TEXTFILE: 'org.apache.hadoop.mapred.TextInputFormat',
+ SEQUENCEFILE: 'org.apache.hadoop.mapred.SequenceFileInputFormat',
+ RCFILE: 'org.apache.hadoop.hive.ql.io.RCFileInputFormat',
+ ORC: 'org.apache.hadoop.hive.ql.io.orc.OrcInputFormat',
+ PARQUET:
'org.apache.hadoop.hive.ql.io.parquet.MapredParquetInputFormat',
+ AVRO: 'org.apache.hadoop.hive.ql.io.avro.AvroContainerInputFormat',
+ JSON: 'org.apache.hadoop.mapred.TextInputFormat',
+ CSV: 'org.apache.hadoop.mapred.TextInputFormat',
+ REGEX: 'org.apache.hadoop.mapred.TextInputFormat'
+ }
+ },
+ {
+ key: 'output-format',
+ defaultValue:
'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat',
+ defaultValueOptions: {
+ TEXTFILE: 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat',
+ SEQUENCEFILE: 'org.apache.hadoop.mapred.SequenceFileOutputFormat',
+ RCFILE: 'org.apache.hadoop.hive.ql.io.RCFileOutputFormat',
+ ORC: 'org.apache.hadoop.hive.ql.io.orc.OrcOutputFormat',
+ PARQUET:
'org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat',
+ AVRO: 'org.apache.hadoop.hive.ql.io.avro.AvroContainerOutputFormat',
+ JSON: 'org.apache.hadoop.mapred.TextOutputFormat',
+ CSV: 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat',
+ REGEX: 'org.apache.hadoop.mapred.TextOutputFormat'
+ }
+ },
+ {
+ key: 'serde-lib',
+ defaultValue: 'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe',
+ defaultValueOptions: {
+ TEXTFILE: 'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe',
+ SEQUENCEFILE: 'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe',
+ RCFILE: 'org.apache.hadoop.hive.serde2.columnar.ColumnarSerDe',
+ ORC: 'org.apache.hadoop.hive.ql.io.orc.OrcSerde',
+ PARQUET: 'org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe',
+ AVRO: 'org.apache.hadoop.hive.serde2.avro.AvroSerDe',
+ JSON: 'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe',
+ CSV: 'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe',
+ REGEX: 'org.apache.hadoop.hive.serde2.RegexSerDe'
+ }
+ }
+ ],
'lakehouse-generic': [
{
key: 'location',
@@ -282,6 +349,62 @@ export const rangerDefaultProps = [
]
export const providerBase = {
+ glue: {
+ label: 'AWS Glue',
+ defaultProps: [
+ {
+ label: 'AWS Region',
+ key: 'aws-region',
+ value: '',
+ required: true,
+ description: 'AWS region, e.g. us-east-1'
+ },
+ {
+ label: 'Warehouse',
+ key: 'warehouse',
+ value: '',
+ required: true,
+ description: 'S3 base path, e.g. s3://my-bucket/warehouse'
+ },
+ {
+ label: 'AWS Glue Catalog ID',
+ key: 'aws-glue-catalog-id',
+ value: '',
+ description: "12-digit AWS account ID; defaults to caller's account"
+ },
+ {
+ label: 'AWS Access Key ID',
+ key: 'aws-access-key-id',
+ value: '',
+ description: 'Static credential; omit to use default credential chain'
+ },
+ {
+ label: 'AWS Secret Access Key',
+ key: 'aws-secret-access-key',
+ value: '',
+ description: 'Static credential paired with aws-access-key-id'
+ },
+ {
+ label: 'AWS Glue Endpoint',
+ key: 'aws-glue-endpoint',
+ value: '',
+ description: 'Custom endpoint URL, e.g. http://localhost:4566 for
LocalStack'
+ },
+ {
+ label: 'Default Table Format',
+ key: 'default-table-format',
+ value: 'hive',
+ select: ['hive', 'iceberg'],
+ description: 'Default format for createTable()'
+ },
+ {
+ label: 'Table Format Filter',
+ key: 'table-format-filter',
+ value: 'all',
+ description: 'Comma-separated formats exposed by listTables()'
+ }
+ ]
+ },
hive: {
label: 'Apache Hive',
defaultProps: [
@@ -761,6 +884,11 @@ export const relationalProviders = [
value: 'hive',
description: 'A distributed, fault-tolerant data warehouse system'
},
+ {
+ label: 'AWS Glue',
+ value: 'glue',
+ description: 'A serverless metadata catalog for analytics and data lakes'
+ },
{
label: 'Apache Hudi',
value: 'lakehouse-hudi',
diff --git a/web-v2/web/src/config/index.js b/web-v2/web/src/config/index.js
index 8ed51c8d53..15c2328518 100644
--- a/web-v2/web/src/config/index.js
+++ b/web-v2/web/src/config/index.js
@@ -127,6 +127,7 @@ export const ColumnSpesicalType = ['union', 'list', 'map',
'struct']
export const UnsupportColumnType = {
'lakehouse-iceberg': ['varchar', 'char', 'byte', 'short', 'interval_day',
'interval_year', 'union'],
+ glue: ['fixed', 'time', 'timestamp_tz', 'uuid'],
hive: ['fixed', 'time', 'timestamp_tz', 'uuid'],
'jdbc-mysql': [
'boolean',
@@ -171,6 +172,11 @@ export const UnsupportColumnType = {
}
const tableLevelPropInfoMap = {
+ glue: {
+ reserved: [],
+ immutable: ['format', 'input-format', 'output-format', 'serde-lib'],
+ allowAdd: true
+ },
hive: {
reserved: ['comment', 'EXTERNAL', 'numFiles', 'totalSize',
'transient_lastDdlTime'],
immutable: [