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 appState from '../../AppState';
import axiosClient from '../../axiosAPIs';
import {
fetchAuthenticationConfig,
getLoggedInUserPermissions,
} from '../../axiosAPIs/miscAPI';
import { fetchAuthenticationConfig } from '../../axiosAPIs/miscAPI';
import {
getLoggedInUser,
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 = () => {
setLoading(true);
getLoggedInUser(userAPIQueryFields)
.then((res) => {
if (res) {
getUserPermissions();
appState.updateUserDetails(res);
fetchAllUsers();
} else {
resetUserDetails();
setLoading(false);
}
})
.catch((err: AxiosError) => {
@ -204,6 +183,9 @@ export const AuthProvider = ({
jsonData['api-error-messages']['fetch-logged-in-user-error']
);
}
})
.finally(() => {
setLoading(false);
});
};
@ -368,8 +350,6 @@ export const AuthProvider = ({
} else {
appState.updateUserDetails(res);
}
getUserPermissions();
fetchAllUsers();
handledVerifiedUser();
// Start expiry timer on successful login
startTokenExpiryTimer();
@ -439,8 +419,6 @@ export const AuthProvider = ({
storeRedirectPath();
showErrorToast(error);
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 { AirflowConfiguration } from '../generated/configuration/airflowConfiguration';
import { AuthenticationConfiguration } from '../generated/configuration/authenticationConfiguration';
import { ResourcePermission } from '../generated/entity/policies/accessControl/resourcePermission';
import { EntitiesCount } from '../generated/entity/utils/entitiesCount';
import { Paging } from '../generated/type/paging';
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 = (
index: SearchIndex,
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 { Operation } from 'fast-json-patch';
import { ResourceEntity } from '../components/PermissionProvider/PermissionProvider.interface';
import { CreatePolicy } from '../generated/api/policies/createPolicy';
import { CreateRole } from '../generated/api/teams/createRole';
import { ResourceDescriptor } from '../generated/entity/policies/accessControl/resourceDescriptor';
import { ResourcePermission } from '../generated/entity/policies/accessControl/resourcePermission';
import { Policy } from '../generated/entity/policies/policy';
import { Role } from '../generated/entity/teams/role';
import { Function } from '../generated/type/function';
@ -156,14 +154,3 @@ export const validateRuleCondition = async (condition: string) => {
*/
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 { AxiosError } from 'axios';
import classNames from 'classnames';
import { isNil } from 'lodash';
import { isEmpty, isNil } from 'lodash';
import moment from 'moment';
import React, {
FC,
Fragment,
HTMLAttributes,
useEffect,
useMemo,
useState,
} from 'react';
import Select, { SingleValue } from 'react-select';
@ -77,21 +78,32 @@ const BotDetails: FC<BotsDetailProp> = ({
const [generateToken, setGenerateToken] = useState<boolean>(false);
const [selectedExpiry, setSelectedExpiry] = useState('7');
const editAllPermission = checkPermission(
Operation.EditAll,
ResourceEntity.BOT,
permissions
const editAllPermission = useMemo(
() =>
!isEmpty(permissions) &&
checkPermission(Operation.EditAll, ResourceEntity.BOT, permissions),
[permissions]
);
const displayNamePermission = checkPermission(
Operation.EditDisplayName,
ResourceEntity.BOT,
permissions
const displayNamePermission = useMemo(
() =>
!isEmpty(permissions) &&
checkPermission(
Operation.EditDisplayName,
ResourceEntity.BOT,
permissions
),
[permissions]
);
const descriptionPermission = checkPermission(
Operation.EditDescription,
ResourceEntity.BOT,
permissions
const descriptionPermission = useMemo(
() =>
!isEmpty(permissions) &&
checkPermission(
Operation.EditDescription,
ResourceEntity.BOT,
permissions
),
[permissions]
);
const getJWTTokenExpiryOptions = () => {

View File

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

View File

@ -89,12 +89,8 @@ const Explore: React.FC<ExploreProps> = ({
fetchData,
showDeleted,
onShowDeleted,
updateTableCount,
updateTopicCount,
updateDashboardCount,
updatePipelineCount,
isFilterSelected,
updateMlModelCount,
handleTabCounts,
}: ExploreProps) => {
const location = useLocation();
const history = useHistory();
@ -273,23 +269,23 @@ const Explore: React.FC<ExploreProps> = ({
const setCount = (count = 0, index = searchIndex) => {
switch (index) {
case SearchIndex.TABLE:
updateTableCount(count);
handleTabCounts({ table: count });
break;
case SearchIndex.DASHBOARD:
updateDashboardCount(count);
handleTabCounts({ dashboard: count });
break;
case SearchIndex.TOPIC:
updateTopicCount(count);
handleTabCounts({ topic: count });
break;
case SearchIndex.PIPELINE:
updatePipelineCount(count);
handleTabCounts({ pipeline: count });
break;
case SearchIndex.MLMODEL:
updateMlModelCount(count);
handleTabCounts({ mlmodel: count });
break;
default:
@ -441,7 +437,7 @@ const Explore: React.FC<ExploreProps> = ({
case SearchIndex.PIPELINE:
return getCountBadge(tabCounts.pipeline, className, isActive);
case SearchIndex.MLMODEL:
return getCountBadge(tabCounts.mlModel, className, isActive);
return getCountBadge(tabCounts.mlmodel, className, isActive);
default:
return getCountBadge();
}
@ -469,18 +465,6 @@ const Explore: React.FC<ExploreProps> = ({
'tw-flex tw-flex-row tw-justify-between tw-gh-tabs-container'
)}>
<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>
{tabsInfo.map((tabDetail, index) => (
<button

View File

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

View File

@ -29,14 +29,7 @@ export type ExploreSearchData = {
};
export interface ExploreProps {
tabCounts: {
table: number;
topic: number;
dashboard: number;
pipeline: number;
dbtModel: number;
mlModel: number;
};
tabCounts: TabCounts;
searchText: string;
initialFilter?: FilterObject;
searchFilter?: FilterObject;
@ -51,17 +44,20 @@ export interface ExploreProps {
handleFilterChange: (data: FilterObject) => void;
handlePathChange: (path: 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;
onShowDeleted: (checked: boolean) => void;
handleTabCounts: (value: { [key: string]: number }) => void;
}
export interface AdvanceField {
key: string;
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 { ItemType } from 'antd/lib/menu/hooks/useItems';
import { AxiosError } from 'axios';
import { camelCase } from 'lodash';
import React from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import { useHistory, useParams } from 'react-router-dom';
import { GLOBAL_SETTINGS_MENU } from '../../constants/globalSettings.constants';
import { Operation } from '../../generated/entity/policies/accessControl/rule';
import { getGlobalSettingMenus } from '../../utils/GlobalSettingsUtils';
import { checkPermission } from '../../utils/PermissionsUtils';
import { GLOBAL_SETTING_PERMISSION_RESOURCES } from '../../constants/globalSettings.constants';
import {
getGlobalSettingMenuItem,
getGlobalSettingsMenuWithPermission,
MenuList,
} from '../../utils/GlobalSettingsUtils';
import { getSettingPath } from '../../utils/RouterUtils';
import { showErrorToast } from '../../utils/ToastUtils';
import Loader from '../Loader/Loader';
import { usePermissionProvider } from '../PermissionProvider/PermissionProvider';
import { ResourceEntity } from '../PermissionProvider/PermissionProvider.interface';
import {
ResourceEntity,
UIPermission,
} from '../PermissionProvider/PermissionProvider.interface';
const GlobalSettingLeftPanel = () => {
const { tab, settingCategory } = useParams<{ [key: string]: string }>();
const { permissions } = usePermissionProvider();
const viewAllPermission = checkPermission(
Operation.ViewAll,
ResourceEntity.ALL,
permissions
);
const history = useHistory();
const items: ItemType[] = GLOBAL_SETTINGS_MENU.filter(({ isProtected }) => {
if (viewAllPermission) {
return viewAllPermission;
}
const { tab, settingCategory } = useParams<{ [key: string]: string }>();
const [settingResourcePermission, setSettingResourcePermission] =
useState<UIPermission>({} as UIPermission);
return !isProtected;
}).map(({ category, items }) => {
return getGlobalSettingMenus(
category,
camelCase(category),
'',
'',
items,
'group',
viewAllPermission
);
});
const [isLoading, setIsLoading] = useState<boolean>(true);
const { getResourcePermission } = usePermissionProvider();
const fetchResourcesPermission = async (resource: ResourceEntity) => {
setIsLoading(true);
try {
const response = await getResourcePermission(resource);
setSettingResourcePermission((prev) => ({
...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) => {
// 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));
};
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 (
<Menu
className="global-setting-left-panel"
items={items}
mode="inline"
selectedKeys={[`${settingCategory}.${tab}`]}
onClick={onClick}
/>
<>
{menuItems.length ? (
<Menu
className="global-setting-left-panel"
items={menuItems}
mode="inline"
selectedKeys={[`${settingCategory}.${tab}`]}
onClick={onClick}
/>
) : null}
</>
);
};

View File

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

View File

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

View File

@ -13,6 +13,8 @@
import { toLower } from 'lodash';
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 { getFilterKey } from '../utils/FilterUtils';
import { Icons } from '../utils/SvgUtils';
@ -301,3 +303,27 @@ export const tabsInfo = [
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.
*/
import React 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 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" />,
},
],
},
];
import { ResourceEntity } from '../components/PermissionProvider/PermissionProvider.interface';
export const customAttributesPath = {
tables: 'table',
@ -187,3 +46,18 @@ export enum GlobalSettingOptions {
TABLES = 'tables',
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 Loader from '../../components/Loader/Loader';
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 { SOCKET_EVENTS } from '../../constants/constants';
import { AssetsType } from '../../enums/entity.enum';
import { FeedFilter } from '../../enums/mydata.enum';
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 { Paging } from '../../generated/type/paging';
import { useAuth } from '../../hooks/authHooks';
import jsonData from '../../jsons/en';
import { deletePost, updateThreadData } from '../../utils/FeedUtils';
import { checkPermission } from '../../utils/PermissionsUtils';
import { showErrorToast } from '../../utils/ToastUtils';
const MyDataPage = () => {
const location = useLocation();
const { permissions } = usePermissionProvider();
const { isAuthDisabled } = useAuth(location.pathname);
const [error, setError] = useState<string>('');
const [entityCounts, setEntityCounts] = useState<EntitiesCount>(
@ -72,6 +77,13 @@ const MyDataPage = () => {
[AppState.userDetails, AppState.nonSecureUserDetails]
);
const viewUserPermission = useMemo(() => {
return (
!isEmpty(permissions) &&
checkPermission(RuleOperation.ViewAll, ResourceEntity.USER, permissions)
);
}, [permissions]);
const fetchEntityCount = () => {
getAllEntityCount()
.then((res) => {
@ -258,7 +270,7 @@ const MyDataPage = () => {
!isEmpty(AppState.userDetails)) &&
(isNil(ownedData) || isNil(followedData))
) {
fetchMyData();
viewUserPermission && fetchMyData();
}
}, [AppState.userDetails, AppState.users, isAuthDisabled]);

View File

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

View File

@ -17,7 +17,6 @@ import React, { useState } from 'react';
import { useHistory } from 'react-router-dom';
import appState from '../../AppState';
import { useAuthContext } from '../../authentication/auth-provider/AuthProvider';
import { getLoggedInUserPermissions } from '../../axiosAPIs/miscAPI';
import { createUser } from '../../axiosAPIs/userAPI';
import { Button } from '../../components/buttons/Button/Button';
import PageContainer from '../../components/containers/PageContainer';
@ -30,7 +29,6 @@ import { getNameFromEmail } from '../../utils/AuthProvider.util';
import { getImages } from '../../utils/CommonUtils';
import SVGIcons, { Icons } from '../../utils/SvgUtils';
import { showErrorToast } from '../../utils/ToastUtils';
import { fetchAllUsers } from '../../utils/UserDataUtils';
const cookieStorage = new CookieStorage();
@ -46,31 +44,12 @@ const Signup = () => {
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) => {
setLoading(true);
createUser(details as CreateUser)
.then((res) => {
if (res) {
appState.updateUserDetails(res);
fetchAllUsers();
getUserPermissions();
cookieStorage.removeItem(REDIRECT_PATHNAME);
setIsSigningIn(false);
history.push(ROUTES.HOME);

View File

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

View File

@ -15,6 +15,7 @@ import { isEmpty, isNil, isUndefined, startCase } from 'lodash';
import { Bucket, LeafNodes, LineagePos } from 'Models';
import React from 'react';
import { EntityData } from '../components/common/PopOverCard/EntityPopOverCard';
import { ResourceEntity } from '../components/PermissionProvider/PermissionProvider.interface';
import TableProfilerGraph from '../components/TableProfiler/TableProfilerGraph.component';
import { FQN_SEPARATOR_CHAR } from '../constants/char.constants';
import {
@ -23,6 +24,7 @@ import {
getTeamAndUserDetailsPath,
} from '../constants/constants';
import { AssetsType, EntityType, FqnPart } from '../enums/entity.enum';
import { SearchIndex } from '../enums/search.enum';
import { ServiceCategory } from '../enums/service.enum';
import { PrimaryTableDataTypes } from '../enums/table.enum';
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));
};
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 { 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,
key: string,
category?: string,
@ -25,8 +247,7 @@ export const getGlobalSettingMenus = (
isProtected: boolean;
icon: React.ReactNode;
}[],
type?: string,
hasAccess?: boolean
type?: string
): {
key: string;
icon: React.ReactNode;
@ -34,16 +255,18 @@ export const getGlobalSettingMenus = (
label: string;
type: string | undefined;
} => {
const subItems = children
? children
.filter((menu) => menu.isProtected)
.map(({ label, icon }) => {
return getGlobalSettingMenuItem(label, camelCase(label), key, icon);
})
: undefined;
return {
key: `${category}.${key}`,
icon,
children: children
? children
.filter((menu) => (hasAccess ? menu : !menu.isProtected))
.map(({ label, icon }) => {
return getGlobalSettingMenus(label, camelCase(label), key, icon);
})
: undefined,
children: subItems,
label,
type,
};