{t('Table Permissions')}
@@ -454,20 +482,6 @@ function DataAccessRuleModal(props: DataAccessRuleModalProps) {
)}
-
- {t('Example')}
-
- {RULE_EXAMPLE}
-
-
>
),
},
diff --git a/superset-frontend/src/features/dataAccessRules/PermissionsTree/index.tsx b/superset-frontend/src/features/dataAccessRules/PermissionsTree/index.tsx
index 4853977da96..e3bdf4e2a95 100644
--- a/superset-frontend/src/features/dataAccessRules/PermissionsTree/index.tsx
+++ b/superset-frontend/src/features/dataAccessRules/PermissionsTree/index.tsx
@@ -164,6 +164,16 @@ const StyledContainer = styled.div`
max-width: 250px;
font-size: 12px;
}
+
+ .count-allowed {
+ color: ${theme.colorSuccess};
+ font-weight: ${theme.fontWeightSemiBold};
+ }
+
+ .count-denied {
+ color: ${theme.colorError};
+ font-weight: ${theme.fontWeightSemiBold};
+ }
`}
`;
@@ -684,13 +694,9 @@ function PermissionsTree({
{hasCustomRules && (
(
-
- {counts.allowed}
-
+ {counts.allowed}
{' / '}
-
- {counts.denied}
-
+ {counts.denied}
)
)}
diff --git a/superset-frontend/src/features/dataAccessRules/types.ts b/superset-frontend/src/features/dataAccessRules/types.ts
index 1c4e804442d..9b4cfa194d8 100644
--- a/superset-frontend/src/features/dataAccessRules/types.ts
+++ b/superset-frontend/src/features/dataAccessRules/types.ts
@@ -24,6 +24,8 @@ export type RoleObject = {
export type DataAccessRuleObject = {
id?: number;
+ name?: string;
+ description?: string;
role_id: number;
role?: RoleObject;
rule: string;
diff --git a/superset-frontend/src/pages/DataAccessRulesList/index.tsx b/superset-frontend/src/pages/DataAccessRulesList/index.tsx
index e2f29e0a50d..721a1443e9a 100644
--- a/superset-frontend/src/pages/DataAccessRulesList/index.tsx
+++ b/superset-frontend/src/pages/DataAccessRulesList/index.tsx
@@ -126,6 +126,44 @@ function DataAccessRulesList(props: DataAccessRulesListProps) {
const columns = useMemo(
() => [
+ {
+ Cell: ({
+ row: {
+ original: { name },
+ },
+ }: {
+ row: { original: DataAccessRuleObject };
+ }) => name || '-',
+ accessor: 'name',
+ Header: t('Name'),
+ size: 'lg',
+ id: 'name',
+ },
+ {
+ Cell: ({
+ row: {
+ original: { description },
+ },
+ }: {
+ row: { original: DataAccessRuleObject };
+ }) => {
+ if (!description) return '-';
+ const truncated =
+ description.length > 100
+ ? `${description.substring(0, 100)}...`
+ : description;
+ return (
+
+ {truncated}
+
+ );
+ },
+ accessor: 'description',
+ Header: t('Description'),
+ size: 'xl',
+ id: 'description',
+ disableSortBy: true,
+ },
{
Cell: ({
row: {
@@ -140,32 +178,6 @@ function DataAccessRulesList(props: DataAccessRulesListProps) {
id: 'role',
disableSortBy: true,
},
- {
- Cell: ({
- row: {
- original: { rule },
- },
- }: {
- row: { original: DataAccessRuleObject };
- }) => {
- const displayRule =
- typeof rule === 'string' ? rule : JSON.stringify(rule);
- const truncated =
- displayRule.length > 100
- ? `${displayRule.substring(0, 100)}...`
- : displayRule;
- return (
-
- {truncated}
-
- );
- },
- accessor: 'rule',
- Header: t('Rule (JSON)'),
- size: 'xxl',
- id: 'rule',
- disableSortBy: true,
- },
{
Cell: ({
row: {
diff --git a/superset/data_access_rules/api.py b/superset/data_access_rules/api.py
index fcd54474fbf..639b900377d 100644
--- a/superset/data_access_rules/api.py
+++ b/superset/data_access_rules/api.py
@@ -63,6 +63,8 @@ class DataAccessRulesRestApi(BaseSupersetModelRestApi):
list_columns = [
"id",
+ "name",
+ "description",
"role_id",
"role.id",
"role.name",
@@ -74,19 +76,26 @@ class DataAccessRulesRestApi(BaseSupersetModelRestApi):
]
order_columns = [
"id",
+ "name",
"role_id",
"changed_on_delta_humanized",
]
add_columns = [
+ "name",
+ "description",
"role_id",
"rule",
]
edit_columns = [
+ "name",
+ "description",
"role_id",
"rule",
]
show_columns = [
"id",
+ "name",
+ "description",
"role_id",
"role.name",
"role.id",
diff --git a/superset/data_access_rules/models.py b/superset/data_access_rules/models.py
index 1aa166f7c2a..f87e03da96d 100644
--- a/superset/data_access_rules/models.py
+++ b/superset/data_access_rules/models.py
@@ -68,7 +68,7 @@ from __future__ import annotations
from typing import Any
from flask_appbuilder import Model
-from sqlalchemy import Column, ForeignKey, Integer, Text
+from sqlalchemy import Column, ForeignKey, Integer, String, Text
from sqlalchemy.orm import relationship
from superset import security_manager
@@ -87,6 +87,8 @@ class DataAccessRule(Model, AuditMixinNullable):
__tablename__ = "data_access_rules"
id = Column(Integer, primary_key=True)
+ name = Column(String(250), nullable=True)
+ description = Column(Text, nullable=True)
role_id = Column(Integer, ForeignKey("ab_role.id"), nullable=False)
rule = Column(Text, nullable=False)
@@ -97,7 +99,7 @@ class DataAccessRule(Model, AuditMixinNullable):
)
def __repr__(self) -> str:
- return f"
"
+ return f""
@property
def rule_dict(self) -> dict[str, Any]:
diff --git a/superset/data_access_rules/schemas.py b/superset/data_access_rules/schemas.py
index a1f7f1137a5..cc344d5019b 100644
--- a/superset/data_access_rules/schemas.py
+++ b/superset/data_access_rules/schemas.py
@@ -66,6 +66,8 @@ class DataAccessRuleListSchema(Schema):
"""Schema for listing data access rules."""
id = fields.Integer(metadata={"description": "Unique ID of the rule"})
+ name = fields.String(metadata={"description": "Name of the rule"})
+ description = fields.String(metadata={"description": "Description of the rule"})
role_id = fields.Integer(metadata={"description": "ID of the associated role"})
role = fields.Nested(RoleSchema)
rule = fields.String(metadata={"description": rule_description})
@@ -80,6 +82,8 @@ class DataAccessRuleShowSchema(Schema):
"""Schema for showing a single data access rule."""
id = fields.Integer(metadata={"description": "Unique ID of the rule"})
+ name = fields.String(metadata={"description": "Name of the rule"})
+ description = fields.String(metadata={"description": "Description of the rule"})
role_id = fields.Integer(metadata={"description": "ID of the associated role"})
role = fields.Nested(RoleSchema)
rule = fields.String(metadata={"description": rule_description})
@@ -92,6 +96,16 @@ class DataAccessRuleShowSchema(Schema):
class DataAccessRulePostSchema(Schema):
"""Schema for creating a data access rule."""
+ name = fields.String(
+ metadata={"description": "Name for this rule (optional)"},
+ required=False,
+ allow_none=True,
+ )
+ description = fields.String(
+ metadata={"description": "Description of the rule (optional)"},
+ required=False,
+ allow_none=True,
+ )
role_id = fields.Integer(
metadata={"description": "ID of the role this rule applies to"},
required=True,
@@ -159,6 +173,16 @@ class DataAccessRulePostSchema(Schema):
class DataAccessRulePutSchema(Schema):
"""Schema for updating a data access rule."""
+ name = fields.String(
+ metadata={"description": "Name for this rule (optional)"},
+ required=False,
+ allow_none=True,
+ )
+ description = fields.String(
+ metadata={"description": "Description of the rule (optional)"},
+ required=False,
+ allow_none=True,
+ )
role_id = fields.Integer(
metadata={"description": "ID of the role this rule applies to"},
required=False,
diff --git a/superset/migrations/versions/2025-12-17_12-00_b463d8709290_add_name_description_to_data_access_rules.py b/superset/migrations/versions/2025-12-17_12-00_b463d8709290_add_name_description_to_data_access_rules.py
new file mode 100644
index 00000000000..1068e9bbcb5
--- /dev/null
+++ b/superset/migrations/versions/2025-12-17_12-00_b463d8709290_add_name_description_to_data_access_rules.py
@@ -0,0 +1,43 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""add name and description to data_access_rules
+
+Revision ID: b463d8709290
+Revises: a352d7609189
+Create Date: 2025-12-17 12:00:00.000000
+
+"""
+
+import sqlalchemy as sa
+
+from superset.migrations.shared.utils import add_columns, drop_columns
+
+# revision identifiers, used by Alembic.
+revision = "b463d8709290"
+down_revision = "a352d7609189"
+
+
+def upgrade():
+ add_columns(
+ "data_access_rules",
+ sa.Column("name", sa.String(250), nullable=True),
+ sa.Column("description", sa.Text(), nullable=True),
+ )
+
+
+def downgrade():
+ drop_columns("data_access_rules", "name", "description")