Update MyData and Explore with new roles and policy (#7018)

* Update MyData and Explore with new roles and policy

* Add placeholder for explore side panel

* Fix unit tests

* Remove Permission API call from AuthProvider

* Fix loading issue

* Update global settings left panel

* Remove feed check from mydata page

* Remove permission check from explore page

* Minor change

* Add resource permission method to provider

* Change global settings left panel

* Remove unwanted codes

* Add comments

* Addressing review comments
This commit is contained in:
Sachin Chaurasiya 2022-08-30 14:42:55 +05:30 committed by GitHub
parent a6a4e08af1
commit 106f99f4c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 613 additions and 499 deletions

View File

@ -33,10 +33,7 @@ import React, {
import { useHistory, useLocation } from 'react-router-dom'; import { useHistory, useLocation } from 'react-router-dom';
import appState from '../../AppState'; import appState from '../../AppState';
import axiosClient from '../../axiosAPIs'; import axiosClient from '../../axiosAPIs';
import { import { fetchAuthenticationConfig } from '../../axiosAPIs/miscAPI';
fetchAuthenticationConfig,
getLoggedInUserPermissions,
} from '../../axiosAPIs/miscAPI';
import { import {
getLoggedInUser, getLoggedInUser,
getUserByName, getUserByName,
@ -166,32 +163,14 @@ export const AuthProvider = ({
} }
}; };
const getUserPermissions = () => {
setLoading(true);
getLoggedInUserPermissions()
.then((res) => {
appState.updateUserPermissions(res.data);
})
.catch((err: AxiosError) => {
showErrorToast(
err,
jsonData['api-error-messages']['fetch-user-permission-error']
);
})
.finally(() => setLoading(false));
};
const getLoggedInUserDetails = () => { const getLoggedInUserDetails = () => {
setLoading(true); setLoading(true);
getLoggedInUser(userAPIQueryFields) getLoggedInUser(userAPIQueryFields)
.then((res) => { .then((res) => {
if (res) { if (res) {
getUserPermissions();
appState.updateUserDetails(res); appState.updateUserDetails(res);
fetchAllUsers();
} else { } else {
resetUserDetails(); resetUserDetails();
setLoading(false);
} }
}) })
.catch((err: AxiosError) => { .catch((err: AxiosError) => {
@ -204,6 +183,9 @@ export const AuthProvider = ({
jsonData['api-error-messages']['fetch-logged-in-user-error'] jsonData['api-error-messages']['fetch-logged-in-user-error']
); );
} }
})
.finally(() => {
setLoading(false);
}); });
}; };
@ -368,8 +350,6 @@ export const AuthProvider = ({
} else { } else {
appState.updateUserDetails(res); appState.updateUserDetails(res);
} }
getUserPermissions();
fetchAllUsers();
handledVerifiedUser(); handledVerifiedUser();
// Start expiry timer on successful login // Start expiry timer on successful login
startTokenExpiryTimer(); startTokenExpiryTimer();
@ -439,8 +419,6 @@ export const AuthProvider = ({
storeRedirectPath(); storeRedirectPath();
showErrorToast(error); showErrorToast(error);
resetUserDetails(true); resetUserDetails(true);
} else if (status === ClientErrors.FORBIDDEN) {
showErrorToast(jsonData['api-error-messages']['forbidden-error']);
} }
} }

View File

@ -18,7 +18,6 @@ import { WILD_CARD_CHAR } from '../constants/char.constants';
import { SearchIndex } from '../enums/search.enum'; import { SearchIndex } from '../enums/search.enum';
import { AirflowConfiguration } from '../generated/configuration/airflowConfiguration'; import { AirflowConfiguration } from '../generated/configuration/airflowConfiguration';
import { AuthenticationConfiguration } from '../generated/configuration/authenticationConfiguration'; import { AuthenticationConfiguration } from '../generated/configuration/authenticationConfiguration';
import { ResourcePermission } from '../generated/entity/policies/accessControl/resourcePermission';
import { EntitiesCount } from '../generated/entity/utils/entitiesCount'; import { EntitiesCount } from '../generated/entity/utils/entitiesCount';
import { Paging } from '../generated/type/paging'; import { Paging } from '../generated/type/paging';
import { getURLWithQueryFields } from '../utils/APIUtils'; import { getURLWithQueryFields } from '../utils/APIUtils';
@ -121,18 +120,6 @@ export const deleteLineageEdge: Function = (
); );
}; };
export const getLoggedInUserPermissions = async () => {
const params = {
limit: 100,
};
const response = await APIClient.get<{
data: ResourcePermission[];
paging: Paging;
}>('/permissions', { params });
return response.data;
};
export const getInitialEntity = ( export const getInitialEntity = (
index: SearchIndex, index: SearchIndex,
params = {} as AxiosRequestConfig params = {} as AxiosRequestConfig

View File

@ -0,0 +1,48 @@
/*
* Copyright 2021 Collate
* Licensed 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.
*/
import { ResourceEntity } from '../components/PermissionProvider/PermissionProvider.interface';
import { ResourcePermission } from '../generated/entity/policies/accessControl/resourcePermission';
import { Paging } from '../generated/type/paging';
import APIClient from './index';
export const getLoggedInUserPermissions = async () => {
const params = {
limit: 100,
};
const response = await APIClient.get<{
data: ResourcePermission[];
paging: Paging;
}>('/permissions', { params });
return response.data;
};
export const getEntityPermissionById = async (
resource: ResourceEntity,
entityId: string
) => {
const response = await APIClient.get<ResourcePermission>(
`/permissions/${resource}/${entityId}`
);
return response.data;
};
export const getResourcePermission = async (resource: ResourceEntity) => {
const response = await APIClient.get<ResourcePermission>(
`/permissions/${resource}`
);
return response.data;
};

View File

@ -13,11 +13,9 @@
import { AxiosResponse } from 'axios'; import { AxiosResponse } from 'axios';
import { Operation } from 'fast-json-patch'; import { Operation } from 'fast-json-patch';
import { ResourceEntity } from '../components/PermissionProvider/PermissionProvider.interface';
import { CreatePolicy } from '../generated/api/policies/createPolicy'; import { CreatePolicy } from '../generated/api/policies/createPolicy';
import { CreateRole } from '../generated/api/teams/createRole'; import { CreateRole } from '../generated/api/teams/createRole';
import { ResourceDescriptor } from '../generated/entity/policies/accessControl/resourceDescriptor'; import { ResourceDescriptor } from '../generated/entity/policies/accessControl/resourceDescriptor';
import { ResourcePermission } from '../generated/entity/policies/accessControl/resourcePermission';
import { Policy } from '../generated/entity/policies/policy'; import { Policy } from '../generated/entity/policies/policy';
import { Role } from '../generated/entity/teams/role'; import { Role } from '../generated/entity/teams/role';
import { Function } from '../generated/type/function'; import { Function } from '../generated/type/function';
@ -156,14 +154,3 @@ export const validateRuleCondition = async (condition: string) => {
*/ */
return response; return response;
}; };
export const getEntityPermissionById = async (
resource: ResourceEntity,
entityId: string
) => {
const response = await APIClient.get<ResourcePermission>(
`/permissions/${resource}/${entityId}`
);
return response.data;
};

View File

@ -15,13 +15,14 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Card } from 'antd'; import { Card } from 'antd';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import classNames from 'classnames'; import classNames from 'classnames';
import { isNil } from 'lodash'; import { isEmpty, isNil } from 'lodash';
import moment from 'moment'; import moment from 'moment';
import React, { import React, {
FC, FC,
Fragment, Fragment,
HTMLAttributes, HTMLAttributes,
useEffect, useEffect,
useMemo,
useState, useState,
} from 'react'; } from 'react';
import Select, { SingleValue } from 'react-select'; import Select, { SingleValue } from 'react-select';
@ -77,21 +78,32 @@ const BotDetails: FC<BotsDetailProp> = ({
const [generateToken, setGenerateToken] = useState<boolean>(false); const [generateToken, setGenerateToken] = useState<boolean>(false);
const [selectedExpiry, setSelectedExpiry] = useState('7'); const [selectedExpiry, setSelectedExpiry] = useState('7');
const editAllPermission = checkPermission( const editAllPermission = useMemo(
Operation.EditAll, () =>
ResourceEntity.BOT, !isEmpty(permissions) &&
permissions checkPermission(Operation.EditAll, ResourceEntity.BOT, permissions),
[permissions]
); );
const displayNamePermission = checkPermission( const displayNamePermission = useMemo(
Operation.EditDisplayName, () =>
ResourceEntity.BOT, !isEmpty(permissions) &&
permissions checkPermission(
Operation.EditDisplayName,
ResourceEntity.BOT,
permissions
),
[permissions]
); );
const descriptionPermission = checkPermission( const descriptionPermission = useMemo(
Operation.EditDescription, () =>
ResourceEntity.BOT, !isEmpty(permissions) &&
permissions checkPermission(
Operation.EditDescription,
ResourceEntity.BOT,
permissions
),
[permissions]
); );
const getJWTTokenExpiryOptions = () => { const getJWTTokenExpiryOptions = () => {

View File

@ -14,6 +14,7 @@
import { Button, Col, Row, Space, Table, Tooltip } from 'antd'; import { Button, Col, Row, Space, Table, Tooltip } from 'antd';
import { ColumnsType } from 'antd/lib/table'; import { ColumnsType } from 'antd/lib/table';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import { isEmpty } from 'lodash';
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { getBots } from '../../axiosAPIs/botsAPI'; import { getBots } from '../../axiosAPIs/botsAPI';
@ -46,10 +47,11 @@ const BotListV1 = ({ showDeleted }: BotListV1Props) => {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [currentPage, setCurrentPage] = useState<number>(INITIAL_PAGING_VALUE); const [currentPage, setCurrentPage] = useState<number>(INITIAL_PAGING_VALUE);
const deletePermission = checkPermission( const deletePermission = useMemo(
Operation.Delete, () =>
ResourceEntity.BOT, !isEmpty(permissions) &&
permissions checkPermission(Operation.Delete, ResourceEntity.BOT, permissions),
[permissions]
); );
/** /**

View File

@ -89,12 +89,8 @@ const Explore: React.FC<ExploreProps> = ({
fetchData, fetchData,
showDeleted, showDeleted,
onShowDeleted, onShowDeleted,
updateTableCount,
updateTopicCount,
updateDashboardCount,
updatePipelineCount,
isFilterSelected, isFilterSelected,
updateMlModelCount, handleTabCounts,
}: ExploreProps) => { }: ExploreProps) => {
const location = useLocation(); const location = useLocation();
const history = useHistory(); const history = useHistory();
@ -273,23 +269,23 @@ const Explore: React.FC<ExploreProps> = ({
const setCount = (count = 0, index = searchIndex) => { const setCount = (count = 0, index = searchIndex) => {
switch (index) { switch (index) {
case SearchIndex.TABLE: case SearchIndex.TABLE:
updateTableCount(count); handleTabCounts({ table: count });
break; break;
case SearchIndex.DASHBOARD: case SearchIndex.DASHBOARD:
updateDashboardCount(count); handleTabCounts({ dashboard: count });
break; break;
case SearchIndex.TOPIC: case SearchIndex.TOPIC:
updateTopicCount(count); handleTabCounts({ topic: count });
break; break;
case SearchIndex.PIPELINE: case SearchIndex.PIPELINE:
updatePipelineCount(count); handleTabCounts({ pipeline: count });
break; break;
case SearchIndex.MLMODEL: case SearchIndex.MLMODEL:
updateMlModelCount(count); handleTabCounts({ mlmodel: count });
break; break;
default: default:
@ -441,7 +437,7 @@ const Explore: React.FC<ExploreProps> = ({
case SearchIndex.PIPELINE: case SearchIndex.PIPELINE:
return getCountBadge(tabCounts.pipeline, className, isActive); return getCountBadge(tabCounts.pipeline, className, isActive);
case SearchIndex.MLMODEL: case SearchIndex.MLMODEL:
return getCountBadge(tabCounts.mlModel, className, isActive); return getCountBadge(tabCounts.mlmodel, className, isActive);
default: default:
return getCountBadge(); return getCountBadge();
} }
@ -469,18 +465,6 @@ const Explore: React.FC<ExploreProps> = ({
'tw-flex tw-flex-row tw-justify-between tw-gh-tabs-container' 'tw-flex tw-flex-row tw-justify-between tw-gh-tabs-container'
)}> )}>
<div className="tw-flex"> <div className="tw-flex">
{/* <div className="tw-w-64 tw-mr-5 tw-flex-shrink-0">
<Button
className={classNames('tw-underline tw-mt-5', {
'tw-invisible': !getFilterCount(filters),
})}
size="custom"
theme="primary"
variant="link"
onClick={() => resetFilters(true)}>
Clear All
</Button>
</div> */}
<div> <div>
{tabsInfo.map((tabDetail, index) => ( {tabsInfo.map((tabDetail, index) => (
<button <button

View File

@ -95,6 +95,7 @@ describe('Test Explore component', () => {
handleFilterChange={mockFunction} handleFilterChange={mockFunction}
handlePathChange={mockFunction} handlePathChange={mockFunction}
handleSearchText={mockFunction} handleSearchText={mockFunction}
handleTabCounts={mockFunction}
searchQuery="" searchQuery=""
searchResult={mockSearchResult} searchResult={mockSearchResult}
searchText="" searchText=""
@ -106,15 +107,8 @@ describe('Test Explore component', () => {
topic: 2, topic: 2,
dashboard: 8, dashboard: 8,
pipeline: 5, pipeline: 5,
dbtModel: 7, mlmodel: 2,
mlModel: 2,
}} }}
updateDashboardCount={mockFunction}
updateDbtModelCount={mockFunction}
updateMlModelCount={mockFunction}
updatePipelineCount={mockFunction}
updateTableCount={mockFunction}
updateTopicCount={mockFunction}
onShowDeleted={mockFunction} onShowDeleted={mockFunction}
/>, />,
{ {

View File

@ -29,14 +29,7 @@ export type ExploreSearchData = {
}; };
export interface ExploreProps { export interface ExploreProps {
tabCounts: { tabCounts: TabCounts;
table: number;
topic: number;
dashboard: number;
pipeline: number;
dbtModel: number;
mlModel: number;
};
searchText: string; searchText: string;
initialFilter?: FilterObject; initialFilter?: FilterObject;
searchFilter?: FilterObject; searchFilter?: FilterObject;
@ -51,17 +44,20 @@ export interface ExploreProps {
handleFilterChange: (data: FilterObject) => void; handleFilterChange: (data: FilterObject) => void;
handlePathChange: (path: string) => void; handlePathChange: (path: string) => void;
handleSearchText: (text: string) => void; handleSearchText: (text: string) => void;
updateTableCount: (count: number) => void;
updateTopicCount: (count: number) => void;
updateDashboardCount: (count: number) => void;
updatePipelineCount: (count: number) => void;
updateDbtModelCount: (count: number) => void;
updateMlModelCount: (count: number) => void;
fetchData: (value: SearchDataFunctionType[]) => void; fetchData: (value: SearchDataFunctionType[]) => void;
onShowDeleted: (checked: boolean) => void; onShowDeleted: (checked: boolean) => void;
handleTabCounts: (value: { [key: string]: number }) => void;
} }
export interface AdvanceField { export interface AdvanceField {
key: string; key: string;
value: string | undefined; value: string | undefined;
} }
export interface TabCounts {
table: number;
topic: number;
dashboard: number;
pipeline: number;
mlmodel: number;
}

View File

@ -13,46 +13,72 @@
import { Menu, MenuProps } from 'antd'; import { Menu, MenuProps } from 'antd';
import { ItemType } from 'antd/lib/menu/hooks/useItems'; import { ItemType } from 'antd/lib/menu/hooks/useItems';
import { AxiosError } from 'axios';
import { camelCase } from 'lodash'; import { camelCase } from 'lodash';
import React from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import { useHistory, useParams } from 'react-router-dom'; import { useHistory, useParams } from 'react-router-dom';
import { GLOBAL_SETTINGS_MENU } from '../../constants/globalSettings.constants'; import { GLOBAL_SETTING_PERMISSION_RESOURCES } from '../../constants/globalSettings.constants';
import { Operation } from '../../generated/entity/policies/accessControl/rule'; import {
import { getGlobalSettingMenus } from '../../utils/GlobalSettingsUtils'; getGlobalSettingMenuItem,
import { checkPermission } from '../../utils/PermissionsUtils'; getGlobalSettingsMenuWithPermission,
MenuList,
} from '../../utils/GlobalSettingsUtils';
import { getSettingPath } from '../../utils/RouterUtils'; import { getSettingPath } from '../../utils/RouterUtils';
import { showErrorToast } from '../../utils/ToastUtils';
import Loader from '../Loader/Loader';
import { usePermissionProvider } from '../PermissionProvider/PermissionProvider'; import { usePermissionProvider } from '../PermissionProvider/PermissionProvider';
import { ResourceEntity } from '../PermissionProvider/PermissionProvider.interface'; import {
ResourceEntity,
UIPermission,
} from '../PermissionProvider/PermissionProvider.interface';
const GlobalSettingLeftPanel = () => { const GlobalSettingLeftPanel = () => {
const { tab, settingCategory } = useParams<{ [key: string]: string }>();
const { permissions } = usePermissionProvider();
const viewAllPermission = checkPermission(
Operation.ViewAll,
ResourceEntity.ALL,
permissions
);
const history = useHistory(); const history = useHistory();
const items: ItemType[] = GLOBAL_SETTINGS_MENU.filter(({ isProtected }) => { const { tab, settingCategory } = useParams<{ [key: string]: string }>();
if (viewAllPermission) { const [settingResourcePermission, setSettingResourcePermission] =
return viewAllPermission; useState<UIPermission>({} as UIPermission);
}
return !isProtected; const [isLoading, setIsLoading] = useState<boolean>(true);
}).map(({ category, items }) => {
return getGlobalSettingMenus( const { getResourcePermission } = usePermissionProvider();
category,
camelCase(category), const fetchResourcesPermission = async (resource: ResourceEntity) => {
'', setIsLoading(true);
'', try {
items, const response = await getResourcePermission(resource);
'group', setSettingResourcePermission((prev) => ({
viewAllPermission ...prev,
); [resource]: response,
}); }));
} catch (error) {
showErrorToast(error as AxiosError);
} finally {
setIsLoading(false);
}
};
const menuItems: ItemType[] = useMemo(
() =>
getGlobalSettingsMenuWithPermission(settingResourcePermission).reduce(
(acc: ItemType[], curr: MenuList) => {
const menuItem = getGlobalSettingMenuItem(
curr.category,
camelCase(curr.category),
'',
'',
curr.items,
'group'
);
if (menuItem.children?.length) {
return [...acc, menuItem];
} else {
return acc;
}
},
[] as ItemType[]
),
[setSettingResourcePermission]
);
const onClick: MenuProps['onClick'] = (e) => { const onClick: MenuProps['onClick'] = (e) => {
// As we are setting key as "category.option" and extracting here category and option // As we are setting key as "category.option" and extracting here category and option
@ -60,14 +86,29 @@ const GlobalSettingLeftPanel = () => {
history.push(getSettingPath(category, option)); history.push(getSettingPath(category, option));
}; };
useEffect(() => {
// TODO: This will make number of API calls, need to think of better solution
GLOBAL_SETTING_PERMISSION_RESOURCES.forEach((resource) => {
fetchResourcesPermission(resource);
});
}, []);
if (isLoading) {
return <Loader />;
}
return ( return (
<Menu <>
className="global-setting-left-panel" {menuItems.length ? (
items={items} <Menu
mode="inline" className="global-setting-left-panel"
selectedKeys={[`${settingCategory}.${tab}`]} items={menuItems}
onClick={onClick} mode="inline"
/> selectedKeys={[`${settingCategory}.${tab}`]}
onClick={onClick}
/>
) : null}
</>
); );
}; };

View File

@ -11,7 +11,6 @@
* limitations under the License. * limitations under the License.
*/ */
import { AxiosError } from 'axios';
import { Operation } from '../../generated/entity/policies/accessControl/resourcePermission'; import { Operation } from '../../generated/entity/policies/accessControl/resourcePermission';
export type UIPermission = { export type UIPermission = {
@ -65,7 +64,10 @@ export interface PermissionContextType {
getEntityPermission: ( getEntityPermission: (
resource: ResourceEntity, resource: ResourceEntity,
entityId: string entityId: string
) => Promise<OperationPermission | AxiosError>; ) => Promise<OperationPermission>;
getResourcePermission: (
resource: ResourceEntity
) => Promise<OperationPermission>;
} }
export interface EntityPermissionMap { export interface EntityPermissionMap {

View File

@ -23,8 +23,11 @@ import React, {
useState, useState,
} from 'react'; } from 'react';
import AppState from '../../AppState'; import AppState from '../../AppState';
import { getLoggedInUserPermissions } from '../../axiosAPIs/miscAPI'; import {
import { getEntityPermissionById } from '../../axiosAPIs/rolesAPIV1'; getEntityPermissionById,
getLoggedInUserPermissions,
getResourcePermission,
} from '../../axiosAPIs/permissionAPI';
import { import {
getOperationPermissions, getOperationPermissions,
getUIPermission, getUIPermission,
@ -36,12 +39,12 @@ import {
ResourceEntity, ResourceEntity,
UIPermission, UIPermission,
} from './PermissionProvider.interface'; } from './PermissionProvider.interface';
/** /**
* Permission Context * Permission Context
* Returns ResourcePermission List for loggedIn User * Returns ResourcePermission List for loggedIn User
* @returns PermissionMap * @returns PermissionMap
*/ */
export const PermissionContext = createContext<PermissionContextType>( export const PermissionContext = createContext<PermissionContextType>(
{} as PermissionContextType {} as PermissionContextType
); );
@ -63,6 +66,10 @@ const PermissionProvider: FC<PermissionProviderProps> = ({ children }) => {
const [entitiesPermission, setEntitiesPermission] = const [entitiesPermission, setEntitiesPermission] =
useState<EntityPermissionMap>({} as EntityPermissionMap); useState<EntityPermissionMap>({} as EntityPermissionMap);
const [resourcesPermission, setResourcesPermission] = useState<UIPermission>(
{} as UIPermission
);
// Update current user details of AppState change // Update current user details of AppState change
const currentUser = useMemo(() => { const currentUser = useMemo(() => {
return AppState.getCurrentUserDetails(); return AppState.getCurrentUserDetails();
@ -84,22 +91,45 @@ const PermissionProvider: FC<PermissionProviderProps> = ({ children }) => {
resource: ResourceEntity, resource: ResourceEntity,
entityId: string entityId: string
) => { ) => {
try { const entityPermission = entitiesPermission[entityId];
const entityPermission = entitiesPermission[entityId]; if (entityPermission) {
if (entityPermission) { return entityPermission;
return entityPermission; } else {
} else { const response = await getEntityPermissionById(resource, entityId);
const response = await getEntityPermissionById(resource, entityId); const operationPermission = getOperationPermissions(response);
const operationPermission = getOperationPermissions(response); setEntitiesPermission((prev) => ({
setEntitiesPermission((prev) => ({ ...prev,
...prev, [entityId]: operationPermission,
[entityId]: operationPermission, }));
}));
return operationPermission; return operationPermission;
} }
} catch (error) { };
return error as AxiosError;
const fetchResourcePermission = async (resource: ResourceEntity) => {
const resourcePermission = resourcesPermission[resource];
if (resourcePermission) {
return resourcePermission;
} else {
const response = await getResourcePermission(resource);
const operationPermission = getOperationPermissions(response);
/**
* Store resource permission if it's not exits
*/
setResourcesPermission((prev) => ({
...prev,
[resource]: operationPermission,
}));
/**
* Store updated resource permission
*/
setPermissions((prev) => ({
...prev,
[resource]: operationPermission,
}));
return operationPermission;
} }
}; };
@ -117,6 +147,7 @@ const PermissionProvider: FC<PermissionProviderProps> = ({ children }) => {
value={{ value={{
permissions, permissions,
getEntityPermission: fetchEntityPermission, getEntityPermission: fetchEntityPermission,
getResourcePermission: fetchResourcePermission,
}}> }}>
{children} {children}
</PermissionContext.Provider> </PermissionContext.Provider>

View File

@ -13,6 +13,8 @@
import { toLower } from 'lodash'; import { toLower } from 'lodash';
import { AggregationType, Bucket, FilterObject } from 'Models'; import { AggregationType, Bucket, FilterObject } from 'Models';
import { TabCounts } from '../components/Explore/explore.interface';
import { EntityType } from '../enums/entity.enum';
import { SearchIndex } from '../enums/search.enum'; import { SearchIndex } from '../enums/search.enum';
import { getFilterKey } from '../utils/FilterUtils'; import { getFilterKey } from '../utils/FilterUtils';
import { Icons } from '../utils/SvgUtils'; import { Icons } from '../utils/SvgUtils';
@ -301,3 +303,27 @@ export const tabsInfo = [
selectedIcon: '', selectedIcon: '',
}, },
]; ];
export const INITIAL_TAB_COUNTS: TabCounts = {
table: 0,
topic: 0,
dashboard: 0,
pipeline: 0,
mlmodel: 0,
};
export const getEntityTypeByIndex = (index: SearchIndex) => {
switch (index) {
case SearchIndex.TOPIC:
return EntityType.TOPIC;
case SearchIndex.DASHBOARD:
return EntityType.DASHBOARD;
case SearchIndex.PIPELINE:
return EntityType.PIPELINE;
case SearchIndex.MLMODEL:
return EntityType.MLMODEL;
case SearchIndex.TABLE:
default:
return EntityType.TABLE;
}
};

View File

@ -11,148 +11,7 @@
* limitations under the License. * limitations under the License.
*/ */
import React from 'react'; import { ResourceEntity } from '../components/PermissionProvider/PermissionProvider.interface';
import { ReactComponent as BotIcon } from '../../src/assets/svg/bot-profile.svg';
import { ReactComponent as DashboardIcon } from '../../src/assets/svg/dashboard-grey.svg';
import { ReactComponent as RolesIcon } from '../../src/assets/svg/icon-role-grey.svg';
import { ReactComponent as MlModelIcon } from '../../src/assets/svg/mlmodal.svg';
import { ReactComponent as MSTeams } from '../../src/assets/svg/ms-teams.svg';
import { ReactComponent as PipelineIcon } from '../../src/assets/svg/pipeline-grey.svg';
import { ReactComponent as PoliciesIcon } from '../../src/assets/svg/policies.svg';
import { ReactComponent as SlackIcon } from '../../src/assets/svg/slack.svg';
import { ReactComponent as TableIcon } from '../../src/assets/svg/table-grey.svg';
import { ReactComponent as TeamsIcon } from '../../src/assets/svg/teams-grey.svg';
import { ReactComponent as TopicIcon } from '../../src/assets/svg/topic-grey.svg';
import { ReactComponent as UsersIcon } from '../../src/assets/svg/user.svg';
import { ReactComponent as WebhookIcon } from '../../src/assets/svg/webhook-grey.svg';
export const GLOBAL_SETTINGS_MENU = [
{
category: 'Members',
isProtected: false,
items: [
{
label: 'Teams',
isProtected: false,
icon: <TeamsIcon className="side-panel-icons" />,
},
{
label: 'Users',
isProtected: true,
icon: <UsersIcon className="side-panel-icons" />,
},
{
label: 'Admins',
isProtected: true,
icon: <UsersIcon className="side-panel-icons" />,
},
],
},
{
category: 'Access',
isProtected: true,
items: [
{
label: 'Roles',
isProtected: true,
icon: <RolesIcon className="side-panel-icons" />,
},
{
label: 'Policies',
isProtected: true,
icon: <PoliciesIcon className="side-panel-icons" />,
},
],
},
{
category: 'Services',
isProtected: false,
items: [
{
label: 'Databases',
isProtected: false,
icon: <TableIcon className="side-panel-icons" />,
},
{
label: 'Messaging',
isProtected: false,
icon: <TopicIcon className="side-panel-icons" />,
},
{
label: 'Dashboards',
isProtected: false,
icon: <DashboardIcon className="side-panel-icons" />,
},
{
label: 'Pipelines',
isProtected: false,
icon: <PipelineIcon className="side-panel-icons" />,
},
{
label: 'ML Models',
isProtected: false,
icon: <MlModelIcon className="side-panel-icons" />,
},
],
},
{
category: 'Custom Attributes',
isProtected: true,
items: [
{
label: 'Tables',
isProtected: true,
icon: <TableIcon className="side-panel-icons" />,
},
{
label: 'Topics',
isProtected: true,
icon: <TopicIcon className="side-panel-icons" />,
},
{
label: 'Dashboards',
isProtected: true,
icon: <DashboardIcon className="side-panel-icons" />,
},
{
label: 'Pipelines',
isProtected: true,
icon: <PipelineIcon className="side-panel-icons" />,
},
{
label: 'ML Models',
isProtected: true,
icon: <MlModelIcon className="side-panel-icons" />,
},
],
},
{
category: 'Integrations',
isProtected: true,
items: [
{
label: 'Webhook',
isProtected: true,
icon: <WebhookIcon className="tw-w-4 side-panel-icons" />,
},
{
label: 'Slack',
isProtected: true,
icon: <SlackIcon className="tw-w-4 side-panel-icons" />,
},
{
label: 'MS Teams',
isProtected: true,
icon: <MSTeams className="tw-w-4 side-panel-icons" />,
},
{
label: 'Bots',
isProtected: true,
icon: <BotIcon className="tw-w-4 side-panel-icons" />,
},
],
},
];
export const customAttributesPath = { export const customAttributesPath = {
tables: 'table', tables: 'table',
@ -187,3 +46,18 @@ export enum GlobalSettingOptions {
TABLES = 'tables', TABLES = 'tables',
MSTEAMS = 'msteams', MSTEAMS = 'msteams',
} }
export const GLOBAL_SETTING_PERMISSION_RESOURCES = [
ResourceEntity.TEAM,
ResourceEntity.USER,
ResourceEntity.ROLE,
ResourceEntity.POLICY,
ResourceEntity.DATABASE_SERVICE,
ResourceEntity.MESSAGING_SERVICE,
ResourceEntity.DASHBOARD_SERVICE,
ResourceEntity.PIPELINE_SERVICE,
ResourceEntity.ML_MODEL_SERVICE,
ResourceEntity.TYPE,
ResourceEntity.WEBHOOK,
ResourceEntity.BOT,
];

View File

@ -32,20 +32,25 @@ import PageContainerV1 from '../../components/containers/PageContainerV1';
import GithubStarButton from '../../components/GithubStarButton/GithubStarButton'; import GithubStarButton from '../../components/GithubStarButton/GithubStarButton';
import Loader from '../../components/Loader/Loader'; import Loader from '../../components/Loader/Loader';
import MyData from '../../components/MyData/MyData.component'; import MyData from '../../components/MyData/MyData.component';
import { usePermissionProvider } from '../../components/PermissionProvider/PermissionProvider';
import { ResourceEntity } from '../../components/PermissionProvider/PermissionProvider.interface';
import { useWebSocketConnector } from '../../components/web-scoket/web-scoket.provider'; import { useWebSocketConnector } from '../../components/web-scoket/web-scoket.provider';
import { SOCKET_EVENTS } from '../../constants/constants'; import { SOCKET_EVENTS } from '../../constants/constants';
import { AssetsType } from '../../enums/entity.enum'; import { AssetsType } from '../../enums/entity.enum';
import { FeedFilter } from '../../enums/mydata.enum'; import { FeedFilter } from '../../enums/mydata.enum';
import { Post, Thread, ThreadType } from '../../generated/entity/feed/thread'; import { Post, Thread, ThreadType } from '../../generated/entity/feed/thread';
import { Operation as RuleOperation } from '../../generated/entity/policies/accessControl/rule';
import { EntitiesCount } from '../../generated/entity/utils/entitiesCount'; import { EntitiesCount } from '../../generated/entity/utils/entitiesCount';
import { Paging } from '../../generated/type/paging'; import { Paging } from '../../generated/type/paging';
import { useAuth } from '../../hooks/authHooks'; import { useAuth } from '../../hooks/authHooks';
import jsonData from '../../jsons/en'; import jsonData from '../../jsons/en';
import { deletePost, updateThreadData } from '../../utils/FeedUtils'; import { deletePost, updateThreadData } from '../../utils/FeedUtils';
import { checkPermission } from '../../utils/PermissionsUtils';
import { showErrorToast } from '../../utils/ToastUtils'; import { showErrorToast } from '../../utils/ToastUtils';
const MyDataPage = () => { const MyDataPage = () => {
const location = useLocation(); const location = useLocation();
const { permissions } = usePermissionProvider();
const { isAuthDisabled } = useAuth(location.pathname); const { isAuthDisabled } = useAuth(location.pathname);
const [error, setError] = useState<string>(''); const [error, setError] = useState<string>('');
const [entityCounts, setEntityCounts] = useState<EntitiesCount>( const [entityCounts, setEntityCounts] = useState<EntitiesCount>(
@ -72,6 +77,13 @@ const MyDataPage = () => {
[AppState.userDetails, AppState.nonSecureUserDetails] [AppState.userDetails, AppState.nonSecureUserDetails]
); );
const viewUserPermission = useMemo(() => {
return (
!isEmpty(permissions) &&
checkPermission(RuleOperation.ViewAll, ResourceEntity.USER, permissions)
);
}, [permissions]);
const fetchEntityCount = () => { const fetchEntityCount = () => {
getAllEntityCount() getAllEntityCount()
.then((res) => { .then((res) => {
@ -258,7 +270,7 @@ const MyDataPage = () => {
!isEmpty(AppState.userDetails)) && !isEmpty(AppState.userDetails)) &&
(isNil(ownedData) || isNil(followedData)) (isNil(ownedData) || isNil(followedData))
) { ) {
fetchMyData(); viewUserPermission && fetchMyData();
} }
}, [AppState.userDetails, AppState.users, isAuthDisabled]); }, [AppState.userDetails, AppState.users, isAuthDisabled]);

View File

@ -19,13 +19,7 @@ import {
SearchDataFunctionType, SearchDataFunctionType,
SearchResponse, SearchResponse,
} from 'Models'; } from 'Models';
import React, { import React, { FunctionComponent, useEffect, useMemo, useState } from 'react';
Fragment,
FunctionComponent,
useEffect,
useMemo,
useState,
} from 'react';
import { useHistory, useLocation, useParams } from 'react-router-dom'; import { useHistory, useLocation, useParams } from 'react-router-dom';
import AppState from '../../AppState'; import AppState from '../../AppState';
import { searchData } from '../../axiosAPIs/miscAPI'; import { searchData } from '../../axiosAPIs/miscAPI';
@ -33,6 +27,7 @@ import PageContainerV1 from '../../components/containers/PageContainerV1';
import Explore from '../../components/Explore/Explore.component'; import Explore from '../../components/Explore/Explore.component';
import { import {
ExploreSearchData, ExploreSearchData,
TabCounts,
UrlParams, UrlParams,
} from '../../components/Explore/explore.interface'; } from '../../components/Explore/explore.interface';
import { getExplorePathWithSearch, PAGE_SIZE } from '../../constants/constants'; import { getExplorePathWithSearch, PAGE_SIZE } from '../../constants/constants';
@ -40,11 +35,13 @@ import {
emptyValue, emptyValue,
getCurrentIndex, getCurrentIndex,
getCurrentTab, getCurrentTab,
getEntityTypeByIndex,
getInitialFilter, getInitialFilter,
getQueryParam, getQueryParam,
getSearchFilter, getSearchFilter,
INITIAL_FROM, INITIAL_FROM,
INITIAL_SORT_ORDER, INITIAL_SORT_ORDER,
INITIAL_TAB_COUNTS,
tabsInfo, tabsInfo,
ZERO_SIZE, ZERO_SIZE,
} from '../../constants/explore.constants'; } from '../../constants/explore.constants';
@ -68,46 +65,21 @@ const ExplorePage: FunctionComponent = () => {
const [error, setError] = useState<string>(''); const [error, setError] = useState<string>('');
const { searchQuery, tab } = useParams<UrlParams>(); const { searchQuery, tab } = useParams<UrlParams>();
const [searchText, setSearchText] = useState<string>(searchQuery || ''); const [searchText, setSearchText] = useState<string>(searchQuery || '');
const [tableCount, setTableCount] = useState<number>(0); const [tabCounts, setTabCounts] = useState<TabCounts>(INITIAL_TAB_COUNTS);
const [topicCount, setTopicCount] = useState<number>(0);
const [dashboardCount, setDashboardCount] = useState<number>(0);
const [pipelineCount, setPipelineCount] = useState<number>(0);
const [dbtModelCount, setDbtModelCount] = useState<number>(0);
const [mlModelCount, setMlModelCount] = useState<number>(0);
const [searchResult, setSearchResult] = useState<ExploreSearchData>(); const [searchResult, setSearchResult] = useState<ExploreSearchData>();
const [showDeleted, setShowDeleted] = useState(false); const [showDeleted, setShowDeleted] = useState(false);
const [initialSortField] = useState<string>( const [initialSortField] = useState<string>(
tabsInfo[getCurrentTab(tab) - 1].sortField tabsInfo[getCurrentTab(tab) - 1].sortField
); );
const handleTabCounts = (value: { [key: string]: number }) => {
setTabCounts((prev) => ({ ...prev, ...value }));
};
const handleSearchText = (text: string) => { const handleSearchText = (text: string) => {
setSearchText(text); setSearchText(text);
}; };
const handleTableCount = (count: number) => {
setTableCount(count);
};
const handleTopicCount = (count: number) => {
setTopicCount(count);
};
const handleDashboardCount = (count: number) => {
setDashboardCount(count);
};
const handlePipelineCount = (count: number) => {
setPipelineCount(count);
};
const handleDbtModelCount = (count: number) => {
setDbtModelCount(count);
};
const handleMlModelCount = (count: number) => {
setMlModelCount(count);
};
const handlePathChange = (path: string) => { const handlePathChange = (path: string) => {
AppState.updateExplorePageTab(path); AppState.updateExplorePageTab(path);
}; };
@ -127,86 +99,42 @@ const ExplorePage: FunctionComponent = () => {
}); });
}; };
const fetchCounts = () => { const fetchEntityCount = async (indexType: SearchIndex) => {
const entities = [ const entityType = getEntityTypeByIndex(indexType);
SearchIndex.TABLE, try {
SearchIndex.TOPIC, const { data } = await searchData(
SearchIndex.DASHBOARD,
SearchIndex.PIPELINE,
SearchIndex.MLMODEL,
];
const entityCounts = entities.map((entity) =>
searchData(
searchText, searchText,
0, 0,
0, 0,
getFilterString(initialFilter), getFilterString(initialFilter),
emptyValue, emptyValue,
emptyValue, emptyValue,
entity, indexType,
showDeleted, showDeleted,
true true
) );
); const count = getTotalEntityCountByType(
data.aggregations?.['sterms#EntityType']?.buckets as Bucket[]
);
Promise.allSettled(entityCounts) setTabCounts((prev) => ({ ...prev, [entityType]: count }));
.then( } catch (_error) {
([ showErrorToast(
table, jsonData['api-error-messages']['fetch-entity-count-error']
topic, );
dashboard, }
pipeline, };
mlmodel,
]: PromiseSettledResult<SearchResponse>[]) => { const fetchCounts = () => {
setTableCount( fetchEntityCount(SearchIndex.TABLE);
table.status === 'fulfilled'
? getTotalEntityCountByType( fetchEntityCount(SearchIndex.TOPIC);
table.value.data.aggregations?.['sterms#EntityType']
?.buckets as Bucket[] fetchEntityCount(SearchIndex.DASHBOARD);
)
: 0 fetchEntityCount(SearchIndex.PIPELINE);
);
setTopicCount( fetchEntityCount(SearchIndex.MLMODEL);
topic.status === 'fulfilled'
? getTotalEntityCountByType(
topic.value.data.aggregations?.['sterms#EntityType']
?.buckets as Bucket[]
)
: 0
);
setDashboardCount(
dashboard.status === 'fulfilled'
? getTotalEntityCountByType(
dashboard.value.data.aggregations?.['sterms#EntityType']
?.buckets as Bucket[]
)
: 0
);
setPipelineCount(
pipeline.status === 'fulfilled'
? getTotalEntityCountByType(
pipeline.value.data.aggregations?.['sterms#EntityType']
?.buckets as Bucket[]
)
: 0
);
setMlModelCount(
mlmodel.status === 'fulfilled'
? getTotalEntityCountByType(
mlmodel.value.data.aggregations?.['sterms#EntityType']
?.buckets as Bucket[]
)
: 0
);
}
)
.catch((err: AxiosError) => {
showErrorToast(
err,
jsonData['api-error-messages']['fetch-entity-count-error']
);
});
}; };
const fetchData = (value: SearchDataFunctionType[]) => { const fetchData = (value: SearchDataFunctionType[]) => {
@ -262,6 +190,7 @@ const ExplorePage: FunctionComponent = () => {
useEffect(() => { useEffect(() => {
setSearchResult(undefined); setSearchResult(undefined);
fetchData([ fetchData([
{ {
queryString: searchText, queryString: searchText,
@ -303,42 +232,28 @@ const ExplorePage: FunctionComponent = () => {
}, []); }, []);
return ( return (
<Fragment> <PageContainerV1>
<PageContainerV1> <Explore
<Explore error={error}
error={error} fetchCount={fetchCounts}
fetchCount={fetchCounts} fetchData={fetchData}
fetchData={fetchData} handleFilterChange={handleFilterChange}
handleFilterChange={handleFilterChange} handlePathChange={handlePathChange}
handlePathChange={handlePathChange} handleSearchText={handleSearchText}
handleSearchText={handleSearchText} handleTabCounts={handleTabCounts}
initialFilter={initialFilter} initialFilter={initialFilter}
isFilterSelected={!isEmpty(searchFilter) || !isEmpty(initialFilter)} isFilterSelected={!isEmpty(searchFilter) || !isEmpty(initialFilter)}
searchFilter={searchFilter} searchFilter={searchFilter}
searchQuery={searchQuery} searchQuery={searchQuery}
searchResult={searchResult} searchResult={searchResult}
searchText={searchText} searchText={searchText}
showDeleted={showDeleted} showDeleted={showDeleted}
sortValue={initialSortField} sortValue={initialSortField}
tab={tab} tab={tab}
tabCounts={{ tabCounts={tabCounts}
table: tableCount, onShowDeleted={(checked) => setShowDeleted(checked)}
topic: topicCount, />
dashboard: dashboardCount, </PageContainerV1>
pipeline: pipelineCount,
dbtModel: dbtModelCount,
mlModel: mlModelCount,
}}
updateDashboardCount={handleDashboardCount}
updateDbtModelCount={handleDbtModelCount}
updateMlModelCount={handleMlModelCount}
updatePipelineCount={handlePipelineCount}
updateTableCount={handleTableCount}
updateTopicCount={handleTopicCount}
onShowDeleted={(checked) => setShowDeleted(checked)}
/>
</PageContainerV1>
</Fragment>
); );
}; };

View File

@ -17,7 +17,6 @@ import React, { useState } from 'react';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import appState from '../../AppState'; import appState from '../../AppState';
import { useAuthContext } from '../../authentication/auth-provider/AuthProvider'; import { useAuthContext } from '../../authentication/auth-provider/AuthProvider';
import { getLoggedInUserPermissions } from '../../axiosAPIs/miscAPI';
import { createUser } from '../../axiosAPIs/userAPI'; import { createUser } from '../../axiosAPIs/userAPI';
import { Button } from '../../components/buttons/Button/Button'; import { Button } from '../../components/buttons/Button/Button';
import PageContainer from '../../components/containers/PageContainer'; import PageContainer from '../../components/containers/PageContainer';
@ -30,7 +29,6 @@ import { getNameFromEmail } from '../../utils/AuthProvider.util';
import { getImages } from '../../utils/CommonUtils'; import { getImages } from '../../utils/CommonUtils';
import SVGIcons, { Icons } from '../../utils/SvgUtils'; import SVGIcons, { Icons } from '../../utils/SvgUtils';
import { showErrorToast } from '../../utils/ToastUtils'; import { showErrorToast } from '../../utils/ToastUtils';
import { fetchAllUsers } from '../../utils/UserDataUtils';
const cookieStorage = new CookieStorage(); const cookieStorage = new CookieStorage();
@ -46,31 +44,12 @@ const Signup = () => {
const history = useHistory(); const history = useHistory();
const getUserPermissions = () => {
getLoggedInUserPermissions()
.then((res) => {
if (res.data) {
appState.updateUserPermissions(res.data);
} else {
throw jsonData['api-error-messages']['unexpected-server-response'];
}
})
.catch((err: AxiosError) => {
showErrorToast(
err,
jsonData['api-error-messages']['fetch-user-permission-error']
);
});
};
const createNewUser = (details: User | CreateUser) => { const createNewUser = (details: User | CreateUser) => {
setLoading(true); setLoading(true);
createUser(details as CreateUser) createUser(details as CreateUser)
.then((res) => { .then((res) => {
if (res) { if (res) {
appState.updateUserDetails(res); appState.updateUserDetails(res);
fetchAllUsers();
getUserPermissions();
cookieStorage.removeItem(REDIRECT_PATHNAME); cookieStorage.removeItem(REDIRECT_PATHNAME);
setIsSigningIn(false); setIsSigningIn(false);
history.push(ROUTES.HOME); history.push(ROUTES.HOME);

View File

@ -48,7 +48,7 @@ const exploreCount = {
dashboard: 0, dashboard: 0,
pipeline: 0, pipeline: 0,
dbtModel: 0, dbtModel: 0,
mlModel: 0, mlmodel: 0,
}; };
const TourPage = () => { const TourPage = () => {
@ -164,6 +164,7 @@ const TourPage = () => {
handleFilterChange={handleFilterChange} handleFilterChange={handleFilterChange}
handlePathChange={handleCountChange} handlePathChange={handleCountChange}
handleSearchText={() => setExploreSearchResult(exploreSearchData)} handleSearchText={() => setExploreSearchResult(exploreSearchData)}
handleTabCounts={handleCountChange}
searchQuery="" searchQuery=""
searchResult={exploreSearchResult as unknown as ExploreSearchData} searchResult={exploreSearchResult as unknown as ExploreSearchData}
searchText="" searchText=""
@ -171,12 +172,6 @@ const TourPage = () => {
sortValue="" sortValue=""
tab="" tab=""
tabCounts={explorePageCounts} tabCounts={explorePageCounts}
updateDashboardCount={handleCountChange}
updateDbtModelCount={handleCountChange}
updateMlModelCount={handleCountChange}
updatePipelineCount={handleCountChange}
updateTableCount={handleCountChange}
updateTopicCount={handleCountChange}
onShowDeleted={() => { onShowDeleted={() => {
return; return;
}} }}

View File

@ -15,6 +15,7 @@ import { isEmpty, isNil, isUndefined, startCase } from 'lodash';
import { Bucket, LeafNodes, LineagePos } from 'Models'; import { Bucket, LeafNodes, LineagePos } from 'Models';
import React from 'react'; import React from 'react';
import { EntityData } from '../components/common/PopOverCard/EntityPopOverCard'; import { EntityData } from '../components/common/PopOverCard/EntityPopOverCard';
import { ResourceEntity } from '../components/PermissionProvider/PermissionProvider.interface';
import TableProfilerGraph from '../components/TableProfiler/TableProfilerGraph.component'; import TableProfilerGraph from '../components/TableProfiler/TableProfilerGraph.component';
import { FQN_SEPARATOR_CHAR } from '../constants/char.constants'; import { FQN_SEPARATOR_CHAR } from '../constants/char.constants';
import { import {
@ -23,6 +24,7 @@ import {
getTeamAndUserDetailsPath, getTeamAndUserDetailsPath,
} from '../constants/constants'; } from '../constants/constants';
import { AssetsType, EntityType, FqnPart } from '../enums/entity.enum'; import { AssetsType, EntityType, FqnPart } from '../enums/entity.enum';
import { SearchIndex } from '../enums/search.enum';
import { ServiceCategory } from '../enums/service.enum'; import { ServiceCategory } from '../enums/service.enum';
import { PrimaryTableDataTypes } from '../enums/table.enum'; import { PrimaryTableDataTypes } from '../enums/table.enum';
import { Dashboard } from '../generated/entity/data/dashboard'; import { Dashboard } from '../generated/entity/data/dashboard';
@ -443,3 +445,29 @@ export const filterEntityAssets = (data: EntityReference[]) => {
return data.filter((d) => includedEntity.includes(d.type as AssetsType)); return data.filter((d) => includedEntity.includes(d.type as AssetsType));
}; };
export const getResourceEntityFromEntityType = (entityType: string) => {
switch (entityType) {
case EntityType.TABLE:
case SearchIndex.TABLE:
return ResourceEntity.TABLE;
case EntityType.TOPIC:
case SearchIndex.TOPIC:
return ResourceEntity.TOPIC;
case EntityType.DASHBOARD:
case SearchIndex.DASHBOARD:
return ResourceEntity.DASHBOARD;
case EntityType.PIPELINE:
case SearchIndex.PIPELINE:
return ResourceEntity.PIPELINE;
case EntityType.MLMODEL:
case SearchIndex.MLMODEL:
return ResourceEntity.ML_MODEL;
}
return ResourceEntity.ALL;
};

View File

@ -13,9 +13,231 @@
import { ItemType } from 'antd/lib/menu/hooks/useItems'; import { ItemType } from 'antd/lib/menu/hooks/useItems';
import { camelCase } from 'lodash'; import { camelCase } from 'lodash';
import React from 'react'; import React, { ReactNode } from 'react';
import { ReactComponent as BotIcon } from '../../src/assets/svg/bot-profile.svg';
import { ReactComponent as DashboardIcon } from '../../src/assets/svg/dashboard-grey.svg';
import { ReactComponent as RolesIcon } from '../../src/assets/svg/icon-role-grey.svg';
import { ReactComponent as MlModelIcon } from '../../src/assets/svg/mlmodal.svg';
import { ReactComponent as PipelineIcon } from '../../src/assets/svg/pipeline-grey.svg';
import { ReactComponent as PoliciesIcon } from '../../src/assets/svg/policies.svg';
import { ReactComponent as SlackIcon } from '../../src/assets/svg/slack.svg';
import { ReactComponent as TableIcon } from '../../src/assets/svg/table-grey.svg';
import { ReactComponent as TeamsIcon } from '../../src/assets/svg/teams-grey.svg';
import { ReactComponent as TopicIcon } from '../../src/assets/svg/topic-grey.svg';
import { ReactComponent as UsersIcon } from '../../src/assets/svg/user.svg';
import { ReactComponent as WebhookIcon } from '../../src/assets/svg/webhook-grey.svg';
import {
ResourceEntity,
UIPermission,
} from '../components/PermissionProvider/PermissionProvider.interface';
import { Operation } from '../generated/entity/policies/accessControl/rule';
import { checkPermission } from '../utils/PermissionsUtils';
export const getGlobalSettingMenus = ( export interface MenuListItem {
label: string;
isProtected: boolean;
icon: ReactNode;
}
export interface MenuList {
category: string;
items: MenuListItem[];
}
export const getGlobalSettingsMenuWithPermission = (
permissions: UIPermission
) => {
return [
{
category: 'Members',
items: [
{
label: 'Teams',
isProtected: checkPermission(
Operation.ViewAll,
ResourceEntity.TEAM,
permissions
),
icon: <TeamsIcon className="side-panel-icons" />,
},
{
label: 'Users',
isProtected: checkPermission(
Operation.ViewAll,
ResourceEntity.USER,
permissions
),
icon: <UsersIcon className="side-panel-icons" />,
},
{
label: 'Admins',
isProtected: checkPermission(
Operation.ViewAll,
ResourceEntity.USER,
permissions
),
icon: <UsersIcon className="side-panel-icons" />,
},
],
},
{
category: 'Access',
items: [
{
label: 'Roles',
isProtected: checkPermission(
Operation.ViewAll,
ResourceEntity.ROLE,
permissions
),
icon: <RolesIcon className="side-panel-icons" />,
},
{
label: 'Policies',
isProtected: checkPermission(
Operation.ViewAll,
ResourceEntity.POLICY,
permissions
),
icon: <PoliciesIcon className="side-panel-icons" />,
},
],
},
{
category: 'Services',
items: [
{
label: 'Databases',
isProtected: checkPermission(
Operation.ViewAll,
ResourceEntity.DATABASE_SERVICE,
permissions
),
icon: <TableIcon className="side-panel-icons" />,
},
{
label: 'Messaging',
isProtected: checkPermission(
Operation.ViewAll,
ResourceEntity.MESSAGING_SERVICE,
permissions
),
icon: <TopicIcon className="side-panel-icons" />,
},
{
label: 'Dashboards',
isProtected: checkPermission(
Operation.ViewAll,
ResourceEntity.DASHBOARD_SERVICE,
permissions
),
icon: <DashboardIcon className="side-panel-icons" />,
},
{
label: 'Pipelines',
isProtected: checkPermission(
Operation.ViewAll,
ResourceEntity.PIPELINE_SERVICE,
permissions
),
icon: <PipelineIcon className="side-panel-icons" />,
},
{
label: 'ML Models',
isProtected: checkPermission(
Operation.ViewAll,
ResourceEntity.ML_MODEL_SERVICE,
permissions
),
icon: <MlModelIcon className="side-panel-icons" />,
},
],
},
{
category: 'Custom Attributes',
items: [
{
label: 'Tables',
isProtected: checkPermission(
Operation.ViewAll,
ResourceEntity.TYPE,
permissions
),
icon: <TableIcon className="side-panel-icons" />,
},
{
label: 'Topics',
isProtected: checkPermission(
Operation.ViewAll,
ResourceEntity.TYPE,
permissions
),
icon: <TopicIcon className="side-panel-icons" />,
},
{
label: 'Dashboards',
isProtected: checkPermission(
Operation.ViewAll,
ResourceEntity.TYPE,
permissions
),
icon: <DashboardIcon className="side-panel-icons" />,
},
{
label: 'Pipelines',
isProtected: checkPermission(
Operation.ViewAll,
ResourceEntity.TYPE,
permissions
),
icon: <PipelineIcon className="side-panel-icons" />,
},
{
label: 'ML Models',
isProtected: checkPermission(
Operation.ViewAll,
ResourceEntity.TYPE,
permissions
),
icon: <MlModelIcon className="side-panel-icons" />,
},
],
},
{
category: 'Integrations',
items: [
{
label: 'Webhook',
isProtected: checkPermission(
Operation.ViewAll,
ResourceEntity.WEBHOOK,
permissions
),
icon: <WebhookIcon className="tw-w-4 side-panel-icons" />,
},
{
label: 'Slack',
isProtected: checkPermission(
Operation.ViewAll,
ResourceEntity.WEBHOOK,
permissions
),
icon: <SlackIcon className="tw-w-4 side-panel-icons" />,
},
{
label: 'Bots',
isProtected: checkPermission(
Operation.ViewAll,
ResourceEntity.BOT,
permissions
),
icon: <BotIcon className="tw-w-4 side-panel-icons" />,
},
],
},
];
};
export const getGlobalSettingMenuItem = (
label: string, label: string,
key: string, key: string,
category?: string, category?: string,
@ -25,8 +247,7 @@ export const getGlobalSettingMenus = (
isProtected: boolean; isProtected: boolean;
icon: React.ReactNode; icon: React.ReactNode;
}[], }[],
type?: string, type?: string
hasAccess?: boolean
): { ): {
key: string; key: string;
icon: React.ReactNode; icon: React.ReactNode;
@ -34,16 +255,18 @@ export const getGlobalSettingMenus = (
label: string; label: string;
type: string | undefined; type: string | undefined;
} => { } => {
const subItems = children
? children
.filter((menu) => menu.isProtected)
.map(({ label, icon }) => {
return getGlobalSettingMenuItem(label, camelCase(label), key, icon);
})
: undefined;
return { return {
key: `${category}.${key}`, key: `${category}.${key}`,
icon, icon,
children: children children: subItems,
? children
.filter((menu) => (hasAccess ? menu : !menu.isProtected))
.map(({ label, icon }) => {
return getGlobalSettingMenus(label, camelCase(label), key, icon);
})
: undefined,
label, label,
type, type,
}; };