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