Skip to content

Commit

Permalink
EPMRPP-96257 || Search for users on Project team page
Browse files Browse the repository at this point in the history
  • Loading branch information
BlazarQSO committed Nov 18, 2024
1 parent 43d75c5 commit 2f64887
Show file tree
Hide file tree
Showing 17 changed files with 104 additions and 38 deletions.
6 changes: 4 additions & 2 deletions app/localization/translated/be.json
Original file line number Diff line number Diff line change
Expand Up @@ -1776,10 +1776,12 @@
"ProjectStatusPage.oneMonth": "1 месяц",
"ProjectStatusPage.sixMonths": "6 месяцаў",
"ProjectStatusPage.threeMonths": "3 месяца",
"ProjectTeamPage.title": "Праектная група",
"ProjectTeamPage.noUsers": "Карыстальнікаў пакуль не дададзена",
"ProjectTeamPage.description": "Спіс карыстальнікаў у дадзены момант пусты. Каб атрымаць максімальную аддачу ад свайго праекта, Запрасіце членаў сваёй каманды і эфектыўна супрацоўнічайце.",
"ProjectTeamPage.inviteUser": "Запрасіць карыстальніка",
"ProjectTeamPage.noResultsDescription": "Вынікі пошуку або фільтрацыі не супалі ні з адным з крытэраў. Калі ласка, паспрабуйце выкарыстоўваць іншыя ключавыя словы або змяніць налады фільтра.",
"ProjectTeamPage.noUsers": "Карыстальнікаў пакуль не дададзена",
"ProjectTeamPage.searchPlaceholder": "Пошук па назве",
"ProjectTeamPage.title": "Праектная група",
"ProjectsAndRolesColumn.more": "яшчэ",
"ProjectsGrid.lastLaunchCol": "Дата апошняга запуску",
"ProjectsGrid.lastLaunchColShort": "Дата зпск",
Expand Down
6 changes: 4 additions & 2 deletions app/localization/translated/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -1771,10 +1771,12 @@
"ProjectStatusPage.oneMonth": "1 месяц",
"ProjectStatusPage.sixMonths": "6 месяцев",
"ProjectStatusPage.threeMonths": "3 месяца",
"ProjectTeamPage.title": "Проектная группа",
"ProjectTeamPage.noUsers": "Пользователей пока не добавлено",
"ProjectTeamPage.description": "Список пользователей в данный момент пуст. Чтобы получить максимальную отдачу от своего проекта, пригласите членов своей команды и эффективно сотрудничайте.",
"ProjectTeamPage.inviteUser": "Пригласить пользователя",
"ProjectTeamPage.noResultsDescription": "Результаты поиска или фильтрации не совпали ни с одним из критериев. Пожалуйста, попробуйте использовать другие ключевые слова или измените настройки фильтра.",
"ProjectTeamPage.noUsers": "Пользователей пока не добавлено",
"ProjectTeamPage.searchPlaceholder": "Поиск по названию",
"ProjectTeamPage.title": "Проектная группа",
"ProjectsAndRolesColumn.more": "еще",
"ProjectsGrid.lastLaunchCol": "Дата последнего запуска",
"ProjectsGrid.lastLaunchColShort": "Дата зпск",
Expand Down
6 changes: 4 additions & 2 deletions app/localization/translated/uk.json
Original file line number Diff line number Diff line change
Expand Up @@ -1773,10 +1773,12 @@
"ProjectStatusPage.oneMonth": "1 місяць",
"ProjectStatusPage.sixMonths": "6 місяців",
"ProjectStatusPage.threeMonths": "3 місяці",
"ProjectTeamPage.title": "Проектна група",
"ProjectTeamPage.noUsers": "Користувачів поки не додано",
"ProjectTeamPage.description": "Список користувачів на даний момент порожній. Щоб отримати максимальну віддачу від свого проекту, запросіть членів своєї команди та ефективно співпрацюйте.",
"ProjectTeamPage.inviteUser": "Запросити користувача",
"ProjectTeamPage.noResultsDescription": "Результати пошуку або фільтрації не співпали з жодним із критеріїв. Будь ласка, спробуйте використовувати інші ключові слова або змініть налаштування фільтра.",
"ProjectTeamPage.noUsers": "Користувачів поки не додано",
"ProjectTeamPage.searchPlaceholder": "Пошук по назві",
"ProjectTeamPage.title": "Проектна група",
"ProjectsAndRolesColumn.more": "ще",
"ProjectsGrid.lastLaunchCol": "Дата останнього запуску",
"ProjectsGrid.lastLaunchColShort": "Дата зпск",
Expand Down
6 changes: 4 additions & 2 deletions app/localization/translated/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -1773,10 +1773,12 @@
"ProjectStatusPage.oneMonth": "1个月",
"ProjectStatusPage.sixMonths": "6个月",
"ProjectStatusPage.threeMonths": "3个月",
"ProjectTeamPage.title": "Project team",
"ProjectTeamPage.noUsers": "No users added yet",
"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.",
"ProjectTeamPage.noUsers": "No users added yet",
"ProjectTeamPage.searchPlaceholder": "Type to search by name",
"ProjectTeamPage.title": "Project team",
"ProjectsAndRolesColumn.more": "更多",
"ProjectsGrid.lastLaunchCol": "上个测试任务日期",
"ProjectsGrid.lastLaunchColShort": "测试任务日期",
Expand Down
2 changes: 1 addition & 1 deletion app/src/controllers/instance/organizations/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@ export const querySelector = createAlternativeQueryParametersSelector({
defaultPagination: DEFAULT_PAGINATION,
defaultSorting: SORTING_ASC,
sortingKey: SORTING_KEY,
alternativeNamespace: NAMESPACE,
namespace: NAMESPACE,
});
1 change: 1 addition & 0 deletions app/src/controllers/members/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ export const FETCH_MEMBERS = 'fetchMembers';
export const NAMESPACE = 'members';
export const DEFAULT_SORTING = formatSortingString([USER], SORTING_ASC);
export const DEFAULT_SORT_COLUMN = 'fullName';
export const SEARCH_KEY = 'filter.cnt.fullName';
export const DEFAULT_PAGE_SIZE_OPTIONS = [20, 50, 100, 300];
2 changes: 1 addition & 1 deletion app/src/controllers/organization/projects/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,5 @@ export const querySelector = createAlternativeQueryParametersSelector({
defaultPagination: DEFAULT_PAGINATION,
defaultSorting: SORTING_ASC,
sortingKey: SORTING_KEY,
alternativeNamespace: NAMESPACE,
namespace: NAMESPACE,
});
2 changes: 1 addition & 1 deletion app/src/controllers/organization/users/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,5 @@ export const querySelector = createAlternativeQueryParametersSelector({
defaultPagination: DEFAULT_PAGINATION,
defaultSorting: SORTING_ASC,
sortingKey: SORTING_KEY,
alternativeNamespace: NAMESPACE,
namespace: NAMESPACE,
});
12 changes: 5 additions & 7 deletions app/src/controllers/pages/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,16 +107,13 @@ export const prevPagePropertiesSelector = (

export const createQueryParametersSelector = ({
namespace: staticNamespace,
alternativeNamespace,
defaultPagination,
defaultSorting,
sortingKey = SORTING_KEY,
} = {}) => (state, namespace) => {
const calculatedNamespace = staticNamespace || namespace || alternativeNamespace;
const calculatedNamespace = staticNamespace || namespace;
const calculatedPagination = defaultPagination || DEFAULT_PAGINATION;
const query = alternativeNamespace
? querySelector(state)
: pagePropertiesSelector(state, calculatedNamespace);
const query = pagePropertiesSelector(state, calculatedNamespace);
const queryParameters = {
...calculatedPagination,
[sortingKey]: defaultSorting || '',
Expand All @@ -134,21 +131,22 @@ export const createQueryParametersSelector = ({
const userSettings = getStorageItem(`${userId}_settings`) || {};
queryParameters[SIZE_KEY] = userSettings[`${calculatedNamespace}PageSize`] || defaultPageSize;
}

return queryParameters;
};

export const createAlternativeQueryParametersSelector = ({
defaultPagination,
defaultSorting,
sortingKey,
alternativeNamespace,
namespace,
} = {}) =>
createSelector(
createQueryParametersSelector({
defaultPagination,
defaultSorting,
sortingKey,
alternativeNamespace,
namespace,
}),
({ [SIZE_KEY]: limit, [SORTING_KEY]: sort, [PAGE_KEY]: pageNumber, ...rest }) => {
return { ...getAlternativePaginationAndSortParams(sort, limit, pageNumber), ...rest };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { useIntl } from 'react-intl';
import { SearchField } from 'components/fields/searchField';
import { SEARCH_KEY } from 'controllers/organization/projects/constants';
import { withFilter } from 'controllers/filter';
import { NAMESPACE } from 'controllers/instance/organizations/constants';
import { useSelector } from 'react-redux';
import { organizationsListLoadingSelector } from 'controllers/instance/organizations';
import { ORGANIZATION_PAGE_EVENTS } from 'components/main/analytics/events/ga4Events/organizationsPageEvents';
Expand All @@ -34,7 +35,9 @@ import styles from './organizationsPageHeader.scss';

const cx = classNames.bind(styles);

const SearchFieldWithFilter = withFilter({ filterKey: SEARCH_KEY })(SearchField);
const SearchFieldWithFilter = withFilter({ filterKey: SEARCH_KEY, namespace: NAMESPACE })(
SearchField,
);

export const OrganizationsPageHeader = ({
isEmpty,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export const OrganizationsPanelView = withSortingURL({
defaultDirection: SORTING_ASC,
defaultFields: [DEFAULT_SORT_COLUMN],
sortingKey: SORTING_KEY,
namespace: NAMESPACE,
})(
withPagination({
paginationSelector: organizationsListPaginationSelector,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,13 @@ export const messages = defineMessages({
id: 'ProjectTeamPage.inviteUser',
defaultMessage: 'Invite user',
},
searchPlaceholder: {
id: 'ProjectTeamPage.searchPlaceholder',
defaultMessage: 'Type to search by name',
},
noResultsDescription: {
id: 'ProjectTeamPage.noResultsDescription',
defaultMessage:
"Your search or filter criteria didn't match any results. Please try different keywords or adjust your filter settings.",
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ export const ProjectsListTableWrapper = withSortingURL({
defaultFields: [DEFAULT_SORT_COLUMN],
defaultDirection: SORTING_ASC,
sortingKey: SORTING_KEY,
namespace: NAMESPACE,
})(
withPagination({
paginationSelector: projectsPaginationSelector,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { Breadcrumbs } from 'componentLibrary/breadcrumbs';
import { activeOrganizationSelector } from 'controllers/organization';
import { loadingSelector } from 'controllers/organization/projects';
import { SearchField } from 'components/fields/searchField';
import { SEARCH_KEY } from 'controllers/organization/projects/constants';
import { SEARCH_KEY, NAMESPACE } from 'controllers/organization/projects/constants';
import { withFilter } from 'controllers/filter';
import projectsIcon from './img/projects-inline.svg';
import styles from './projectsPageHeader.scss';
Expand All @@ -35,7 +35,9 @@ import userIcon from './img/user-inline.svg';

const cx = classNames.bind(styles);

const SearchFieldWithFilter = withFilter({ filterKey: SEARCH_KEY })(SearchField);
const SearchFieldWithFilter = withFilter({ filterKey: SEARCH_KEY, namespace: NAMESPACE })(
SearchField,
);

export const ProjectsPageHeader = ({
hasPermission,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ ProjectTeamListTableWrapped.defaultProps = {
export const ProjectTeamListTable = withSortingURL({
defaultFields: [DEFAULT_SORT_COLUMN],
defaultDirection: SORTING_ASC,
namespace: NAMESPACE,
})(
withPagination({
paginationSelector: membersPaginationSelector,
Expand Down
39 changes: 29 additions & 10 deletions app/src/pages/organization/projectTeamPage/projectTeamPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,19 @@
* limitations under the License.
*/

import { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { useIntl } from 'react-intl';
import { userRolesSelector } from 'controllers/pages';
import { canInviteInternalUser } from 'common/utils/permissions';
import classNames from 'classnames/bind';
import { loadingSelector, membersSelector, fetchMembersAction } from 'controllers/members';
import { ScrollWrapper } from 'components/main/scrollWrapper';
import { showModalAction } from 'controllers/modal';
import { EmptyPageState } from 'pages/common';
import NoResultsIcon from 'common/img/newIcons/no-results-icon-inline.svg';
import { COMMON_LOCALE_KEYS } from 'common/constants/localization';
import { messages } from '../common/membersPage/membersPageHeader/messages';
import { EmptyMembersPageState } from '../common/membersPage/emptyMembersPageState';
import { ProjectTeamPageHeader } from './projectTeamPageHeader';
import { ProjectTeamListTable } from './projectTeamListTable';
Expand All @@ -29,11 +35,13 @@ import styles from './projectTeamPage.scss';
const cx = classNames.bind(styles);

export const ProjectTeamPage = () => {
const { formatMessage } = useIntl();
const dispatch = useDispatch();
const userRoles = useSelector(userRolesSelector);
const hasPermission = canInviteInternalUser(userRoles);
const members = useSelector(membersSelector);
const isMembersLoading = useSelector(loadingSelector);
const [searchValue, setSearchValue] = useState(null);
const isEmptyMembers = members.length === 0;

const onInvite = () => {
Expand All @@ -49,23 +57,34 @@ export const ProjectTeamPage = () => {
);
};

const getEmptyPageState = () => {
return searchValue === null ? (
<EmptyMembersPageState
isLoading={isMembersLoading}
hasPermission={hasPermission}
showInviteUserModal={showInviteUserModal}
/>
) : (
<EmptyPageState
label={formatMessage(COMMON_LOCALE_KEYS.NO_RESULTS)}
description={formatMessage(messages.noResultsDescription)}
emptyIcon={NoResultsIcon}
hasPermission={false}
/>
);
};

return (
<ScrollWrapper>
<div className={cx('project-team-page')}>
<ProjectTeamPageHeader
hasPermission={hasPermission}
isNotEmpty={!isEmptyMembers}
onInvite={showInviteUserModal}
isMembersLoading={isMembersLoading}
searchValue={searchValue}
setSearchValue={setSearchValue}
/>
{isEmptyMembers ? (
<EmptyMembersPageState
isLoading={isMembersLoading}
hasPermission={hasPermission}
showInviteUserModal={showInviteUserModal}
/>
) : (
<ProjectTeamListTable members={members} />
)}
{isEmptyMembers ? getEmptyPageState() : <ProjectTeamListTable members={members} />}
</div>
</ScrollWrapper>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,50 @@
*/

import React from 'react';
import { useSelector } from 'react-redux';
import PropTypes from 'prop-types';
import Parser from 'html-react-parser';
import classNames from 'classnames/bind';
import { Button, SearchIcon } from '@reportportal/ui-kit';
import filterIcon from 'common/img/newIcons/filters-outline-inline.svg';
import { Button } from '@reportportal/ui-kit';
import { useIntl } from 'react-intl';
import { projectMembersSelector } 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 { messages } from '../../common/membersPage/membersPageHeader/messages';
import styles from './projectTeamPageHeader.scss';
import { MembersPageHeader } from '../../common/membersPage/membersPageHeader';

const cx = classNames.bind(styles);

export const ProjectTeamPageHeader = ({ hasPermission, isNotEmpty, onInvite }) => {
const SearchFieldWithFilter = withFilter({ filterKey: SEARCH_KEY, namespace: NAMESPACE })(
SearchField,
);

export const ProjectTeamPageHeader = ({
hasPermission,
onInvite,
isMembersLoading,
searchValue,
setSearchValue,
}) => {
const { formatMessage } = useIntl();
const projectMembers = useSelector(projectMembersSelector);
const isNotEmptyMembers = !(projectMembers.length === 0);

return (
<MembersPageHeader title={formatMessage(messages.projectTeamTitle)}>
<div className={cx('actions')}>
{isNotEmpty && (
{isNotEmptyMembers && (
<>
<div className={cx('icons')}>
<i className={cx('search-icon')}>
<SearchIcon />
</i>
<SearchFieldWithFilter
isLoading={isMembersLoading}
searchValue={searchValue}
setSearchValue={setSearchValue}
placeholder={formatMessage(messages.searchPlaceholder)}
/>
<i className={cx('filters-icon')}>{Parser(filterIcon)}</i>
</div>
{hasPermission && (
Expand All @@ -54,6 +74,9 @@ export const ProjectTeamPageHeader = ({ hasPermission, isNotEmpty, onInvite }) =
};

ProjectTeamPageHeader.propTypes = {
isMembersLoading: PropTypes.bool.isRequired,
searchValue: PropTypes.string.isRequired,
setSearchValue: PropTypes.func.isRequired,
hasPermission: PropTypes.bool,
isNotEmpty: PropTypes.bool,
onInvite: PropTypes.func,
Expand Down

0 comments on commit 2f64887

Please sign in to comment.