Refactor(UI): Global settings Teams page refactoring and roles and policies update. (#7032)

* - Made organisation page default page for global settings page for teams option
- Unnecessary actions for organisation removed

* Added support for new permissions API in teams page

* Replaced hard coded path with existing util function.

* Worked on comments

* Fixed minor bug and cypress test

* fixed failing cypress tests
This commit is contained in:
Aniket Katkar 2022-08-31 00:11:02 +05:30 committed by GitHub
parent a39c4db8e7
commit 6547beeffc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 360 additions and 155 deletions

View File

@ -11,7 +11,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { addNewTagToEntity } from '../../common/common'; import { searchEntity } from '../../common/common';
import { DELETE_TERM, NEW_GLOSSARY, NEW_GLOSSARY_TERMS, SEARCH_ENTITY_TABLE } from '../../constants/constants'; import { DELETE_TERM, NEW_GLOSSARY, NEW_GLOSSARY_TERMS, SEARCH_ENTITY_TABLE } from '../../constants/constants';
const createGlossaryTerm = (term) => { const createGlossaryTerm = (term) => {
@ -227,6 +227,11 @@ describe('Glossary page should work properly', () => {
.as('synonyms'); .as('synonyms');
cy.get('@synonyms').clear(); cy.get('@synonyms').clear();
cy.get('@synonyms').type(uSynonyms); cy.get('@synonyms').type(uSynonyms);
cy.intercept({ method: 'PATCH', url: '/api/v1/glossaryTerms/*' }).as(
'getGlossary'
);
cy.get('[data-testid="saveAssociatedTag"]').should('be.visible').click(); cy.get('[data-testid="saveAssociatedTag"]').should('be.visible').click();
cy.wait(100); cy.wait(100);
cy.get('[data-testid="synonyms-container"]') cy.get('[data-testid="synonyms-container"]')
@ -237,6 +242,8 @@ describe('Glossary page should work properly', () => {
cy.get('@synonyms-container').contains(synonym).should('be.visible'); cy.get('@synonyms-container').contains(synonym).should('be.visible');
}); });
cy.wait('@getGlossary').its('response.statusCode').should('eq', 200);
// updating References // updating References
cy.get('[data-testid="section-references"] [data-testid="add-button"]') cy.get('[data-testid="section-references"] [data-testid="add-button"]')
.should('exist') .should('exist')
@ -316,6 +323,7 @@ describe('Glossary page should work properly', () => {
}); });
it('Assets Tab should work properly', () => { it('Assets Tab should work properly', () => {
const glossary = NEW_GLOSSARY.name;
const term = NEW_GLOSSARY_TERMS.term_1.name; const term = NEW_GLOSSARY_TERMS.term_1.name;
const entity = SEARCH_ENTITY_TABLE.table_3.term; const entity = SEARCH_ENTITY_TABLE.table_3.term;
goToAssetsTab(term); goToAssetsTab(term);
@ -323,7 +331,46 @@ describe('Glossary page should work properly', () => {
.contains('No assets available.') .contains('No assets available.')
.should('be.visible'); .should('be.visible');
addNewTagToEntity(entity, term); searchEntity(entity);
cy.wait(500);
cy.get('[data-testid="table-link"]').first().contains(entity).click();
//Add tag to breadcrumb
cy.get('[data-testid="tag-container"] [data-testid="tags"]')
.eq(0)
.should('be.visible')
.click();
cy.get('[class*="-control"]').should('be.visible').type(term);
cy.wait(500);
cy.get('[id*="-option-0"]').should('be.visible').click();
cy.get(
'[data-testid="tags-wrapper"] [data-testid="tag-container"]'
).contains(term);
cy.get('[data-testid="saveAssociatedTag"]').should('be.visible').click();
cy.wait(1000);
cy.get('[data-testid="entity-tags"]')
.scrollIntoView()
.should('be.visible')
.contains(term);
//Add tag to schema table
cy.get('[data-testid="tag-container"] [data-testid="tags"]')
.eq(0)
.should('be.visible')
.click();
cy.get('[class*="-control"]').should('be.visible').type(term);
cy.wait(500);
cy.get('[id*="-option-0"]').should('be.visible').click();
cy.get(
'[data-testid="tags-wrapper"] [data-testid="tag-container"]'
).contains(term);
cy.get('[data-testid="saveAssociatedTag"]').should('be.visible').click();
cy.wait(1000);
cy.get(`[data-testid="tag-${glossary}.${term}"]`)
.scrollIntoView()
.should('be.visible')
.contains(term);
cy.get('[data-testid="appbar-item-glossary"]') cy.get('[data-testid="appbar-item-glossary"]')
.should('exist') .should('exist')
@ -353,34 +400,17 @@ describe('Glossary page should work properly', () => {
.scrollIntoView() .scrollIntoView()
.should('be.visible') .should('be.visible')
.click(); .click();
cy.get(':nth-child(1) > .css-xb97g8') cy.get('[role="button"]').eq(0).should('be.visible').click();
.scrollIntoView() cy.get('[role="button"]').eq(0).should('be.visible').click();
.should('be.visible')
.click();
cy.get(':nth-child(1) > .css-xb97g8')
.scrollIntoView()
.should('be.visible')
.click();
cy.get('[data-testid="saveAssociatedTag"]').scrollIntoView().click(); cy.get('[data-testid="saveAssociatedTag"]').scrollIntoView().click();
//Remove the added column tag from entity //Remove the added column tag from entity
cy.get(
':nth-child(1) > :nth-child(5) span.tw-text-primary > [data-testid="tags"]'
)
.scrollIntoView()
.should('be.visible')
.click();
cy.get(':nth-child(1) > .css-xb97g8') cy.get('[data-testid="remove"]').eq(0).should('be.visible').click();
.scrollIntoView()
.should('be.visible') cy.wait(500);
.click(); cy.get('[data-testid="remove"]').eq(0).should('be.visible').click();
cy.get(':nth-child(1) > .css-xb97g8')
.scrollIntoView()
.should('be.visible')
.click();
cy.get('[data-testid="saveAssociatedTag"]').scrollIntoView().click();
cy.get('[data-testid="appbar-item-glossary"]') cy.get('[data-testid="appbar-item-glossary"]')
.should('exist') .should('exist')

View File

@ -16,12 +16,14 @@ import { ItemType } from 'antd/lib/menu/hooks/useItems';
import { camelCase } from 'lodash'; import { camelCase } from 'lodash';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { useHistory, useParams } from 'react-router-dom'; import { useHistory, useParams } from 'react-router-dom';
import { GlobalSettingOptions } from '../../constants/globalSettings.constants';
import { TeamType } from '../../generated/entity/teams/team';
import { import {
getGlobalSettingMenuItem, getGlobalSettingMenuItem,
getGlobalSettingsMenuWithPermission, getGlobalSettingsMenuWithPermission,
MenuList, MenuList,
} from '../../utils/GlobalSettingsUtils'; } from '../../utils/GlobalSettingsUtils';
import { getSettingPath } from '../../utils/RouterUtils'; import { getSettingPath, getTeamsWithFqnPath } from '../../utils/RouterUtils';
import { usePermissionProvider } from '../PermissionProvider/PermissionProvider'; import { usePermissionProvider } from '../PermissionProvider/PermissionProvider';
const GlobalSettingLeftPanel = () => { const GlobalSettingLeftPanel = () => {
@ -56,7 +58,11 @@ const GlobalSettingLeftPanel = () => {
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
const [category, option] = e.key.split('.'); const [category, option] = e.key.split('.');
history.push(getSettingPath(category, option)); if (option === GlobalSettingOptions.TEAMS) {
history.push(getTeamsWithFqnPath(TeamType.Organization));
} else {
history.push(getSettingPath(category, option));
}
}; };
return menuItems.length ? ( return menuItems.length ? (

View File

@ -15,6 +15,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { import {
Button as ButtonAntd, Button as ButtonAntd,
Col, Col,
Empty,
Modal, Modal,
Row, Row,
Space, Space,
@ -24,6 +25,7 @@ import {
Typography, Typography,
} from 'antd'; } from 'antd';
import { ColumnsType } from 'antd/lib/table'; import { ColumnsType } from 'antd/lib/table';
import { AxiosError } from 'axios';
import classNames from 'classnames'; import classNames from 'classnames';
import { compare } from 'fast-json-patch'; import { compare } from 'fast-json-patch';
import { cloneDeep, isEmpty, isUndefined, orderBy } from 'lodash'; import { cloneDeep, isEmpty, isUndefined, orderBy } from 'lodash';
@ -35,13 +37,15 @@ import {
getTeamAndUserDetailsPath, getTeamAndUserDetailsPath,
getUserPath, getUserPath,
PAGE_SIZE, PAGE_SIZE,
TITLE_FOR_NON_ADMIN_ACTION,
TITLE_FOR_NON_OWNER_ACTION,
} from '../../constants/constants'; } from '../../constants/constants';
import { import {
GlobalSettingOptions, GlobalSettingOptions,
GlobalSettingsMenuCategory, GlobalSettingsMenuCategory,
} from '../../constants/globalSettings.constants'; } from '../../constants/globalSettings.constants';
import {
NO_PERMISSION_FOR_ACTION,
NO_PERMISSION_TO_VIEW,
} from '../../constants/HelperTextUtil';
import { EntityType } from '../../enums/entity.enum'; import { EntityType } from '../../enums/entity.enum';
import { OwnerType } from '../../enums/user.enum'; import { OwnerType } from '../../enums/user.enum';
import { Operation } from '../../generated/entity/policies/policy'; import { Operation } from '../../generated/entity/policies/policy';
@ -51,8 +55,8 @@ import {
User, User,
} from '../../generated/entity/teams/user'; } from '../../generated/entity/teams/user';
import { EntityReference } from '../../generated/type/entityReference'; import { EntityReference } from '../../generated/type/entityReference';
import { useAuth } from '../../hooks/authHooks';
import { TeamDetailsProp } from '../../interface/teamsAndUsers.interface'; import { TeamDetailsProp } from '../../interface/teamsAndUsers.interface';
import jsonData from '../../jsons/en';
import AddAttributeModal from '../../pages/RolesPage/AddAttributeModal/AddAttributeModal'; import AddAttributeModal from '../../pages/RolesPage/AddAttributeModal/AddAttributeModal';
import UserCard from '../../pages/teams/UserCard'; import UserCard from '../../pages/teams/UserCard';
import { import {
@ -61,9 +65,10 @@ import {
hasEditAccess, hasEditAccess,
} from '../../utils/CommonUtils'; } from '../../utils/CommonUtils';
import { filterEntityAssets } from '../../utils/EntityUtils'; import { filterEntityAssets } from '../../utils/EntityUtils';
import { hasPemission } from '../../utils/PermissionsUtils'; import { checkPermission } from '../../utils/PermissionsUtils';
import { getSettingPath, getTeamsWithFqnPath } from '../../utils/RouterUtils'; import { getSettingPath, getTeamsWithFqnPath } from '../../utils/RouterUtils';
import SVGIcons, { Icons } from '../../utils/SvgUtils'; import SVGIcons, { Icons } from '../../utils/SvgUtils';
import { showErrorToast } from '../../utils/ToastUtils';
import { Button } from '../buttons/Button/Button'; import { Button } from '../buttons/Button/Button';
import Description from '../common/description/Description'; import Description from '../common/description/Description';
import Ellipses from '../common/Ellipses/Ellipses'; import Ellipses from '../common/Ellipses/Ellipses';
@ -71,14 +76,19 @@ import ManageButton from '../common/entityPageInfo/ManageButton/ManageButton';
import EntitySummaryDetails from '../common/EntitySummaryDetails/EntitySummaryDetails'; import EntitySummaryDetails from '../common/EntitySummaryDetails/EntitySummaryDetails';
import ErrorPlaceHolder from '../common/error-with-placeholder/ErrorPlaceHolder'; import ErrorPlaceHolder from '../common/error-with-placeholder/ErrorPlaceHolder';
import NextPrevious from '../common/next-previous/NextPrevious'; import NextPrevious from '../common/next-previous/NextPrevious';
import NonAdminAction from '../common/non-admin-action/NonAdminAction';
import Searchbar from '../common/searchbar/Searchbar'; import Searchbar from '../common/searchbar/Searchbar';
import TabsPane from '../common/TabsPane/TabsPane'; import TabsPane from '../common/TabsPane/TabsPane';
import TitleBreadcrumb from '../common/title-breadcrumb/title-breadcrumb.component'; import TitleBreadcrumb from '../common/title-breadcrumb/title-breadcrumb.component';
import { TitleBreadcrumbProps } from '../common/title-breadcrumb/title-breadcrumb.interface'; import { TitleBreadcrumbProps } from '../common/title-breadcrumb/title-breadcrumb.interface';
import Loader from '../Loader/Loader'; import Loader from '../Loader/Loader';
import ConfirmationModal from '../Modals/ConfirmationModal/ConfirmationModal'; import ConfirmationModal from '../Modals/ConfirmationModal/ConfirmationModal';
import { usePermissionProvider } from '../PermissionProvider/PermissionProvider';
import {
OperationPermission,
ResourceEntity,
} from '../PermissionProvider/PermissionProvider.interface';
import ListEntities from './RolesAndPoliciesList'; import ListEntities from './RolesAndPoliciesList';
import { getTabs } from './TeamDetailsV1.utils';
import TeamHierarchy from './TeamHierarchy'; import TeamHierarchy from './TeamHierarchy';
import './teams.less'; import './teams.less';
interface AddAttribute { interface AddAttribute {
@ -109,14 +119,13 @@ const TeamDetailsV1 = ({
removeUserFromTeam, removeUserFromTeam,
afterDeleteAction, afterDeleteAction,
}: TeamDetailsProp) => { }: TeamDetailsProp) => {
const isOrganization = const isOrganization = currentTeam.name === TeamType.Organization;
currentTeam.name === TeamType.Organization.toLowerCase();
const DELETE_USER_INITIAL_STATE = { const DELETE_USER_INITIAL_STATE = {
user: undefined, user: undefined,
state: false, state: false,
leave: false, leave: false,
}; };
const { userPermissions } = useAuth(); const { permissions, getEntityPermission } = usePermissionProvider();
const [currentTab, setCurrentTab] = useState(1); const [currentTab, setCurrentTab] = useState(1);
const [isHeadingEditing, setIsHeadingEditing] = useState(false); const [isHeadingEditing, setIsHeadingEditing] = useState(false);
const [currentUser, setCurrentUser] = useState<User>(); const [currentUser, setCurrentUser] = useState<User>();
@ -138,6 +147,15 @@ const TeamDetailsV1 = ({
attribute: 'defaultRoles' | 'policies'; attribute: 'defaultRoles' | 'policies';
record: EntityReference; record: EntityReference;
}>(); }>();
const [entityPermissions, setEntityPermissions] =
useState<OperationPermission>({} as OperationPermission);
const createTeamPermission = useMemo(
() =>
!isEmpty(permissions) &&
checkPermission(Operation.Create, ResourceEntity.TEAM, permissions),
[permissions]
);
/** /**
* Check if current team is the owner or not * Check if current team is the owner or not
@ -162,39 +180,6 @@ const TeamDetailsV1 = ({
setDeletingUser({ user, state: true, leave }); setDeletingUser({ user, state: true, leave });
}; };
const tabs = [
{
name: 'Teams',
isProtected: false,
position: 1,
count: currentTeam.children?.length || 0,
},
{
name: 'Users',
isProtected: false,
position: 2,
count: teamUserPagin?.total,
},
{
name: 'Assets',
isProtected: false,
position: 3,
count: filterEntityAssets(currentTeam?.owns || []).length,
},
{
name: 'Roles',
isProtected: false,
position: 4,
count: currentTeam?.defaultRoles?.length,
},
{
name: 'Policies',
isProtected: false,
position: 5,
count: currentTeam?.policies?.length,
},
];
const columns: ColumnsType<User> = useMemo(() => { const columns: ColumnsType<User> = useMemo(() => {
return [ return [
{ {
@ -230,8 +215,13 @@ const TeamDetailsV1 = ({
align="center" align="center"
className="tw-w-full tw-justify-center remove-icon" className="tw-w-full tw-justify-center remove-icon"
size={8}> size={8}>
<Tooltip placement="bottom" title="Remove"> <Tooltip
placement="bottomRight"
title={
entityPermissions?.EditAll ? 'Remove' : NO_PERMISSION_FOR_ACTION
}>
<ButtonAntd <ButtonAntd
disabled={!entityPermissions?.EditAll}
icon={ icon={
<SVGIcons <SVGIcons
alt="Remove" alt="Remove"
@ -425,6 +415,25 @@ const TeamDetailsV1 = ({
updateTeamHandler(updatedTeamData); updateTeamHandler(updatedTeamData);
}; };
const fetchPermissions = async () => {
try {
const perms = await getEntityPermission(
ResourceEntity.TEAM,
currentTeam.id
);
setEntityPermissions(perms);
} catch (error) {
showErrorToast(
error as AxiosError,
jsonData['api-error-messages']['fetch-user-permission-error']
);
}
};
useEffect(() => {
!isEmpty(currentTeam) && fetchPermissions();
}, [currentTeam]);
useEffect(() => { useEffect(() => {
if (currentTeam) { if (currentTeam) {
const perents = const perents =
@ -493,22 +502,23 @@ const TeamDetailsV1 = ({
{currentTeamUsers.length > 0 && isActionAllowed() && ( {currentTeamUsers.length > 0 && isActionAllowed() && (
<div> <div>
<NonAdminAction <Button
isOwner={isActionAllowed()} className="tw-h-8 tw-px-2"
position="bottom" data-testid="add-user"
title={TITLE_FOR_NON_OWNER_ACTION}> disabled={!entityPermissions?.EditAll}
<Button size="small"
className="tw-h-8 tw-px-2" theme="primary"
data-testid="add-user" title={
size="small" entityPermissions?.EditAll
theme="primary" ? 'Add User'
variant="contained" : NO_PERMISSION_FOR_ACTION
onClick={() => { }
handleAddUser(true); variant="contained"
}}> onClick={() => {
Add User handleAddUser(true);
</Button> }}>
</NonAdminAction> Add User
</Button>
</div> </div>
)} )}
</div> </div>
@ -524,26 +534,22 @@ const TeamDetailsV1 = ({
? `as ${teamUsersSearchText}.` ? `as ${teamUsersSearchText}.`
: `added yet.`} : `added yet.`}
</p> </p>
{isActionAllowed( <p>Would like to start adding some?</p>
hasPemission( <Button
Operation.EditUsers, className="tw-h-8 tw-rounded tw-my-2"
EntityType.TEAM, data-testid="add-new-user"
userPermissions disabled={!entityPermissions?.EditAll}
) size="small"
) ? ( theme="primary"
<> title={
<p>Would like to start adding some?</p> entityPermissions?.EditAll
<Button ? 'Add New User'
className="tw-h-8 tw-rounded tw-my-2" : NO_PERMISSION_FOR_ACTION
data-testid="add-new-user" }
size="small" variant="contained"
theme="primary" onClick={() => handleAddUser(true)}>
variant="contained" Add new user
onClick={() => handleAddUser(true)}> </Button>
Add new user
</Button>
</>
) : null}
</div> </div>
) : ( ) : (
<Fragment> <Fragment>
@ -649,7 +655,7 @@ const TeamDetailsV1 = ({
const getTeamHeading = () => { const getTeamHeading = () => {
return ( return (
<div className="tw-heading tw-text-link tw-text-base tw-mb-0"> <div className="tw-heading tw-text-link tw-text-base tw-mb-2">
{isHeadingEditing ? ( {isHeadingEditing ? (
<div className="tw-flex tw-items-center tw-gap-1"> <div className="tw-flex tw-items-center tw-gap-1">
<input <input
@ -690,21 +696,26 @@ const TeamDetailsV1 = ({
</Ellipses> </Ellipses>
{isActionAllowed() && ( {isActionAllowed() && (
<div className={classNames('tw-w-5 tw-min-w-max')}> <div className={classNames('tw-w-5 tw-min-w-max')}>
<NonAdminAction <Tooltip
position="right" placement="bottomLeft"
title={TITLE_FOR_NON_ADMIN_ACTION}> title={
entityPermissions?.EditDisplayName
? 'Edit Display Name'
: NO_PERMISSION_FOR_ACTION
}>
<button <button
className="tw-ml-2 focus:tw-outline-none" className="tw-ml-2 focus:tw-outline-none"
data-testid="edit-synonyms" data-testid="edit-synonyms"
disabled={!entityPermissions?.EditDisplayName}
onClick={() => setIsHeadingEditing(true)}> onClick={() => setIsHeadingEditing(true)}>
<SVGIcons <SVGIcons
alt="edit" alt="edit"
className="tw-mb-1"
icon="icon-edit" icon="icon-edit"
title="Edit"
width="16px" width="16px"
/> />
</button> </button>
</NonAdminAction> </Tooltip>
</div> </div>
)} )}
</div> </div>
@ -713,7 +724,10 @@ const TeamDetailsV1 = ({
); );
}; };
return ( const viewPermission =
!isEmpty(entityPermissions) && entityPermissions.ViewAll;
return viewPermission ? (
<div <div
className="tw-h-full tw-flex tw-flex-col tw-flex-grow" className="tw-h-full tw-flex tw-flex-col tw-flex-grow"
data-testid="team-details-container"> data-testid="team-details-container">
@ -724,37 +738,43 @@ const TeamDetailsV1 = ({
className="tw-flex tw-justify-between tw-items-center" className="tw-flex tw-justify-between tw-items-center"
data-testid="header"> data-testid="header">
{getTeamHeading()} {getTeamHeading()}
<Space align="center"> {!isOrganization && (
{!isUndefined(currentUser) && <Space align="center">
teamActionButton( {!isUndefined(currentUser) &&
!isAlreadyJoinedTeam(currentTeam.id), teamActionButton(
currentTeam.isJoinable || false !isAlreadyJoinedTeam(currentTeam.id),
)} currentTeam.isJoinable || false
<NonAdminAction )}
position="bottom"
title={TITLE_FOR_NON_ADMIN_ACTION}>
<ManageButton <ManageButton
afterDeleteAction={afterDeleteAction} afterDeleteAction={afterDeleteAction}
buttonClassName="tw-p-4" buttonClassName="tw-p-4"
disabled={!entityPermissions.EditAll}
entityId={currentTeam.id} entityId={currentTeam.id}
entityName={ entityName={
currentTeam.fullyQualifiedName || currentTeam.name currentTeam.fullyQualifiedName || currentTeam.name
} }
entityType="team" entityType="team"
title={
entityPermissions.EditAll
? 'Manage'
: NO_PERMISSION_FOR_ACTION
}
/> />
</NonAdminAction> </Space>
</Space> )}
</div>
<div className="tw-mb-3">
<Switch
checked={currentTeam.isJoinable}
className="tw-mr-2"
size="small"
title="Open Group"
onChange={handleOpenToJoinToggle}
/>
<span>Open Group</span>
</div> </div>
{!isOrganization && (
<div className="tw-mb-3">
<Switch
checked={currentTeam.isJoinable}
className="tw-mr-2"
size="small"
title="Open Group"
onChange={handleOpenToJoinToggle}
/>
<span>Open Group</span>
</div>
)}
<EntitySummaryDetails data={extraInfo} updateOwner={updateOwner} /> <EntitySummaryDetails data={extraInfo} updateOwner={updateOwner} />
<div <div
className="tw-mb-3 tw--ml-5 tw-mt-2" className="tw-mb-3 tw--ml-5 tw-mt-2"
@ -762,7 +782,7 @@ const TeamDetailsV1 = ({
<Description <Description
description={currentTeam?.description || ''} description={currentTeam?.description || ''}
entityName={currentTeam?.displayName ?? currentTeam?.name} entityName={currentTeam?.displayName ?? currentTeam?.name}
hasEditAccess={isOwner()} hasEditAccess={entityPermissions.EditDescription}
isEdit={isDescriptionEditable} isEdit={isDescriptionEditable}
onCancel={() => descriptionHandler(false)} onCancel={() => descriptionHandler(false)}
onDescriptionEdit={() => descriptionHandler(true)} onDescriptionEdit={() => descriptionHandler(true)}
@ -774,7 +794,7 @@ const TeamDetailsV1 = ({
<TabsPane <TabsPane
activeTab={currentTab} activeTab={currentTab}
setActiveTab={(tab) => setCurrentTab(tab)} setActiveTab={(tab) => setCurrentTab(tab)}
tabs={tabs} tabs={getTabs(currentTeam, teamUserPagin, isOrganization)}
/> />
<div className="tw-flex-grow tw-flex tw-flex-col tw-pt-4"> <div className="tw-flex-grow tw-flex tw-flex-col tw-pt-4">
@ -795,6 +815,12 @@ const TeamDetailsV1 = ({
className="tw-w-full" className="tw-w-full"
direction="vertical"> direction="vertical">
<ButtonAntd <ButtonAntd
disabled={!createTeamPermission}
title={
createTeamPermission
? 'Add Team'
: NO_PERMISSION_FOR_ACTION
}
type="primary" type="primary"
onClick={() => handleAddTeam(true)}> onClick={() => handleAddTeam(true)}>
Add Team Add Team
@ -819,6 +845,12 @@ const TeamDetailsV1 = ({
direction="vertical"> direction="vertical">
<ButtonAntd <ButtonAntd
data-testid="add-role" data-testid="add-role"
disabled={!entityPermissions.EditAll}
title={
entityPermissions.EditAll
? 'Add Role'
: NO_PERMISSION_FOR_ACTION
}
type="primary" type="primary"
onClick={() => onClick={() =>
setAddAttribute({ setAddAttribute({
@ -843,6 +875,12 @@ const TeamDetailsV1 = ({
direction="vertical"> direction="vertical">
<ButtonAntd <ButtonAntd
data-testid="add-policy" data-testid="add-policy"
disabled={!entityPermissions.EditAll}
title={
entityPermissions.EditAll
? 'Add Policy'
: NO_PERMISSION_FOR_ACTION
}
type="primary" type="primary"
onClick={() => onClick={() =>
setAddAttribute({ setAddAttribute({
@ -868,18 +906,17 @@ const TeamDetailsV1 = ({
<ErrorPlaceHolder> <ErrorPlaceHolder>
<p className="tw-text-lg tw-text-center">No Teams Added.</p> <p className="tw-text-lg tw-text-center">No Teams Added.</p>
<div className="tw-text-lg tw-text-center"> <div className="tw-text-lg tw-text-center">
<NonAdminAction <Button
position="bottom" disabled={!createTeamPermission}
title={TITLE_FOR_NON_ADMIN_ACTION}> size="small"
<Button theme="primary"
disabled={!isActionAllowed()} title={
size="small" createTeamPermission ? 'Add Team' : NO_PERMISSION_FOR_ACTION
theme="primary" }
variant="outlined" variant="outlined"
onClick={() => handleAddTeam(true)}> onClick={() => handleAddTeam(true)}>
Click here Click here
</Button> </Button>
</NonAdminAction>
{' to add new Team'} {' to add new Team'}
</div> </div>
</ErrorPlaceHolder> </ErrorPlaceHolder>
@ -931,6 +968,12 @@ const TeamDetailsV1 = ({
</Modal> </Modal>
)} )}
</div> </div>
) : (
<Row align="middle" className="tw-h-full">
<Col span={24}>
<Empty description={NO_PERMISSION_TO_VIEW} />
</Col>
</Row>
); );
}; };

View File

@ -0,0 +1,71 @@
/*
* Copyright 2022 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 { Team } from '../../generated/entity/teams/team';
import { Paging } from '../../generated/type/paging';
import { filterEntityAssets } from '../../utils/EntityUtils';
export const getTabs = (
currentTeam: Team,
teamUserPagin: Paging,
isOrganization: boolean
) => {
const commonTabs = [
{
name: 'Roles',
isProtected: false,
position: 4,
count: currentTeam?.defaultRoles?.length,
},
{
name: 'Policies',
isProtected: false,
position: 5,
count: currentTeam?.policies?.length,
},
];
if (isOrganization) {
return [
{
name: 'Teams',
isProtected: false,
position: 1,
count: currentTeam.children?.length || 0,
},
...commonTabs,
];
}
return [
{
name: 'Teams',
isProtected: false,
position: 1,
count: currentTeam.children?.length || 0,
},
{
name: 'Users',
isProtected: false,
position: 2,
count: teamUserPagin?.total,
},
{
name: 'Assets',
isProtected: false,
position: 3,
count: filterEntityAssets(currentTeam?.owns || []).length,
},
...commonTabs,
];
};

View File

@ -11,9 +11,21 @@
* limitations under the License. * limitations under the License.
*/ */
import { Button, Col, Row, Space, Switch } from 'antd'; import { Button, Col, Empty, Row, Space, Switch, Tooltip } from 'antd';
import React, { FC, useMemo } from 'react'; import { AxiosError } from 'axios';
import React, { FC, useEffect, useMemo, useState } from 'react';
import {
NO_PERMISSION_FOR_ACTION,
NO_PERMISSION_TO_VIEW,
} from '../../constants/HelperTextUtil';
import { Team } from '../../generated/entity/teams/team'; import { Team } from '../../generated/entity/teams/team';
import jsonData from '../../jsons/en';
import { showErrorToast } from '../../utils/ToastUtils';
import { usePermissionProvider } from '../PermissionProvider/PermissionProvider';
import {
OperationPermission,
ResourceEntity,
} from '../PermissionProvider/PermissionProvider.interface';
import TeamHierarchy from './TeamHierarchy'; import TeamHierarchy from './TeamHierarchy';
import './teams.less'; import './teams.less';
@ -36,6 +48,10 @@ const Teams: FC<TeamsProps> = ({
onAddTeamClick, onAddTeamClick,
onTeamExpand, onTeamExpand,
}) => { }) => {
const { getResourcePermission } = usePermissionProvider();
const [resourcePermissions, setResourcePermissions] =
useState<OperationPermission>();
const filteredData = useMemo( const filteredData = useMemo(
() => () =>
data.filter( data.filter(
@ -45,7 +61,23 @@ const Teams: FC<TeamsProps> = ({
[data, showDeletedTeam] [data, showDeletedTeam]
); );
return ( const fetchPermissions = async () => {
try {
const perms = await getResourcePermission(ResourceEntity.TEAM);
setResourcePermissions(perms);
} catch (error) {
showErrorToast(
error as AxiosError,
jsonData['api-error-messages']['fetch-user-permission-error']
);
}
};
useEffect(() => {
fetchPermissions();
}, []);
return resourcePermissions?.ViewAll ? (
<Row className="team-list-container" gutter={[16, 16]}> <Row className="team-list-container" gutter={[16, 16]}>
<Col span={24}> <Col span={24}>
<Space align="center" className="tw-w-full tw-justify-end" size={16}> <Space align="center" className="tw-w-full tw-justify-end" size={16}>
@ -57,15 +89,30 @@ const Teams: FC<TeamsProps> = ({
/> />
<span>Deleted Teams</span> <span>Deleted Teams</span>
</Space> </Space>
<Button type="primary" onClick={() => onAddTeamClick(true)}> <Tooltip
Add Team placement="bottom"
</Button> title={
resourcePermissions.Create ? 'Add Team' : NO_PERMISSION_FOR_ACTION
}>
<Button
disabled={!resourcePermissions.Create}
type="primary"
onClick={() => onAddTeamClick(true)}>
Add Team
</Button>
</Tooltip>
</Space> </Space>
</Col> </Col>
<Col span={24}> <Col span={24}>
<TeamHierarchy data={filteredData} onTeamExpand={onTeamExpand} /> <TeamHierarchy data={filteredData} onTeamExpand={onTeamExpand} />
</Col> </Col>
</Row> </Row>
) : (
<Row align="middle" className="tw-h-full">
<Col span={24}>
<Empty description={NO_PERMISSION_TO_VIEW} />
</Col>
</Row>
); );
}; };

View File

@ -25,12 +25,14 @@ interface Props {
allowSoftDelete?: boolean; allowSoftDelete?: boolean;
afterDeleteAction?: () => void; afterDeleteAction?: () => void;
buttonClassName?: string; buttonClassName?: string;
disabled?: boolean;
entityName: string; entityName: string;
entityId?: string; entityId?: string;
entityType?: string; entityType?: string;
entityFQN?: string; entityFQN?: string;
isRecursiveDelete?: boolean; isRecursiveDelete?: boolean;
deleteMessage?: string; deleteMessage?: string;
title?: string;
onAnnouncementClick?: () => void; onAnnouncementClick?: () => void;
} }
@ -39,10 +41,12 @@ const ManageButton: FC<Props> = ({
afterDeleteAction, afterDeleteAction,
buttonClassName, buttonClassName,
deleteMessage, deleteMessage,
disabled,
entityName, entityName,
entityType, entityType,
entityId, entityId,
isRecursiveDelete, isRecursiveDelete,
title,
onAnnouncementClick, onAnnouncementClick,
}) => { }) => {
const [showActions, setShowActions] = useState<boolean>(false); const [showActions, setShowActions] = useState<boolean>(false);
@ -114,6 +118,7 @@ const ManageButton: FC<Props> = ({
<> <>
<Dropdown <Dropdown
align={{ targetOffset: [-12, 0] }} align={{ targetOffset: [-12, 0] }}
disabled={disabled}
overlay={menu} overlay={menu}
overlayStyle={{ width: '350px' }} overlayStyle={{ width: '350px' }}
placement="bottomRight" placement="bottomRight"
@ -126,7 +131,9 @@ const ManageButton: FC<Props> = ({
buttonClassName buttonClassName
)} )}
data-testid="manage-button" data-testid="manage-button"
disabled={disabled}
size="small" size="small"
title={title ?? 'Manage'}
type="default" type="default"
onClick={() => setShowActions(true)}> onClick={() => setShowActions(true)}>
<FontAwesomeIcon <FontAwesomeIcon

View File

@ -20,6 +20,7 @@ import {
GlobalSettingsMenuCategory, GlobalSettingsMenuCategory,
} from '../constants/globalSettings.constants'; } from '../constants/globalSettings.constants';
import { Operation } from '../generated/entity/policies/policy'; import { Operation } from '../generated/entity/policies/policy';
import { TeamType } from '../generated/entity/teams/team';
import TeamsPage from '../pages/teams/TeamsPage'; import TeamsPage from '../pages/teams/TeamsPage';
import { checkPermission } from '../utils/PermissionsUtils'; import { checkPermission } from '../utils/PermissionsUtils';
import { getSettingCategoryPath, getSettingPath } from '../utils/RouterUtils'; import { getSettingCategoryPath, getSettingPath } from '../utils/RouterUtils';
@ -80,10 +81,10 @@ const GlobalSettingRouter = () => {
<Switch> <Switch>
<Route exact path={getSettingPath()}> <Route exact path={getSettingPath()}>
<Redirect <Redirect
to={getSettingPath( to={`${getSettingPath(
GlobalSettingsMenuCategory.MEMBERS, GlobalSettingsMenuCategory.MEMBERS,
GlobalSettingOptions.TEAMS GlobalSettingOptions.TEAMS
)} )}/${TeamType.Organization}`}
/> />
</Route> </Route>
<Route <Route