mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-31 12:39:01 +00:00
* Fix #5091 Make "Data Quality" column on Table schema page optional to hide * Fix unit test * Improve checks
This commit is contained in:
parent
84aede2eaf
commit
be553eb017
@ -176,13 +176,15 @@ const BotListV1 = ({
|
|||||||
<ErrorPlaceHolder
|
<ErrorPlaceHolder
|
||||||
buttons={
|
buttons={
|
||||||
<div className="tw-text-lg tw-text-center">
|
<div className="tw-text-lg tw-text-center">
|
||||||
<Button
|
<Tooltip
|
||||||
ghost
|
title={createPermission ? 'Add Bot' : NO_PERMISSION_FOR_ACTION}>
|
||||||
title="Add Team"
|
<Button
|
||||||
type="primary"
|
disabled={!createPermission}
|
||||||
onClick={handleAddBotClick}>
|
type="primary"
|
||||||
Add Bot
|
onClick={handleAddBotClick}>
|
||||||
</Button>
|
Add Bot
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
doc={BOTS_DOCS}
|
doc={BOTS_DOCS}
|
||||||
|
@ -48,11 +48,7 @@ import { getEntityFeedLink } from '../../utils/EntityUtils';
|
|||||||
import { getDefaultValue } from '../../utils/FeedElementUtils';
|
import { getDefaultValue } from '../../utils/FeedElementUtils';
|
||||||
import { getEntityFieldThreadCounts } from '../../utils/FeedUtils';
|
import { getEntityFieldThreadCounts } from '../../utils/FeedUtils';
|
||||||
import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils';
|
import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils';
|
||||||
import {
|
import { getTagsWithoutTier, getUsagePercentile } from '../../utils/TableUtils';
|
||||||
getTableTestsValue,
|
|
||||||
getTagsWithoutTier,
|
|
||||||
getUsagePercentile,
|
|
||||||
} from '../../utils/TableUtils';
|
|
||||||
import { showErrorToast } from '../../utils/ToastUtils';
|
import { showErrorToast } from '../../utils/ToastUtils';
|
||||||
import ActivityFeedList from '../ActivityFeed/ActivityFeedList/ActivityFeedList';
|
import ActivityFeedList from '../ActivityFeed/ActivityFeedList/ActivityFeedList';
|
||||||
import ActivityThreadPanel from '../ActivityFeed/ActivityThreadPanel/ActivityThreadPanel';
|
import ActivityThreadPanel from '../ActivityFeed/ActivityThreadPanel/ActivityThreadPanel';
|
||||||
@ -125,7 +121,6 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
|
|||||||
postFeedHandler,
|
postFeedHandler,
|
||||||
feedCount,
|
feedCount,
|
||||||
entityFieldThreadCount,
|
entityFieldThreadCount,
|
||||||
tableTestCase,
|
|
||||||
createThread,
|
createThread,
|
||||||
qualityTestFormHandler,
|
qualityTestFormHandler,
|
||||||
deletePostHandler,
|
deletePostHandler,
|
||||||
@ -408,7 +403,6 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
|
|||||||
key: 'Rows',
|
key: 'Rows',
|
||||||
value: prepareTableRowInfo(),
|
value: prepareTableRowInfo(),
|
||||||
},
|
},
|
||||||
{ key: 'Tests', value: getTableTestsValue(tableTestCase) },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const onDescriptionEdit = (): void => {
|
const onDescriptionEdit = (): void => {
|
||||||
@ -745,9 +739,7 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
|
|||||||
)}
|
)}
|
||||||
{activeTab === 5 && (
|
{activeTab === 5 && (
|
||||||
<TableProfilerV1
|
<TableProfilerV1
|
||||||
hasEditAccess={
|
permissions={tablePermissions}
|
||||||
tablePermissions.EditAll || tablePermissions.EditDataProfile
|
|
||||||
}
|
|
||||||
table={tableDetails}
|
table={tableDetails}
|
||||||
onAddTestClick={qualityTestFormHandler}
|
onAddTestClick={qualityTestFormHandler}
|
||||||
/>
|
/>
|
||||||
|
@ -833,20 +833,6 @@ const EntityTable = ({
|
|||||||
return renderCell(TABLE_HEADERS_V1.dataTypeDisplay, record, index);
|
return renderCell(TABLE_HEADERS_V1.dataTypeDisplay, record, index);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: 'Data Quality',
|
|
||||||
dataIndex: 'columnTests',
|
|
||||||
key: 'columnTests',
|
|
||||||
accessor: 'columnTests',
|
|
||||||
width: 200,
|
|
||||||
render: (
|
|
||||||
_: Array<unknown>,
|
|
||||||
record: ModifiedTableColumn,
|
|
||||||
index: number
|
|
||||||
) => {
|
|
||||||
return renderCell(TABLE_HEADERS_V1.columnTests, record, index);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: 'Description',
|
title: 'Description',
|
||||||
dataIndex: 'description',
|
dataIndex: 'description',
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import { Button, Col, Form, Radio, Row, Select, Space } from 'antd';
|
import { Button, Col, Form, Radio, Row, Select, Space, Tooltip } from 'antd';
|
||||||
import { RadioChangeEvent } from 'antd/lib/radio';
|
import { RadioChangeEvent } from 'antd/lib/radio';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import { EntityTags, ExtraInfo } from 'Models';
|
import { EntityTags, ExtraInfo } from 'Models';
|
||||||
@ -25,6 +25,7 @@ import {
|
|||||||
getTableTabPath,
|
getTableTabPath,
|
||||||
getTeamAndUserDetailsPath,
|
getTeamAndUserDetailsPath,
|
||||||
} from '../../constants/constants';
|
} from '../../constants/constants';
|
||||||
|
import { NO_PERMISSION_FOR_ACTION } from '../../constants/HelperTextUtil';
|
||||||
import { PROFILER_FILTER_RANGE } from '../../constants/profiler.constant';
|
import { PROFILER_FILTER_RANGE } from '../../constants/profiler.constant';
|
||||||
import { EntityType, FqnPart } from '../../enums/entity.enum';
|
import { EntityType, FqnPart } from '../../enums/entity.enum';
|
||||||
import { ServiceCategory } from '../../enums/service.enum';
|
import { ServiceCategory } from '../../enums/service.enum';
|
||||||
@ -480,9 +481,19 @@ const ProfilerDashboard: React.FC<ProfilerDashboardProps> = ({
|
|||||||
onChange={handleTimeRangeChange}
|
onChange={handleTimeRangeChange}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Button type="primary" onClick={handleAddTestClick}>
|
<Tooltip
|
||||||
Add Test
|
title={
|
||||||
</Button>
|
permission.EditAll || permission.EditTests
|
||||||
|
? 'Add Test'
|
||||||
|
: NO_PERMISSION_FOR_ACTION
|
||||||
|
}>
|
||||||
|
<Button
|
||||||
|
disabled={!(permission.EditAll || permission.EditTests)}
|
||||||
|
type="primary"
|
||||||
|
onClick={handleAddTestClick}>
|
||||||
|
Add Test
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
</Space>
|
</Space>
|
||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
import { Column, Table } from '../../generated/entity/data/table';
|
import { Column, Table } from '../../generated/entity/data/table';
|
||||||
import { TestCase } from '../../generated/tests/testCase';
|
import { TestCase } from '../../generated/tests/testCase';
|
||||||
import { DatasetTestModeType } from '../../interface/dataQuality.interface';
|
import { DatasetTestModeType } from '../../interface/dataQuality.interface';
|
||||||
|
import { OperationPermission } from '../PermissionProvider/PermissionProvider.interface';
|
||||||
|
|
||||||
export interface TableProfilerProps {
|
export interface TableProfilerProps {
|
||||||
onAddTestClick: (
|
onAddTestClick: (
|
||||||
@ -22,7 +23,7 @@ export interface TableProfilerProps {
|
|||||||
columnName?: string
|
columnName?: string
|
||||||
) => void;
|
) => void;
|
||||||
table: Table;
|
table: Table;
|
||||||
hasEditAccess: boolean;
|
permissions: OperationPermission;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TableTestsType = {
|
export type TableTestsType = {
|
||||||
|
@ -21,6 +21,7 @@ import {
|
|||||||
} from '@testing-library/react';
|
} from '@testing-library/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { MOCK_TABLE, TEST_CASE } from '../../mocks/TableData.mock';
|
import { MOCK_TABLE, TEST_CASE } from '../../mocks/TableData.mock';
|
||||||
|
import { OperationPermission } from '../PermissionProvider/PermissionProvider.interface';
|
||||||
import { TableProfilerProps } from './TableProfiler.interface';
|
import { TableProfilerProps } from './TableProfiler.interface';
|
||||||
// internal imports
|
// internal imports
|
||||||
import TableProfilerV1 from './TableProfilerV1';
|
import TableProfilerV1 from './TableProfilerV1';
|
||||||
@ -60,7 +61,28 @@ jest.mock('../../axiosAPIs/testAPI', () => ({
|
|||||||
|
|
||||||
const mockProps: TableProfilerProps = {
|
const mockProps: TableProfilerProps = {
|
||||||
table: MOCK_TABLE,
|
table: MOCK_TABLE,
|
||||||
hasEditAccess: true,
|
permissions: {
|
||||||
|
Create: true,
|
||||||
|
Delete: true,
|
||||||
|
EditAll: true,
|
||||||
|
EditCustomFields: true,
|
||||||
|
EditDataProfile: true,
|
||||||
|
EditDescription: true,
|
||||||
|
EditDisplayName: true,
|
||||||
|
EditLineage: true,
|
||||||
|
EditOwner: true,
|
||||||
|
EditQueries: true,
|
||||||
|
EditSampleData: true,
|
||||||
|
EditTags: true,
|
||||||
|
EditTests: true,
|
||||||
|
EditTier: true,
|
||||||
|
ViewAll: true,
|
||||||
|
ViewDataProfile: true,
|
||||||
|
ViewQueries: true,
|
||||||
|
ViewSampleData: true,
|
||||||
|
ViewTests: true,
|
||||||
|
ViewUsage: true,
|
||||||
|
} as OperationPermission,
|
||||||
onAddTestClick: jest.fn(),
|
onAddTestClick: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -57,9 +57,9 @@ import './tableProfiler.less';
|
|||||||
const TableProfilerV1: FC<TableProfilerProps> = ({
|
const TableProfilerV1: FC<TableProfilerProps> = ({
|
||||||
table,
|
table,
|
||||||
onAddTestClick,
|
onAddTestClick,
|
||||||
hasEditAccess,
|
permissions,
|
||||||
}) => {
|
}) => {
|
||||||
const { profile, columns } = table;
|
const { profile, columns = [] } = table;
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const [settingModalVisible, setSettingModalVisible] = useState(false);
|
const [settingModalVisible, setSettingModalVisible] = useState(false);
|
||||||
const [columnTests, setColumnTests] = useState<TestCase[]>([]);
|
const [columnTests, setColumnTests] = useState<TestCase[]>([]);
|
||||||
@ -71,6 +71,10 @@ const TableProfilerV1: FC<TableProfilerProps> = ({
|
|||||||
ProfilerDashboardTab.SUMMARY
|
ProfilerDashboardTab.SUMMARY
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const viewTest = permissions.ViewAll || permissions.ViewTests;
|
||||||
|
const viewProfiler = permissions.ViewAll || permissions.ViewDataProfile;
|
||||||
|
const editTest = permissions.EditAll || permissions.EditTests;
|
||||||
|
|
||||||
const handleSettingModal = (value: boolean) => {
|
const handleSettingModal = (value: boolean) => {
|
||||||
setSettingModalVisible(value);
|
setSettingModalVisible(value);
|
||||||
};
|
};
|
||||||
@ -106,11 +110,18 @@ const TableProfilerV1: FC<TableProfilerProps> = ({
|
|||||||
];
|
];
|
||||||
}, [profile, tableTests]);
|
}, [profile, tableTests]);
|
||||||
|
|
||||||
const tabOptions = useMemo(() => {
|
const tabOptions = [
|
||||||
return Object.values(ProfilerDashboardTab).filter(
|
{
|
||||||
(value) => value !== ProfilerDashboardTab.PROFILER
|
label: ProfilerDashboardTab.SUMMARY,
|
||||||
);
|
value: ProfilerDashboardTab.SUMMARY,
|
||||||
}, []);
|
disabled: !viewProfiler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: ProfilerDashboardTab.DATA_QUALITY,
|
||||||
|
value: ProfilerDashboardTab.DATA_QUALITY,
|
||||||
|
disabled: !viewTest,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const handleTabChange = (e: RadioChangeEvent) => {
|
const handleTabChange = (e: RadioChangeEvent) => {
|
||||||
const value = e.target.value as ProfilerDashboardTab;
|
const value = e.target.value as ProfilerDashboardTab;
|
||||||
@ -158,8 +169,9 @@ const TableProfilerV1: FC<TableProfilerProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isEmpty(table)) return;
|
if (!isEmpty(table) && viewTest) {
|
||||||
fetchAllTests();
|
fetchAllTests();
|
||||||
|
}
|
||||||
}, [table]);
|
}, [table]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -177,11 +189,10 @@ const TableProfilerV1: FC<TableProfilerProps> = ({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Space>
|
<Space>
|
||||||
<Tooltip
|
<Tooltip title={editTest ? 'Add Test' : NO_PERMISSION_FOR_ACTION}>
|
||||||
title={hasEditAccess ? 'Add Test' : NO_PERMISSION_FOR_ACTION}>
|
|
||||||
<Link
|
<Link
|
||||||
to={
|
to={
|
||||||
hasEditAccess
|
editTest
|
||||||
? getAddDataQualityTableTestPath(
|
? getAddDataQualityTableTestPath(
|
||||||
ProfilerDashboardType.TABLE,
|
ProfilerDashboardType.TABLE,
|
||||||
`${table.fullyQualifiedName}`
|
`${table.fullyQualifiedName}`
|
||||||
@ -191,18 +202,17 @@ const TableProfilerV1: FC<TableProfilerProps> = ({
|
|||||||
<Button
|
<Button
|
||||||
className="tw-rounded"
|
className="tw-rounded"
|
||||||
data-testid="profiler-add-table-test-btn"
|
data-testid="profiler-add-table-test-btn"
|
||||||
disabled={!hasEditAccess}
|
disabled={!editTest}
|
||||||
type="primary">
|
type="primary">
|
||||||
Add Test
|
Add Test
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip
|
<Tooltip title={editTest ? 'Settings' : NO_PERMISSION_FOR_ACTION}>
|
||||||
title={hasEditAccess ? 'Settings' : NO_PERMISSION_FOR_ACTION}>
|
|
||||||
<Button
|
<Button
|
||||||
className="profiler-setting-btn tw-border tw-border-primary tw-rounded tw-text-primary"
|
className="profiler-setting-btn tw-border tw-border-primary tw-rounded tw-text-primary"
|
||||||
data-testid="profiler-setting-btn"
|
data-testid="profiler-setting-btn"
|
||||||
disabled={!hasEditAccess}
|
disabled={!editTest}
|
||||||
icon={<SVGIcons alt="setting" icon={Icons.SETTINGS_PRIMERY} />}
|
icon={<SVGIcons alt="setting" icon={Icons.SETTINGS_PRIMERY} />}
|
||||||
type="default"
|
type="default"
|
||||||
onClick={() => handleSettingModal(true)}>
|
onClick={() => handleSettingModal(true)}>
|
||||||
@ -258,7 +268,7 @@ const TableProfilerV1: FC<TableProfilerProps> = ({
|
|||||||
...col,
|
...col,
|
||||||
key: col.name,
|
key: col.name,
|
||||||
}))}
|
}))}
|
||||||
hasEditAccess={hasEditAccess}
|
hasEditAccess={editTest}
|
||||||
onAddTestClick={onAddTestClick}
|
onAddTestClick={onAddTestClick}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import { compare } from 'fast-json-patch';
|
import { compare } from 'fast-json-patch';
|
||||||
|
import { isEmpty } from 'lodash';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
@ -25,6 +26,11 @@ import { getListTestCase } from '../../axiosAPIs/testAPI';
|
|||||||
import ErrorPlaceHolder from '../../components/common/error-with-placeholder/ErrorPlaceHolder';
|
import ErrorPlaceHolder from '../../components/common/error-with-placeholder/ErrorPlaceHolder';
|
||||||
import PageContainerV1 from '../../components/containers/PageContainerV1';
|
import PageContainerV1 from '../../components/containers/PageContainerV1';
|
||||||
import Loader from '../../components/Loader/Loader';
|
import Loader from '../../components/Loader/Loader';
|
||||||
|
import { usePermissionProvider } from '../../components/PermissionProvider/PermissionProvider';
|
||||||
|
import {
|
||||||
|
OperationPermission,
|
||||||
|
ResourceEntity,
|
||||||
|
} from '../../components/PermissionProvider/PermissionProvider.interface';
|
||||||
import ProfilerDashboard from '../../components/ProfilerDashboard/ProfilerDashboard';
|
import ProfilerDashboard from '../../components/ProfilerDashboard/ProfilerDashboard';
|
||||||
import { API_RES_MAX_SIZE } from '../../constants/constants';
|
import { API_RES_MAX_SIZE } from '../../constants/constants';
|
||||||
import { ProfilerDashboardType } from '../../enums/table.enum';
|
import { ProfilerDashboardType } from '../../enums/table.enum';
|
||||||
@ -35,6 +41,7 @@ import {
|
|||||||
getNameFromFQN,
|
getNameFromFQN,
|
||||||
getTableFQNFromColumnFQN,
|
getTableFQNFromColumnFQN,
|
||||||
} from '../../utils/CommonUtils';
|
} from '../../utils/CommonUtils';
|
||||||
|
import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils';
|
||||||
import { generateEntityLink } from '../../utils/TableUtils';
|
import { generateEntityLink } from '../../utils/TableUtils';
|
||||||
import { showErrorToast } from '../../utils/ToastUtils';
|
import { showErrorToast } from '../../utils/ToastUtils';
|
||||||
|
|
||||||
@ -47,6 +54,27 @@ const ProfilerDashboardPage = () => {
|
|||||||
const [error, setError] = useState(false);
|
const [error, setError] = useState(false);
|
||||||
const [testCases, setTestCases] = useState<TestCase[]>([]);
|
const [testCases, setTestCases] = useState<TestCase[]>([]);
|
||||||
|
|
||||||
|
const [tablePermissions, setTablePermissions] = useState<OperationPermission>(
|
||||||
|
DEFAULT_ENTITY_PERMISSION
|
||||||
|
);
|
||||||
|
|
||||||
|
const { getEntityPermission } = usePermissionProvider();
|
||||||
|
|
||||||
|
const fetchResourcePermission = async () => {
|
||||||
|
try {
|
||||||
|
const tablePermission = await getEntityPermission(
|
||||||
|
ResourceEntity.TABLE,
|
||||||
|
table.id
|
||||||
|
);
|
||||||
|
|
||||||
|
setTablePermissions(tablePermission);
|
||||||
|
} catch (error) {
|
||||||
|
showErrorToast(
|
||||||
|
jsonData['api-error-messages']['fetch-entity-permissions-error']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const fetchProfilerData = async (fqn: string, days = 3) => {
|
const fetchProfilerData = async (fqn: string, days = 3) => {
|
||||||
try {
|
try {
|
||||||
const startTs = moment().subtract(days, 'days').unix();
|
const startTs = moment().subtract(days, 'days').unix();
|
||||||
@ -98,11 +126,6 @@ const ProfilerDashboardPage = () => {
|
|||||||
}`;
|
}`;
|
||||||
const data = await getTableDetailsByFQN(fqn, field);
|
const data = await getTableDetailsByFQN(fqn, field);
|
||||||
setTable(data ?? ({} as Table));
|
setTable(data ?? ({} as Table));
|
||||||
if (isColumnView) {
|
|
||||||
fetchProfilerData(entityTypeFQN);
|
|
||||||
} else {
|
|
||||||
fetchTestCases(generateEntityLink(entityTypeFQN));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast(
|
showErrorToast(
|
||||||
error as AxiosError,
|
error as AxiosError,
|
||||||
@ -127,6 +150,16 @@ const ProfilerDashboardPage = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getProfilerDashboard = (permission: OperationPermission) => {
|
||||||
|
if (isColumnView && (permission.ViewAll || permission.ViewDataProfile)) {
|
||||||
|
fetchProfilerData(entityTypeFQN);
|
||||||
|
} else {
|
||||||
|
if (permission.ViewAll || permission.ViewTests) {
|
||||||
|
fetchTestCases(generateEntityLink(entityTypeFQN));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (entityTypeFQN) {
|
if (entityTypeFQN) {
|
||||||
fetchTableEntity();
|
fetchTableEntity();
|
||||||
@ -136,6 +169,18 @@ const ProfilerDashboardPage = () => {
|
|||||||
}
|
}
|
||||||
}, [entityTypeFQN]);
|
}, [entityTypeFQN]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isEmpty(table)) {
|
||||||
|
fetchResourcePermission();
|
||||||
|
}
|
||||||
|
}, [table]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isEmpty(table)) {
|
||||||
|
getProfilerDashboard(tablePermissions);
|
||||||
|
}
|
||||||
|
}, [table, tablePermissions]);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <Loader />;
|
return <Loader />;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user