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: [

Reply via email to