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>
This commit is contained in:
Ashish Gupta 2023-10-02 18:12:23 +05:30 committed by GitHub
parent e879d512d3
commit 16a4033645
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 1358 additions and 796 deletions

View File

@ -37,7 +37,7 @@ export const BASE_WAIT_TIME = 20000;
const ADMIN = 'admin'; const ADMIN = 'admin';
const RETRIES_COUNT = 4; const RETRIES_COUNT = 4;
const TEAM_TYPES = ['BusinessUnit', 'Department', 'Division', 'Group']; const TEAM_TYPES = ['Department', 'Division', 'Group'];
export const replaceAllSpacialCharWith_ = (text) => { export const replaceAllSpacialCharWith_ = (text) => {
return text.replaceAll(/[&/\\#, +()$~%.'":*?<>{}]/g, '_'); return text.replaceAll(/[&/\\#, +()$~%.'":*?<>{}]/g, '_');
@ -53,8 +53,31 @@ export const checkServiceFieldSectionHighlighting = (field) => {
); );
}; };
const checkTeamTypeOptions = () => { const getTeamType = (currentTeam) => {
for (const teamType of TEAM_TYPES) { 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}"]`) cy.get(`.ant-select-dropdown [title="${teamType}"]`)
.should('exist') .should('exist')
.should('be.visible'); .should('be.visible');
@ -961,12 +984,28 @@ export const addTeam = (TEAM_DETAILS, index) => {
.should('be.visible') .should('be.visible')
.click(); .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}"]`) cy.get(`.ant-select-dropdown [title='BusinessUnit']`)
.should('exist') .should('exist')
.should('be.visible') .should('be.visible')
.click(); .click();
}
cy.get(descriptionBox) cy.get(descriptionBox)
.should('exist') .should('exist')

View File

@ -24,12 +24,12 @@ import {
verifyResponseStatusCode, verifyResponseStatusCode,
} from '../../common/common'; } 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 teamName = 'team-group-test-430116' ?? `team-ct-test-${uuid()}`;
const TEAM_DETAILS = { const TEAM_DETAILS = {
name: teamName, name: teamName,
updatedname: `${teamName}-updated`, updatedName: `${teamName}-updated`,
teamType: 'Group', teamType: 'Group',
description: `This is ${teamName} description`, description: `This is ${teamName} description`,
username: 'Aaron Johnson', username: 'Aaron Johnson',
@ -125,10 +125,8 @@ describe('Teams flow should work properly', () => {
.contains(TEAM_DETAILS.name) .contains(TEAM_DETAILS.name)
.click(); .click();
verifyResponseStatusCode('@permissions', 200); verifyResponseStatusCode('@permissions', 200);
cy.get('[data-testid="add-new-user"]') cy.get('[data-testid="users"]').click();
.should('be.visible') cy.get('[data-testid="add-new-user"]').scrollIntoView().click();
.scrollIntoView();
cy.get('[data-testid="add-new-user"]').click();
verifyResponseStatusCode('@getUsers', 200); verifyResponseStatusCode('@getUsers', 200);
cy.get('[data-testid="selectable-list"] [data-testid="searchbar"]').type( cy.get('[data-testid="selectable-list"] [data-testid="searchbar"]').type(
TEAM_DETAILS.username TEAM_DETAILS.username
@ -154,6 +152,7 @@ describe('Teams flow should work properly', () => {
cy.get(`[data-row-key="${TEAM_DETAILS.name}"]`) cy.get(`[data-row-key="${TEAM_DETAILS.name}"]`)
.contains(TEAM_DETAILS.name) .contains(TEAM_DETAILS.name)
.click(); .click();
cy.get('[data-testid="users"]').click();
verifyResponseStatusCode('@getUserDetails', 200); verifyResponseStatusCode('@getUserDetails', 200);
verifyResponseStatusCode('@permissions', 200); verifyResponseStatusCode('@permissions', 200);
cy.get('[data-testid="add-new-user"]').should('be.visible').click(); 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) .contains(TEAM_DETAILS.name)
.click(); .click();
cy.get('[data-testid="users"]').click();
verifyResponseStatusCode('@getUsers', 200); verifyResponseStatusCode('@getUsers', 200);
// Click on join teams button // 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 // Verify toast notification
toastNotification('Team joined successfully!'); toastNotification('Team joined successfully!');
@ -201,14 +205,14 @@ describe('Teams flow should work properly', () => {
verifyResponseStatusCode('@getSelectedTeam', 200); verifyResponseStatusCode('@getSelectedTeam', 200);
// Click on edit display name // 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 // Enter the updated team name
cy.get('[data-testid="synonyms"]') cy.get('[data-testid="team-name-input"]')
.should('exist') .should('exist')
.should('be.visible') .should('be.visible')
.clear() .clear()
.type(TEAM_DETAILS.updatedname); .type(TEAM_DETAILS.updatedName);
// Save the updated display name // Save the updated display name
cy.get('[data-testid="saveAssociatedTag"]') cy.get('[data-testid="saveAssociatedTag"]')
@ -220,12 +224,12 @@ describe('Teams flow should work properly', () => {
verifyResponseStatusCode('@getSelectedTeam', 200); verifyResponseStatusCode('@getSelectedTeam', 200);
// Validate the updated display name // Validate the updated display name
cy.get('[data-testid="team-heading"]').then(($el) => { 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"]') cy.get('[data-testid="inactive-link"]')
.should('be.visible') .scrollIntoView()
.should('contain', TEAM_DETAILS.updatedname); .should('contain', TEAM_DETAILS.updatedName);
}); });
it('Update description for created team', () => { it('Update description for created team', () => {
@ -245,12 +249,12 @@ describe('Teams flow should work properly', () => {
// Validate the updated display name // Validate the updated display name
cy.get('[data-testid="team-heading"]').should( cy.get('[data-testid="team-heading"]').should(
'contain', 'contain',
`${TEAM_DETAILS.updatedname}` `${TEAM_DETAILS.updatedName}`
); );
cy.get('[data-testid="inactive-link"]') cy.get('[data-testid="inactive-link"]')
.should('be.visible') .should('be.visible')
.should('contain', TEAM_DETAILS.updatedname); .should('contain', TEAM_DETAILS.updatedName);
// Click on edit description button // Click on edit description button
cy.get('[data-testid="edit-description"]') cy.get('[data-testid="edit-description"]')
@ -258,7 +262,7 @@ describe('Teams flow should work properly', () => {
.click({ force: true }); .click({ force: true });
// Entering updated description // Entering updated description
cy.get(descriptionBox).clear().type(updateddescription); cy.get(descriptionBox).clear().type(updatedDescription);
cy.get('[data-testid="save"]').should('be.visible').click(); cy.get('[data-testid="save"]').should('be.visible').click();
verifyResponseStatusCode('@patchDescription', 200); verifyResponseStatusCode('@patchDescription', 200);
@ -266,7 +270,7 @@ describe('Teams flow should work properly', () => {
// Validating the updated description // Validating the updated description
cy.get('[data-testid="description"] p').should( cy.get('[data-testid="description"] p').should(
'contain', 'contain',
updateddescription updatedDescription
); );
}); });
@ -287,7 +291,7 @@ describe('Teams flow should work properly', () => {
.should('be.visible') .should('be.visible')
.contains(TEAM_DETAILS.name); .contains(TEAM_DETAILS.name);
// //Click on Leave team // //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 // //Click on confirm button
cy.get('[data-testid="save-button"]').should('be.visible').click(); cy.get('[data-testid="save-button"]').should('be.visible').click();
@ -312,8 +316,8 @@ describe('Teams flow should work properly', () => {
verifyResponseStatusCode('@getSelectedTeam', 200); verifyResponseStatusCode('@getSelectedTeam', 200);
cy.get('[data-testid="team-heading"]') cy.get('[data-testid="team-heading"]')
.should('be.visible') .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('exist')
.should('be.visible') .should('be.visible')
.click(); .click();
@ -380,9 +384,9 @@ describe('Teams flow should work properly', () => {
cy.get('[data-testid="team-heading"]') cy.get('[data-testid="team-heading"]')
.should('be.visible') .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('exist')
.should('be.visible') .should('be.visible')
.click(); .click();
@ -436,7 +440,7 @@ describe('Teams flow should work properly', () => {
.click(); .click();
verifyResponseStatusCode('@getSelectedTeam', 200); verifyResponseStatusCode('@getSelectedTeam', 200);
cy.get('[data-testid="header"] [data-testid="manage-button"]') cy.get('[data-testid="manage-button"]')
.should('exist') .should('exist')
.should('be.visible') .should('be.visible')
.click(); .click();

View File

@ -0,0 +1,12 @@
<svg viewBox="0 0 25 27" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_4154_24860)">
<path d="M5.67376 14.5249V6.05078H1.97121C0.88131 6.05078 0 6.93731 0 8.02721V25.5439C0 26.4252 1.06383 26.8633 1.6844 26.2427L5.7572 22.1699H17.355C18.4449 22.1699 19.3262 21.2886 19.3262 20.1987V16.4961H7.65019C6.56028 16.4961 5.67376 15.6148 5.67376 14.5249Z" fill="#00AC47"/>
<path d="M23.0288 0.460938H7.65017C6.56026 0.460938 5.67896 1.34225 5.67896 2.43215V6.05126H17.355C18.4449 6.05126 19.3262 6.93257 19.3262 8.02247V16.4914H23.0288C24.1187 16.4914 25 15.6101 25 14.5202V2.43215C25 1.34225 24.1187 0.460938 23.0288 0.460938Z" fill="#5BB974"/>
<path d="M17.355 6.05078H5.67371V14.5197C5.67371 15.6096 6.55502 16.4909 7.64492 16.4909H19.321V8.02721C19.3262 6.93731 18.4449 6.05078 17.355 6.05078Z" fill="#00832D"/>
</g>
<defs>
<clipPath id="clip0_4154_24860">
<rect width="25" height="26.0743" fill="white" transform="translate(0 0.460938)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 991 B

View File

@ -0,0 +1,5 @@
<svg viewBox="0 0 105 65" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M36.9019 2.53811C29.7245 1.986 15.3697 2.5371 15.3697 18.5493C15.3697 32.6833 21.995 36.9517 25.3076 37.3192V43.3924L10.4007 51.674C7.82417 53.1463 2.67114 57.416 2.67114 62.7162H25.3076L44.0794 52.7782V44.4966C36.9019 37.3192 35.2456 30.0108 35.2456 20.2038C35.2456 8.54045 41.3188 2.53811 52.9131 2.53811" stroke="currentColor" stroke-width="4" stroke-linecap="round"/>
<path d="M68.3725 2.53811C75.5499 1.986 89.9047 2.5371 89.9047 18.5493C89.9047 32.6833 83.2794 36.9517 79.9668 37.3192V43.3924L94.8737 51.674C97.4502 53.1463 102.603 57.416 102.603 62.7162H79.9668L61.195 52.7782V44.4966C68.3725 37.3192 70.0288 30.0108 70.0288 20.2038C70.0288 8.54045 63.9556 2.53811 52.3613 2.53811" stroke="currentColor" stroke-width="4" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 844 B

View File

@ -138,7 +138,7 @@ const TeamHierarchy: FC<TeamHierarchyProps> = ({
), ),
}, },
]; ];
}, [data, onTeamExpand]); }, [data, isFetchingAllTeamAdvancedDetails, onTeamExpand]);
const handleMoveRow = useCallback( const handleMoveRow = useCallback(
async (dragRecord: Team, dropRecord: Team) => { async (dragRecord: Team, dropRecord: Team) => {

View File

@ -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<boolean>(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<void> => {
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<void> => {
if (currentTeam) {
const updatedData: Team = {
...currentTeam,
teamType: type,
};
await updateTeamHandler(updatedData);
setShowTypeSelector(false);
}
};
const teamHeadingRender = useMemo(
() =>
isHeadingEditing ? (
<Space>
<Input
className="w-48"
data-testid="team-name-input"
placeholder={t('message.enter-comma-separated-field', {
field: t('label.term-lowercase'),
})}
type="text"
value={heading}
onChange={(e) => setHeading(e.target.value)}
/>
<Space data-testid="buttons" size={4}>
<Button
className="rounded-4 text-sm p-xss"
data-testid="cancelAssociatedTag"
type="primary"
onMouseDown={() => setIsHeadingEditing(false)}>
<CloseOutlined />
</Button>
<Button
className="rounded-4 text-sm p-xss"
data-testid="saveAssociatedTag"
type="primary"
onMouseDown={onHeadingSave}>
<CheckOutlined />
</Button>
</Space>
</Space>
) : (
<Space align="baseline">
<Typography.Title
className="m-b-0 w-max-200"
data-testid="team-heading"
ellipsis={{ tooltip: true }}
level={5}>
{heading}
</Typography.Title>
{(hasAccess || isCurrentTeamOwner) && (
<Tooltip
placement="right"
title={
hasEditDisplayNamePermission
? t('label.edit-entity', {
entity: t('label.display-name'),
})
: t('message.no-permission-for-action')
}>
<Icon
className="align-middle"
component={EditIcon}
data-testid="edit-team-name"
disabled={!hasEditDisplayNamePermission}
style={{ fontSize: '16px' }}
onClick={() => setIsHeadingEditing(true)}
/>
</Tooltip>
)}
</Space>
),
[heading, isHeadingEditing, hasEditDisplayNamePermission]
);
const emailRender = useMemo(
() => (
<Space align="center" size={4}>
<Typography.Text className="text-grey-muted">{`${t(
'label.email'
)} :`}</Typography.Text>
{isEmailEdit ? (
<Form initialValues={{ email }} onFinish={onEmailSave}>
<Space align="baseline">
<Form.Item
className="m-b-0"
name="email"
rules={[
{
pattern: EMAIL_REG_EX,
type: 'email',
message: t('message.field-text-is-invalid', {
fieldText: t('label.email'),
}),
},
]}>
<Input
className="w-48"
data-testid="email-input"
placeholder={t('label.enter-entity', {
entity: t('label.email-lowercase'),
})}
/>
</Form.Item>
<Space size={4}>
<Button
className="h-8 p-x-xss"
data-testid="cancel-edit-email"
size="small"
type="primary"
onClick={() => setIsEmailEdit(false)}>
<CloseOutlined />
</Button>
<Button
className="h-8 p-x-xss"
data-testid="save-edit-email"
htmlType="submit"
size="small"
type="primary">
<CheckOutlined />
</Button>
</Space>
</Space>
</Form>
) : (
<Space align="center">
<Typography.Text className="font-medium" data-testid="email-value">
{email ?? NO_DATA_PLACEHOLDER}
</Typography.Text>
{hasEditPermission && (
<Tooltip
placement="right"
title={
hasEditPermission
? t('label.edit-entity', {
entity: t('label.email'),
})
: t('message.no-permission-for-action')
}>
<Icon
className="toolbar-button align-middle"
component={EditIcon}
data-testid="edit-email"
style={{ fontSize: '16px' }}
onClick={() => setIsEmailEdit(true)}
/>
</Tooltip>
)}
</Space>
)}
</Space>
),
[email, isEmailEdit, hasEditPermission]
);
const teamTypeElement = useMemo(() => {
if (teamType === TeamType.Organization) {
return null;
}
return (
<Space size={4}>
<Divider type="vertical" />
<Typography.Text className="text-grey-muted">
{`${t('label.type')} :`}
</Typography.Text>
{showTypeSelector ? (
<TeamTypeSelect
handleShowTypeSelector={setShowTypeSelector}
parentTeamType={
last(parentTeams)?.teamType ?? TeamType.Organization
}
showGroupOption={!childTeamsCount}
teamType={teamType ?? TeamType.Department}
updateTeamType={hasEditPermission ? updateTeamType : undefined}
/>
) : (
<>
<Typography.Text className="font-medium" data-testid="team-type">
{teamType}
</Typography.Text>
{hasEditPermission && (
<Icon
className={classNames('vertical-middle m-l-xs', {
'opacity-50': isGroupType,
})}
data-testid="edit-team-type-icon"
title={
isGroupType
? t('message.group-team-type-change-message')
: t('label.edit-entity', {
entity: t('label.team-type'),
})
}
onClick={
isGroupType ? undefined : () => setShowTypeSelector(true)
}>
<EditIcon />
</Icon>
)}
</>
)}
</Space>
);
}, [
teamType,
parentTeams,
isGroupType,
childTeamsCount,
showTypeSelector,
hasEditPermission,
updateTeamType,
setShowTypeSelector,
]);
useEffect(() => {
if (currentTeam) {
setHeading(currentTeam.displayName ?? currentTeam.name);
}
}, [currentTeam]);
return (
<Space size={4}>
{teamHeadingRender}
<Divider type="vertical" />
<OwnerLabel
className="text-sm"
hasPermission={hasAccess}
owner={owner}
onUpdate={updateOwner}
/>
<Divider type="vertical" />
{emailRender}
{teamTypeElement}
</Space>
);
};
export default TeamsInfo;

View File

@ -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<boolean>(false);
const [isLoading, setIsLoading] = useState<boolean>(false);
const getWebhookIconByKey = useCallback((item: SUBSCRIPTION_WEBHOOK) => {
const Icon = getWebhookIcon(item);
return <Icon data-testid={`${item}-icon`} height={20} width={20} />;
}, []);
// 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) => (
<Space align="start">
{getWebhookIconByKey(key as SUBSCRIPTION_WEBHOOK)}
<Typography.Text className="text-xs text-grey-muted">
{value.endpoint}
</Typography.Text>
</Space>
),
[]
);
const subscriptionRenderElement = useMemo(() => {
const webhook = Object.entries(subscription ?? {})?.[0];
return isEmpty(subscription) && hasEditPermission ? (
<div onClick={() => setEditSubscription(true)}>
<TagsV1 startWith={TAG_START_WITH.PLUS} tag={TAG_CONSTANT} />
</div>
) : (
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 (
<Space align="start" data-testid="teams-subscription">
<Typography.Text className="right-panel-label font-normal">
{`${t('label.subscription')} :`}
</Typography.Text>
{subscriptionRenderElement}
{!editSubscription && !isEmpty(subscription) && hasEditPermission && (
<EditIcon
className="cursor-pointer align-middle"
color={DE_ACTIVE_COLOR}
data-testid="edit-roles"
{...ICON_DIMENSION}
onClick={() => setEditSubscription(true)}
/>
)}
{editSubscription && (
<Modal
centered
open
closable={false}
confirmLoading={isLoading}
maskClosable={false}
okButtonProps={{
form: 'subscription-form',
type: 'primary',
htmlType: 'submit',
}}
okText={t('label.confirm')}
title={t('label.add-entity', {
entity: t('label.subscription'),
})}
onCancel={() => setEditSubscription(false)}>
<Form
data-testid="subscription-modal"
form={form}
id="subscription-form"
layout="vertical"
onFinish={handleSave}>
<Form.Item label={t('label.webhook')} name="webhook">
<Select
options={subscriptionOptions}
placeholder={t('label.select-field', {
field: t('label.condition'),
})}
/>
</Form.Item>
<Form.Item
label={t('label.endpoint')}
name="endpoint"
rules={[
{
required: true,
message: t('label.field-required-plural', {
field: t('label.endpoint'),
}),
},
{
type: 'url',
message: t('message.endpoint-should-be-valid'),
},
]}>
<Input
placeholder={t('label.enter-entity-value', {
entity: t('label.endpoint'),
})}
/>
</Form.Item>
</Form>
</Modal>
)}
</Space>
);
};
export default TeamsSubscription;

View File

@ -214,7 +214,7 @@ export const UserTab = ({
} }
return ( return (
<Row gutter={[16, 16]}> <Row className="p-md" gutter={[16, 16]}>
<Col span={24}> <Col span={24}>
<Row justify="space-between"> <Row justify="space-between">
<Col span={8}> <Col span={8}>

View File

@ -11,6 +11,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { OperationPermission } from 'components/PermissionProvider/PermissionProvider.interface';
import { MessagingProvider } from 'generated/type/profile';
import { Team } from '../../../generated/entity/teams/team'; import { Team } from '../../../generated/entity/teams/team';
export interface TeamHierarchyProps { export interface TeamHierarchyProps {
@ -55,3 +57,23 @@ export enum TeamsPageTab {
ROLES = 'roles', ROLES = 'roles',
POLICIES = 'policies', POLICIES = 'policies',
} }
export interface TeamsInfoProps {
parentTeams: Team[];
isGroupType: boolean;
childTeamsCount: number;
currentTeam: Team;
entityPermissions: OperationPermission;
updateTeamHandler: (data: Team) => Promise<void>;
}
export interface TeamsSubscriptionProps {
hasEditPermission: boolean;
subscription?: MessagingProvider;
updateTeamSubscription: (value: SubscriptionWebhook) => Promise<void>;
}
export interface SubscriptionWebhook {
webhook: string;
endpoint: string;
}

View File

@ -14,6 +14,7 @@
@import url('../../../styles/variables.less'); @import url('../../../styles/variables.less');
.team-list-container { .team-list-container {
padding: 20px;
.ant-btn { .ant-btn {
border-radius: 4px; 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 { .team-assets-right-panel {
margin-top: -24px;
margin-bottom: -24px;
.summary-panel-container { .summary-panel-container {
height: 100%; height: 100%;
border: 0; border: 0;

View File

@ -14,6 +14,7 @@ import Icon from '@ant-design/icons';
import { Typography } from 'antd'; import { Typography } from 'antd';
import { ReactComponent as IconTeamsGrey } from 'assets/svg/teams-grey.svg'; import { ReactComponent as IconTeamsGrey } from 'assets/svg/teams-grey.svg';
import { ReactComponent as IconUser } from 'assets/svg/user.svg'; import { ReactComponent as IconUser } from 'assets/svg/user.svg';
import classNames from 'classnames';
import { getTeamAndUserDetailsPath, getUserPath } from 'constants/constants'; import { getTeamAndUserDetailsPath, getUserPath } from 'constants/constants';
import { OwnerType } from 'enums/user.enum'; import { OwnerType } from 'enums/user.enum';
import { EntityReference } from 'generated/entity/data/table'; import { EntityReference } from 'generated/entity/data/table';
@ -27,11 +28,13 @@ import { UserTeamSelectableList } from '../UserTeamSelectableList/UserTeamSelect
export const OwnerLabel = ({ export const OwnerLabel = ({
owner, owner,
className,
onUpdate, onUpdate,
hasPermission, hasPermission,
ownerDisplayName, ownerDisplayName,
}: { }: {
owner?: EntityReference; owner?: EntityReference;
className?: string;
onUpdate?: (owner?: EntityReference) => void; onUpdate?: (owner?: EntityReference) => void;
hasPermission?: boolean; hasPermission?: boolean;
ownerDisplayName?: ReactNode; ownerDisplayName?: ReactNode;
@ -74,7 +77,10 @@ export const OwnerLabel = ({
{displayName ? ( {displayName ? (
<Link <Link
className="text-primary font-medium text-xs no-underline" className={classNames(
'text-primary font-medium text-xs no-underline',
className
)}
data-testid="owner-link" data-testid="owner-link"
to={ to={
owner?.type === 'team' owner?.type === 'team'
@ -85,7 +91,7 @@ export const OwnerLabel = ({
</Link> </Link>
) : ( ) : (
<Typography.Text <Typography.Text
className="font-medium text-xs" className={classNames('font-medium text-xs', className)}
data-testid="owner-link"> data-testid="owner-link">
{t('label.no-entity', { entity: t('label.owner') })} {t('label.no-entity', { entity: t('label.owner') })}
</Typography.Text> </Typography.Text>

View File

@ -17,7 +17,6 @@ import React, { useMemo, useState } from 'react';
import { getTeamOptionsFromType } from 'utils/TeamUtils'; import { getTeamOptionsFromType } from 'utils/TeamUtils';
import { TeamType } from '../../../generated/entity/teams/team'; import { TeamType } from '../../../generated/entity/teams/team';
import { TeamTypeSelectProps } from './TeamTypeSelect.interface'; import { TeamTypeSelectProps } from './TeamTypeSelect.interface';
import './TeamTypeSelect.style.less';
function TeamTypeSelect({ function TeamTypeSelect({
handleShowTypeSelector, handleShowTypeSelector,
@ -63,17 +62,23 @@ function TeamTypeSelect({
value={value} value={value}
onSelect={handleSelect} onSelect={handleSelect}
/> />
<Space className="edit-team-type-buttons" size={4}> <Space className="m-l-xs" size={4}>
<Button <Button
className="h-8 p-x-xss"
data-testid="cancel-btn" data-testid="cancel-btn"
icon={<CloseOutlined />} size="small"
onClick={handleCancel} type="primary"
/> onClick={handleCancel}>
<CloseOutlined />
</Button>
<Button <Button
className="h-8 p-x-xss"
data-testid="save-btn" data-testid="save-btn"
icon={<CheckOutlined />} size="small"
onClick={handleSubmit} type="primary"
/> onClick={handleSubmit}>
<CheckOutlined />
</Button>
</Space> </Space>
</Space> </Space>
); );

View File

@ -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;
}
}

View File

@ -12,6 +12,7 @@
*/ */
import DraggableBodyRow from 'components/Team/TeamDetails/DraggableBodyRow'; import DraggableBodyRow from 'components/Team/TeamDetails/DraggableBodyRow';
import { t } from 'i18next';
export const DRAGGABLE_BODY_ROW = 'DraggableBodyRow'; export const DRAGGABLE_BODY_ROW = 'DraggableBodyRow';
@ -20,3 +21,25 @@ export const TABLE_CONSTANTS = {
row: DraggableBodyRow, 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,
},
];

View File

@ -102,7 +102,6 @@ export interface TeamDetailsProp {
teamUsersSearchText: string; teamUsersSearchText: string;
isDescriptionEditable: boolean; isDescriptionEditable: boolean;
isTeamMemberLoading: number; isTeamMemberLoading: number;
hasAccess: boolean;
isFetchingAdvancedDetails: boolean; isFetchingAdvancedDetails: boolean;
isFetchingAllTeamAdvancedDetails: boolean; isFetchingAllTeamAdvancedDetails: boolean;
entityPermissions: OperationPermission; entityPermissions: OperationPermission;

View File

@ -345,6 +345,7 @@
"enter": "Eingeben", "enter": "Eingeben",
"enter-entity": "{{entity}} eingeben", "enter-entity": "{{entity}} eingeben",
"enter-entity-name": "Geben Sie einen Namen für {{entity}} ein", "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-field-description": "Geben Sie eine Beschreibung für {{field}} ein",
"enter-property-value": "Geben Sie einen Wert für die Eigenschaft ein", "enter-property-value": "Geben Sie einen Wert für die Eigenschaft ein",
"enter-type-password": "{{type}}-Passwort eingeben", "enter-type-password": "{{type}}-Passwort eingeben",
@ -382,6 +383,7 @@
"feature-plural": "funktionen", "feature-plural": "funktionen",
"feature-plural-used": "verwendete funktionen", "feature-plural-used": "verwendete funktionen",
"february": "Februar", "february": "Februar",
"feed-filter-plural": "Feed filters",
"feed-lowercase": "feed", "feed-lowercase": "feed",
"feed-plural": "feeds", "feed-plural": "feeds",
"field": "Feld", "field": "Feld",
@ -903,6 +905,7 @@
"sub-domain-plural": "Subdomains", "sub-domain-plural": "Subdomains",
"sub-team-plural": "Unter-Teams", "sub-team-plural": "Unter-Teams",
"submit": "Einreichen", "submit": "Einreichen",
"subscription": "Subscription",
"success": "Erfolg", "success": "Erfolg",
"successfully-lowercase": "erfolgreich", "successfully-lowercase": "erfolgreich",
"successfully-uploaded": "Erfolgreich hochgeladen", "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.", "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", "copied-to-clipboard": "In die Zwischenablage kopiert",
"copy-to-clipboard": "Link 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-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.", "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", "created-this-task-lowercase": "hat diese Aufgabe erstellt",
@ -1215,6 +1219,9 @@
"error-while-fetching-access-token": "Fehler beim Abrufen des Zugriffstokens.", "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.", "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}}</0> wurde {{entityStatus}}, aber das Bereitstellen ist fehlgeschlagen.", "failed-status-for-entity-deploy": "<0>{{entity}}</0> 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-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.", "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.", "field-ca-certs-description": "Der Zertifikatpfad muss in der Konfiguration hinzugefügt werden. Der Pfad sollte lokal im Ingestion-Container sein.",

View File

@ -345,6 +345,7 @@
"enter": "Enter", "enter": "Enter",
"enter-entity": "Enter {{entity}}", "enter-entity": "Enter {{entity}}",
"enter-entity-name": "Enter {{entity}} name", "enter-entity-name": "Enter {{entity}} name",
"enter-entity-value": "Enter {{entity}} Value",
"enter-field-description": "Enter {{field}} description", "enter-field-description": "Enter {{field}} description",
"enter-property-value": "Enter Property Value", "enter-property-value": "Enter Property Value",
"enter-type-password": "Enter {{type}} Password", "enter-type-password": "Enter {{type}} Password",
@ -904,6 +905,7 @@
"sub-domain-plural": "Sub Domains", "sub-domain-plural": "Sub Domains",
"sub-team-plural": "Sub Teams", "sub-team-plural": "Sub Teams",
"submit": "Submit", "submit": "Submit",
"subscription": "Subscription",
"success": "Success", "success": "Success",
"successfully-lowercase": "successfully", "successfully-lowercase": "successfully",
"successfully-uploaded": "Successfully Uploaded", "successfully-uploaded": "Successfully Uploaded",

View File

@ -345,6 +345,7 @@
"enter": "Entrar", "enter": "Entrar",
"enter-entity": "Ingrese {{entity}}", "enter-entity": "Ingrese {{entity}}",
"enter-entity-name": "Ingrese el nombre de {{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-field-description": "Ingrese la descripción de {{field}}",
"enter-property-value": "Ingrese el valor de la propiedad", "enter-property-value": "Ingrese el valor de la propiedad",
"enter-type-password": "Ingrese la contraseña de {{type}}", "enter-type-password": "Ingrese la contraseña de {{type}}",
@ -904,6 +905,7 @@
"sub-domain-plural": "Sub Domains", "sub-domain-plural": "Sub Domains",
"sub-team-plural": "Sub Equipos", "sub-team-plural": "Sub Equipos",
"submit": "Enviar", "submit": "Enviar",
"subscription": "Subscription",
"success": "Éxito", "success": "Éxito",
"successfully-lowercase": "successfully", "successfully-lowercase": "successfully",
"successfully-uploaded": "Cargado Exitosamente", "successfully-uploaded": "Cargado Exitosamente",

View File

@ -345,6 +345,7 @@
"enter": "Entrer", "enter": "Entrer",
"enter-entity": "Entrer {{entity}}", "enter-entity": "Entrer {{entity}}",
"enter-entity-name": "Entrer un nom pour {{entity}}", "enter-entity-name": "Entrer un nom pour {{entity}}",
"enter-entity-value": "Enter {{entity}} Value",
"enter-field-description": "Entrer une description pour {{field}}", "enter-field-description": "Entrer une description pour {{field}}",
"enter-property-value": "Entrer une valeur pour la propriété", "enter-property-value": "Entrer une valeur pour la propriété",
"enter-type-password": "Entrer un mot de passe {{type}}", "enter-type-password": "Entrer un mot de passe {{type}}",
@ -904,6 +905,7 @@
"sub-domain-plural": "Sous-Domaines", "sub-domain-plural": "Sous-Domaines",
"sub-team-plural": "Sous-Équipes", "sub-team-plural": "Sous-Équipes",
"submit": "Soumettre", "submit": "Soumettre",
"subscription": "Subscription",
"success": "Succès", "success": "Succès",
"successfully-lowercase": "avec succès", "successfully-lowercase": "avec succès",
"successfully-uploaded": "Téléchargé avec succès", "successfully-uploaded": "Téléchargé avec succès",

View File

@ -345,6 +345,7 @@
"enter": "入力", "enter": "入力",
"enter-entity": "{{entity}}を入力", "enter-entity": "{{entity}}を入力",
"enter-entity-name": "{{entity}}の名前を入力", "enter-entity-name": "{{entity}}の名前を入力",
"enter-entity-value": "Enter {{entity}} Value",
"enter-field-description": "{{field}}の説明を入力", "enter-field-description": "{{field}}の説明を入力",
"enter-property-value": "プロパティの値を入力", "enter-property-value": "プロパティの値を入力",
"enter-type-password": "{{type}} のパスワードを入力", "enter-type-password": "{{type}} のパスワードを入力",
@ -904,6 +905,7 @@
"sub-domain-plural": "Sub Domains", "sub-domain-plural": "Sub Domains",
"sub-team-plural": "サブチーム", "sub-team-plural": "サブチーム",
"submit": "Submit", "submit": "Submit",
"subscription": "Subscription",
"success": "成功", "success": "成功",
"successfully-lowercase": "successfully", "successfully-lowercase": "successfully",
"successfully-uploaded": "アップロード成功", "successfully-uploaded": "アップロード成功",

View File

@ -345,6 +345,7 @@
"enter": "Enter", "enter": "Enter",
"enter-entity": "Introduzir {{entity}}", "enter-entity": "Introduzir {{entity}}",
"enter-entity-name": "Introduzir nome da {{entity}}", "enter-entity-name": "Introduzir nome da {{entity}}",
"enter-entity-value": "Enter {{entity}} Value",
"enter-field-description": "Introduzir descrição do {{entity}}", "enter-field-description": "Introduzir descrição do {{entity}}",
"enter-property-value": "Introduzir valor da propriedade", "enter-property-value": "Introduzir valor da propriedade",
"enter-type-password": "Introduzir {{type}} da senha", "enter-type-password": "Introduzir {{type}} da senha",
@ -904,6 +905,7 @@
"sub-domain-plural": "Sub Domains", "sub-domain-plural": "Sub Domains",
"sub-team-plural": "Sub-equipes", "sub-team-plural": "Sub-equipes",
"submit": "Enviar", "submit": "Enviar",
"subscription": "Subscription",
"success": "Sucesso", "success": "Sucesso",
"successfully-lowercase": "successfully", "successfully-lowercase": "successfully",
"successfully-uploaded": "Enviado com sucesso", "successfully-uploaded": "Enviado com sucesso",

View File

@ -345,6 +345,7 @@
"enter": "Введите", "enter": "Введите",
"enter-entity": "Введите {{entity}}", "enter-entity": "Введите {{entity}}",
"enter-entity-name": "Введите имя {{entity}}", "enter-entity-name": "Введите имя {{entity}}",
"enter-entity-value": "Enter {{entity}} Value",
"enter-field-description": "Введите описание {{field}}", "enter-field-description": "Введите описание {{field}}",
"enter-property-value": "Введите значение свойства", "enter-property-value": "Введите значение свойства",
"enter-type-password": "Введите {{type}} пароль", "enter-type-password": "Введите {{type}} пароль",
@ -904,6 +905,7 @@
"sub-domain-plural": "Sub Domains", "sub-domain-plural": "Sub Domains",
"sub-team-plural": "Подгруппы", "sub-team-plural": "Подгруппы",
"submit": "Подтвердить", "submit": "Подтвердить",
"subscription": "Subscription",
"success": "Успешно", "success": "Успешно",
"successfully-lowercase": "successfully", "successfully-lowercase": "successfully",
"successfully-uploaded": "Успешно загружено", "successfully-uploaded": "Успешно загружено",

View File

@ -345,6 +345,7 @@
"enter": "输入", "enter": "输入",
"enter-entity": "输入{{entity}}", "enter-entity": "输入{{entity}}",
"enter-entity-name": "输入{{entity}}的名称", "enter-entity-name": "输入{{entity}}的名称",
"enter-entity-value": "Enter {{entity}} Value",
"enter-field-description": "输入{{field}}的描述", "enter-field-description": "输入{{field}}的描述",
"enter-property-value": "输入属性值", "enter-property-value": "输入属性值",
"enter-type-password": "输入{{type}}密码", "enter-type-password": "输入{{type}}密码",
@ -904,6 +905,7 @@
"sub-domain-plural": "子域", "sub-domain-plural": "子域",
"sub-team-plural": "子团队", "sub-team-plural": "子团队",
"submit": "提交", "submit": "提交",
"subscription": "Subscription",
"success": "成功", "success": "成功",
"successfully-lowercase": "successfully", "successfully-lowercase": "successfully",
"successfully-uploaded": "上传成功", "successfully-uploaded": "上传成功",

View File

@ -12,7 +12,6 @@
*/ */
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import { useAuthContext } from 'components/authentication/auth-provider/AuthProvider';
import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder'; import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder';
import { PagingHandlerParams } from 'components/common/next-previous/NextPrevious.interface'; import { PagingHandlerParams } from 'components/common/next-previous/NextPrevious.interface';
import Loader from 'components/Loader/Loader'; import Loader from 'components/Loader/Loader';
@ -39,7 +38,7 @@ import {
patchTeamDetail, patchTeamDetail,
} from 'rest/teamsAPI'; } from 'rest/teamsAPI';
import { getUsers, updateUserDetail } from 'rest/userAPI'; import { getUsers, updateUserDetail } from 'rest/userAPI';
import { getEncodedFqn } from 'utils/StringsUtils'; import { getDecodedFqn, getEncodedFqn } from 'utils/StringsUtils';
import AppState from '../../AppState'; import AppState from '../../AppState';
import { import {
INITIAL_PAGING_VALUE, INITIAL_PAGING_VALUE,
@ -52,7 +51,6 @@ import { EntityReference } from '../../generated/entity/data/table';
import { Team } from '../../generated/entity/teams/team'; import { Team } from '../../generated/entity/teams/team';
import { User } from '../../generated/entity/teams/user'; import { User } from '../../generated/entity/teams/user';
import { Paging } from '../../generated/type/paging'; import { Paging } from '../../generated/type/paging';
import { useAuth } from '../../hooks/authHooks';
import { SearchResponse } from '../../interface/search.interface'; import { SearchResponse } from '../../interface/search.interface';
import { formatUsersResponse } from '../../utils/APIUtils'; import { formatUsersResponse } from '../../utils/APIUtils';
import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils'; import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils';
@ -64,8 +62,6 @@ const TeamsPage = () => {
const history = useHistory(); const history = useHistory();
const { t } = useTranslation(); const { t } = useTranslation();
const { getEntityPermissionByFqn } = usePermissionProvider(); const { getEntityPermissionByFqn } = usePermissionProvider();
const { isAdminUser } = useAuth();
const { isAuthDisabled } = useAuthContext();
const { fqn } = useParams<{ fqn: string }>(); const { fqn } = useParams<{ fqn: string }>();
const [currentFqn, setCurrentFqn] = useState<string>(''); const [currentFqn, setCurrentFqn] = useState<string>('');
const [allTeam, setAllTeam] = useState<Team[]>([]); const [allTeam, setAllTeam] = useState<Team[]>([]);
@ -223,7 +219,7 @@ const TeamsPage = () => {
getUsers({ getUsers({
fields: 'teams,roles', fields: 'teams,roles',
limit: PAGE_SIZE_BASE, limit: PAGE_SIZE_BASE,
team, team: getDecodedFqn(team),
...paging, ...paging,
}) })
.then((res) => { .then((res) => {
@ -285,7 +281,11 @@ const TeamsPage = () => {
const fetchTeamBasicDetails = async (name: string, loadPage = false) => { const fetchTeamBasicDetails = async (name: string, loadPage = false) => {
setIsPageLoading(loadPage); setIsPageLoading(loadPage);
try { try {
const data = await getTeamByName(name, ['owner', 'parents'], 'all'); const data = await getTeamByName(
name,
['owner', 'parents', 'profile'],
'all'
);
setSelectedTeam(data); setSelectedTeam(data);
if (!isEmpty(data.parents) && data.parents?.[0].name) { if (!isEmpty(data.parents) && data.parents?.[0].name) {
@ -621,7 +621,6 @@ const TeamsPage = () => {
handleJoinTeamClick={handleJoinTeamClick} handleJoinTeamClick={handleJoinTeamClick}
handleLeaveTeamClick={handleLeaveTeamClick} handleLeaveTeamClick={handleLeaveTeamClick}
handleTeamUsersSearchAction={handleUsersSearchAction} handleTeamUsersSearchAction={handleUsersSearchAction}
hasAccess={isAuthDisabled || isAdminUser}
isDescriptionEditable={isDescriptionEditable} isDescriptionEditable={isDescriptionEditable}
isFetchingAdvancedDetails={isFetchingAdvancedDetails} isFetchingAdvancedDetails={isFetchingAdvancedDetails}
isFetchingAllTeamAdvancedDetails={isFetchAllTeamAdvancedDetails} isFetchingAllTeamAdvancedDetails={isFetchAllTeamAdvancedDetails}

View File

@ -175,6 +175,12 @@ a[href].link-text-grey,
border-color: @warning-color; border-color: @warning-color;
} }
// Line height
.line-height-0 {
line-height: 0;
}
.line-height-16 { .line-height-16 {
line-height: 16px; line-height: 16px;
} }

View File

@ -48,6 +48,7 @@ pre {
} }
.text-sm { .text-sm {
font-size: 14px; font-size: 14px;
line-height: 1rem /* 16px */;
} }
.text-md { .text-md {
font-size: 16px; font-size: 16px;

View File

@ -84,7 +84,7 @@
@tag-background-color: rgba(0, 0, 0, 0.03); @tag-background-color: rgba(0, 0, 0, 0.03);
@trigger-btn-hover-bg: #efefef; @trigger-btn-hover-bg: #efefef;
@text-highlighter: #ffc34e40; @text-highlighter: #ffc34e40;
@team-avatar-bg: #0950c51a;
@navbar-height: 64px; @navbar-height: 64px;
@sidebar-width: 60px; @sidebar-width: 60px;

View File

@ -21,6 +21,11 @@ import {
} from '../generated/entity/teams/team'; } from '../generated/entity/teams/team';
import { getEntityIdArray } from './CommonUtils'; 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 * To get filtered list of non-deleted(active) users
* @param users List of users * @param users List of users
@ -86,6 +91,23 @@ export const getMovedTeamData = (team: Team, parents: string[]): CreateTeam => {
} as 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) => { export const getTeamOptionsFromType = (parentType: TeamType) => {
switch (parentType) { switch (parentType) {
case TeamType.Organization: case TeamType.Organization: