diff --git a/app/localization/translated/be.json b/app/localization/translated/be.json index 72ad550050..1f94f69efe 100644 --- a/app/localization/translated/be.json +++ b/app/localization/translated/be.json @@ -1784,6 +1784,7 @@ "ProjectStatusPage.oneMonth": "1 месяц", "ProjectStatusPage.sixMonths": "6 месяцаў", "ProjectStatusPage.threeMonths": "3 месяца", + "ProjectTeamPage.allOrganizations": "Усе арганізацыі", "ProjectTeamPage.description": "Спіс карыстальнікаў у дадзены момант пусты. Каб атрымаць максімальную аддачу ад свайго праекта, Запрасіце членаў сваёй каманды і эфектыўна супрацоўнічайце.", "ProjectTeamPage.inviteUser": "Запрасіць карыстальніка", "ProjectTeamPage.noResultsDescription": "Вынікі пошуку або фільтрацыі не супалі ні з адным з крытэраў. Калі ласка, паспрабуйце выкарыстоўваць іншыя ключавыя словы або змяніць налады фільтра.", diff --git a/app/localization/translated/es.json b/app/localization/translated/es.json index 6fddeb4049..00ab123867 100644 --- a/app/localization/translated/es.json +++ b/app/localization/translated/es.json @@ -1783,6 +1783,7 @@ "ProjectStatusPage.oneMonth": "1 mes", "ProjectStatusPage.sixMonths": "6 meses", "ProjectStatusPage.threeMonths": "3 meses", + "ProjectTeamPage.allOrganizations": "All Organizations", "ProjectTeamPage.description": "User list is currently empty. To make the most out of your project, invite your team members and collaborate efficiently.", "ProjectTeamPage.inviteUser": "Invite user", "ProjectTeamPage.noResultsDescription": "Your search or filter criteria didn't match any results. Please try different keywords or adjust your filter settings.", diff --git a/app/localization/translated/ru.json b/app/localization/translated/ru.json index 767665618e..aac3f200fe 100644 --- a/app/localization/translated/ru.json +++ b/app/localization/translated/ru.json @@ -1780,6 +1780,7 @@ "ProjectStatusPage.oneMonth": "1 месяц", "ProjectStatusPage.sixMonths": "6 месяцев", "ProjectStatusPage.threeMonths": "3 месяца", + "ProjectTeamPage.allOrganizations": "Все организации", "ProjectTeamPage.description": "Список пользователей в данный момент пуст. Чтобы получить максимальную отдачу от своего проекта, пригласите членов своей команды и эффективно сотрудничайте.", "ProjectTeamPage.inviteUser": "Пригласить пользователя", "ProjectTeamPage.noResultsDescription": "Результаты поиска или фильтрации не совпали ни с одним из критериев. Пожалуйста, попробуйте использовать другие ключевые слова или измените настройки фильтра.", diff --git a/app/localization/translated/uk.json b/app/localization/translated/uk.json index 80d30adf13..1c304afdec 100644 --- a/app/localization/translated/uk.json +++ b/app/localization/translated/uk.json @@ -1782,6 +1782,7 @@ "ProjectStatusPage.oneMonth": "1 місяць", "ProjectStatusPage.sixMonths": "6 місяців", "ProjectStatusPage.threeMonths": "3 місяці", + "ProjectTeamPage.allOrganizations": "Всі організації", "ProjectTeamPage.description": "Список користувачів на даний момент порожній. Щоб отримати максимальну віддачу від свого проекту, запросіть членів своєї команди та ефективно співпрацюйте.", "ProjectTeamPage.inviteUser": "Запросити користувача", "ProjectTeamPage.noResultsDescription": "Результати пошуку або фільтрації не співпали з жодним із критеріїв. Будь ласка, спробуйте використовувати інші ключові слова або змініть налаштування фільтра.", diff --git a/app/localization/translated/zh.json b/app/localization/translated/zh.json index 8277f49306..3dad4c944c 100644 --- a/app/localization/translated/zh.json +++ b/app/localization/translated/zh.json @@ -1782,6 +1782,7 @@ "ProjectStatusPage.oneMonth": "1个月", "ProjectStatusPage.sixMonths": "6个月", "ProjectStatusPage.threeMonths": "3个月", + "ProjectTeamPage.allOrganizations": "All Organizations", "ProjectTeamPage.description": "User list is currently empty. To make the most out of your project, invite your team members and collaborate efficiently.", "ProjectTeamPage.inviteUser": "Invite user", "ProjectTeamPage.noResultsDescription": "Your search or filter criteria didn't match any results. Please try different keywords or adjust your filter settings.", diff --git a/app/src/common/urls.js b/app/src/common/urls.js index 6e6ecaa61b..d57283ad43 100644 --- a/app/src/common/urls.js +++ b/app/src/common/urls.js @@ -243,7 +243,6 @@ export const URLS = { logSearch: (projectKey, itemId) => `${urlBase}${projectKey}/log/search/${itemId}`, bulkLastLogs: (projectKey) => `${urlBase}${projectKey}/log/under`, users: (ids = []) => `${urlCommonBase}users?ids=${ids.join(',')}`, - usersMe: () => `${urlCommonBase}users/me`, userRegistration: () => `${urlCommonBase}users/registration`, userValidateRegistrationInfo: () => `${urlCommonBase}users/registration/info`, userPasswordReset: () => `${urlCommonBase}users/password/reset`, @@ -266,7 +265,7 @@ export const URLS = { events: () => `${urlBase}activities/searches`, searchEventsBySubjectName: (projectName) => (searchTerm = '') => `${urlBase}activities/${projectName}/subjectName?filter.cnt.subjectName=${searchTerm}`, - allUsers: () => `${urlCommonBase}users`, + allUsers: () => `${urlCommonBase}users/all`, exportUsers: (filterEntities) => `${urlCommonBase}users/export${getQueryParams({ diff --git a/app/src/controllers/user/sagas.js b/app/src/controllers/user/sagas.js index 94c610042a..5eb052b208 100644 --- a/app/src/controllers/user/sagas.js +++ b/app/src/controllers/user/sagas.js @@ -126,7 +126,7 @@ function* unassignFromProject({ payload: project }) { function* fetchUserWorker() { let user; try { - user = yield call(fetch, URLS.usersMe()); + user = yield call(fetch, URLS.users()); yield put(fetchUserSuccessAction(user)); } catch (err) { yield put(fetchUserErrorAction()); diff --git a/app/src/pages/organization/common/membersPage/membersPageHeader/membersPageHeader.jsx b/app/src/pages/organization/common/membersPage/membersPageHeader/membersPageHeader.jsx index 61b598ec47..b5e95c7dfd 100644 --- a/app/src/pages/organization/common/membersPage/membersPageHeader/membersPageHeader.jsx +++ b/app/src/pages/organization/common/membersPage/membersPageHeader/membersPageHeader.jsx @@ -17,13 +17,15 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames/bind'; +import { UserPageLocationLevel } from './userPageLocationLevel'; import styles from './membersPageHeader.scss'; const cx = classNames.bind(styles); -export const MembersPageHeader = ({ title, children }) => { +export const MembersPageHeader = ({ title, children, organizationName, projectName }) => { return (
+
{title} {children} @@ -35,8 +37,11 @@ export const MembersPageHeader = ({ title, children }) => { MembersPageHeader.propTypes = { title: PropTypes.string.isRequired, children: PropTypes.node, + organizationName: PropTypes.string.isRequired, + projectName: PropTypes.string, }; MembersPageHeader.defaultProps = { children: null, + projectName: null, }; diff --git a/app/src/pages/organization/common/membersPage/membersPageHeader/membersPageHeader.scss b/app/src/pages/organization/common/membersPage/membersPageHeader/membersPageHeader.scss index 0ab16a39b4..a5a599d0b9 100644 --- a/app/src/pages/organization/common/membersPage/membersPageHeader/membersPageHeader.scss +++ b/app/src/pages/organization/common/membersPage/membersPageHeader/membersPageHeader.scss @@ -15,7 +15,7 @@ */ .members-page-header-container { - padding: 48px 32px 16px 32px; + padding: 16px 32px 16px 32px; border-bottom: 1px solid $COLOR--e-100; background: $COLOR--bg-000; box-sizing: border-box; diff --git a/app/src/pages/organization/common/membersPage/membersPageHeader/messages.js b/app/src/pages/organization/common/membersPage/membersPageHeader/messages.js index c963bc3650..e625b85c85 100644 --- a/app/src/pages/organization/common/membersPage/membersPageHeader/messages.js +++ b/app/src/pages/organization/common/membersPage/membersPageHeader/messages.js @@ -47,4 +47,8 @@ export const messages = defineMessages({ defaultMessage: "Your search or filter criteria didn't match any results. Please try different keywords or adjust your filter settings.", }, + allOrganizations: { + id: 'ProjectTeamPage.allOrganizations', + defaultMessage: 'All Organizations', + }, }); diff --git a/app/src/pages/organization/common/membersPage/membersPageHeader/userPageLocationLevel/img/sub-level-inline.svg b/app/src/pages/organization/common/membersPage/membersPageHeader/userPageLocationLevel/img/sub-level-inline.svg new file mode 100644 index 0000000000..87cbd65e2c --- /dev/null +++ b/app/src/pages/organization/common/membersPage/membersPageHeader/userPageLocationLevel/img/sub-level-inline.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/src/pages/organization/common/membersPage/membersPageHeader/userPageLocationLevel/img/tree-inline.svg b/app/src/pages/organization/common/membersPage/membersPageHeader/userPageLocationLevel/img/tree-inline.svg new file mode 100644 index 0000000000..9efe01b5c1 --- /dev/null +++ b/app/src/pages/organization/common/membersPage/membersPageHeader/userPageLocationLevel/img/tree-inline.svg @@ -0,0 +1,4 @@ + + + + diff --git a/app/src/pages/organization/common/membersPage/membersPageHeader/userPageLocationLevel/index.js b/app/src/pages/organization/common/membersPage/membersPageHeader/userPageLocationLevel/index.js new file mode 100644 index 0000000000..45adab2eb6 --- /dev/null +++ b/app/src/pages/organization/common/membersPage/membersPageHeader/userPageLocationLevel/index.js @@ -0,0 +1,17 @@ +/*! + * Copyright 2024 EPAM Systems + * + * 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. + */ + +export { UserPageLocationLevel } from './userPageLocationLevel'; diff --git a/app/src/pages/organization/common/membersPage/membersPageHeader/userPageLocationLevel/userPageLocationLevel.jsx b/app/src/pages/organization/common/membersPage/membersPageHeader/userPageLocationLevel/userPageLocationLevel.jsx new file mode 100644 index 0000000000..9464033373 --- /dev/null +++ b/app/src/pages/organization/common/membersPage/membersPageHeader/userPageLocationLevel/userPageLocationLevel.jsx @@ -0,0 +1,76 @@ +/*! + * Copyright 2024 EPAM Systems + * + * 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 { useState } from 'react'; +import PropTypes from 'prop-types'; +import { useIntl } from 'react-intl'; +import Parser from 'html-react-parser'; +import classNames from 'classnames/bind'; +import { BaseIconButton, Popover } from '@reportportal/ui-kit'; +import TreeIcon from './img/tree-inline.svg'; +import SubLevelIcon from './img/sub-level-inline.svg'; +import { messages } from '../messages'; +import styles from './userPageLocationLevel.scss'; + +const cx = classNames.bind(styles); + +export const UserPageLocationLevel = ({ organizationName, projectName }) => { + const { formatMessage } = useIntl(); + const [isOpen, setIsOpen] = useState(false); + + const getContent = () => ( +
+
{formatMessage(messages.allOrganizations)}
+
+ {Parser(SubLevelIcon)} + {organizationName} +
+ {projectName && ( +
+ {Parser(SubLevelIcon)} + {projectName} +
+ )} +
+ ); + + return ( +
+ + + {Parser(TreeIcon)} + + +
{projectName || organizationName}
+
+ ); +}; + +UserPageLocationLevel.propTypes = { + organizationName: PropTypes.string.isRequired, + projectName: PropTypes.string, +}; + +UserPageLocationLevel.defaultProps = { + projectName: null, +}; diff --git a/app/src/pages/organization/common/membersPage/membersPageHeader/userPageLocationLevel/userPageLocationLevel.scss b/app/src/pages/organization/common/membersPage/membersPageHeader/userPageLocationLevel/userPageLocationLevel.scss new file mode 100644 index 0000000000..f428ea6eed --- /dev/null +++ b/app/src/pages/organization/common/membersPage/membersPageHeader/userPageLocationLevel/userPageLocationLevel.scss @@ -0,0 +1,74 @@ +/*! + * Copyright 2024 EPAM Systems + * + * 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. + */ + +.user-page-location-level { + display: flex; + align-items: center; + gap: 16px; + color: $COLOR--e-300; + font-size: 13px; + margin-bottom: 12px; + + svg { + width: 16px; + height: 16px; + } +} + +.tree-button { + width: 16px; + height: 16px; +} + +.name { + font-size: 13px; + line-height: 20px; +} + +.open { + svg path { + fill: $COLOR--topaz-pressed; + } + + &:hover:not(.disabled) { + svg path { + fill: $COLOR--topaz-2; + } + } +} + +.popover { + font-size: 13px; + line-height: 20px; + min-width: 175px; +} + +.project-name, +.organization-name { + display: flex; + align-items: center; + gap: 4px; + margin-top: 8px; +} + +.project-name { + color: $COLOR--topaz-2; + margin-left: 16px; +} + +.active { + color: $COLOR--topaz-2; +} diff --git a/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/organizationUsersPageHeader.jsx b/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/organizationUsersPageHeader.jsx index 5290658f02..d891e44df3 100644 --- a/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/organizationUsersPageHeader.jsx +++ b/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/organizationUsersPageHeader.jsx @@ -47,7 +47,10 @@ export const OrganizationUsersPageHeader = ({ const isNotEmpty = usersCount > 0; return ( - +
{isNotEmpty && ( <> diff --git a/app/src/pages/organization/projectTeamPage/projectTeamPageHeader/projectTeamPageHeader.jsx b/app/src/pages/organization/projectTeamPage/projectTeamPageHeader/projectTeamPageHeader.jsx index f943909ce2..bfcff49831 100644 --- a/app/src/pages/organization/projectTeamPage/projectTeamPageHeader/projectTeamPageHeader.jsx +++ b/app/src/pages/organization/projectTeamPage/projectTeamPageHeader/projectTeamPageHeader.jsx @@ -21,12 +21,13 @@ import Parser from 'html-react-parser'; import classNames from 'classnames/bind'; import { Button } from '@reportportal/ui-kit'; import { useIntl } from 'react-intl'; -import { projectMembersSelector } from 'controllers/project'; +import { projectMembersSelector, projectNameSelector } from 'controllers/project'; import { SearchField } from 'components/fields/searchField'; import { NAMESPACE, SEARCH_KEY } from 'controllers/members/constants'; import { withFilter } from 'controllers/filter'; import filterIcon from 'common/img/newIcons/filters-outline-inline.svg'; import { PROJECT_PAGE_EVENTS } from 'components/main/analytics/events/ga4Events/projectPageEvents'; +import { activeOrganizationNameSelector } from 'controllers/organization'; import { messages } from '../../common/membersPage/membersPageHeader/messages'; import { MembersPageHeader } from '../../common/membersPage/membersPageHeader'; import styles from './projectTeamPageHeader.scss'; @@ -46,10 +47,16 @@ export const ProjectTeamPageHeader = ({ }) => { const { formatMessage } = useIntl(); const projectMembers = useSelector(projectMembersSelector); + const projectName = useSelector(projectNameSelector); + const organizationName = useSelector(activeOrganizationNameSelector); const isNotEmptyMembers = projectMembers.length !== 0; return ( - +
{isNotEmptyMembers && ( <>