From 16a4033645e76f04a3d1b6a75b58f4327dc115dc Mon Sep 17 00:00:00 2001 From: Ashish Gupta Date: Mon, 2 Oct 2023 18:12:23 +0530 Subject: [PATCH] fix(ui): revamp teams page added supported subscription webhooks (#13296) * revamp teams page added supported subscription webhook * minor changes * minor changes * changes teams header page layout and subscription * minor changes * fix cypress and addressed comments * fix cypress for teams hierarchy (#13352) * fix sonar errors and users not showing in teams having space * code smell and bugs fixes * fix teams page cypress --------- Co-authored-by: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> --- .../resources/ui/cypress/common/common.js | 55 +- .../ui/cypress/e2e/Pages/Teams.spec.js | 50 +- .../resources/ui/src/assets/svg/gchat.svg | 12 + .../resources/ui/src/assets/svg/teams.svg | 5 + .../Team/TeamDetails/TeamDetailsV1.tsx | 1262 ++++++++--------- .../Team/TeamDetails/TeamHierarchy.tsx | 2 +- .../TeamsInfo.component.tsx | 357 +++++ .../TeamsSubscription.component.tsx | 187 +++ .../TeamDetails/UserTab/UserTab.component.tsx | 2 +- .../Team/TeamDetails/team.interface.ts | 22 + .../components/Team/TeamDetails/teams.less | 43 +- .../OwnerLabel/OwnerLabel.component.tsx | 10 +- .../TeamTypeSelect.component.tsx | 21 +- .../TeamTypeSelect/TeamTypeSelect.style.less | 33 - .../ui/src/constants/Teams.constants.ts | 23 + .../src/interface/teamsAndUsers.interface.ts | 1 - .../ui/src/locale/languages/de-de.json | 7 + .../ui/src/locale/languages/en-us.json | 2 + .../ui/src/locale/languages/es-es.json | 2 + .../ui/src/locale/languages/fr-fr.json | 4 +- .../ui/src/locale/languages/ja-jp.json | 2 + .../ui/src/locale/languages/pt-br.json | 2 + .../ui/src/locale/languages/ru-ru.json | 2 + .../ui/src/locale/languages/zh-cn.json | 2 + .../ui/src/pages/teams/TeamsPage.tsx | 15 +- .../src/main/resources/ui/src/styles/app.less | 6 + .../main/resources/ui/src/styles/fonts.less | 1 + .../resources/ui/src/styles/variables.less | 2 +- .../main/resources/ui/src/utils/TeamUtils.ts | 22 + 29 files changed, 1358 insertions(+), 796 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/assets/svg/gchat.svg create mode 100644 openmetadata-ui/src/main/resources/ui/src/assets/svg/teams.svg create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/Team/TeamDetails/TeamsHeaderSection/TeamsInfo.component.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/Team/TeamDetails/TeamsHeaderSection/TeamsSubscription.component.tsx delete mode 100644 openmetadata-ui/src/main/resources/ui/src/components/common/TeamTypeSelect/TeamTypeSelect.style.less diff --git a/openmetadata-ui/src/main/resources/ui/cypress/common/common.js b/openmetadata-ui/src/main/resources/ui/cypress/common/common.js index 419fbf7727e..28f907c0852 100644 --- a/openmetadata-ui/src/main/resources/ui/cypress/common/common.js +++ b/openmetadata-ui/src/main/resources/ui/cypress/common/common.js @@ -37,7 +37,7 @@ export const BASE_WAIT_TIME = 20000; const ADMIN = 'admin'; const RETRIES_COUNT = 4; -const TEAM_TYPES = ['BusinessUnit', 'Department', 'Division', 'Group']; +const TEAM_TYPES = ['Department', 'Division', 'Group']; export const replaceAllSpacialCharWith_ = (text) => { return text.replaceAll(/[&/\\#, +()$~%.'":*?<>{}]/g, '_'); @@ -53,8 +53,31 @@ export const checkServiceFieldSectionHighlighting = (field) => { ); }; -const checkTeamTypeOptions = () => { - for (const teamType of TEAM_TYPES) { +const getTeamType = (currentTeam) => { + switch (currentTeam) { + case 'BusinessUnit': + return { + childTeamType: 'Division', + teamTypeOptions: TEAM_TYPES, + }; + + case 'Division': + return { + childTeamType: 'Department', + teamTypeOptions: TEAM_TYPES, + }; + + case 'Department': + return { + childTeamType: 'Group', + teamTypeOptions: ['Department', 'Group'], + }; + } +}; + +const checkTeamTypeOptions = (type) => { + cy.log('check', type); + for (const teamType of getTeamType(type).teamTypeOptions) { cy.get(`.ant-select-dropdown [title="${teamType}"]`) .should('exist') .should('be.visible'); @@ -961,12 +984,28 @@ export const addTeam = (TEAM_DETAILS, index) => { .should('be.visible') .click(); - checkTeamTypeOptions(); + if (index > 0) { + cy.get('[data-testid="team-type"]') + .invoke('text') + .then((text) => { + cy.log(text); + checkTeamTypeOptions(text); + cy.log('check type', text); + cy.get( + `.ant-select-dropdown [title="${getTeamType(text).childTeamType}"]` + ) + .should('exist') + .should('be.visible') + .click(); + }); + } else { + checkTeamTypeOptions('BusinessUnit'); - cy.get(`.ant-select-dropdown [title="${TEAM_DETAILS.teamType}"]`) - .should('exist') - .should('be.visible') - .click(); + cy.get(`.ant-select-dropdown [title='BusinessUnit']`) + .should('exist') + .should('be.visible') + .click(); + } cy.get(descriptionBox) .should('exist') diff --git a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Teams.spec.js b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Teams.spec.js index 5394e059e3a..1be3751c583 100644 --- a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Teams.spec.js +++ b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Teams.spec.js @@ -24,12 +24,12 @@ import { verifyResponseStatusCode, } from '../../common/common'; -const updateddescription = 'This is updated description'; +const updatedDescription = 'This is updated description'; const teamName = 'team-group-test-430116' ?? `team-ct-test-${uuid()}`; const TEAM_DETAILS = { name: teamName, - updatedname: `${teamName}-updated`, + updatedName: `${teamName}-updated`, teamType: 'Group', description: `This is ${teamName} description`, username: 'Aaron Johnson', @@ -125,10 +125,8 @@ describe('Teams flow should work properly', () => { .contains(TEAM_DETAILS.name) .click(); verifyResponseStatusCode('@permissions', 200); - cy.get('[data-testid="add-new-user"]') - .should('be.visible') - .scrollIntoView(); - cy.get('[data-testid="add-new-user"]').click(); + cy.get('[data-testid="users"]').click(); + cy.get('[data-testid="add-new-user"]').scrollIntoView().click(); verifyResponseStatusCode('@getUsers', 200); cy.get('[data-testid="selectable-list"] [data-testid="searchbar"]').type( TEAM_DETAILS.username @@ -154,6 +152,7 @@ describe('Teams flow should work properly', () => { cy.get(`[data-row-key="${TEAM_DETAILS.name}"]`) .contains(TEAM_DETAILS.name) .click(); + cy.get('[data-testid="users"]').click(); verifyResponseStatusCode('@getUserDetails', 200); verifyResponseStatusCode('@permissions', 200); cy.get('[data-testid="add-new-user"]').should('be.visible').click(); @@ -176,10 +175,15 @@ describe('Teams flow should work properly', () => { .contains(TEAM_DETAILS.name) .click(); + cy.get('[data-testid="users"]').click(); + verifyResponseStatusCode('@getUsers', 200); // Click on join teams button - cy.get('[data-testid="join-teams"]').should('be.visible').click(); + cy.get('[data-testid="join-teams"]') + .scrollIntoView() + .should('be.visible') + .click(); // Verify toast notification toastNotification('Team joined successfully!'); @@ -201,14 +205,14 @@ describe('Teams flow should work properly', () => { verifyResponseStatusCode('@getSelectedTeam', 200); // Click on edit display name - cy.get('[data-testid="edit-synonyms"]').should('be.visible').click(); + cy.get('[data-testid="edit-team-name"]').should('be.visible').click(); // Enter the updated team name - cy.get('[data-testid="synonyms"]') + cy.get('[data-testid="team-name-input"]') .should('exist') .should('be.visible') .clear() - .type(TEAM_DETAILS.updatedname); + .type(TEAM_DETAILS.updatedName); // Save the updated display name cy.get('[data-testid="saveAssociatedTag"]') @@ -220,12 +224,12 @@ describe('Teams flow should work properly', () => { verifyResponseStatusCode('@getSelectedTeam', 200); // Validate the updated display name cy.get('[data-testid="team-heading"]').then(($el) => { - cy.wrap($el).should('have.text', TEAM_DETAILS.updatedname); + cy.wrap($el).should('have.text', TEAM_DETAILS.updatedName); }); cy.get('[data-testid="inactive-link"]') - .should('be.visible') - .should('contain', TEAM_DETAILS.updatedname); + .scrollIntoView() + .should('contain', TEAM_DETAILS.updatedName); }); it('Update description for created team', () => { @@ -245,12 +249,12 @@ describe('Teams flow should work properly', () => { // Validate the updated display name cy.get('[data-testid="team-heading"]').should( 'contain', - `${TEAM_DETAILS.updatedname}` + `${TEAM_DETAILS.updatedName}` ); cy.get('[data-testid="inactive-link"]') .should('be.visible') - .should('contain', TEAM_DETAILS.updatedname); + .should('contain', TEAM_DETAILS.updatedName); // Click on edit description button cy.get('[data-testid="edit-description"]') @@ -258,7 +262,7 @@ describe('Teams flow should work properly', () => { .click({ force: true }); // Entering updated description - cy.get(descriptionBox).clear().type(updateddescription); + cy.get(descriptionBox).clear().type(updatedDescription); cy.get('[data-testid="save"]').should('be.visible').click(); verifyResponseStatusCode('@patchDescription', 200); @@ -266,7 +270,7 @@ describe('Teams flow should work properly', () => { // Validating the updated description cy.get('[data-testid="description"] p').should( 'contain', - updateddescription + updatedDescription ); }); @@ -287,7 +291,7 @@ describe('Teams flow should work properly', () => { .should('be.visible') .contains(TEAM_DETAILS.name); // //Click on Leave team - cy.get('[data-testid="leave-team-button"]').should('be.visible').click(); + cy.get('[data-testid="leave-team-button"]').click(); // //Click on confirm button cy.get('[data-testid="save-button"]').should('be.visible').click(); @@ -312,8 +316,8 @@ describe('Teams flow should work properly', () => { verifyResponseStatusCode('@getSelectedTeam', 200); cy.get('[data-testid="team-heading"]') .should('be.visible') - .contains(TEAM_DETAILS.updatedname); - cy.get('[data-testid="header"] [data-testid="manage-button"]') + .contains(TEAM_DETAILS.updatedName); + cy.get('[data-testid="manage-button"]') .should('exist') .should('be.visible') .click(); @@ -380,9 +384,9 @@ describe('Teams flow should work properly', () => { cy.get('[data-testid="team-heading"]') .should('be.visible') - .contains(TEAM_DETAILS.updatedname); + .contains(TEAM_DETAILS.updatedName); - cy.get('[data-testid="header"] [data-testid="manage-button"]') + cy.get('[data-testid="manage-button"]') .should('exist') .should('be.visible') .click(); @@ -436,7 +440,7 @@ describe('Teams flow should work properly', () => { .click(); verifyResponseStatusCode('@getSelectedTeam', 200); - cy.get('[data-testid="header"] [data-testid="manage-button"]') + cy.get('[data-testid="manage-button"]') .should('exist') .should('be.visible') .click(); diff --git a/openmetadata-ui/src/main/resources/ui/src/assets/svg/gchat.svg b/openmetadata-ui/src/main/resources/ui/src/assets/svg/gchat.svg new file mode 100644 index 00000000000..3373cca771a --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/assets/svg/gchat.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/openmetadata-ui/src/main/resources/ui/src/assets/svg/teams.svg b/openmetadata-ui/src/main/resources/ui/src/assets/svg/teams.svg new file mode 100644 index 00000000000..8b982bb8da7 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/assets/svg/teams.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Team/TeamDetails/TeamDetailsV1.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Team/TeamDetails/TeamDetailsV1.tsx index 39f360a1be3..eef02fb3df8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Team/TeamDetails/TeamDetailsV1.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Team/TeamDetails/TeamDetailsV1.tsx @@ -11,63 +11,49 @@ * limitations under the License. */ -import Icon, { - CheckOutlined, - CloseOutlined, - PlusOutlined, -} from '@ant-design/icons'; +import { PlusOutlined } from '@ant-design/icons'; import { + Avatar, Button, + Card, Col, - Divider, - Form, - FormProps, - Input, + Collapse, Modal, Row, Space, Switch, Tabs, - Tooltip, Typography, } from 'antd'; import { ItemType } from 'antd/lib/menu/hooks/useItems'; -import { - ReactComponent as EditIcon, - ReactComponent as IconEdit, -} from 'assets/svg/edit-new.svg'; +import { ReactComponent as EditIcon } from 'assets/svg/edit-new.svg'; import { ReactComponent as ExportIcon } from 'assets/svg/ic-export.svg'; import { ReactComponent as ImportIcon } from 'assets/svg/ic-import.svg'; import { ReactComponent as IconRestore } from 'assets/svg/ic-restore.svg'; import { ReactComponent as IconOpenLock } from 'assets/svg/open-lock.svg'; +import { ReactComponent as IconTeams } from 'assets/svg/teams.svg'; + import { AxiosError } from 'axios'; -import classNames from 'classnames'; +import { useAuthContext } from 'components/authentication/auth-provider/AuthProvider'; +import Description from 'components/common/description/Description'; import { ManageButtonItemLabel } from 'components/common/ManageButtonContentItem/ManageButtonContentItem.component'; -import { OwnerLabel } from 'components/common/OwnerLabel/OwnerLabel.component'; -import TeamTypeSelect from 'components/common/TeamTypeSelect/TeamTypeSelect.component'; import { useEntityExportModalProvider } from 'components/Entity/EntityExportModalProvider/EntityExportModalProvider.component'; import EntitySummaryPanel from 'components/Explore/EntitySummaryPanel/EntitySummaryPanel.component'; import { EntityDetailsObjectInterface } from 'components/Explore/explore.interface'; import AssetsTabs from 'components/Glossary/GlossaryTerms/tabs/AssetsTabs.component'; import { AssetsOfEntity } from 'components/Glossary/GlossaryTerms/tabs/AssetsTabs.interface'; +import TabsLabel from 'components/TabsLabel/TabsLabel.component'; import { ROUTES } from 'constants/constants'; import { GlobalSettingOptions, GlobalSettingsMenuCategory, } from 'constants/GlobalSettings.constants'; import { DROPDOWN_ICON_SIZE_PROPS } from 'constants/ManageButton.constants'; -import { EMAIL_REG_EX } from 'constants/regex.constants'; import { ERROR_PLACEHOLDER_TYPE } from 'enums/common.enum'; import { SearchIndex } from 'enums/search.enum'; import { compare } from 'fast-json-patch'; -import { - cloneDeep, - isEmpty, - isNil, - isUndefined, - last, - lowerCase, -} from 'lodash'; +import { useAuth } from 'hooks/authHooks'; +import { cloneDeep, isEmpty, isUndefined } from 'lodash'; import AddAttributeModal from 'pages/RolesPage/AddAttributeModal/AddAttributeModal'; import { ImportType } from 'pages/teams/ImportTeamsPage/ImportTeamsPage.interface'; import Qs from 'qs'; @@ -77,6 +63,7 @@ import { useHistory, useLocation } from 'react-router-dom'; import { getSuggestions } from 'rest/miscAPI'; import { exportTeam, restoreTeam } from 'rest/teamsAPI'; import AppState from '../../../AppState'; +import { DE_ACTIVE_COLOR, ICON_DIMENSION } from '../../../constants/constants'; import { ROLE_DOCS, TEAMS_DOCS } from '../../../constants/docs.constants'; import { EntityAction, EntityType } from '../../../enums/entity.enum'; import { OwnerType } from '../../../enums/user.enum'; @@ -92,7 +79,6 @@ import { PlaceholderProps, TeamDetailsProp, } from '../../../interface/teamsAndUsers.interface'; -import { getCountBadge, hasEditAccess } from '../../../utils/CommonUtils'; import { getEntityName } from '../../../utils/EntityUtils'; import { checkPermission } from '../../../utils/PermissionsUtils'; import { @@ -104,7 +90,6 @@ import { getDeleteMessagePostFix, } from '../../../utils/TeamUtils'; import { showErrorToast, showSuccessToast } from '../../../utils/ToastUtils'; -import Description from '../../common/description/Description'; import ManageButton from '../../common/entityPageInfo/ManageButton/ManageButton'; import ErrorPlaceHolder from '../../common/error-with-placeholder/ErrorPlaceHolder'; import Searchbar from '../../common/searchbar/Searchbar'; @@ -115,15 +100,16 @@ import ConfirmationModal from '../../Modals/ConfirmationModal/ConfirmationModal' import { usePermissionProvider } from '../../PermissionProvider/PermissionProvider'; import { ResourceEntity } from '../../PermissionProvider/PermissionProvider.interface'; import ListEntities from './RolesAndPoliciesList'; -import { TeamsPageTab } from './team.interface'; +import { SubscriptionWebhook, TeamsPageTab } from './team.interface'; import { getTabs } from './TeamDetailsV1.utils'; import TeamHierarchy from './TeamHierarchy'; import './teams.less'; +import TeamsInfo from './TeamsHeaderSection/TeamsInfo.component'; +import TeamsSubscription from './TeamsHeaderSection/TeamsSubscription.component'; import { UserTab } from './UserTab/UserTab.component'; const TeamDetailsV1 = ({ assetsCount, - hasAccess, currentTeam, currentTeamUsers, teamUserPaging, @@ -155,6 +141,8 @@ const TeamDetailsV1 = ({ const { t } = useTranslation(); const history = useHistory(); const location = useLocation(); + const { isAdminUser } = useAuth(); + const { isAuthDisabled } = useAuthContext(); const { activeTab } = useMemo(() => { const param = location.search; @@ -179,11 +167,7 @@ const TeamDetailsV1 = ({ return isGroupType ? TeamsPageTab.USERS : TeamsPageTab.TEAMS; }, [activeTab, isGroupType]); - const [isHeadingEditing, setIsHeadingEditing] = useState(false); const [currentUser, setCurrentUser] = useState(); - const [heading, setHeading] = useState( - currentTeam ? currentTeam.displayName : '' - ); const [deletingUser, setDeletingUser] = useState<{ user: UserTeams | undefined; state: boolean; @@ -199,11 +183,9 @@ const TeamDetailsV1 = ({ attribute: 'defaultRoles' | 'policies'; record: EntityReference; }>(); - const [isModalLoading, setIsModalLoading] = useState(false); - const [isEmailEdit, setIsEmailEdit] = useState(false); + const [isModalLoading, setIsModalLoading] = useState(false); const [previewAsset, setPreviewAsset] = useState(); - const [showTypeSelector, setShowTypeSelector] = useState(false); const { showModal } = useEntityExportModalProvider(); const addPolicy = t('label.add-entity', { @@ -216,6 +198,12 @@ const TeamDetailsV1 = ({ const addTeam = t('label.add-entity', { entity: t('label.team') }); + const hasEditSubscriptionPermission = useMemo( + () => + entityPermissions.EditAll || currentTeam.owner?.id === currentUser?.id, + [entityPermissions, currentTeam, currentUser] + ); + const teamCount = useMemo( () => isOrganization && currentTeam && currentTeam.childrenCount @@ -227,37 +215,6 @@ const TeamDetailsV1 = ({ history.push({ search: Qs.stringify({ activeTab: key }) }); }; - const tabs = useMemo(() => { - const allTabs = getTabs( - currentTeam, - isGroupType, - isOrganization, - teamCount, - assetsCount - ).map((tab) => ({ - ...tab, - label: ( -
- {tab.name} - - {!isNil(tab.count) - ? getCountBadge(tab.count, '', currentTab === tab.key) - : getCountBadge()} - -
- ), - })); - - return allTabs; - }, [ - currentTeam, - teamUserPaging, - searchTerm, - teamCount, - currentTab, - assetsCount, - ]); - const createTeamPermission = useMemo( () => !isEmpty(permissions) && @@ -265,17 +222,6 @@ const TeamDetailsV1 = ({ [permissions] ); - /** - * Check if current team is the owner or not - * @returns - True true or false based on hasEditAccess response - */ - const isOwner = () => { - return hasEditAccess( - currentTeam?.owner?.type || '', - currentTeam?.owner?.id || '' - ); - }; - /** * Take user id as input to find out the user data and set it for delete * @param id - user id @@ -328,10 +274,6 @@ const TeamDetailsV1 = ({ } }; - const isActionAllowed = (operation = false) => { - return hasAccess || isOwner() || operation; - }; - const handleOpenToJoinToggle = () => { if (currentTeam) { const updatedData: Team = { @@ -342,26 +284,6 @@ const TeamDetailsV1 = ({ } }; - const isAlreadyJoinedTeam = (teamId: string) => { - if (currentUser) { - return currentUser.teams?.find((team) => team.id === teamId); - } - - return false; - }; - - const handleHeadingSave = () => { - if (heading && currentTeam) { - const updatedData: Team = { - ...currentTeam, - displayName: heading, - }; - - updateTeamHandler(updatedData); - setIsHeadingEditing(false); - } - }; - const joinTeam = () => { if (currentUser && currentTeam) { const newTeams = cloneDeep(currentUser.teams ?? []); @@ -412,31 +334,18 @@ const TeamDetailsV1 = ({ } }; - const updateOwner = useCallback( - (owner?: EntityReference) => { - if (currentTeam) { - const updatedData: Team = { - ...currentTeam, - owner, - }; - - return updateTeamHandler(updatedData); - } - - return Promise.reject(); - }, - [currentTeam] - ); - - const updateTeamType = async (type: TeamType) => { + const updateTeamSubscription = async (data: SubscriptionWebhook) => { if (currentTeam) { const updatedData: Team = { ...currentTeam, - teamType: type, + profile: { + subscription: { + [data.webhook]: { endpoint: data.endpoint }, + }, + }, }; await updateTeamHandler(updatedData); - setShowTypeSelector(false); } }; @@ -525,19 +434,6 @@ const TeamDetailsV1 = ({ } }; - const handleUpdateEmail: FormProps['onFinish'] = (values) => { - const { email } = values; - if (currentTeam) { - const updatedData: Team = { - ...currentTeam, - email: isEmpty(email) ? undefined : email, - }; - - updateTeamHandler(updatedData); - setIsEmailEdit(false); - } - }; - useEffect(() => { if (currentTeam) { const parents = @@ -545,7 +441,7 @@ const TeamDetailsV1 = ({ ? parentTeams.map((parent) => ({ name: getEntityName(parent), url: getTeamsWithFqnPath( - parent.name || parent.fullyQualifiedName || '' + parent.name ?? parent.fullyQualifiedName ?? '' ), })) : []; @@ -557,7 +453,6 @@ const TeamDetailsV1 = ({ }, ]; setSlashedTeamName(breadcrumb); - setHeading(currentTeam.displayName || currentTeam.name); } }, [currentTeam, parentTeams, showDeletedTeam]); @@ -709,604 +604,557 @@ const TeamDetailsV1 = ({ ] ); - const teamActionButton = (alreadyJoined: boolean, isJoinable: boolean) => { - return alreadyJoined ? ( - isJoinable || hasAccess ? ( - - ) : null - ) : ( - - ); - }; + const isAlreadyJoinedTeam = useMemo( + () => currentUser?.teams?.find((team) => team.id === currentTeam.id), + [currentTeam.id, currentUser] + ); - const getTeamHeading = () => { - return ( -
- {isHeadingEditing ? ( - - + currentTeam.childrenCount === 0 && !searchTerm ? ( + fetchErrorPlaceHolder({ + onClick: () => handleAddTeam(true), + permission: createTeamPermission, + heading: t('label.team'), + }) + ) : ( + + + setHeading(e.target.value)} + searchValue={searchTerm} + typingInterval={500} + onSearch={handleTeamSearch} /> - - - - - - ) : ( - - - {heading} - - {isActionAllowed() && ( - -
- ); - }; + + {t('label.deleted')} + + - const teamTypeElement = useMemo(() => { - if (currentTeam.teamType === TeamType.Organization) { - return null; - } - - return ( - <> - {t('label.type') + ' - '} - {currentTeam.teamType ? ( - showTypeSelector ? ( - - ) : ( - <> - {currentTeam.teamType} - {entityPermissions.EditAll && ( - setShowTypeSelector(true) - }> - - - )} - - ) - ) : ( - {currentTeam.teamType} - )} - - ); - }, [ - currentTeam, - showTypeSelector, - setShowTypeSelector, - parentTeams, - isGroupType, - childTeams, - ]); - - const emailElement = useMemo( - () => ( - - {isEmailEdit ? ( -
- - - - - + {createTeamPermission && ( - - + )} -
- ) : ( - <> - - {currentTeam.email || - t('label.no-entity', { entity: t('label.email') })} - - + + + + + ), + [ + addTeam, + searchTerm, + currentTeam, + childTeamList, + showDeletedTeam, + createTeamPermission, + isFetchingAllTeamAdvancedDetails, + onTeamExpand, + handleAddTeam, + handleTeamSearch, + onShowDeletedTeamChange, + ] + ); + + const userTabRender = useMemo( + () => ( + + ), + [ + currentTeamUserPage, + currentTeam, + isTeamMemberLoading, + teamUserPaging, + entityPermissions, + teamUsersSearchText, + currentTeamUsers, + handleAddUser, + teamUserPagingHandler, + removeUserFromTeam, + handleTeamUsersSearchAction, + ] + ); + + const assetTabRender = useMemo( + () => ( + history.push(ROUTES.EXPLORE)} + onAssetClick={setPreviewAsset} + /> + ), + [entityPermissions, setPreviewAsset] + ); + + const rolesTabRender = useMemo( + () => + isEmpty(currentTeam.defaultRoles ?? []) ? ( + fetchErrorPlaceHolder({ + permission: entityPermissions.EditAll, + heading: t('label.role'), + doc: ROLE_DOCS, + children: t('message.assigning-team-entity-description', { + entity: t('label.role'), + name: currentTeam.name, + }), + type: ERROR_PLACEHOLDER_TYPE.ASSIGN, + button: ( + + ), + }) + ) : ( + + + + setEntity({ record, attribute: 'defaultRoles' }) + } + /> + + ), + [currentTeam, entityPermissions, addRole] + ); + + const policiesTabRender = useMemo( + () => + isEmpty(currentTeam.policies) ? ( + fetchErrorPlaceHolder({ + permission: entityPermissions.EditAll, + children: t('message.assigning-team-entity-description', { + entity: t('label.policy-plural'), + name: currentTeam.name, + }), + type: ERROR_PLACEHOLDER_TYPE.ASSIGN, + button: ( + + ), + }) + ) : ( + + + setEntity({ record, attribute: 'policies' })} + /> + + ), + [currentTeam, entityPermissions, addPolicy] + ); + + const teamActionButton = useMemo( + () => + !isOrganization && + !isUndefined(currentUser) && + (isAlreadyJoinedTeam ? ( + + ) : ( + (Boolean(currentTeam.isJoinable) || isAuthDisabled || isAdminUser) && ( + + ) + )), + + [currentUser, isAlreadyJoinedTeam, isAuthDisabled, isAdminUser] + ); + + const teamsCollapseHeader = useMemo( + () => ( + + + + + + + + {!isOrganization && ( + + )} + + + + + + + {teamActionButton} + {!isOrganization ? ( + entityPermissions.EditAll && ( + - - - )} + ) + ) : ( + + )} +
), - [isEmailEdit, currentTeam, entityPermissions] + [ + isGroupType, + parentTeams, + childTeams, + currentTeam, + isOrganization, + slashedTeamName, + entityPermissions, + teamActionButton, + extraDropdownContent, + updateTeamHandler, + afterDeleteAction, + getDeleteMessagePostFix, + ] + ); + + const getTabChildren = useCallback( + (key: TeamsPageTab) => { + switch (key) { + case TeamsPageTab.ASSETS: + return assetTabRender; + case TeamsPageTab.POLICIES: + return policiesTabRender; + case TeamsPageTab.ROLES: + return rolesTabRender; + case TeamsPageTab.TEAMS: + return teamsTableRender; + case TeamsPageTab.USERS: + return userTabRender; + } + }, + [ + assetTabRender, + policiesTabRender, + rolesTabRender, + teamsTableRender, + userTabRender, + ] + ); + + const tabsChildrenRender = useCallback( + (key: TeamsPageTab) => ( + + + {isFetchingAdvancedDetails ? : getTabChildren(key)} + + {previewAsset && ( + + setPreviewAsset(undefined)} + /> + + )} + + ), + [previewAsset, isFetchingAdvancedDetails, getTabChildren] + ); + + const tabs = useMemo( + () => + getTabs( + currentTeam, + isGroupType, + isOrganization, + teamCount, + assetsCount + ).map((tab) => ({ + ...tab, + label: ( + + ), + children: tabsChildrenRender(tab.key), + })), + [ + currentTeam, + teamUserPaging, + searchTerm, + teamCount, + currentTab, + assetsCount, + getTabChildren, + tabsChildrenRender, + ] ); if (isTeamMemberLoading > 0) { return ; } + if (isEmpty(currentTeam)) { + return fetchErrorPlaceHolder({ + onClick: () => handleAddTeam(true), + permission: createTeamPermission, + heading: t('label.team-plural'), + doc: TEAMS_DOCS, + }); + } + return ( - - {!isEmpty(currentTeam) ? ( - - {!isOrganization && ( - - )} -
- {getTeamHeading()} - {!isOrganization ? ( - - {!isUndefined(currentUser) && - teamActionButton( - !isAlreadyJoinedTeam(currentTeam.id), - currentTeam.isJoinable || false - )} - {entityPermissions.EditAll && ( - - )} - - ) : ( - - )} -
- {emailElement} - - - {!isOrganization && } - {teamTypeElement} - -
- descriptionHandler(false)} - onDescriptionEdit={() => descriptionHandler(true)} - onDescriptionUpdate={onDescriptionUpdate} - /> -
- -
- - {isFetchingAdvancedDetails ? ( - - ) : ( -
- {currentTab === TeamsPageTab.TEAMS && - (currentTeam.childrenCount === 0 && !searchTerm ? ( - fetchErrorPlaceHolder({ - onClick: () => handleAddTeam(true), - permission: createTeamPermission, - heading: t('label.team'), - }) - ) : ( - - - - - - - - - - {t('label.deleted')} - - - - {createTeamPermission && ( - - )} - - - - - - - ))} - - {currentTab === TeamsPageTab.USERS && ( - +
+ + + + + + {(currentTeam.owner?.id === currentUser?.id || + entityPermissions.EditAll || + isAlreadyJoinedTeam) && ( + + + )} - {currentTab === TeamsPageTab.ASSETS && ( - history.push(ROUTES.EXPLORE)} - onAssetClick={setPreviewAsset} - /> - )} - - {currentTab === TeamsPageTab.ROLES && - (isEmpty(currentTeam.defaultRoles || []) ? ( - fetchErrorPlaceHolder({ - permission: entityPermissions.EditAll, - heading: t('label.role'), - doc: ROLE_DOCS, - children: t('message.assigning-team-entity-description', { - entity: t('label.role'), - name: currentTeam.name, - }), - type: ERROR_PLACEHOLDER_TYPE.ASSIGN, - button: ( - - ), - }) - ) : ( - - - - setEntity({ record, attribute: 'defaultRoles' }) - } - /> - - ))} - {currentTab === TeamsPageTab.POLICIES && - (isEmpty(currentTeam.policies) ? ( - fetchErrorPlaceHolder({ - permission: entityPermissions.EditAll, - children: t('message.assigning-team-entity-description', { - entity: t('label.policy-plural'), - name: currentTeam.name, - }), - type: ERROR_PLACEHOLDER_TYPE.ASSIGN, - button: ( - - ), - }) - ) : ( - - - - setEntity({ record, attribute: 'policies' }) - } - /> - - ))} -
- )} -
+ + + + {t('label.description')} + + {(entityPermissions.EditDescription || + entityPermissions.EditAll) && ( + descriptionHandler(true)} + /> + )} + + }> + descriptionHandler(false)} + onDescriptionUpdate={onDescriptionUpdate} + /> + + + + + - ) : ( - fetchErrorPlaceHolder({ - onClick: () => handleAddTeam(true), - permission: createTeamPermission, - heading: t('label.team-plural'), - doc: TEAMS_DOCS, - }) - )} - {previewAsset && ( - - setPreviewAsset(undefined)} + + - )} - setDeletingUser(DELETE_USER_INITIAL_STATE)} - onConfirm={handleRemoveUser} - /> - - {addAttribute && ( - data.id)} - title={`${t('label.add')} ${addAttribute.type}`} - type={addAttribute.type} - onCancel={() => setAddAttribute(undefined)} - onSave={(data) => handleAddAttribute(data)} + setDeletingUser(DELETE_USER_INITIAL_STATE)} + onConfirm={handleRemoveUser} /> - )} - {selectedEntity && ( - setEntity(undefined)} - onOk={async () => { - await handleAttributeDelete( - selectedEntity.record, - selectedEntity.attribute - ); - setEntity(undefined); - }}> - - {t('message.are-you-sure-you-want-to-remove-child-from-parent', { - child: getEntityName(selectedEntity.record), - parent: getEntityName(currentTeam), - })} - - - )} - + {addAttribute && ( + data.id)} + title={`${t('label.add')} ${addAttribute.type}`} + type={addAttribute.type} + onCancel={() => setAddAttribute(undefined)} + onSave={(data) => handleAddAttribute(data)} + /> + )} + {selectedEntity && ( + setEntity(undefined)} + onOk={async () => { + await handleAttributeDelete( + selectedEntity.record, + selectedEntity.attribute + ); + setEntity(undefined); + }}> + + {t('message.are-you-sure-you-want-to-remove-child-from-parent', { + child: getEntityName(selectedEntity.record), + parent: getEntityName(currentTeam), + })} + + + )} + +
); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Team/TeamDetails/TeamHierarchy.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Team/TeamDetails/TeamHierarchy.tsx index a27284ab2dc..671d255e466 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Team/TeamDetails/TeamHierarchy.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Team/TeamDetails/TeamHierarchy.tsx @@ -138,7 +138,7 @@ const TeamHierarchy: FC = ({ ), }, ]; - }, [data, onTeamExpand]); + }, [data, isFetchingAllTeamAdvancedDetails, onTeamExpand]); const handleMoveRow = useCallback( async (dragRecord: Team, dropRecord: Team) => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Team/TeamDetails/TeamsHeaderSection/TeamsInfo.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Team/TeamDetails/TeamsHeaderSection/TeamsInfo.component.tsx new file mode 100644 index 00000000000..7f9cad00a57 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Team/TeamDetails/TeamsHeaderSection/TeamsInfo.component.tsx @@ -0,0 +1,357 @@ +/* + * Copyright 2023 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 { CheckOutlined, CloseOutlined } from '@ant-design/icons'; +import { Button, Divider, Form, Input, Space, Tooltip, Typography } from 'antd'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; + +import Icon from '@ant-design/icons/lib/components/Icon'; +import { useAuthContext } from 'components/authentication/auth-provider/AuthProvider'; +import { OwnerLabel } from 'components/common/OwnerLabel/OwnerLabel.component'; +import { EMAIL_REG_EX } from 'constants/regex.constants'; +import { useAuth } from 'hooks/authHooks'; +import { isEmpty, last } from 'lodash'; +import { useTranslation } from 'react-i18next'; +import { hasEditAccess } from 'utils/CommonUtils'; +import { ReactComponent as EditIcon } from '../../../../assets/svg/edit-new.svg'; + +import classNames from 'classnames'; +import TeamTypeSelect from 'components/common/TeamTypeSelect/TeamTypeSelect.component'; +import { NO_DATA_PLACEHOLDER } from 'constants/constants'; +import { Team, TeamType } from 'generated/entity/teams/team'; +import { EntityReference } from 'generated/entity/type'; +import { TeamsInfoProps } from '../team.interface'; + +const TeamsInfo = ({ + parentTeams, + isGroupType, + childTeamsCount, + entityPermissions, + currentTeam, + updateTeamHandler, +}: TeamsInfoProps) => { + const { t } = useTranslation(); + + const { isAdminUser } = useAuth(); + const { isAuthDisabled } = useAuthContext(); + + const [isHeadingEditing, setIsHeadingEditing] = useState(false); + const [isEmailEdit, setIsEmailEdit] = useState(false); + const [showTypeSelector, setShowTypeSelector] = useState(false); + const [heading, setHeading] = useState( + currentTeam ? currentTeam.displayName : '' + ); + + const { email, owner, teamType } = useMemo(() => currentTeam, [currentTeam]); + + const { hasEditPermission, hasEditDisplayNamePermission, hasAccess } = + useMemo( + () => ({ + hasEditPermission: entityPermissions.EditAll, + hasEditDisplayNamePermission: + entityPermissions.EditDisplayName || entityPermissions.EditAll, + hasAccess: isAuthDisabled || isAdminUser, + }), + + [entityPermissions] + ); + + /** + * Check if current team is the owner or not + * @returns - True true or false based on hasEditAccess response + */ + const isCurrentTeamOwner = useMemo( + () => hasEditAccess(owner?.type ?? '', owner?.id ?? ''), + [owner] + ); + + const onHeadingSave = async (): Promise => { + if (heading && currentTeam) { + const updatedData: Team = { + ...currentTeam, + displayName: heading, + }; + + await updateTeamHandler(updatedData); + } + setIsHeadingEditing(false); + }; + + const onEmailSave = async (data: { email: string }) => { + if (currentTeam) { + const updatedData: Team = { + ...currentTeam, + email: isEmpty(data.email) ? undefined : data.email, + }; + + await updateTeamHandler(updatedData); + } + setIsEmailEdit(false); + }; + + const updateOwner = useCallback( + async (owner?: EntityReference) => { + if (currentTeam) { + const updatedData: Team = { + ...currentTeam, + owner, + }; + + await updateTeamHandler(updatedData); + } + }, + [currentTeam] + ); + + const updateTeamType = async (type: TeamType): Promise => { + if (currentTeam) { + const updatedData: Team = { + ...currentTeam, + teamType: type, + }; + + await updateTeamHandler(updatedData); + + setShowTypeSelector(false); + } + }; + + const teamHeadingRender = useMemo( + () => + isHeadingEditing ? ( + + setHeading(e.target.value)} + /> + + + + + + ) : ( + + + {heading} + + {(hasAccess || isCurrentTeamOwner) && ( + + setIsHeadingEditing(true)} + /> + + )} + + ), + [heading, isHeadingEditing, hasEditDisplayNamePermission] + ); + + const emailRender = useMemo( + () => ( + + {`${t( + 'label.email' + )} :`} + {isEmailEdit ? ( +
+ + + + + + + + + +
+ ) : ( + + + {email ?? NO_DATA_PLACEHOLDER} + + {hasEditPermission && ( + + setIsEmailEdit(true)} + /> + + )} + + )} +
+ ), + [email, isEmailEdit, hasEditPermission] + ); + + const teamTypeElement = useMemo(() => { + if (teamType === TeamType.Organization) { + return null; + } + + return ( + + + + {`${t('label.type')} :`} + + {showTypeSelector ? ( + + ) : ( + <> + + {teamType} + + + {hasEditPermission && ( + setShowTypeSelector(true) + }> + + + )} + + )} + + ); + }, [ + teamType, + parentTeams, + isGroupType, + childTeamsCount, + showTypeSelector, + hasEditPermission, + updateTeamType, + setShowTypeSelector, + ]); + + useEffect(() => { + if (currentTeam) { + setHeading(currentTeam.displayName ?? currentTeam.name); + } + }, [currentTeam]); + + return ( + + {teamHeadingRender} + + + + {emailRender} + {teamTypeElement} + + ); +}; + +export default TeamsInfo; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Team/TeamDetails/TeamsHeaderSection/TeamsSubscription.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Team/TeamDetails/TeamsHeaderSection/TeamsSubscription.component.tsx new file mode 100644 index 00000000000..66576a7f154 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Team/TeamDetails/TeamsHeaderSection/TeamsSubscription.component.tsx @@ -0,0 +1,187 @@ +/* + * Copyright 2023 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 { Form, Input, Modal, Select, Space, Typography } from 'antd'; +import { DE_ACTIVE_COLOR, ICON_DIMENSION } from 'constants/constants'; +import { + SUBSCRIPTION_WEBHOOK, + SUBSCRIPTION_WEBHOOK_OPTIONS, +} from 'constants/Teams.constants'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { ReactComponent as EditIcon } from '../../../../assets/svg/edit-new.svg'; + +import { useForm } from 'antd/lib/form/Form'; +import TagsV1 from 'components/Tag/TagsV1/TagsV1.component'; +import { TAG_CONSTANT, TAG_START_WITH } from 'constants/Tag.constants'; +import { Webhook } from 'generated/type/profile'; +import { isEmpty } from 'lodash'; +import { getWebhookIcon } from 'utils/TeamUtils'; +import { SubscriptionWebhook, TeamsSubscriptionProps } from '../team.interface'; + +const TeamsSubscription = ({ + subscription, + hasEditPermission, + updateTeamSubscription, +}: TeamsSubscriptionProps) => { + const [form] = useForm(); + const { t } = useTranslation(); + const [editSubscription, setEditSubscription] = useState(false); + const [isLoading, setIsLoading] = useState(false); + + const getWebhookIconByKey = useCallback((item: SUBSCRIPTION_WEBHOOK) => { + const Icon = getWebhookIcon(item); + + return ; + }, []); + + // Watchers + const webhooks: { + webhook: string; + endpoint: string; + }[] = Form.useWatch(['subscriptions'], form); + + // Run time values needed for conditional rendering + const subscriptionOptions = useMemo(() => { + const exitingWebhook = webhooks?.map((f) => f?.webhook) ?? []; + + return SUBSCRIPTION_WEBHOOK_OPTIONS.map((func) => ({ + label: func.label, + value: func.value, + disabled: exitingWebhook.includes(func.value), + })); + }, [webhooks]); + + const cellItem = useCallback( + (key: string, value: Webhook) => ( + + {getWebhookIconByKey(key as SUBSCRIPTION_WEBHOOK)} + + {value.endpoint} + + + ), + [] + ); + + const subscriptionRenderElement = useMemo(() => { + const webhook = Object.entries(subscription ?? {})?.[0]; + + return isEmpty(subscription) && hasEditPermission ? ( +
setEditSubscription(true)}> + +
+ ) : ( + cellItem(webhook[0], webhook[1]) + ); + }, [subscription]); + + const handleSave = async (values: SubscriptionWebhook) => { + setIsLoading(true); + + try { + await updateTeamSubscription(values); + } catch { + // parent block will throw error + } finally { + setEditSubscription(false); + setIsLoading(false); + } + }; + + useEffect(() => { + if (subscription) { + const data = Object.entries(subscription)[0]; + form.setFieldsValue({ + webhook: data[0], + endpoint: data[1].endpoint, + }); + } + }, [subscription, editSubscription]); + + return ( + + + {`${t('label.subscription')} :`} + + {subscriptionRenderElement} + + {!editSubscription && !isEmpty(subscription) && hasEditPermission && ( + setEditSubscription(true)} + /> + )} + + {editSubscription && ( + setEditSubscription(false)}> +
+ + + +
+
+ )} +
+ ); +}; + +export default TeamsSubscription; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Team/TeamDetails/UserTab/UserTab.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Team/TeamDetails/UserTab/UserTab.component.tsx index 340b9a219cb..ba2fff29b6c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Team/TeamDetails/UserTab/UserTab.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Team/TeamDetails/UserTab/UserTab.component.tsx @@ -214,7 +214,7 @@ export const UserTab = ({ } return ( - + diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Team/TeamDetails/team.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/Team/TeamDetails/team.interface.ts index b094a41bb5f..5bb43607f57 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Team/TeamDetails/team.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/Team/TeamDetails/team.interface.ts @@ -11,6 +11,8 @@ * limitations under the License. */ +import { OperationPermission } from 'components/PermissionProvider/PermissionProvider.interface'; +import { MessagingProvider } from 'generated/type/profile'; import { Team } from '../../../generated/entity/teams/team'; export interface TeamHierarchyProps { @@ -55,3 +57,23 @@ export enum TeamsPageTab { ROLES = 'roles', POLICIES = 'policies', } + +export interface TeamsInfoProps { + parentTeams: Team[]; + isGroupType: boolean; + childTeamsCount: number; + currentTeam: Team; + entityPermissions: OperationPermission; + updateTeamHandler: (data: Team) => Promise; +} + +export interface TeamsSubscriptionProps { + hasEditPermission: boolean; + subscription?: MessagingProvider; + updateTeamSubscription: (value: SubscriptionWebhook) => Promise; +} + +export interface SubscriptionWebhook { + webhook: string; + endpoint: string; +} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Team/TeamDetails/teams.less b/openmetadata-ui/src/main/resources/ui/src/components/Team/TeamDetails/teams.less index 1155ab665b4..20f014aa9e8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Team/TeamDetails/teams.less +++ b/openmetadata-ui/src/main/resources/ui/src/components/Team/TeamDetails/teams.less @@ -14,6 +14,7 @@ @import url('../../../styles/variables.less'); .team-list-container { + padding: 20px; .ant-btn { border-radius: 4px; } @@ -76,9 +77,47 @@ } } +.teams-layout { + margin: -16px -16px 0 -16px; + + .ant-card-head-title { + padding-top: 0; + padding-bottom: 12px; + } + + .teams-profile { + background-color: @team-avatar-bg; + } + + .teams-profile-container { + background: @user-profile-background; + + .ant-card { + background: none; + } + } + + .teams-tabs-content-container { + width: 100%; + .teams-scroll-component { + width: 100%; + height: calc(100vh - 120px); + overflow-y: scroll; + } + } + + .site-collapse-custom-collapse .site-collapse-custom-panel { + overflow: hidden; + padding: 0; + background: @user-profile-background; + border: 0px; + + .ant-collapse-content-box { + padding: 0; + } + } +} .team-assets-right-panel { - margin-top: -24px; - margin-bottom: -24px; .summary-panel-container { height: 100%; border: 0; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/OwnerLabel/OwnerLabel.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/OwnerLabel/OwnerLabel.component.tsx index 883d649e0b7..0a067eb412d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/OwnerLabel/OwnerLabel.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/OwnerLabel/OwnerLabel.component.tsx @@ -14,6 +14,7 @@ import Icon from '@ant-design/icons'; import { Typography } from 'antd'; import { ReactComponent as IconTeamsGrey } from 'assets/svg/teams-grey.svg'; import { ReactComponent as IconUser } from 'assets/svg/user.svg'; +import classNames from 'classnames'; import { getTeamAndUserDetailsPath, getUserPath } from 'constants/constants'; import { OwnerType } from 'enums/user.enum'; import { EntityReference } from 'generated/entity/data/table'; @@ -27,11 +28,13 @@ import { UserTeamSelectableList } from '../UserTeamSelectableList/UserTeamSelect export const OwnerLabel = ({ owner, + className, onUpdate, hasPermission, ownerDisplayName, }: { owner?: EntityReference; + className?: string; onUpdate?: (owner?: EntityReference) => void; hasPermission?: boolean; ownerDisplayName?: ReactNode; @@ -74,7 +77,10 @@ export const OwnerLabel = ({ {displayName ? ( ) : ( {t('label.no-entity', { entity: t('label.owner') })} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/TeamTypeSelect/TeamTypeSelect.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/TeamTypeSelect/TeamTypeSelect.component.tsx index b19d494ada1..637f3b95a82 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/TeamTypeSelect/TeamTypeSelect.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/TeamTypeSelect/TeamTypeSelect.component.tsx @@ -17,7 +17,6 @@ import React, { useMemo, useState } from 'react'; import { getTeamOptionsFromType } from 'utils/TeamUtils'; import { TeamType } from '../../../generated/entity/teams/team'; import { TeamTypeSelectProps } from './TeamTypeSelect.interface'; -import './TeamTypeSelect.style.less'; function TeamTypeSelect({ handleShowTypeSelector, @@ -63,17 +62,23 @@ function TeamTypeSelect({ value={value} onSelect={handleSelect} /> - + ); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/TeamTypeSelect/TeamTypeSelect.style.less b/openmetadata-ui/src/main/resources/ui/src/components/common/TeamTypeSelect/TeamTypeSelect.style.less deleted file mode 100644 index 755d1d2d13c..00000000000 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/TeamTypeSelect/TeamTypeSelect.style.less +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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 url('../../../styles/variables.less'); - -.team-type-select { - .ant-select { - width: 150px; - } - - .edit-team-type-buttons { - .ant-btn { - background-color: @primary-color; - color: white; - width: 24px; - height: 24px; - } - } - - .edit-team-icons { - font-size: 12px; - } -} diff --git a/openmetadata-ui/src/main/resources/ui/src/constants/Teams.constants.ts b/openmetadata-ui/src/main/resources/ui/src/constants/Teams.constants.ts index e045f67a586..88bdd70c5d9 100644 --- a/openmetadata-ui/src/main/resources/ui/src/constants/Teams.constants.ts +++ b/openmetadata-ui/src/main/resources/ui/src/constants/Teams.constants.ts @@ -12,6 +12,7 @@ */ import DraggableBodyRow from 'components/Team/TeamDetails/DraggableBodyRow'; +import { t } from 'i18next'; export const DRAGGABLE_BODY_ROW = 'DraggableBodyRow'; @@ -20,3 +21,25 @@ export const TABLE_CONSTANTS = { row: DraggableBodyRow, }, }; + +export enum SUBSCRIPTION_WEBHOOK { + MS_TEAMS = 'msTeams', + SLACK = 'slack', + G_CHAT = 'gChat', + GENERIC = 'generic', +} + +export const SUBSCRIPTION_WEBHOOK_OPTIONS = [ + { + label: t('label.ms-team-plural'), + value: SUBSCRIPTION_WEBHOOK.MS_TEAMS, + }, + { + label: t('label.slack'), + value: SUBSCRIPTION_WEBHOOK.SLACK, + }, + { + label: t('label.g-chat'), + value: SUBSCRIPTION_WEBHOOK.G_CHAT, + }, +]; diff --git a/openmetadata-ui/src/main/resources/ui/src/interface/teamsAndUsers.interface.ts b/openmetadata-ui/src/main/resources/ui/src/interface/teamsAndUsers.interface.ts index 2050c9d765f..dc0d4d9e1c8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/interface/teamsAndUsers.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/interface/teamsAndUsers.interface.ts @@ -102,7 +102,6 @@ export interface TeamDetailsProp { teamUsersSearchText: string; isDescriptionEditable: boolean; isTeamMemberLoading: number; - hasAccess: boolean; isFetchingAdvancedDetails: boolean; isFetchingAllTeamAdvancedDetails: boolean; entityPermissions: OperationPermission; diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/de-de.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/de-de.json index 7091a333040..3e14ccea819 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/de-de.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/de-de.json @@ -345,6 +345,7 @@ "enter": "Eingeben", "enter-entity": "{{entity}} eingeben", "enter-entity-name": "Geben Sie einen Namen für {{entity}} ein", + "enter-entity-value": "Enter {{entity}} Value", "enter-field-description": "Geben Sie eine Beschreibung für {{field}} ein", "enter-property-value": "Geben Sie einen Wert für die Eigenschaft ein", "enter-type-password": "{{type}}-Passwort eingeben", @@ -382,6 +383,7 @@ "feature-plural": "funktionen", "feature-plural-used": "verwendete funktionen", "february": "Februar", + "feed-filter-plural": "Feed filters", "feed-lowercase": "feed", "feed-plural": "feeds", "field": "Feld", @@ -903,6 +905,7 @@ "sub-domain-plural": "Subdomains", "sub-team-plural": "Unter-Teams", "submit": "Einreichen", + "subscription": "Subscription", "success": "Erfolg", "successfully-lowercase": "erfolgreich", "successfully-uploaded": "Erfolgreich hochgeladen", @@ -1129,6 +1132,7 @@ "connection-test-warning": "Der Test der Verbindung war teilweise erfolgreich: Einige Schritte hatten Fehler, es werden nur teilweise Metadaten übernommen.", "copied-to-clipboard": "In die Zwischenablage kopiert", "copy-to-clipboard": "Link in die Zwischenablage kopiert", + "create-new-domain-guide": "A data mesh is a decentralized data architecture that organizes data by a specific business domain following the concepts of domain-oriented design. Teams take ownership of both operational and analytical data that belongs to the domain. Based on the Data Contract, consumers are provided with Data as a Product. It is powered by Domain-agnostic self-serve data infrastructure. There are three types of domains: Consumer-aligned, Aggregate, and Source-aligned. Domains have Enabling Teams for consulting.", "create-new-glossary-guide": "Ein Glossar ist ein kontrolliertes Vokabular, das verwendet wird, um die Konzepte und Terminologie in einer Organisation zu definieren. Glossare können spezifisch für einen bestimmten Bereich sein (z. B. Business Glossar, Technisches Glossar). Im Glossar können die Standardbegriffe und Konzepte definiert werden, zusammen mit Synonymen und verwandten Begriffen. Es kann festgelegt werden, wie und von wem Begriffe im Glossar hinzugefügt werden können.", "create-or-update-email-account-for-bot": "Die Änderung der Kontaktemail aktualisiert oder erstellt einen neuen Bot-Benutzer.", "created-this-task-lowercase": "hat diese Aufgabe erstellt", @@ -1215,6 +1219,9 @@ "error-while-fetching-access-token": "Fehler beim Abrufen des Zugriffstokens.", "export-entity-help": "Laden Sie alle Ihre {{entity}} als CSV-Datei herunter und teilen Sie sie mit Ihrem Team.", "failed-status-for-entity-deploy": "<0>{{entity}} wurde {{entityStatus}}, aber das Bereitstellen ist fehlgeschlagen.", + "feed-filter-all": "Feeds for all the data assets that you own and follow", + "feed-filter-following": "Feeds for all the data assets that you follow", + "feed-filter-owner": "Feeds for all the data assets that you own", "fetch-dbt-files": "Hier sind die verfügbaren Quellen zum Abrufen von dbt-Katalog- und Manifestdateien.", "fetch-pipeline-status-error": "Fehler beim Abrufen des Pipeline-Status.", "field-ca-certs-description": "Der Zertifikatpfad muss in der Konfiguration hinzugefügt werden. Der Pfad sollte lokal im Ingestion-Container sein.", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json index 136d2c6d54a..f2f6c74fd7d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json @@ -345,6 +345,7 @@ "enter": "Enter", "enter-entity": "Enter {{entity}}", "enter-entity-name": "Enter {{entity}} name", + "enter-entity-value": "Enter {{entity}} Value", "enter-field-description": "Enter {{field}} description", "enter-property-value": "Enter Property Value", "enter-type-password": "Enter {{type}} Password", @@ -904,6 +905,7 @@ "sub-domain-plural": "Sub Domains", "sub-team-plural": "Sub Teams", "submit": "Submit", + "subscription": "Subscription", "success": "Success", "successfully-lowercase": "successfully", "successfully-uploaded": "Successfully Uploaded", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json index 711a4fbd344..7e4f41a8679 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json @@ -345,6 +345,7 @@ "enter": "Entrar", "enter-entity": "Ingrese {{entity}}", "enter-entity-name": "Ingrese el nombre de {{entity}}", + "enter-entity-value": "Enter {{entity}} Value", "enter-field-description": "Ingrese la descripción de {{field}}", "enter-property-value": "Ingrese el valor de la propiedad", "enter-type-password": "Ingrese la contraseña de {{type}}", @@ -904,6 +905,7 @@ "sub-domain-plural": "Sub Domains", "sub-team-plural": "Sub Equipos", "submit": "Enviar", + "subscription": "Subscription", "success": "Éxito", "successfully-lowercase": "successfully", "successfully-uploaded": "Cargado Exitosamente", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json index 300898d45f1..8bf52081272 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json @@ -345,6 +345,7 @@ "enter": "Entrer", "enter-entity": "Entrer {{entity}}", "enter-entity-name": "Entrer un nom pour {{entity}}", + "enter-entity-value": "Enter {{entity}} Value", "enter-field-description": "Entrer une description pour {{field}}", "enter-property-value": "Entrer une valeur pour la propriété", "enter-type-password": "Entrer un mot de passe {{type}}", @@ -904,6 +905,7 @@ "sub-domain-plural": "Sous-Domaines", "sub-team-plural": "Sous-Équipes", "submit": "Soumettre", + "subscription": "Subscription", "success": "Succès", "successfully-lowercase": "avec succès", "successfully-uploaded": "Téléchargé avec succès", @@ -1130,7 +1132,7 @@ "connection-test-warning": "Le test de connexion a réussi partiellement : Certaines étapes ont échoué, nous n'ingérerons que les métadonnées partielles.", "copied-to-clipboard": "Copié dans le presse-papiers", "copy-to-clipboard": "Lien copié dans le presse-papiers", - "create-new-domain-guide": "A data mesh is a decentralized data architecture that organizes data by a specific business domain following the concepts of domain-oriented design. Teams take ownership of both operational and analytical data that belongs to the domain. Based on the Data Contract, consumers are provided with Data as a Product. It is powered by Domain-agnostic self-serve data infrastructure. There are three types of domains: Consumer-aligned, Aggregate, and Source-aligned. Domains have Enabling Teams for consulting.", + "create-new-domain-guide": "A data mesh is a decentralized data architecture that organizes data by a specific business domain following the concepts of domain-oriented design. Teams take ownership of both operational and analytical data that belongs to the domain. Based on the Data Contract, consumers are provided with Data as a Product. It is powered by Domain-agnostic self-serve data infrastructure. There are three types of domains: Consumer-aligned, Aggregate, and Source-aligned. Domains have Enabling Teams for consulting.", "create-new-glossary-guide": "Un Glossaire est un recueil de termes et vocabulaire utilisé pour définir des concepts et terminologies. Glossaires peuvent être spécifiques à certains domaines (e.g., Glossaire Business, Glossaire Technique, etc.). Dans le glossaire, les termes et concepts peuvent être définis tout en spécifiant des synonymes et des termes liés. Il est possible de contrôler qui peut ajouter des termes dans le dans le glossaire et comment ces termes peuvent être ajoutés.", "create-or-update-email-account-for-bot": "Changer l'email créera un nouveau ou mettra à jour l'agent numérique", "created-this-task-lowercase": "a créé cette tâche", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json index 9b4a7338b79..52f4750814e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json @@ -345,6 +345,7 @@ "enter": "入力", "enter-entity": "{{entity}}を入力", "enter-entity-name": "{{entity}}の名前を入力", + "enter-entity-value": "Enter {{entity}} Value", "enter-field-description": "{{field}}の説明を入力", "enter-property-value": "プロパティの値を入力", "enter-type-password": "{{type}} のパスワードを入力", @@ -904,6 +905,7 @@ "sub-domain-plural": "Sub Domains", "sub-team-plural": "サブチーム", "submit": "Submit", + "subscription": "Subscription", "success": "成功", "successfully-lowercase": "successfully", "successfully-uploaded": "アップロード成功", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json index 23423e27340..9fcb61b060b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json @@ -345,6 +345,7 @@ "enter": "Enter", "enter-entity": "Introduzir {{entity}}", "enter-entity-name": "Introduzir nome da {{entity}}", + "enter-entity-value": "Enter {{entity}} Value", "enter-field-description": "Introduzir descrição do {{entity}}", "enter-property-value": "Introduzir valor da propriedade", "enter-type-password": "Introduzir {{type}} da senha", @@ -904,6 +905,7 @@ "sub-domain-plural": "Sub Domains", "sub-team-plural": "Sub-equipes", "submit": "Enviar", + "subscription": "Subscription", "success": "Sucesso", "successfully-lowercase": "successfully", "successfully-uploaded": "Enviado com sucesso", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ru-ru.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ru-ru.json index 4419f9b976a..7d351958629 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ru-ru.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ru-ru.json @@ -345,6 +345,7 @@ "enter": "Введите", "enter-entity": "Введите {{entity}}", "enter-entity-name": "Введите имя {{entity}}", + "enter-entity-value": "Enter {{entity}} Value", "enter-field-description": "Введите описание {{field}}", "enter-property-value": "Введите значение свойства", "enter-type-password": "Введите {{type}} пароль", @@ -904,6 +905,7 @@ "sub-domain-plural": "Sub Domains", "sub-team-plural": "Подгруппы", "submit": "Подтвердить", + "subscription": "Subscription", "success": "Успешно", "successfully-lowercase": "successfully", "successfully-uploaded": "Успешно загружено", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json index 2d359771761..ca7c63bfb0d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json @@ -345,6 +345,7 @@ "enter": "输入", "enter-entity": "输入{{entity}}", "enter-entity-name": "输入{{entity}}的名称", + "enter-entity-value": "Enter {{entity}} Value", "enter-field-description": "输入{{field}}的描述", "enter-property-value": "输入属性值", "enter-type-password": "输入{{type}}密码", @@ -904,6 +905,7 @@ "sub-domain-plural": "子域", "sub-team-plural": "子团队", "submit": "提交", + "subscription": "Subscription", "success": "成功", "successfully-lowercase": "successfully", "successfully-uploaded": "上传成功", diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/teams/TeamsPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/teams/TeamsPage.tsx index d8d742a85da..79d27524c87 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/teams/TeamsPage.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/teams/TeamsPage.tsx @@ -12,7 +12,6 @@ */ import { AxiosError } from 'axios'; -import { useAuthContext } from 'components/authentication/auth-provider/AuthProvider'; import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder'; import { PagingHandlerParams } from 'components/common/next-previous/NextPrevious.interface'; import Loader from 'components/Loader/Loader'; @@ -39,7 +38,7 @@ import { patchTeamDetail, } from 'rest/teamsAPI'; import { getUsers, updateUserDetail } from 'rest/userAPI'; -import { getEncodedFqn } from 'utils/StringsUtils'; +import { getDecodedFqn, getEncodedFqn } from 'utils/StringsUtils'; import AppState from '../../AppState'; import { INITIAL_PAGING_VALUE, @@ -52,7 +51,6 @@ import { EntityReference } from '../../generated/entity/data/table'; import { Team } from '../../generated/entity/teams/team'; import { User } from '../../generated/entity/teams/user'; import { Paging } from '../../generated/type/paging'; -import { useAuth } from '../../hooks/authHooks'; import { SearchResponse } from '../../interface/search.interface'; import { formatUsersResponse } from '../../utils/APIUtils'; import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils'; @@ -64,8 +62,6 @@ const TeamsPage = () => { const history = useHistory(); const { t } = useTranslation(); const { getEntityPermissionByFqn } = usePermissionProvider(); - const { isAdminUser } = useAuth(); - const { isAuthDisabled } = useAuthContext(); const { fqn } = useParams<{ fqn: string }>(); const [currentFqn, setCurrentFqn] = useState(''); const [allTeam, setAllTeam] = useState([]); @@ -223,7 +219,7 @@ const TeamsPage = () => { getUsers({ fields: 'teams,roles', limit: PAGE_SIZE_BASE, - team, + team: getDecodedFqn(team), ...paging, }) .then((res) => { @@ -285,7 +281,11 @@ const TeamsPage = () => { const fetchTeamBasicDetails = async (name: string, loadPage = false) => { setIsPageLoading(loadPage); try { - const data = await getTeamByName(name, ['owner', 'parents'], 'all'); + const data = await getTeamByName( + name, + ['owner', 'parents', 'profile'], + 'all' + ); setSelectedTeam(data); if (!isEmpty(data.parents) && data.parents?.[0].name) { @@ -621,7 +621,6 @@ const TeamsPage = () => { handleJoinTeamClick={handleJoinTeamClick} handleLeaveTeamClick={handleLeaveTeamClick} handleTeamUsersSearchAction={handleUsersSearchAction} - hasAccess={isAuthDisabled || isAdminUser} isDescriptionEditable={isDescriptionEditable} isFetchingAdvancedDetails={isFetchingAdvancedDetails} isFetchingAllTeamAdvancedDetails={isFetchAllTeamAdvancedDetails} diff --git a/openmetadata-ui/src/main/resources/ui/src/styles/app.less b/openmetadata-ui/src/main/resources/ui/src/styles/app.less index de72b624957..518259adb22 100644 --- a/openmetadata-ui/src/main/resources/ui/src/styles/app.less +++ b/openmetadata-ui/src/main/resources/ui/src/styles/app.less @@ -175,6 +175,12 @@ a[href].link-text-grey, border-color: @warning-color; } +// Line height + +.line-height-0 { + line-height: 0; +} + .line-height-16 { line-height: 16px; } diff --git a/openmetadata-ui/src/main/resources/ui/src/styles/fonts.less b/openmetadata-ui/src/main/resources/ui/src/styles/fonts.less index d25584a8769..0806c7d2f61 100644 --- a/openmetadata-ui/src/main/resources/ui/src/styles/fonts.less +++ b/openmetadata-ui/src/main/resources/ui/src/styles/fonts.less @@ -48,6 +48,7 @@ pre { } .text-sm { font-size: 14px; + line-height: 1rem /* 16px */; } .text-md { font-size: 16px; diff --git a/openmetadata-ui/src/main/resources/ui/src/styles/variables.less b/openmetadata-ui/src/main/resources/ui/src/styles/variables.less index c40b85a9790..71b49a0859c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/styles/variables.less +++ b/openmetadata-ui/src/main/resources/ui/src/styles/variables.less @@ -84,7 +84,7 @@ @tag-background-color: rgba(0, 0, 0, 0.03); @trigger-btn-hover-bg: #efefef; @text-highlighter: #ffc34e40; - +@team-avatar-bg: #0950c51a; @navbar-height: 64px; @sidebar-width: 60px; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/TeamUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/TeamUtils.ts index d8cc985adc5..91be59edc4d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/TeamUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/TeamUtils.ts @@ -21,6 +21,11 @@ import { } from '../generated/entity/teams/team'; import { getEntityIdArray } from './CommonUtils'; +import { SUBSCRIPTION_WEBHOOK } from 'constants/Teams.constants'; +import { ReactComponent as GChatIcon } from '../assets/svg/gchat.svg'; +import { ReactComponent as MsTeamsIcon } from '../assets/svg/ms-teams.svg'; +import { ReactComponent as SlackIcon } from '../assets/svg/slack.svg'; + /** * To get filtered list of non-deleted(active) users * @param users List of users @@ -86,6 +91,23 @@ export const getMovedTeamData = (team: Team, parents: string[]): CreateTeam => { } as CreateTeam; }; +/** + * To get webhook svg icon + * @param item webhook key + * @returns SvgComponent + */ +export const getWebhookIcon = (item: SUBSCRIPTION_WEBHOOK): SvgComponent => { + switch (item) { + case SUBSCRIPTION_WEBHOOK.SLACK: + return SlackIcon; + + case SUBSCRIPTION_WEBHOOK.G_CHAT: + return GChatIcon; + + default: + return MsTeamsIcon; + } +}; export const getTeamOptionsFromType = (parentType: TeamType) => { switch (parentType) { case TeamType.Organization: