fix(AllEntities): Display action buttons according to the user permissions (#33553)

This commit is contained in:
Vitor Avila
2025-05-22 16:01:26 -03:00
committed by GitHub
parent 5b2f1bbf9e
commit 546945e7a6
4 changed files with 95 additions and 22 deletions

View File

@@ -91,12 +91,13 @@ describe('AllEntitiesTable', () => {
jest.restoreAllMocks();
});
it('renders when empty', () => {
it('renders when empty with button to tag if user has perm', () => {
render(
<AllEntitiesTable
search=""
setShowTagModal={mockSetShowTagModal}
objects={mockObjects}
canEditTag
/>,
{ useRouter: true },
);
@@ -108,25 +109,70 @@ describe('AllEntitiesTable', () => {
expect(screen.getByText('Add tag to entities')).toBeInTheDocument();
});
it('renders when empty without button to tag if user does not have perm', () => {
render(
<AllEntitiesTable
search=""
setShowTagModal={mockSetShowTagModal}
objects={mockObjects}
canEditTag={false}
/>,
{ useRouter: true },
);
expect(
screen.getByText('No entities have this tag currently assigned'),
).toBeInTheDocument();
expect(screen.queryByText('Add tag to entities')).not.toBeInTheDocument();
});
it('renders the correct tags for each object type, excluding the current tag', () => {
render(
<AllEntitiesTable
search=""
setShowTagModal={mockSetShowTagModal}
objects={mockObjectsWithTags}
canEditTag
/>,
{ useRouter: true },
);
expect(screen.getByText('Dashboards')).toBeInTheDocument();
expect(screen.getByText('Sales Dashboard')).toBeInTheDocument();
expect(screen.getByText('Sales')).toBeInTheDocument();
expect(screen.getByText('Charts')).toBeInTheDocument();
expect(screen.getByText('Monthly Revenue')).toBeInTheDocument();
expect(screen.getByText('Revenue')).toBeInTheDocument();
expect(screen.getByText('Queries')).toBeInTheDocument();
expect(screen.getByText('User Engagement')).toBeInTheDocument();
expect(screen.getByText('Engagement')).toBeInTheDocument();
expect(screen.queryByText('Current Tag')).not.toBeInTheDocument();
});
it('Only list asset types that have entities', () => {
const mockObjects = {
dashboard: [],
chart: [mockObjectsWithTags.chart[0]],
query: [],
};
render(
<AllEntitiesTable
search=""
setShowTagModal={mockSetShowTagModal}
objects={mockObjects}
canEditTag
/>,
{ useRouter: true },
);
expect(screen.queryByText('Dashboards')).not.toBeInTheDocument();
expect(screen.getByText('Charts')).toBeInTheDocument();
expect(screen.getByText('Monthly Revenue')).toBeInTheDocument();
expect(screen.queryByText('Queries')).not.toBeInTheDocument();
});
});

View File

@@ -53,20 +53,22 @@ interface AllEntitiesTableProps {
search?: string;
setShowTagModal: (show: boolean) => void;
objects: TaggedObjects;
canEditTag: boolean;
}
export default function AllEntitiesTable({
search = '',
setShowTagModal,
objects,
canEditTag,
}: AllEntitiesTableProps) {
type objectType = 'dashboard' | 'chart' | 'query';
const [tagId] = useQueryParam('id', NumberParam);
const showListViewObjs =
objects.dashboard.length > 0 ||
objects.chart.length > 0 ||
objects.query.length > 0;
const showDashboardList = objects.dashboard.length > 0;
const showChartList = objects.chart.length > 0;
const showQueryList = objects.query.length > 0;
const showListViewObjs = showDashboardList || showChartList || showQueryList;
const renderTable = (type: objectType) => {
const data = objects[type].map((o: TaggedObject) => ({
@@ -134,20 +136,34 @@ export default function AllEntitiesTable({
<AllEntitiesTableContainer>
{showListViewObjs ? (
<>
<div className="entity-title">{t('Dashboards')}</div>
{renderTable('dashboard')}
<div className="entity-title">{t('Charts')}</div>
{renderTable('chart')}
<div className="entity-title">{t('Queries')}</div>
{renderTable('query')}
{showDashboardList && (
<>
<div className="entity-title">{t('Dashboards')}</div>
{renderTable('dashboard')}
</>
)}
{showChartList && (
<>
<div className="entity-title">{t('Charts')}</div>
{renderTable('chart')}
</>
)}
{showQueryList && (
<>
<div className="entity-title">{t('Queries')}</div>
{renderTable('query')}
</>
)}
</>
) : (
<EmptyState
image="dashboard.svg"
size="large"
title={t('No entities have this tag currently assigned')}
buttonAction={() => setShowTagModal(true)}
buttonText={t('Add tag to entities')}
{...(canEditTag && {
buttonAction: () => setShowTagModal(true),
buttonText: t('Add tag to entities'),
})}
/>
)}
</AllEntitiesTableContainer>

View File

@@ -35,6 +35,9 @@ import { fetchObjectsByTagIds, fetchSingleTag } from 'src/features/tags/tags';
import Loading from 'src/components/Loading';
import getOwnerName from 'src/utils/getOwnerName';
import { TaggedObject, TaggedObjects } from 'src/types/TaggedObject';
import { findPermission } from 'src/utils/findPermission';
import { useSelector } from 'react-redux';
import { RootState } from 'src/dashboard/types';
const additionalItemsStyles = (theme: SupersetTheme) => css`
display: flex;
@@ -100,6 +103,10 @@ function AllEntities() {
query: [],
});
const canEditTag = useSelector((state: RootState) =>
findPermission('can_write', 'Tag', state.user?.roles),
);
const editableTitleProps = {
title: tag?.name || '',
placeholder: 'testing',
@@ -211,14 +218,16 @@ function AllEntities() {
}
rightPanelAdditionalItems={
<>
<Button
data-test="bulk-select-action"
buttonStyle="secondary"
onClick={() => setShowTagModal(true)}
showMarginRight={false}
>
{t('Edit Tag')}{' '}
</Button>
{canEditTag && (
<Button
data-test="bulk-select-action"
buttonStyle="secondary"
onClick={() => setShowTagModal(true)}
showMarginRight={false}
>
{t('Edit tag')}{' '}
</Button>
)}
</>
}
menuDropdownProps={{
@@ -232,6 +241,7 @@ function AllEntities() {
search={tag?.name || ''}
setShowTagModal={setShowTagModal}
objects={objects}
canEditTag={canEditTag}
/>
</div>
</AllEntitiesContainer>

View File

@@ -22,6 +22,7 @@ from flask_appbuilder.models.sqla.interface import SQLAInterface
from flask_appbuilder.security.decorators import has_access
from superset import is_feature_enabled
from superset.constants import RouteMethod
from superset.superset_typing import FlaskResponse
from superset.tags.models import Tag
from superset.views.base import SupersetModelView
@@ -33,7 +34,7 @@ class TaggedObjectsModelView(SupersetModelView):
route_base = "/superset/all_entities"
datamodel = SQLAInterface(Tag)
class_permission_name = "Tags"
include_route_methods = {"list"}
include_route_methods = {RouteMethod.LIST}
@has_access
@expose("/")