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 a77d8610ea [#11150][#11151] support create role for function privilege 
and add Associated Roles tab page in function list view (#11161)
a77d8610ea is described below

commit a77d8610eaf51c2ba2fd9635e31139f9facba95a
Author: Qian Xia <[email protected]>
AuthorDate: Wed May 20 17:44:54 2026 +0800

    [#11150][#11151] support create role for function privilege and add 
Associated Roles tab page in function list view (#11161)
    
    ### What changes were proposed in this pull request?
    
    1. support create role for function privilege
    2. add `Associated Roles` tab page in function list view
    <img width="2296" height="1314" alt="image"
    
src="https://github.com/user-attachments/assets/49a67b97-47c1-4bec-8f56-1a87e71a4ebc";
    />
    <img width="2092" height="1436" alt="image"
    
src="https://github.com/user-attachments/assets/e13133ef-534e-40e7-8456-3190f626a4cf";
    />
    <img width="2270" height="1654" alt="image"
    
src="https://github.com/user-attachments/assets/cb0a0bc8-84b9-4f9b-a103-962ce15ba537";
    />
    
    
    ### Why are the changes needed?
    N/A
    
    Fix: #11150
    Fix: #11151
    
    ### Does this PR introduce _any_ user-facing change?
    N/A
    
    ### How was this patch tested?
    manually
    
    ---------
    
    Co-authored-by: Copilot Autofix powered by AI 
<[email protected]>
---
 .../entitiesContent/FunctionDetailsPage.js         | 39 +++++++++++++++++-----
 .../entitiesContent/SchemaDetailsPage.js           |  2 +-
 .../src/components/SecurableObjectFormFields.js    | 27 +++++++++++----
 web-v2/web/src/config/security.js                  | 12 +++++++
 4 files changed, 64 insertions(+), 16 deletions(-)

diff --git 
a/web-v2/web/src/app/catalogs/rightContent/entitiesContent/FunctionDetailsPage.js
 
b/web-v2/web/src/app/catalogs/rightContent/entitiesContent/FunctionDetailsPage.js
index 221d0971ae..1aa282714a 100644
--- 
a/web-v2/web/src/app/catalogs/rightContent/entitiesContent/FunctionDetailsPage.js
+++ 
b/web-v2/web/src/app/catalogs/rightContent/entitiesContent/FunctionDetailsPage.js
@@ -23,6 +23,7 @@ import { useEffect, useState } from 'react'
 import { Collapse, Descriptions, Space, Spin, Tag, Typography, Flex, Tooltip, 
Divider, Tabs } from 'antd'
 import { useSearchParams } from 'next/navigation'
 import { formatToDateTime, isValidDate } from '@/lib/utils/date'
+import AssociatedTable from '@/components/AssociatedTable'
 import Icons from '@/components/Icons'
 import { useAppSelector } from '@/lib/hooks/useStore'
 
@@ -180,13 +181,21 @@ const FunctionImplTabs = ({ impls }) => {
 
 export default function FunctionDetailsPage() {
   const searchParams = useSearchParams()
+  const currentMetalake = searchParams.get('metalake')
+  const catalog = searchParams.get('catalog')
+  const schema = searchParams.get('schema')
   const functionName = searchParams.get('function')
+  const auth = useAppSelector(state => state.auth)
+  const { anthEnable } = auth
   const store = useAppSelector(state => state.metalakes)
   const functionData = store.activatedDetails
   const [activeDefinitionKeys, setActiveDefinitionKeys] = useState([])
+  const [activeTab, setActiveTab] = useState('Definitions')
+  const hasFunctionRoleContext = Boolean(currentMetalake && catalog && schema 
&& functionName)
 
   useEffect(() => {
     setActiveDefinitionKeys([])
+    setActiveTab('Definitions')
   }, [functionName])
 
   const definitions = functionData?.definitions || []
@@ -268,16 +277,28 @@ export default function FunctionDetailsPage() {
           <div className='max-h-[60vh] overflow-auto'>
             <Tabs
               data-refer='details-tabs'
-              defaultActiveKey={'Definitions'}
-              items={[{ label: 'Definitions', key: 'Definitions' }]}
+              activeKey={activeTab}
+              onChange={setActiveTab}
+              items={[
+                { label: 'Definitions', key: 'Definitions' },
+                ...(anthEnable ? [{ label: 'Associated Roles', key: 
'Associated Roles' }] : [])
+              ]}
             />
-            {definitions.length === 0 ? (
-              <span className='text-slate-400'>No definitions</span>
-            ) : (
-              <Collapse
-                items={definitionItems}
-                activeKey={activeDefinitionKeys}
-                onChange={keys => setActiveDefinitionKeys(Array.isArray(keys) 
? keys : [keys])}
+            {activeTab === 'Definitions' &&
+              (definitions.length === 0 ? (
+                <span className='text-slate-400'>No definitions</span>
+              ) : (
+                <Collapse
+                  items={definitionItems}
+                  activeKey={activeDefinitionKeys}
+                  onChange={keys => 
setActiveDefinitionKeys(Array.isArray(keys) ? keys : [keys])}
+                />
+              ))}
+            {anthEnable && activeTab === 'Associated Roles' && 
hasFunctionRoleContext && (
+              <AssociatedTable
+                metalake={currentMetalake}
+                metadataObjectType={'function'}
+                metadataObjectFullName={`${catalog}.${schema}.${functionName}`}
               />
             )}
           </div>
diff --git 
a/web-v2/web/src/app/catalogs/rightContent/entitiesContent/SchemaDetailsPage.js 
b/web-v2/web/src/app/catalogs/rightContent/entitiesContent/SchemaDetailsPage.js
index 8d8168e78f..32b2a48471 100644
--- 
a/web-v2/web/src/app/catalogs/rightContent/entitiesContent/SchemaDetailsPage.js
+++ 
b/web-v2/web/src/app/catalogs/rightContent/entitiesContent/SchemaDetailsPage.js
@@ -540,7 +540,7 @@ export default function SchemaDetailsPage() {
         <AssociatedTable
           metalake={currentMetalake}
           metadataObjectType={'schema'}
-          metadataObjectFullName={`${catalog}.${store.activatedDetails.name}`}
+          metadataObjectFullName={`${catalog}.${schema}`}
         />
       ) : tabKey === 'Functions' ? (
         <Functions metalake={currentMetalake} catalog={catalog} 
schema={schema} />
diff --git a/web-v2/web/src/components/SecurableObjectFormFields.js 
b/web-v2/web/src/components/SecurableObjectFormFields.js
index d754beaca5..3060b030c5 100644
--- a/web-v2/web/src/components/SecurableObjectFormFields.js
+++ b/web-v2/web/src/components/SecurableObjectFormFields.js
@@ -27,7 +27,8 @@ import {
   fetchTables,
   fetchFilesets,
   fetchTopics,
-  fetchModels
+  fetchModels,
+  fetchFunctions
 } from '@/lib/store/metalakes'
 import { fetchTags } from '@/lib/store/tags'
 import { fetchPolicies } from '@/lib/store/policies'
@@ -218,6 +219,9 @@ export default function SecurableObjectFormFields({ 
fieldName, fieldKey, metalak
           const modelGroup = privilegeOptions.find(g => g.label === 'Model 
privileges')
           modelGroup && groups.push(modelGroup)
         }
+
+        const functionGroup = privilegeOptions.find(g => g.label === 'Function 
privileges')
+        functionGroup && groups.push(functionGroup)
       }
 
       return groups
@@ -245,6 +249,9 @@ export default function SecurableObjectFormFields({ 
fieldName, fieldKey, metalak
           const modelGroup = privilegeOptions.find(g => g.label === 'Model 
privileges')
           modelGroup && groups.push(modelGroup)
         }
+
+        const functionGroup = privilegeOptions.find(g => g.label === 'Function 
privileges')
+        functionGroup && groups.push(functionGroup)
       }
 
       return groups
@@ -284,7 +291,8 @@ export default function SecurableObjectFormFields({ 
fieldName, fieldKey, metalak
       table: 'Table privileges',
       topic: 'Topic privileges',
       fileset: 'Fileset privileges',
-      model: 'Model privileges'
+      model: 'Model privileges',
+      function: 'Function privileges'
     }
 
     const lbl = keyLabelMap[type]
@@ -300,6 +308,8 @@ export default function SecurableObjectFormFields({ 
fieldName, fieldKey, metalak
           options = options.filter(o => !(o.label === 'Create Fileset' || 
o.value === 'create_fileset'))
         } else if (type === 'model') {
           options = options.filter(o => !(o.label === 'Register Model' || 
o.value === 'register_model'))
+        } else if (type === 'function') {
+          options = options.filter(o => !(o.label === 'Register Function' || 
o.value === 'register_function'))
         }
 
         return [{ label: g.label, options }]
@@ -317,6 +327,7 @@ export default function SecurableObjectFormFields({ 
fieldName, fieldKey, metalak
     if (currentType === 'fileset') return catalogType === 'fileset'
     if (currentType === 'topic') return catalogType === 'messaging'
     if (currentType === 'model') return catalogType === 'model'
+    if (currentType === 'function') return ['relational', 'fileset', 
'messaging', 'model'].includes(catalogType)
 
     return true // for schema and other types, show all
   })
@@ -327,7 +338,7 @@ export default function SecurableObjectFormFields({ 
fieldName, fieldKey, metalak
     return parts[parts.length - nFromEnd]
   }
 
-  const isResourceThree = ['table', 'topic', 'fileset', 
'model'].includes(currentType)
+  const isResourceThree = ['table', 'topic', 'fileset', 'model', 
'function'].includes(currentType)
   const catalogSelected = getPart(isResourceThree ? 3 : currentType === 
'schema' ? 2 : 1)
   const schemaSelected = getPart(isResourceThree ? 2 : currentType === 
'schema' ? 1 : 0)
   const resourceSelected = getPart(1)
@@ -346,7 +357,7 @@ export default function SecurableObjectFormFields({ 
fieldName, fieldKey, metalak
     setLocalSchemaVal(schemaSelected || undefined)
     setLocalResourceVal(resourceSelected || undefined)
 
-    const shouldLoadSchemas = ['schema', 'table', 'topic', 'fileset', 
'model'].includes(currentType)
+    const shouldLoadSchemas = ['schema', 'table', 'topic', 'fileset', 'model', 
'function'].includes(currentType)
     if (shouldLoadSchemas && catalogSelected) {
       loadSchemasForCatalog(catalogSelected)
     } else {
@@ -454,6 +465,10 @@ export default function SecurableObjectFormFields({ 
fieldName, fieldKey, metalak
         const res = await dispatch(fetchModels({ metalake, catalog, schema }))
         payload = res.payload
         setResourcesOptions((payload?.models || []).map(m => ({ label: m.name, 
value: m.name })))
+      } else if (t === 'function') {
+        const res = await dispatch(fetchFunctions({ metalake, catalog, schema 
}))
+        payload = res.payload
+        setResourcesOptions((payload?.functions || []).map(f => ({ label: 
f.name, value: f.name })))
       }
     } catch (e) {
       setResourcesOptions([])
@@ -661,8 +676,8 @@ export default function SecurableObjectFormFields({ 
fieldName, fieldKey, metalak
               )
             }
 
-            // For Table/Topic/Fileset/Model types: three linked selects 
(catalog -> schema -> resource)
-            if (['table', 'topic', 'fileset', 'model'].includes(currentType)) {
+            // For Table/Topic/Fileset/Model/Function types: three linked 
selects (catalog -> schema -> resource)
+            if (['table', 'topic', 'fileset', 'model', 
'function'].includes(currentType)) {
               return (
                 <div className='flex gap-2'>
                   <Select
diff --git a/web-v2/web/src/config/security.js 
b/web-v2/web/src/config/security.js
index 6a0cfd73e1..973c45ac95 100644
--- a/web-v2/web/src/config/security.js
+++ b/web-v2/web/src/config/security.js
@@ -46,6 +46,10 @@ export const privilegeTypes = [
     label: 'Model',
     value: 'model'
   },
+  {
+    label: 'Function',
+    value: 'function'
+  },
   {
     label: 'Tag',
     value: 'tag'
@@ -123,6 +127,14 @@ export const privilegeOptions = [
       { label: 'Use Model', value: 'use_model' }
     ]
   },
+  {
+    label: 'Function privileges',
+    options: [
+      { label: 'Register Function', value: 'register_function' },
+      { label: 'Execute Function', value: 'execute_function' },
+      { label: 'Modify Function', value: 'modify_function' }
+    ]
+  },
   {
     label: 'Tag privileges',
     options: [

Reply via email to