diff --git a/openmetadata-ui/src/main/resources/ui/cypress/integration/Pages/Glossary.spec.js b/openmetadata-ui/src/main/resources/ui/cypress/integration/Pages/Glossary.spec.js index a20537e946e..8e45fc3c18b 100644 --- a/openmetadata-ui/src/main/resources/ui/cypress/integration/Pages/Glossary.spec.js +++ b/openmetadata-ui/src/main/resources/ui/cypress/integration/Pages/Glossary.spec.js @@ -11,7 +11,7 @@ * 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'; const createGlossaryTerm = (term) => { @@ -227,6 +227,11 @@ describe('Glossary page should work properly', () => { .as('synonyms'); cy.get('@synonyms').clear(); 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.wait(100); 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.wait('@getGlossary').its('response.statusCode').should('eq', 200); + // updating References cy.get('[data-testid="section-references"] [data-testid="add-button"]') .should('exist') @@ -316,6 +323,7 @@ describe('Glossary page should work properly', () => { }); it('Assets Tab should work properly', () => { + const glossary = NEW_GLOSSARY.name; const term = NEW_GLOSSARY_TERMS.term_1.name; const entity = SEARCH_ENTITY_TABLE.table_3.term; goToAssetsTab(term); @@ -323,7 +331,46 @@ describe('Glossary page should work properly', () => { .contains('No assets available.') .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"]') .should('exist') @@ -353,34 +400,17 @@ describe('Glossary page should work properly', () => { .scrollIntoView() .should('be.visible') .click(); - cy.get(':nth-child(1) > .css-xb97g8') - .scrollIntoView() - .should('be.visible') - .click(); - cy.get(':nth-child(1) > .css-xb97g8') - .scrollIntoView() - .should('be.visible') - .click(); + cy.get('[role="button"]').eq(0).should('be.visible').click(); + cy.get('[role="button"]').eq(0).should('be.visible').click(); cy.get('[data-testid="saveAssociatedTag"]').scrollIntoView().click(); //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') - .scrollIntoView() - .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="remove"]').eq(0).should('be.visible').click(); + + cy.wait(500); + cy.get('[data-testid="remove"]').eq(0).should('be.visible').click(); cy.get('[data-testid="appbar-item-glossary"]') .should('exist') diff --git a/openmetadata-ui/src/main/resources/ui/src/components/GlobalSetting/GlobalSettingLeftPanel.tsx b/openmetadata-ui/src/main/resources/ui/src/components/GlobalSetting/GlobalSettingLeftPanel.tsx index 60b4e0eccbf..568edf5ca67 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/GlobalSetting/GlobalSettingLeftPanel.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/GlobalSetting/GlobalSettingLeftPanel.tsx @@ -16,12 +16,14 @@ import { ItemType } from 'antd/lib/menu/hooks/useItems'; import { camelCase } from 'lodash'; import React, { useMemo } from 'react'; import { useHistory, useParams } from 'react-router-dom'; +import { GlobalSettingOptions } from '../../constants/globalSettings.constants'; +import { TeamType } from '../../generated/entity/teams/team'; import { getGlobalSettingMenuItem, getGlobalSettingsMenuWithPermission, MenuList, } from '../../utils/GlobalSettingsUtils'; -import { getSettingPath } from '../../utils/RouterUtils'; +import { getSettingPath, getTeamsWithFqnPath } from '../../utils/RouterUtils'; import { usePermissionProvider } from '../PermissionProvider/PermissionProvider'; const GlobalSettingLeftPanel = () => { @@ -56,7 +58,11 @@ const GlobalSettingLeftPanel = () => { const onClick: MenuProps['onClick'] = (e) => { // As we are setting key as "category.option" and extracting here category and option 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 ? ( diff --git a/openmetadata-ui/src/main/resources/ui/src/components/TeamDetails/TeamDetailsV1.tsx b/openmetadata-ui/src/main/resources/ui/src/components/TeamDetails/TeamDetailsV1.tsx index c760330bc82..a8ab2f22ebd 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/TeamDetails/TeamDetailsV1.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/TeamDetails/TeamDetailsV1.tsx @@ -15,6 +15,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Button as ButtonAntd, Col, + Empty, Modal, Row, Space, @@ -24,6 +25,7 @@ import { Typography, } from 'antd'; import { ColumnsType } from 'antd/lib/table'; +import { AxiosError } from 'axios'; import classNames from 'classnames'; import { compare } from 'fast-json-patch'; import { cloneDeep, isEmpty, isUndefined, orderBy } from 'lodash'; @@ -35,13 +37,15 @@ import { getTeamAndUserDetailsPath, getUserPath, PAGE_SIZE, - TITLE_FOR_NON_ADMIN_ACTION, - TITLE_FOR_NON_OWNER_ACTION, } from '../../constants/constants'; import { GlobalSettingOptions, GlobalSettingsMenuCategory, } from '../../constants/globalSettings.constants'; +import { + NO_PERMISSION_FOR_ACTION, + NO_PERMISSION_TO_VIEW, +} from '../../constants/HelperTextUtil'; import { EntityType } from '../../enums/entity.enum'; import { OwnerType } from '../../enums/user.enum'; import { Operation } from '../../generated/entity/policies/policy'; @@ -51,8 +55,8 @@ import { User, } from '../../generated/entity/teams/user'; import { EntityReference } from '../../generated/type/entityReference'; -import { useAuth } from '../../hooks/authHooks'; import { TeamDetailsProp } from '../../interface/teamsAndUsers.interface'; +import jsonData from '../../jsons/en'; import AddAttributeModal from '../../pages/RolesPage/AddAttributeModal/AddAttributeModal'; import UserCard from '../../pages/teams/UserCard'; import { @@ -61,9 +65,10 @@ import { hasEditAccess, } from '../../utils/CommonUtils'; import { filterEntityAssets } from '../../utils/EntityUtils'; -import { hasPemission } from '../../utils/PermissionsUtils'; +import { checkPermission } from '../../utils/PermissionsUtils'; import { getSettingPath, getTeamsWithFqnPath } from '../../utils/RouterUtils'; import SVGIcons, { Icons } from '../../utils/SvgUtils'; +import { showErrorToast } from '../../utils/ToastUtils'; import { Button } from '../buttons/Button/Button'; import Description from '../common/description/Description'; import Ellipses from '../common/Ellipses/Ellipses'; @@ -71,14 +76,19 @@ import ManageButton from '../common/entityPageInfo/ManageButton/ManageButton'; import EntitySummaryDetails from '../common/EntitySummaryDetails/EntitySummaryDetails'; import ErrorPlaceHolder from '../common/error-with-placeholder/ErrorPlaceHolder'; import NextPrevious from '../common/next-previous/NextPrevious'; -import NonAdminAction from '../common/non-admin-action/NonAdminAction'; import Searchbar from '../common/searchbar/Searchbar'; import TabsPane from '../common/TabsPane/TabsPane'; import TitleBreadcrumb from '../common/title-breadcrumb/title-breadcrumb.component'; import { TitleBreadcrumbProps } from '../common/title-breadcrumb/title-breadcrumb.interface'; import Loader from '../Loader/Loader'; import ConfirmationModal from '../Modals/ConfirmationModal/ConfirmationModal'; +import { usePermissionProvider } from '../PermissionProvider/PermissionProvider'; +import { + OperationPermission, + ResourceEntity, +} from '../PermissionProvider/PermissionProvider.interface'; import ListEntities from './RolesAndPoliciesList'; +import { getTabs } from './TeamDetailsV1.utils'; import TeamHierarchy from './TeamHierarchy'; import './teams.less'; interface AddAttribute { @@ -109,14 +119,13 @@ const TeamDetailsV1 = ({ removeUserFromTeam, afterDeleteAction, }: TeamDetailsProp) => { - const isOrganization = - currentTeam.name === TeamType.Organization.toLowerCase(); + const isOrganization = currentTeam.name === TeamType.Organization; const DELETE_USER_INITIAL_STATE = { user: undefined, state: false, leave: false, }; - const { userPermissions } = useAuth(); + const { permissions, getEntityPermission } = usePermissionProvider(); const [currentTab, setCurrentTab] = useState(1); const [isHeadingEditing, setIsHeadingEditing] = useState(false); const [currentUser, setCurrentUser] = useState(); @@ -138,6 +147,15 @@ const TeamDetailsV1 = ({ attribute: 'defaultRoles' | 'policies'; record: EntityReference; }>(); + const [entityPermissions, setEntityPermissions] = + useState({} as OperationPermission); + + const createTeamPermission = useMemo( + () => + !isEmpty(permissions) && + checkPermission(Operation.Create, ResourceEntity.TEAM, permissions), + [permissions] + ); /** * Check if current team is the owner or not @@ -162,39 +180,6 @@ const TeamDetailsV1 = ({ 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 = useMemo(() => { return [ { @@ -230,8 +215,13 @@ const TeamDetailsV1 = ({ align="center" className="tw-w-full tw-justify-center remove-icon" size={8}> - + { + 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(() => { if (currentTeam) { const perents = @@ -493,22 +502,23 @@ const TeamDetailsV1 = ({ {currentTeamUsers.length > 0 && isActionAllowed() && (
- - - +
)} @@ -524,26 +534,22 @@ const TeamDetailsV1 = ({ ? `as ${teamUsersSearchText}.` : `added yet.`}

- {isActionAllowed( - hasPemission( - Operation.EditUsers, - EntityType.TEAM, - userPermissions - ) - ) ? ( - <> -

Would like to start adding some?

- - - ) : null} +

Would like to start adding some?

+ ) : ( @@ -649,7 +655,7 @@ const TeamDetailsV1 = ({ const getTeamHeading = () => { return ( -
+
{isHeadingEditing ? (
{isActionAllowed() && (
- + - +
)}
@@ -713,7 +724,10 @@ const TeamDetailsV1 = ({ ); }; - return ( + const viewPermission = + !isEmpty(entityPermissions) && entityPermissions.ViewAll; + + return viewPermission ? (
@@ -724,37 +738,43 @@ const TeamDetailsV1 = ({ className="tw-flex tw-justify-between tw-items-center" data-testid="header"> {getTeamHeading()} - - {!isUndefined(currentUser) && - teamActionButton( - !isAlreadyJoinedTeam(currentTeam.id), - currentTeam.isJoinable || false - )} - + {!isOrganization && ( + + {!isUndefined(currentUser) && + teamActionButton( + !isAlreadyJoinedTeam(currentTeam.id), + currentTeam.isJoinable || false + )} - - -
-
- - Open Group + + )}
+ {!isOrganization && ( +
+ + Open Group +
+ )}
descriptionHandler(false)} onDescriptionEdit={() => descriptionHandler(true)} @@ -774,7 +794,7 @@ const TeamDetailsV1 = ({ setCurrentTab(tab)} - tabs={tabs} + tabs={getTabs(currentTeam, teamUserPagin, isOrganization)} />
@@ -795,6 +815,12 @@ const TeamDetailsV1 = ({ className="tw-w-full" direction="vertical"> handleAddTeam(true)}> Add Team @@ -819,6 +845,12 @@ const TeamDetailsV1 = ({ direction="vertical"> setAddAttribute({ @@ -843,6 +875,12 @@ const TeamDetailsV1 = ({ direction="vertical"> setAddAttribute({ @@ -868,18 +906,17 @@ const TeamDetailsV1 = ({

No Teams Added.

- - - + {' to add new Team'}
@@ -931,6 +968,12 @@ const TeamDetailsV1 = ({ )}
+ ) : ( + + + + + ); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/TeamDetails/TeamDetailsV1.utils.tsx b/openmetadata-ui/src/main/resources/ui/src/components/TeamDetails/TeamDetailsV1.utils.tsx new file mode 100644 index 00000000000..c31fda96be3 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/TeamDetails/TeamDetailsV1.utils.tsx @@ -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, + ]; +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/TeamDetails/Teams.tsx b/openmetadata-ui/src/main/resources/ui/src/components/TeamDetails/Teams.tsx index 46914bb2a6d..45a857ba60a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/TeamDetails/Teams.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/TeamDetails/Teams.tsx @@ -11,9 +11,21 @@ * limitations under the License. */ -import { Button, Col, Row, Space, Switch } from 'antd'; -import React, { FC, useMemo } from 'react'; +import { Button, Col, Empty, Row, Space, Switch, Tooltip } from 'antd'; +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 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 './teams.less'; @@ -36,6 +48,10 @@ const Teams: FC = ({ onAddTeamClick, onTeamExpand, }) => { + const { getResourcePermission } = usePermissionProvider(); + const [resourcePermissions, setResourcePermissions] = + useState(); + const filteredData = useMemo( () => data.filter( @@ -45,7 +61,23 @@ const Teams: FC = ({ [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 ? ( @@ -57,15 +89,30 @@ const Teams: FC = ({ /> Deleted Teams - + + + + ) : ( + + + + + ); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/entityPageInfo/ManageButton/ManageButton.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/entityPageInfo/ManageButton/ManageButton.tsx index fd1d51d4fb3..dee1aaed0bd 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/entityPageInfo/ManageButton/ManageButton.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/entityPageInfo/ManageButton/ManageButton.tsx @@ -25,12 +25,14 @@ interface Props { allowSoftDelete?: boolean; afterDeleteAction?: () => void; buttonClassName?: string; + disabled?: boolean; entityName: string; entityId?: string; entityType?: string; entityFQN?: string; isRecursiveDelete?: boolean; deleteMessage?: string; + title?: string; onAnnouncementClick?: () => void; } @@ -39,10 +41,12 @@ const ManageButton: FC = ({ afterDeleteAction, buttonClassName, deleteMessage, + disabled, entityName, entityType, entityId, isRecursiveDelete, + title, onAnnouncementClick, }) => { const [showActions, setShowActions] = useState(false); @@ -114,6 +118,7 @@ const ManageButton: FC = ({ <> = ({ buttonClassName )} data-testid="manage-button" + disabled={disabled} size="small" + title={title ?? 'Manage'} type="default" onClick={() => setShowActions(true)}> {