From 6ce2e53010d69fb06bb6e73c1d5c5464c0d6f210 Mon Sep 17 00:00:00 2001 From: maria-hambardzumian Date: Fri, 4 Oct 2024 18:02:00 +0400 Subject: [PATCH 1/6] EPMRPP-92003 || Organization Users page --- app/src/common/constants/permissions.js | 4 +- app/src/common/urls.js | 2 + app/src/controllers/organizations/index.js | 6 +- .../organizations/organization/reducer.js | 2 + .../organizations/projects/index.js | 7 +- .../organizations/projects/sagas.js | 2 +- .../organizations/projects/selectors.js | 27 --- app/src/controllers/organizations/sagas.js | 3 +- .../controllers/organizations/selectors.js | 25 +++ .../organizations/users/actionCreators.js | 29 +++ .../organizations/users/constants.js | 19 ++ .../controllers/organizations/users/index.js | 23 ++ .../organizations/users/reducer.js | 37 ++++ .../controllers/organizations/users/sagas.js | 59 +++++ .../organizations/users/selectors.js | 23 ++ app/src/controllers/pages/constants.js | 3 +- app/src/controllers/pages/index.js | 2 +- .../organizationSidebar.jsx | 4 +- .../emptyPageState/emptyPageState.jsx | 0 .../emptyPageState/emptyPageState.scss | 0 .../{ => common}/emptyPageState/index.js | 0 .../emptyMembersPageState.jsx | 40 ++++ .../emptyMembersPageState.scss | 7 + .../img/empty-members-icon-inline.svg | 0 .../emptyMembersPageState/index.js | 17 ++ .../membersPage/membersListTable/index.js | 17 ++ .../membersListTable/membersListTable.jsx | 81 +++++++ .../membersListTable/membersListTable.scss | 22 ++ .../membersPage/membersPageHeader/index.js | 17 ++ .../membersPageHeader/membersPageHeader.jsx | 42 ++++ .../membersPageHeader/membersPageHeader.scss | 42 ++++ .../membersPageHeader}/messages.js | 6 +- .../membersPage}/messages.js | 8 + .../organizationProjectsPage.jsx | 2 +- .../organizationUsersPage/index.js | 17 ++ .../organizationUsersListTable/index.js | 17 ++ .../organizationUsersListTable.jsx | 205 ++++++++++++++++++ .../organizationUsersListTable.scss | 55 +++++ .../organizationUsersPage.jsx | 48 ++++ .../organizationUsersPage.scss | 21 ++ .../organizationUsersPageHeader/index.js | 17 ++ .../organizationUsersPageHeader.jsx | 64 ++++++ .../organizationUsersPageHeader.scss | 43 ++++ .../projectTeamListTable.jsx | 39 ++-- .../projectTeamListTable.scss | 16 +- .../projectTeamPage/projectTeamPage.jsx | 34 +-- .../projectTeamPage/projectTeamPage.scss | 7 - .../projectTeamPageHeader.jsx | 46 ++-- .../projectTeamPageHeader.scss | 27 --- app/src/routes/constants.js | 7 + app/src/routes/routesMap.js | 13 +- 51 files changed, 1090 insertions(+), 164 deletions(-) create mode 100644 app/src/controllers/organizations/users/actionCreators.js create mode 100644 app/src/controllers/organizations/users/constants.js create mode 100644 app/src/controllers/organizations/users/index.js create mode 100644 app/src/controllers/organizations/users/reducer.js create mode 100644 app/src/controllers/organizations/users/sagas.js create mode 100644 app/src/controllers/organizations/users/selectors.js rename app/src/pages/organization/{ => common}/emptyPageState/emptyPageState.jsx (100%) rename app/src/pages/organization/{ => common}/emptyPageState/emptyPageState.scss (100%) rename app/src/pages/organization/{ => common}/emptyPageState/index.js (100%) create mode 100644 app/src/pages/organization/common/membersPage/emptyMembersPageState/emptyMembersPageState.jsx create mode 100644 app/src/pages/organization/common/membersPage/emptyMembersPageState/emptyMembersPageState.scss rename app/src/pages/organization/{projectTeamPage => common/membersPage/emptyMembersPageState}/img/empty-members-icon-inline.svg (100%) create mode 100644 app/src/pages/organization/common/membersPage/emptyMembersPageState/index.js create mode 100644 app/src/pages/organization/common/membersPage/membersListTable/index.js create mode 100644 app/src/pages/organization/common/membersPage/membersListTable/membersListTable.jsx create mode 100644 app/src/pages/organization/common/membersPage/membersListTable/membersListTable.scss create mode 100644 app/src/pages/organization/common/membersPage/membersPageHeader/index.js create mode 100644 app/src/pages/organization/common/membersPage/membersPageHeader/membersPageHeader.jsx create mode 100644 app/src/pages/organization/common/membersPage/membersPageHeader/membersPageHeader.scss rename app/src/pages/organization/{projectTeamPage => common/membersPage/membersPageHeader}/messages.js (88%) rename app/src/pages/organization/{projectTeamPage/projectTeamListTable => common/membersPage}/messages.js (86%) create mode 100644 app/src/pages/organization/organizationUsersPage/index.js create mode 100644 app/src/pages/organization/organizationUsersPage/organizationUsersListTable/index.js create mode 100644 app/src/pages/organization/organizationUsersPage/organizationUsersListTable/organizationUsersListTable.jsx create mode 100644 app/src/pages/organization/organizationUsersPage/organizationUsersListTable/organizationUsersListTable.scss create mode 100644 app/src/pages/organization/organizationUsersPage/organizationUsersPage.jsx create mode 100644 app/src/pages/organization/organizationUsersPage/organizationUsersPage.scss create mode 100644 app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/index.js create mode 100644 app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/organizationUsersPageHeader.jsx create mode 100644 app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/organizationUsersPageHeader.scss diff --git a/app/src/common/constants/permissions.js b/app/src/common/constants/permissions.js index c9e5a4ffc4..9da13dbcbc 100644 --- a/app/src/common/constants/permissions.js +++ b/app/src/common/constants/permissions.js @@ -106,8 +106,8 @@ export const PERMISSIONS_MAP = { }, [MEMBER]: { [VIEWER]: { - [ACTIONS.SEE_SETTINGS]: true, - [ACTIONS.SEE_MEMBERS]: true, + [ACTIONS.SEE_SETTINGS]: false, + [ACTIONS.SEE_MEMBERS]: false, }, [EDITOR]: { [ACTIONS.SEE_SETTINGS]: true, diff --git a/app/src/common/urls.js b/app/src/common/urls.js index 34f574c55d..93d166f36c 100644 --- a/app/src/common/urls.js +++ b/app/src/common/urls.js @@ -129,6 +129,8 @@ export const URLS = { `${urlCommonBase}organizations${getQueryParams(preferencesObj)}`, organizationProjects: (organizationId, preferencesObj = {}) => `${urlCommonBase}organizations/${organizationId}/projects${getQueryParams(preferencesObj)}`, + organizationUsers: (organizationId, preferencesObj = {}) => + `${urlCommonBase}organizations/${organizationId}/users${getQueryParams(preferencesObj)}`, projectByName: (projectKey) => `${urlBase}project/${projectKey}`, project: (ids = []) => `${urlBase}project?ids=${ids.join(',')}`, diff --git a/app/src/controllers/organizations/index.js b/app/src/controllers/organizations/index.js index 30db3f8dda..a7633ccb3c 100644 --- a/app/src/controllers/organizations/index.js +++ b/app/src/controllers/organizations/index.js @@ -17,5 +17,9 @@ export { FETCH_ORGANIZATIONS } from './constants'; export { fetchOrganizationsAction } from './actionCreators'; export { organizationsReducer } from './reducer'; -export { organizationsListSelector, organizationsListLoadingSelector } from './selectors'; +export { + organizationsListSelector, + organizationsListLoadingSelector, + querySelector, +} from './selectors'; export { organizationsSagas } from './sagas'; diff --git a/app/src/controllers/organizations/organization/reducer.js b/app/src/controllers/organizations/organization/reducer.js index b600dc0546..d3ad1e2569 100644 --- a/app/src/controllers/organizations/organization/reducer.js +++ b/app/src/controllers/organizations/organization/reducer.js @@ -18,6 +18,7 @@ import { combineReducers } from 'redux'; import { fetchReducer } from 'controllers/fetch'; import { loadingReducer } from 'controllers/loading'; import { queueReducers } from 'common/utils'; +import { usersReducer } from 'controllers/organizations/users/reducer'; import { projectsReducer } from '../projects/reducer'; import { FETCH_ORGANIZATION_BY_SLUG, SET_ACTIVE_ORGANIZATION } from './constants'; @@ -41,4 +42,5 @@ export const organizationReducer = combineReducers({ ), organizationLoading: loadingReducer(FETCH_ORGANIZATION_BY_SLUG), projects: projectsReducer, + users: usersReducer, }); diff --git a/app/src/controllers/organizations/projects/index.js b/app/src/controllers/organizations/projects/index.js index 7b1d0e1770..383da7579d 100644 --- a/app/src/controllers/organizations/projects/index.js +++ b/app/src/controllers/organizations/projects/index.js @@ -20,12 +20,7 @@ export { navigateToProjectSectionAction, } from './actionCreators'; export { projectsReducer } from './reducer'; -export { - projectsPaginationSelector, - projectsSelector, - loadingSelector, - querySelector, -} from './selectors'; +export { projectsPaginationSelector, projectsSelector, loadingSelector } from './selectors'; export { projectsSagas } from './sagas'; export { DEFAULT_LIMITATION, diff --git a/app/src/controllers/organizations/projects/sagas.js b/app/src/controllers/organizations/projects/sagas.js index 99de0ef08d..0c6f72af00 100644 --- a/app/src/controllers/organizations/projects/sagas.js +++ b/app/src/controllers/organizations/projects/sagas.js @@ -24,7 +24,7 @@ import { ERROR_CODES } from 'controllers/instance/projects/constants'; import { fetchOrganizationBySlugAction } from '../organization'; import { activeOrganizationSelector } from '../organization/selectors'; import { fetchOrganizationProjectsAction } from './actionCreators'; -import { querySelector } from './selectors'; +import { querySelector } from '../selectors'; import { CREATE_PROJECT, FETCH_ORGANIZATION_PROJECTS, NAMESPACE } from './constants'; function* fetchOrganizationProjects({ payload: organizationId }) { diff --git a/app/src/controllers/organizations/projects/selectors.js b/app/src/controllers/organizations/projects/selectors.js index 183f0155c9..01f299ea9f 100644 --- a/app/src/controllers/organizations/projects/selectors.js +++ b/app/src/controllers/organizations/projects/selectors.js @@ -14,11 +14,6 @@ * limitations under the License. */ -import { createSelector } from 'reselect'; -import { createQueryParametersSelector } from 'controllers/pages'; -import { SORTING_ASC } from 'controllers/sorting'; -import { getAlternativePaginationAndSortParams, PAGE_KEY, SIZE_KEY } from 'controllers/pagination'; -import { SORTING_KEY, DEFAULT_PAGINATION } from './constants'; import { organizationSelector } from '../organization/selectors'; const domainSelector = (state) => organizationSelector(state).projects || {}; @@ -26,25 +21,3 @@ const domainSelector = (state) => organizationSelector(state).projects || {}; export const projectsPaginationSelector = (state) => domainSelector(state).pagination; export const projectsSelector = (state) => domainSelector(state).projects; export const loadingSelector = (state) => domainSelector(state).loading || false; - -export const createOrganizationProjectsParametersSelector = ({ - defaultPagination, - defaultSorting, - sortingKey, -} = {}) => - createSelector( - createQueryParametersSelector({ - defaultPagination, - defaultSorting, - sortingKey, - }), - ({ [SIZE_KEY]: limit, [SORTING_KEY]: sort, [PAGE_KEY]: pageNumber, ...rest }) => { - return { ...getAlternativePaginationAndSortParams(sort, limit, pageNumber), ...rest }; - }, - ); - -export const querySelector = createOrganizationProjectsParametersSelector({ - defaultPagination: DEFAULT_PAGINATION, - defaultDirection: SORTING_ASC, - sortingKey: SORTING_KEY, -}); diff --git a/app/src/controllers/organizations/sagas.js b/app/src/controllers/organizations/sagas.js index 12cc042350..47c81ca27a 100644 --- a/app/src/controllers/organizations/sagas.js +++ b/app/src/controllers/organizations/sagas.js @@ -19,6 +19,7 @@ import { URLS } from 'common/urls'; import { showDefaultErrorNotification } from 'controllers/notification'; import { fetchDataAction } from 'controllers/fetch'; import { organizationSagas } from 'controllers/organizations/organization/sagas'; +import { usersSagas } from './users'; import { FETCH_ORGANIZATIONS, NAMESPACE } from './constants'; import { projectsSagas } from './projects'; @@ -35,5 +36,5 @@ function* watchFetchOrganizations() { } export function* organizationsSagas() { - yield all([watchFetchOrganizations(), organizationSagas(), projectsSagas()]); + yield all([watchFetchOrganizations(), organizationSagas(), projectsSagas(), usersSagas()]); } diff --git a/app/src/controllers/organizations/selectors.js b/app/src/controllers/organizations/selectors.js index 5ea88f415d..33a0a29e27 100644 --- a/app/src/controllers/organizations/selectors.js +++ b/app/src/controllers/organizations/selectors.js @@ -14,8 +14,33 @@ * limitations under the License. */ +import { createSelector } from 'reselect'; +import { createQueryParametersSelector } from 'controllers/pages'; +import { getAlternativePaginationAndSortParams, PAGE_KEY, SIZE_KEY } from 'controllers/pagination'; +import { SORTING_KEY } from 'controllers/organizations/projects'; +import { DEFAULT_PAGINATION } from 'controllers/organizations/projects/constants'; +import { SORTING_ASC } from 'controllers/sorting'; + export const organizationsSelector = (state) => state.organizations || {}; export const organizationsListSelector = (state) => organizationsSelector(state).list || []; export const organizationsListLoadingSelector = (state) => organizationsSelector(state).listLoading; + +export const createParametersSelector = ({ defaultPagination, defaultSorting, sortingKey } = {}) => + createSelector( + createQueryParametersSelector({ + defaultPagination, + defaultSorting, + sortingKey, + }), + ({ [SIZE_KEY]: limit, [SORTING_KEY]: sort, [PAGE_KEY]: pageNumber, ...rest }) => { + return { ...getAlternativePaginationAndSortParams(sort, limit, pageNumber), ...rest }; + }, + ); + +export const querySelector = createParametersSelector({ + defaultPagination: DEFAULT_PAGINATION, + defaultDirection: SORTING_ASC, + sortingKey: SORTING_KEY, +}); diff --git a/app/src/controllers/organizations/users/actionCreators.js b/app/src/controllers/organizations/users/actionCreators.js new file mode 100644 index 0000000000..7cd1914e82 --- /dev/null +++ b/app/src/controllers/organizations/users/actionCreators.js @@ -0,0 +1,29 @@ +/* + * 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 { FETCH_ORGANIZATION_USERS, PREPARE_ACTIVE_ORGANIZATION_USERS } from './constants'; + +export const prepareActiveOrganizationUsersAction = (payload) => ({ + type: PREPARE_ACTIVE_ORGANIZATION_USERS, + payload, +}); + +export const fetchOrganizationUsersAction = (params) => { + return { + type: FETCH_ORGANIZATION_USERS, + payload: params, + }; +}; diff --git a/app/src/controllers/organizations/users/constants.js b/app/src/controllers/organizations/users/constants.js new file mode 100644 index 0000000000..0a688fd9dd --- /dev/null +++ b/app/src/controllers/organizations/users/constants.js @@ -0,0 +1,19 @@ +/* + * 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 const FETCH_ORGANIZATION_USERS = 'fetchOrganizationUsers'; +export const PREPARE_ACTIVE_ORGANIZATION_USERS = 'prepareActiveOrganizationUsers'; +export const NAMESPACE = 'organizationUsers'; diff --git a/app/src/controllers/organizations/users/index.js b/app/src/controllers/organizations/users/index.js new file mode 100644 index 0000000000..18b3dd219b --- /dev/null +++ b/app/src/controllers/organizations/users/index.js @@ -0,0 +1,23 @@ +/* + * 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 { NAMESPACE, FETCH_ORGANIZATION_USERS } from './constants'; +export { + prepareActiveOrganizationUsersAction, + fetchOrganizationUsersAction, +} from './actionCreators'; +export { usersPaginationSelector, usersSelector, loadingSelector } from './selectors'; +export { usersSagas } from './sagas'; diff --git a/app/src/controllers/organizations/users/reducer.js b/app/src/controllers/organizations/users/reducer.js new file mode 100644 index 0000000000..ce7daa57bf --- /dev/null +++ b/app/src/controllers/organizations/users/reducer.js @@ -0,0 +1,37 @@ +/* + * 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 { combineReducers } from 'redux'; +import { fetchReducer } from 'controllers/fetch'; +import { alternativePaginationReducer } from 'controllers/pagination'; +import { loadingReducer } from 'controllers/loading'; +import { createPageScopedReducer } from 'common/utils/createPageScopedReducer'; +import { ORGANIZATION_USERS_PAGE } from 'controllers/pages/constants'; +import { NAMESPACE } from './constants'; +import { initialPaginationState } from '../projects/constants'; + +export const usersFetchReducer = fetchReducer(NAMESPACE, { + contentPath: 'items', + initialState: [], +}); + +export const reducer = combineReducers({ + pagination: alternativePaginationReducer(NAMESPACE, initialPaginationState), + loading: loadingReducer(NAMESPACE), + users: usersFetchReducer, +}); + +export const usersReducer = createPageScopedReducer(reducer, ORGANIZATION_USERS_PAGE); diff --git a/app/src/controllers/organizations/users/sagas.js b/app/src/controllers/organizations/users/sagas.js new file mode 100644 index 0000000000..e81f89e149 --- /dev/null +++ b/app/src/controllers/organizations/users/sagas.js @@ -0,0 +1,59 @@ +/* + * 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 { createFetchPredicate, fetchDataAction } from 'controllers/fetch'; +import { URLS } from 'common/urls'; +import { all, put, select, take, takeEvery } from 'redux-saga/effects'; +import { activeOrganizationSelector } from 'controllers/organizations/organization'; +import { FETCH_ORGANIZATION_BY_SLUG } from 'controllers/organizations/organization/constants'; +import { fetchOrganizationUsersAction } from 'controllers/organizations/users/actionCreators'; +import { + FETCH_ORGANIZATION_USERS, + NAMESPACE, + PREPARE_ACTIVE_ORGANIZATION_USERS, +} from './constants'; +import { querySelector } from '../selectors'; + +function* fetchOrganizationUsers({ payload: organizationId }) { + const query = yield select(querySelector); + + yield put(fetchDataAction(NAMESPACE)(URLS.organizationUsers(organizationId, { ...query }))); +} + +function* watchFetchUsers() { + yield takeEvery(FETCH_ORGANIZATION_USERS, fetchOrganizationUsers); +} + +function* prepareActiveOrganizationUsers({ payload: { organizationSlug } }) { + let activeOrganization = yield select(activeOrganizationSelector); + try { + if (!activeOrganization || organizationSlug !== activeOrganization?.slug) { + yield take(createFetchPredicate(FETCH_ORGANIZATION_BY_SLUG)); + activeOrganization = yield select(activeOrganizationSelector); + } + yield put(fetchOrganizationUsersAction(activeOrganization.id)); + } catch (error) { + throw new Error(error); + } +} + +function* watchFetchOrganizationUsers() { + yield takeEvery(PREPARE_ACTIVE_ORGANIZATION_USERS, prepareActiveOrganizationUsers); +} + +export function* usersSagas() { + yield all([watchFetchUsers(), watchFetchOrganizationUsers()]); +} diff --git a/app/src/controllers/organizations/users/selectors.js b/app/src/controllers/organizations/users/selectors.js new file mode 100644 index 0000000000..28083d6b87 --- /dev/null +++ b/app/src/controllers/organizations/users/selectors.js @@ -0,0 +1,23 @@ +/* + * 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 { organizationSelector } from '../organization/selectors'; + +const domainSelector = (state) => organizationSelector(state).users || {}; + +export const usersPaginationSelector = (state) => domainSelector(state).pagination; +export const usersSelector = (state) => domainSelector(state).users; +export const loadingSelector = (state) => domainSelector(state).loading || false; diff --git a/app/src/controllers/pages/constants.js b/app/src/controllers/pages/constants.js index 7d648dc496..5b85f01c5e 100644 --- a/app/src/controllers/pages/constants.js +++ b/app/src/controllers/pages/constants.js @@ -33,7 +33,7 @@ export const PLUGIN_UI_EXTENSION_ADMIN_PAGE = 'PLUGIN_UI_EXTENSION_ADMIN_PAGE'; // inside export const API_PAGE = 'API_PAGE'; export const ORGANIZATION_PROJECTS_PAGE = 'ORGANIZATION_PROJECTS_PAGE'; -export const ORGANIZATION_MEMBERS_PAGE = 'ORGANIZATION_MEMBERS_PAGE'; +export const ORGANIZATION_USERS_PAGE = 'ORGANIZATION_USERS_PAGE'; export const ORGANIZATION_SETTINGS_PAGE = 'ORGANIZATION_SETTINGS_PAGE'; export const PROJECT_PAGE = 'PROJECT_PAGE'; export const PROJECT_DASHBOARD_PAGE = 'PROJECT_DASHBOARD_PAGE'; @@ -72,6 +72,7 @@ export const PROJECT_PLUGIN_PAGE = 'PROJECT_PLUGIN_PAGE'; export const pageNames = { [NOT_FOUND]: NOT_FOUND, ORGANIZATION_PROJECTS_PAGE, + ORGANIZATION_USERS_PAGE, PROJECTS_PAGE, PROJECT_DETAILS_PAGE, ALL_USERS_PAGE, diff --git a/app/src/controllers/pages/index.js b/app/src/controllers/pages/index.js index 47af91266d..023f701fc9 100644 --- a/app/src/controllers/pages/index.js +++ b/app/src/controllers/pages/index.js @@ -93,7 +93,7 @@ export { PLUGIN_UI_EXTENSION_ADMIN_PAGE, PROJECT_PLUGIN_PAGE, ORGANIZATION_PROJECTS_PAGE, - ORGANIZATION_MEMBERS_PAGE, + ORGANIZATION_USERS_PAGE, ORGANIZATION_SETTINGS_PAGE, } from './constants'; export { NOT_FOUND } from 'redux-first-router'; diff --git a/app/src/layouts/organizationLayout/organizationSidebar/organizationSidebar.jsx b/app/src/layouts/organizationLayout/organizationSidebar/organizationSidebar.jsx index b395a3242f..c480df0fbf 100644 --- a/app/src/layouts/organizationLayout/organizationSidebar/organizationSidebar.jsx +++ b/app/src/layouts/organizationLayout/organizationSidebar/organizationSidebar.jsx @@ -23,7 +23,7 @@ import { useIntl } from 'react-intl'; import { canSeeMembers } from 'common/utils/permissions'; import { ORGANIZATION_PROJECTS_PAGE, - ORGANIZATION_MEMBERS_PAGE, + ORGANIZATION_USERS_PAGE, ORGANIZATION_SETTINGS_PAGE, PROJECTS_PAGE, USER_PROFILE_PAGE_ORGANIZATION_LEVEL, @@ -74,7 +74,7 @@ export const OrganizationSidebar = ({ onClickNavBtn }) => { onClick: (isSidebarCollapsed) => onClickButton({ itemName: messages.users.defaultMessage, isSidebarCollapsed }), link: { - type: ORGANIZATION_MEMBERS_PAGE, + type: ORGANIZATION_USERS_PAGE, payload: { organizationSlug }, }, icon: MembersIcon, diff --git a/app/src/pages/organization/emptyPageState/emptyPageState.jsx b/app/src/pages/organization/common/emptyPageState/emptyPageState.jsx similarity index 100% rename from app/src/pages/organization/emptyPageState/emptyPageState.jsx rename to app/src/pages/organization/common/emptyPageState/emptyPageState.jsx diff --git a/app/src/pages/organization/emptyPageState/emptyPageState.scss b/app/src/pages/organization/common/emptyPageState/emptyPageState.scss similarity index 100% rename from app/src/pages/organization/emptyPageState/emptyPageState.scss rename to app/src/pages/organization/common/emptyPageState/emptyPageState.scss diff --git a/app/src/pages/organization/emptyPageState/index.js b/app/src/pages/organization/common/emptyPageState/index.js similarity index 100% rename from app/src/pages/organization/emptyPageState/index.js rename to app/src/pages/organization/common/emptyPageState/index.js diff --git a/app/src/pages/organization/common/membersPage/emptyMembersPageState/emptyMembersPageState.jsx b/app/src/pages/organization/common/membersPage/emptyMembersPageState/emptyMembersPageState.jsx new file mode 100644 index 0000000000..353845a50b --- /dev/null +++ b/app/src/pages/organization/common/membersPage/emptyMembersPageState/emptyMembersPageState.jsx @@ -0,0 +1,40 @@ +import { useIntl } from 'react-intl'; +import { BubblesLoader } from '@reportportal/ui-kit'; +import PropTypes from 'prop-types'; +import classNames from 'classnames/bind'; +import { EmptyPageState } from '../../emptyPageState'; +import { messages } from '../membersPageHeader/messages'; +import EmptyIcon from './img/empty-members-icon-inline.svg'; +import styles from './emptyMembersPageState.scss'; + +const cx = classNames.bind(styles); + +export const EmptyMembersPageState = ({ isLoading, hasPermission, showInviteUserModal }) => { + const { formatMessage } = useIntl(); + return isLoading ? ( +
+ +
+ ) : ( + + ); +}; + +EmptyMembersPageState.propTypes = { + isLoading: PropTypes.bool, + hasPermission: PropTypes.bool, + showInviteUserModal: PropTypes.func, +}; + +EmptyMembersPageState.defaultProps = { + isLoading: false, + hasPermission: false, + showInviteUserModal: () => {}, +}; diff --git a/app/src/pages/organization/common/membersPage/emptyMembersPageState/emptyMembersPageState.scss b/app/src/pages/organization/common/membersPage/emptyMembersPageState/emptyMembersPageState.scss new file mode 100644 index 0000000000..a9f3839aba --- /dev/null +++ b/app/src/pages/organization/common/membersPage/emptyMembersPageState/emptyMembersPageState.scss @@ -0,0 +1,7 @@ +.loader { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + flex: 1; +} \ No newline at end of file diff --git a/app/src/pages/organization/projectTeamPage/img/empty-members-icon-inline.svg b/app/src/pages/organization/common/membersPage/emptyMembersPageState/img/empty-members-icon-inline.svg similarity index 100% rename from app/src/pages/organization/projectTeamPage/img/empty-members-icon-inline.svg rename to app/src/pages/organization/common/membersPage/emptyMembersPageState/img/empty-members-icon-inline.svg diff --git a/app/src/pages/organization/common/membersPage/emptyMembersPageState/index.js b/app/src/pages/organization/common/membersPage/emptyMembersPageState/index.js new file mode 100644 index 0000000000..21692e2098 --- /dev/null +++ b/app/src/pages/organization/common/membersPage/emptyMembersPageState/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 { EmptyMembersPageState } from './emptyMembersPageState'; diff --git a/app/src/pages/organization/common/membersPage/membersListTable/index.js b/app/src/pages/organization/common/membersPage/membersListTable/index.js new file mode 100644 index 0000000000..416b8d5229 --- /dev/null +++ b/app/src/pages/organization/common/membersPage/membersListTable/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 { MembersListTable } from './membersListTable'; diff --git a/app/src/pages/organization/common/membersPage/membersListTable/membersListTable.jsx b/app/src/pages/organization/common/membersPage/membersListTable/membersListTable.jsx new file mode 100644 index 0000000000..7370f3447d --- /dev/null +++ b/app/src/pages/organization/common/membersPage/membersListTable/membersListTable.jsx @@ -0,0 +1,81 @@ +/*! + * 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 classNames from 'classnames/bind'; +import PropTypes from 'prop-types'; +import { Table } from '@reportportal/ui-kit'; +import { DEFAULT_PAGE_SIZE_OPTIONS } from 'controllers/members/constants'; +import { PaginationWrapper } from 'components/main/paginationWrapper'; +import styles from './membersListTable.scss'; + +const cx = classNames.bind(styles); + +export const MembersListTable = ({ + data, + primaryColumn, + fixedColumns, + onTableSorting, + showPagination, + rowActionMenu, + sortingDirection, + pageSize, + activePage, + itemCount, + pageCount, + onChangePage, + onChangePageSize, +}) => { + return ( + + + + ); +}; + +MembersListTable.propTypes = { + data: PropTypes.array.isRequired, + primaryColumn: PropTypes.object.isRequired, + fixedColumns: PropTypes.array.isRequired, + onTableSorting: PropTypes.func.isRequired, + showPagination: PropTypes.bool.isRequired, + rowActionMenu: PropTypes.node, + sortingDirection: PropTypes.string.isRequired, + pageSize: PropTypes.number.isRequired, + activePage: PropTypes.number.isRequired, + itemCount: PropTypes.number.isRequired, + pageCount: PropTypes.number.isRequired, + onChangePage: PropTypes.func.isRequired, + onChangePageSize: PropTypes.func.isRequired, +}; diff --git a/app/src/pages/organization/common/membersPage/membersListTable/membersListTable.scss b/app/src/pages/organization/common/membersPage/membersListTable/membersListTable.scss new file mode 100644 index 0000000000..e7f2bfbd97 --- /dev/null +++ b/app/src/pages/organization/common/membersPage/membersListTable/membersListTable.scss @@ -0,0 +1,22 @@ + /*! + * 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. + */ + +.members-list-table { + margin-top: 24px; + padding: 0 32px 32px 32px; + max-width: 1264px; + box-sizing: border-box; +} diff --git a/app/src/pages/organization/common/membersPage/membersPageHeader/index.js b/app/src/pages/organization/common/membersPage/membersPageHeader/index.js new file mode 100644 index 0000000000..ae997aaaa5 --- /dev/null +++ b/app/src/pages/organization/common/membersPage/membersPageHeader/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 { MembersPageHeader } from './membersPageHeader'; diff --git a/app/src/pages/organization/common/membersPage/membersPageHeader/membersPageHeader.jsx b/app/src/pages/organization/common/membersPage/membersPageHeader/membersPageHeader.jsx new file mode 100644 index 0000000000..79d92e2a1c --- /dev/null +++ b/app/src/pages/organization/common/membersPage/membersPageHeader/membersPageHeader.jsx @@ -0,0 +1,42 @@ +/* + * 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 React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames/bind'; +import styles from './membersPageHeader.scss'; + +const cx = classNames.bind(styles); + +export const MembersPageHeader = ({ title, ActionComponent }) => { + return ( +
+
+ {title} + {ActionComponent && } +
+
+ ); +}; + +MembersPageHeader.propTypes = { + title: PropTypes.string.isRequired, + ActionComponent: PropTypes.func, +}; + +MembersPageHeader.defaultProps = { + ActionComponent: null, +}; diff --git a/app/src/pages/organization/common/membersPage/membersPageHeader/membersPageHeader.scss b/app/src/pages/organization/common/membersPage/membersPageHeader/membersPageHeader.scss new file mode 100644 index 0000000000..0ab16a39b4 --- /dev/null +++ b/app/src/pages/organization/common/membersPage/membersPageHeader/membersPageHeader.scss @@ -0,0 +1,42 @@ +/*! + * 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. + */ + +.members-page-header-container { + padding: 48px 32px 16px 32px; + border-bottom: 1px solid $COLOR--e-100; + background: $COLOR--bg-000; + box-sizing: border-box; + position: sticky; + top: 0; + z-index: 2; +} + +.header { + display: flex; + min-height: 31px; + justify-content: space-between; +} + +.title { + font-family: $FONT-REGULAR; + font-size: 20px; + line-height: 31px; + color: $COLOR--almost-black; + text-transform: capitalize; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/app/src/pages/organization/projectTeamPage/messages.js b/app/src/pages/organization/common/membersPage/membersPageHeader/messages.js similarity index 88% rename from app/src/pages/organization/projectTeamPage/messages.js rename to app/src/pages/organization/common/membersPage/membersPageHeader/messages.js index c6fb7c4840..386bf2fa28 100644 --- a/app/src/pages/organization/projectTeamPage/messages.js +++ b/app/src/pages/organization/common/membersPage/membersPageHeader/messages.js @@ -17,7 +17,11 @@ import { defineMessages } from 'react-intl'; export const messages = defineMessages({ - title: { + organizationUsersTitle: { + id: 'OrganizationUsers.organizationUsersTitle', + defaultMessage: 'Organization users', + }, + projectTeamTitle: { id: 'ProjectTeamPage.title', defaultMessage: 'Project team', }, diff --git a/app/src/pages/organization/projectTeamPage/projectTeamListTable/messages.js b/app/src/pages/organization/common/membersPage/messages.js similarity index 86% rename from app/src/pages/organization/projectTeamPage/projectTeamListTable/messages.js rename to app/src/pages/organization/common/membersPage/messages.js index 6dee0dea73..608d0e4677 100644 --- a/app/src/pages/organization/projectTeamPage/projectTeamListTable/messages.js +++ b/app/src/pages/organization/common/membersPage/messages.js @@ -33,4 +33,12 @@ export const messages = defineMessages({ id: 'MembersListTable.permissions', defaultMessage: 'Permissions', }, + role: { + id: 'MembersListTable.role', + defaultMessage: 'Role', + }, + projects: { + id: 'MembersListTable.projects', + defaultMessage: 'Projects', + }, }); diff --git a/app/src/pages/organization/organizationProjectsPage/organizationProjectsPage.jsx b/app/src/pages/organization/organizationProjectsPage/organizationProjectsPage.jsx index 94265d35b5..185169f4a3 100644 --- a/app/src/pages/organization/organizationProjectsPage/organizationProjectsPage.jsx +++ b/app/src/pages/organization/organizationProjectsPage/organizationProjectsPage.jsx @@ -29,7 +29,7 @@ import { createProjectAction } from 'controllers/organizations/projects/actionCr import { useState } from 'react'; import { COMMON_LOCALE_KEYS } from 'common/constants/localization'; import { ProjectsPageHeader } from './projectsPageHeader'; -import { EmptyPageState } from '../emptyPageState'; +import { EmptyPageState } from '../common/emptyPageState'; import EmptyIcon from './img/empty-projects-icon-inline.svg'; import NoResultsIcon from './img/no-results-icon-inline.svg'; import { messages } from './messages'; diff --git a/app/src/pages/organization/organizationUsersPage/index.js b/app/src/pages/organization/organizationUsersPage/index.js new file mode 100644 index 0000000000..102ff7d3b9 --- /dev/null +++ b/app/src/pages/organization/organizationUsersPage/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 { OrganizationUsersPage } from './organizationUsersPage'; diff --git a/app/src/pages/organization/organizationUsersPage/organizationUsersListTable/index.js b/app/src/pages/organization/organizationUsersPage/organizationUsersListTable/index.js new file mode 100644 index 0000000000..46d3945561 --- /dev/null +++ b/app/src/pages/organization/organizationUsersPage/organizationUsersListTable/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 { OrganizationTeamListTable } from './organizationUsersListTable'; diff --git a/app/src/pages/organization/organizationUsersPage/organizationUsersListTable/organizationUsersListTable.jsx b/app/src/pages/organization/organizationUsersPage/organizationUsersListTable/organizationUsersListTable.jsx new file mode 100644 index 0000000000..32f6d8b67a --- /dev/null +++ b/app/src/pages/organization/organizationUsersPage/organizationUsersListTable/organizationUsersListTable.jsx @@ -0,0 +1,205 @@ +/*! + * 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 classNames from 'classnames/bind'; +import PropTypes from 'prop-types'; +import { FormattedMessage, useIntl } from 'react-intl'; +import { useMemo } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { AbsRelTime } from 'components/main/absRelTime'; +import { MeatballMenuIcon, Popover } from '@reportportal/ui-kit'; +import { urlOrganizationAndProjectSelector } from 'controllers/pages'; +import { SORTING_ASC, withSortingURL } from 'controllers/sorting'; +import { DEFAULT_SORT_COLUMN } from 'controllers/members/constants'; +import { + DEFAULT_PAGE_SIZE, + DEFAULT_PAGINATION, + PAGE_KEY, + withPagination, +} from 'controllers/pagination'; +import { + prepareActiveOrganizationUsersAction, + usersPaginationSelector, +} from 'controllers/organizations/users'; +import { SORTING_KEY } from 'controllers/organizations/projects'; +import { ADMINISTRATOR } from 'common/constants/accountRoles'; +import { MembersListTable } from '../../common/membersPage/membersListTable'; +import { messages } from '../../common/membersPage/messages'; +import styles from './organizationUsersListTable.scss'; + +const cx = classNames.bind(styles); + +const OrgTeamListTableWrapped = ({ + users, + onChangeSorting, + sortingDirection, + pageSize, + activePage, + itemCount, + pageCount, + onChangePage, + onChangePageSize, +}) => { + const { formatMessage } = useIntl(); + const dispatch = useDispatch(); + const { organizationSlug, projectSlug } = useSelector(urlOrganizationAndProjectSelector); + const showPagination = users.length > 0; + const data = useMemo( + () => + users.map( + ({ + id, + email, + full_name: fullName, + relationships, + instance_role: instanceRole, + last_login_at: lastLogin, + org_role: orgRole, + }) => { + const projectsCount = relationships.projects.meta.count; + return { + id, + fullName: { + content: fullName, + component: ( +
+
{fullName}
+ {instanceRole === ADMINISTRATOR && ( +
+ +
+ )} +
+ ), + }, + email, + lastLogin: { + content: lastLogin, + component: lastLogin ? ( + + ) : ( + n/a + ), + }, + permissions: orgRole, + projects: projectsCount, + }; + }, + ), + [users, organizationSlug, projectSlug], + ); + + const primaryColumn = { + key: 'fullName', + header: formatMessage(messages.name), + }; + + const fixedColumns = useMemo( + () => [ + { + key: 'email', + header: formatMessage(messages.email), + width: 208, + align: 'left', + }, + { + key: 'lastLogin', + header: formatMessage(messages.lastLogin), + width: 156, + align: 'left', + }, + { + key: 'permissions', + header: formatMessage(messages.role), + width: 114, + align: 'left', + }, + { + key: 'projects', + header: formatMessage(messages.projects), + width: 104, + align: 'right', + }, + ], + [formatMessage], + ); + + const rowActionMenu = ( + +

Manage assignments

+ + } + > + + + +
+ ); + + const onTableSorting = ({ key }) => { + onChangeSorting(key); + dispatch(prepareActiveOrganizationUsersAction()); + }; + + return ( + + ); +}; + +OrgTeamListTableWrapped.propTypes = { + users: PropTypes.array, + sortingDirection: PropTypes.string, + onChangeSorting: PropTypes.func, + pageSize: PropTypes.number, + activePage: PropTypes.number, + itemCount: PropTypes.number.isRequired, + pageCount: PropTypes.number.isRequired, + onChangePage: PropTypes.func.isRequired, + onChangePageSize: PropTypes.func.isRequired, +}; + +OrgTeamListTableWrapped.defaultProps = { + users: [], + pageSize: DEFAULT_PAGE_SIZE, + activePage: DEFAULT_PAGINATION[PAGE_KEY], +}; + +export const OrganizationTeamListTable = withSortingURL({ + defaultFields: [DEFAULT_SORT_COLUMN], + defaultDirection: SORTING_ASC, + sortingKey: SORTING_KEY, +})( + withPagination({ + paginationSelector: usersPaginationSelector, + })(OrgTeamListTableWrapped), +); diff --git a/app/src/pages/organization/organizationUsersPage/organizationUsersListTable/organizationUsersListTable.scss b/app/src/pages/organization/organizationUsersPage/organizationUsersListTable/organizationUsersListTable.scss new file mode 100644 index 0000000000..4400d7bd6c --- /dev/null +++ b/app/src/pages/organization/organizationUsersPage/organizationUsersListTable/organizationUsersListTable.scss @@ -0,0 +1,55 @@ +/*! + * 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. + */ + +.row-action-menu { + display: flex; + flex-direction: column; + + p { + padding: 7px 16px; + align-items: center; + font-size: 13px; + line-height: 20px; + width: 120px; + color: $COLOR--almost-black; + } +} + +.menu-icon { + svg { + margin-bottom: 3px; + } +} + +.admin-badge { + display: inline-flex; + justify-content: center; + align-items: center; + padding: 2px 8px; + border-radius: 6px; + background-color: $COLOR--tag-value-text; + color: $COLOR--white-two; + font-family: $FONT-ROBOTO-BOLD; + font-size: 11px; + line-height: 16px; + margin-right: 16px; +} + +.member-name-column { + display: flex; + align-items: center; + gap: 16px; +} \ No newline at end of file diff --git a/app/src/pages/organization/organizationUsersPage/organizationUsersPage.jsx b/app/src/pages/organization/organizationUsersPage/organizationUsersPage.jsx new file mode 100644 index 0000000000..4bca2a58f8 --- /dev/null +++ b/app/src/pages/organization/organizationUsersPage/organizationUsersPage.jsx @@ -0,0 +1,48 @@ +/* + * 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 { useSelector } from 'react-redux'; +import { loadingSelector, usersSelector } from 'controllers/organizations/users'; +import { OrganizationTeamListTable } from 'pages/organization/organizationUsersPage/organizationUsersListTable/organizationUsersListTable'; +import { ScrollWrapper } from 'components/main/scrollWrapper'; +import classNames from 'classnames/bind'; +import styles from './organizationUsersPage.scss'; +import { EmptyMembersPageState as EmptyUsersPageState } from '../common/membersPage/emptyMembersPageState'; +import { OrganizationUsersPageHeader } from './organizationUsersPageHeader'; + +const cx = classNames.bind(styles); +export const OrganizationUsersPage = () => { + const users = useSelector(usersSelector); + const isUsersLoading = useSelector(loadingSelector); + const isEmptyUsers = users.length === 0; + + return ( + +
+ + {isEmptyUsers ? ( + + ) : ( + + )} +
+
+ ); +}; diff --git a/app/src/pages/organization/organizationUsersPage/organizationUsersPage.scss b/app/src/pages/organization/organizationUsersPage/organizationUsersPage.scss new file mode 100644 index 0000000000..e005f71e5a --- /dev/null +++ b/app/src/pages/organization/organizationUsersPage/organizationUsersPage.scss @@ -0,0 +1,21 @@ +/* + * 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. + */ + +.organization-users-page { + display: flex; + flex-direction: column; + min-height: calc(100% - 48px); +} \ No newline at end of file diff --git a/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/index.js b/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/index.js new file mode 100644 index 0000000000..c4ced6e20f --- /dev/null +++ b/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/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 { OrganizationUsersPageHeader } from './organizationUsersPageHeader'; diff --git a/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/organizationUsersPageHeader.jsx b/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/organizationUsersPageHeader.jsx new file mode 100644 index 0000000000..9873fd9dc6 --- /dev/null +++ b/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/organizationUsersPageHeader.jsx @@ -0,0 +1,64 @@ +/* + * 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 React from 'react'; +import PropTypes from 'prop-types'; +import Parser from 'html-react-parser'; +import classNames from 'classnames/bind'; +import { Button } from '@reportportal/ui-kit'; +import searchIcon from 'common/img/newIcons/search-outline-inline.svg'; +import { useIntl } from 'react-intl'; +import { messages } from '../../common/membersPage/membersPageHeader/messages'; +import styles from './organizationUsersPageHeader.scss'; +import { MembersPageHeader } from '../../common/membersPage/membersPageHeader'; + +const cx = classNames.bind(styles); + +export const OrganizationUsersPageHeader = ({ isNotEmpty, showInviteUserModal }) => { + const { formatMessage } = useIntl(); + const HeaderActions = () => ( +
+ {isNotEmpty && ( + <> +
+ {Parser(searchIcon)} +
+ + + + )} +
+ ); + + return ( + + ); +}; + +OrganizationUsersPageHeader.propTypes = { + isNotEmpty: PropTypes.bool, + showInviteUserModal: PropTypes.func, +}; + +OrganizationUsersPageHeader.defaultProps = { + isNotEmpty: false, + showInviteUserModal: () => {}, +}; diff --git a/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/organizationUsersPageHeader.scss b/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/organizationUsersPageHeader.scss new file mode 100644 index 0000000000..425202922b --- /dev/null +++ b/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/organizationUsersPageHeader.scss @@ -0,0 +1,43 @@ +/*! + * 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. + */ + +.actions { + display: flex; + align-items: center; + gap: 32px; + + .icons { + display: flex; + align-items: center; + justify-content: center; + + i { + display: flex; + align-items: center; + justify-content: center; + + &.search-icon { + width: 40px; + height: 36px; + } + + svg { + width: 16px; + height: 16px; + } + } + } +} diff --git a/app/src/pages/organization/projectTeamPage/projectTeamListTable/projectTeamListTable.jsx b/app/src/pages/organization/projectTeamPage/projectTeamListTable/projectTeamListTable.jsx index 8a44b6db40..0456eb8c6f 100644 --- a/app/src/pages/organization/projectTeamPage/projectTeamListTable/projectTeamListTable.jsx +++ b/app/src/pages/organization/projectTeamPage/projectTeamListTable/projectTeamListTable.jsx @@ -21,11 +21,11 @@ import { useMemo } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { activeProjectKeySelector } from 'controllers/user'; import { AbsRelTime } from 'components/main/absRelTime'; -import { MeatballMenuIcon, Popover, Table } from '@reportportal/ui-kit'; +import { MeatballMenuIcon, Popover } from '@reportportal/ui-kit'; import { UserAvatar } from 'pages/inside/common/userAvatar'; import { urlOrganizationAndProjectSelector, userRolesSelector } from 'controllers/pages'; import { SORTING_ASC, withSortingURL } from 'controllers/sorting'; -import { DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_SORT_COLUMN } from 'controllers/members/constants'; +import { DEFAULT_SORT_COLUMN } from 'controllers/members/constants'; import { fetchMembersAction, membersPaginationSelector } from 'controllers/members'; import { canSeeEmailMembers, getRoleTitle } from 'common/utils/permissions'; import { canSeeRowActionMenu } from 'common/utils/permissions/permissions'; @@ -35,9 +35,9 @@ import { PAGE_KEY, withPagination, } from 'controllers/pagination'; -import { PaginationWrapper } from 'components/main/paginationWrapper'; -import { messages } from './messages'; +import { messages } from '../../common/membersPage/messages'; import styles from './projectTeamListTable.scss'; +import { MembersListTable } from '../../common/membersPage/membersListTable'; const cx = classNames.bind(styles); @@ -158,28 +158,21 @@ const ProjectTeamListTableWrapped = ({ }; return ( - 0} + rowActionMenu={canSeeRowActionMenu(userRoles) ? rowActionMenu : null} + sortingDirection={sortingDirection} pageSize={pageSize} activePage={activePage} - totalItems={itemCount} - totalPages={pageCount} - pageSizeOptions={DEFAULT_PAGE_SIZE_OPTIONS} - changePage={onChangePage} - changePageSize={onChangePageSize} - > -
- + itemCount={itemCount} + pageCount={pageCount} + onChangePage={onChangePage} + onChangePageSize={onChangePageSize} + /> ); }; diff --git a/app/src/pages/organization/projectTeamPage/projectTeamListTable/projectTeamListTable.scss b/app/src/pages/organization/projectTeamPage/projectTeamListTable/projectTeamListTable.scss index 546daaa234..309112b3c0 100644 --- a/app/src/pages/organization/projectTeamPage/projectTeamListTable/projectTeamListTable.scss +++ b/app/src/pages/organization/projectTeamPage/projectTeamListTable/projectTeamListTable.scss @@ -1,4 +1,4 @@ -/*! + /*! * Copyright 2024 EPAM Systems * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,13 +14,6 @@ * limitations under the License. */ -.project-team-list-table { - margin-top: 24px; - padding: 0 32px; - max-width: 1264px; - box-sizing: border-box; -} - .date { font-family: inherit; color: inherit; @@ -65,10 +58,3 @@ border-radius: 50%; } } - -.loader { - display: flex; - justify-content: center; - align-items: center; - height: 100%; -} diff --git a/app/src/pages/organization/projectTeamPage/projectTeamPage.jsx b/app/src/pages/organization/projectTeamPage/projectTeamPage.jsx index bf0dad265a..43f5d656a1 100644 --- a/app/src/pages/organization/projectTeamPage/projectTeamPage.jsx +++ b/app/src/pages/organization/projectTeamPage/projectTeamPage.jsx @@ -19,22 +19,17 @@ import { userRolesSelector } from 'controllers/pages'; import { canInviteInternalUser } from 'common/utils/permissions'; import classNames from 'classnames/bind'; import { loadingSelector, membersSelector, fetchMembersAction } from 'controllers/members'; -import { useIntl } from 'react-intl'; -import { BubblesLoader } from '@reportportal/ui-kit'; import { ScrollWrapper } from 'components/main/scrollWrapper'; import { showModalAction } from 'controllers/modal'; +import { EmptyMembersPageState } from '../common/membersPage/emptyMembersPageState'; import { ProjectTeamPageHeader } from './projectTeamPageHeader'; -import { EmptyPageState } from '../emptyPageState'; import { ProjectTeamListTable } from './projectTeamListTable'; -import EmptyIcon from './img/empty-members-icon-inline.svg'; -import { messages } from './messages'; import styles from './projectTeamPage.scss'; const cx = classNames.bind(styles); export const ProjectTeamPage = () => { const dispatch = useDispatch(); - const { formatMessage } = useIntl(); const userRoles = useSelector(userRolesSelector); const hasPermission = canInviteInternalUser(userRoles); const members = useSelector(membersSelector); @@ -54,32 +49,23 @@ export const ProjectTeamPage = () => { ); }; - const getEmptyPageState = () => - isMembersLoading ? ( -
- -
- ) : ( - - ); - return (
- {isEmptyMembers ? getEmptyPageState() : } + {isEmptyMembers ? ( + + ) : ( + + )}
); diff --git a/app/src/pages/organization/projectTeamPage/projectTeamPage.scss b/app/src/pages/organization/projectTeamPage/projectTeamPage.scss index 673e460dc7..b7aebd6eb5 100644 --- a/app/src/pages/organization/projectTeamPage/projectTeamPage.scss +++ b/app/src/pages/organization/projectTeamPage/projectTeamPage.scss @@ -19,10 +19,3 @@ flex-direction: column; min-height: 100%; } - -.loader { - display: flex; - justify-content: center; - align-items: center; - height: 100%; -} diff --git a/app/src/pages/organization/projectTeamPage/projectTeamPageHeader/projectTeamPageHeader.jsx b/app/src/pages/organization/projectTeamPage/projectTeamPageHeader/projectTeamPageHeader.jsx index d5c30268f4..0f8b72f4e9 100644 --- a/app/src/pages/organization/projectTeamPage/projectTeamPageHeader/projectTeamPageHeader.jsx +++ b/app/src/pages/organization/projectTeamPage/projectTeamPageHeader/projectTeamPageHeader.jsx @@ -22,45 +22,49 @@ import { Button } from '@reportportal/ui-kit'; import searchIcon from 'common/img/newIcons/search-outline-inline.svg'; import filterIcon from 'common/img/newIcons/filters-outline-inline.svg'; import { useIntl } from 'react-intl'; -import { messages } from '../messages'; +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, showInviteUserModal }) => { const { formatMessage } = useIntl(); + const HeaderActions = () => ( +
+ {isNotEmpty && ( + <> +
+ {Parser(searchIcon)} - return ( -
-
- {formatMessage(messages.title)} -
- {isNotEmpty && ( - <> -
- {Parser(searchIcon)} - {Parser(filterIcon)} -
- {hasPermission && ( - - )} - + {Parser(filterIcon)} +
+ {hasPermission && ( + )} -
-
+ + )}
); + + return ( + + ); }; ProjectTeamPageHeader.propTypes = { hasPermission: PropTypes.bool, isNotEmpty: PropTypes.bool, - showInviteUserModal: PropTypes.func.isRequired, + showInviteUserModal: PropTypes.func, }; ProjectTeamPageHeader.defaultProps = { hasPermission: false, isNotEmpty: false, + showInviteUserModal: () => {}, }; diff --git a/app/src/pages/organization/projectTeamPage/projectTeamPageHeader/projectTeamPageHeader.scss b/app/src/pages/organization/projectTeamPage/projectTeamPageHeader/projectTeamPageHeader.scss index 014f95d9c1..f3ea7a276d 100644 --- a/app/src/pages/organization/projectTeamPage/projectTeamPageHeader/projectTeamPageHeader.scss +++ b/app/src/pages/organization/projectTeamPage/projectTeamPageHeader/projectTeamPageHeader.scss @@ -14,33 +14,6 @@ * limitations under the License. */ -.project-team-page-header-container { - padding: 16px 32px; - border-bottom: 1px solid $COLOR--e-100; - background: $COLOR--bg-000; - box-sizing: border-box; - position: sticky; - top: 0; - z-index: 2; -} - -.header { - display: flex; - min-height: 31px; - justify-content: space-between; -} - -.title { - font-family: $FONT-REGULAR; - font-size: 20px; - line-height: 31px; - color: $COLOR--almost-black; - text-transform: capitalize; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - .actions { display: flex; align-items: center; diff --git a/app/src/routes/constants.js b/app/src/routes/constants.js index dfc8ed5910..b4e209695a 100644 --- a/app/src/routes/constants.js +++ b/app/src/routes/constants.js @@ -56,6 +56,7 @@ import { USER_PROFILE_SUB_PAGE, USER_PROFILE_SUB_PAGE_ORGANIZATION_LEVEL, USER_PROFILE_SUB_PAGE_PROJECT_LEVEL, + ORGANIZATION_USERS_PAGE, } from 'controllers/pages'; import { AdminUiExtensionPage } from 'pages/instance/adminUiExtensionPage'; import { AccountRemovedPage } from 'pages/outside/accountRemovedPage'; @@ -65,6 +66,7 @@ import { ProjectTeamPage } from 'pages/organization/projectTeamPage'; import { ProjectLayout } from 'layouts/projectLayout'; import { OrganizationLayout } from 'layouts/organizationLayout'; import { InstanceLayout } from 'layouts/instanceLayout'; +import { OrganizationUsersPage } from 'pages/organization/organizationUsersPage'; export const ANONYMOUS_ACCESS = 'anonymous'; export const ADMIN_ACCESS = 'admin'; @@ -90,6 +92,11 @@ export const pageRendering = { [USER_PROFILE_PAGE_PROJECT_LEVEL]: { component: ProfilePage, layout: ProjectLayout }, [USER_PROFILE_SUB_PAGE_PROJECT_LEVEL]: { component: ProfilePage, layout: ProjectLayout }, API_PAGE: { component: ApiPage, layout: ProjectLayout }, + [ORGANIZATION_USERS_PAGE]: { + component: OrganizationUsersPage, + layout: OrganizationLayout, + rawContent: true, + }, ORGANIZATION_PROJECTS_PAGE: { component: OrganizationProjectsPage, layout: OrganizationLayout, diff --git a/app/src/routes/routesMap.js b/app/src/routes/routesMap.js index a31150077b..d78286d23f 100644 --- a/app/src/routes/routesMap.js +++ b/app/src/routes/routesMap.js @@ -58,7 +58,7 @@ import { PROJECT_PLUGIN_PAGE, userAssignedSelector, ORGANIZATION_PROJECTS_PAGE, - ORGANIZATION_MEMBERS_PAGE, + ORGANIZATION_USERS_PAGE, ORGANIZATION_SETTINGS_PAGE, USER_PROFILE_PAGE, USER_PROFILE_PAGE_ORGANIZATION_LEVEL, @@ -105,6 +105,7 @@ import { fetchOrganizationBySlugAction, prepareActiveOrganizationProjectsAction, } from 'controllers/organizations/organization/actionCreators'; +import { prepareActiveOrganizationUsersAction } from 'controllers/organizations/users'; import { pageRendering, ANONYMOUS_ACCESS, ADMIN_ACCESS } from './constants'; const redirectRoute = (path, createNewAction, onRedirect = () => {}) => ({ @@ -201,8 +202,14 @@ const routesMap = { }, }, - [ORGANIZATION_MEMBERS_PAGE]: { - path: '/organizations/:organizationSlug/members', + [ORGANIZATION_USERS_PAGE]: { + path: '/organizations/:organizationSlug/users', + thunk: (dispatch, getState) => { + const { + location: { payload }, + } = getState(); + dispatch(prepareActiveOrganizationUsersAction(payload)); + }, }, [ORGANIZATION_SETTINGS_PAGE]: { From 5e113a91aefcec977c08b045a33b3ede8be545d3 Mon Sep 17 00:00:00 2001 From: maria-hambardzumian Date: Fri, 4 Oct 2024 18:14:27 +0400 Subject: [PATCH 2/6] EPMRPP-92003 || sonar fix --- .../organizationUsersPageHeader.jsx | 4 ++-- .../projectTeamPageHeader/projectTeamPageHeader.jsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/organizationUsersPageHeader.jsx b/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/organizationUsersPageHeader.jsx index 9873fd9dc6..3dabbd296e 100644 --- a/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/organizationUsersPageHeader.jsx +++ b/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/organizationUsersPageHeader.jsx @@ -29,7 +29,7 @@ const cx = classNames.bind(styles); export const OrganizationUsersPageHeader = ({ isNotEmpty, showInviteUserModal }) => { const { formatMessage } = useIntl(); - const HeaderActions = () => ( + const getHeaderActions = () => (
{isNotEmpty && ( <> @@ -48,7 +48,7 @@ export const OrganizationUsersPageHeader = ({ isNotEmpty, showInviteUserModal }) return ( ); }; diff --git a/app/src/pages/organization/projectTeamPage/projectTeamPageHeader/projectTeamPageHeader.jsx b/app/src/pages/organization/projectTeamPage/projectTeamPageHeader/projectTeamPageHeader.jsx index 0f8b72f4e9..0da15c3adf 100644 --- a/app/src/pages/organization/projectTeamPage/projectTeamPageHeader/projectTeamPageHeader.jsx +++ b/app/src/pages/organization/projectTeamPage/projectTeamPageHeader/projectTeamPageHeader.jsx @@ -30,7 +30,7 @@ const cx = classNames.bind(styles); export const ProjectTeamPageHeader = ({ hasPermission, isNotEmpty, showInviteUserModal }) => { const { formatMessage } = useIntl(); - const HeaderActions = () => ( + const getHeaderActions = () => (
{isNotEmpty && ( <> @@ -52,7 +52,7 @@ export const ProjectTeamPageHeader = ({ hasPermission, isNotEmpty, showInviteUse return ( ); }; From 3706b323e5877b352967b31d8bf2799811b27071 Mon Sep 17 00:00:00 2001 From: maria-hambardzumian Date: Thu, 10 Oct 2024 16:32:19 +0400 Subject: [PATCH 3/6] EPMRPP-95408 || code review fix -1 --- .../membersPageHeader/membersPageHeader.jsx | 8 ++-- .../organizationUsersPageHeader.jsx | 39 +++++++--------- .../organizationUsersPageHeader.scss | 9 ++-- .../projectTeamPageHeader.jsx | 44 +++++++++---------- .../projectTeamPageHeader.scss | 14 ++++-- 5 files changed, 56 insertions(+), 58 deletions(-) diff --git a/app/src/pages/organization/common/membersPage/membersPageHeader/membersPageHeader.jsx b/app/src/pages/organization/common/membersPage/membersPageHeader/membersPageHeader.jsx index 79d92e2a1c..61b598ec47 100644 --- a/app/src/pages/organization/common/membersPage/membersPageHeader/membersPageHeader.jsx +++ b/app/src/pages/organization/common/membersPage/membersPageHeader/membersPageHeader.jsx @@ -21,12 +21,12 @@ import styles from './membersPageHeader.scss'; const cx = classNames.bind(styles); -export const MembersPageHeader = ({ title, ActionComponent }) => { +export const MembersPageHeader = ({ title, children }) => { return (
{title} - {ActionComponent && } + {children}
); @@ -34,9 +34,9 @@ export const MembersPageHeader = ({ title, ActionComponent }) => { MembersPageHeader.propTypes = { title: PropTypes.string.isRequired, - ActionComponent: PropTypes.func, + children: PropTypes.node, }; MembersPageHeader.defaultProps = { - ActionComponent: null, + children: null, }; diff --git a/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/organizationUsersPageHeader.jsx b/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/organizationUsersPageHeader.jsx index 3dabbd296e..eeb303377d 100644 --- a/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/organizationUsersPageHeader.jsx +++ b/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/organizationUsersPageHeader.jsx @@ -16,10 +16,8 @@ import React from 'react'; import PropTypes from 'prop-types'; -import Parser from 'html-react-parser'; import classNames from 'classnames/bind'; -import { Button } from '@reportportal/ui-kit'; -import searchIcon from 'common/img/newIcons/search-outline-inline.svg'; +import { Button, SearchIcon } from '@reportportal/ui-kit'; import { useIntl } from 'react-intl'; import { messages } from '../../common/membersPage/membersPageHeader/messages'; import styles from './organizationUsersPageHeader.scss'; @@ -29,27 +27,24 @@ const cx = classNames.bind(styles); export const OrganizationUsersPageHeader = ({ isNotEmpty, showInviteUserModal }) => { const { formatMessage } = useIntl(); - const getHeaderActions = () => ( -
- {isNotEmpty && ( - <> -
- {Parser(searchIcon)} -
- - - - )} -
- ); return ( - + +
+ {isNotEmpty && ( + <> +
+ + + +
+ + + )} +
+
); }; diff --git a/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/organizationUsersPageHeader.scss b/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/organizationUsersPageHeader.scss index 425202922b..b02ac4065d 100644 --- a/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/organizationUsersPageHeader.scss +++ b/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/organizationUsersPageHeader.scss @@ -32,11 +32,12 @@ &.search-icon { width: 40px; height: 36px; - } - svg { - width: 16px; - height: 16px; + svg, svg * { + width: 18px; + height: 18px; + fill: $COLOR--e-300; + } } } } diff --git a/app/src/pages/organization/projectTeamPage/projectTeamPageHeader/projectTeamPageHeader.jsx b/app/src/pages/organization/projectTeamPage/projectTeamPageHeader/projectTeamPageHeader.jsx index 0da15c3adf..f2d1751a3d 100644 --- a/app/src/pages/organization/projectTeamPage/projectTeamPageHeader/projectTeamPageHeader.jsx +++ b/app/src/pages/organization/projectTeamPage/projectTeamPageHeader/projectTeamPageHeader.jsx @@ -18,8 +18,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import Parser from 'html-react-parser'; import classNames from 'classnames/bind'; -import { Button } from '@reportportal/ui-kit'; -import searchIcon from 'common/img/newIcons/search-outline-inline.svg'; +import { Button, SearchIcon } from '@reportportal/ui-kit'; import filterIcon from 'common/img/newIcons/filters-outline-inline.svg'; import { useIntl } from 'react-intl'; import { messages } from '../../common/membersPage/membersPageHeader/messages'; @@ -30,30 +29,27 @@ const cx = classNames.bind(styles); export const ProjectTeamPageHeader = ({ hasPermission, isNotEmpty, showInviteUserModal }) => { const { formatMessage } = useIntl(); - const getHeaderActions = () => ( -
- {isNotEmpty && ( - <> -
- {Parser(searchIcon)} - - {Parser(filterIcon)} -
- {hasPermission && ( - - )} - - )} -
- ); return ( - + +
+ {isNotEmpty && ( + <> +
+ + + + {Parser(filterIcon)} +
+ {hasPermission && ( + + )} + + )} +
+
); }; diff --git a/app/src/pages/organization/projectTeamPage/projectTeamPageHeader/projectTeamPageHeader.scss b/app/src/pages/organization/projectTeamPage/projectTeamPageHeader/projectTeamPageHeader.scss index f3ea7a276d..dcacd0a962 100644 --- a/app/src/pages/organization/projectTeamPage/projectTeamPageHeader/projectTeamPageHeader.scss +++ b/app/src/pages/organization/projectTeamPage/projectTeamPageHeader/projectTeamPageHeader.scss @@ -32,16 +32,22 @@ &.search-icon { width: 40px; height: 36px; + + svg, svg * { + width: 18px; + height: 18px; + fill: $COLOR--e-300; + } } &.filters-icon { width: 40px; height: 36px; - } - svg { - width: 16px; - height: 16px; + svg { + width: 16px; + height: 16px; + } } } } From f2648009e37008d435c4f26b06644277d70da01a Mon Sep 17 00:00:00 2001 From: maria-hambardzumian Date: Thu, 10 Oct 2024 22:38:37 +0400 Subject: [PATCH 4/6] EPMRPP-95408 || merge fix --- .../common/emptyPageState/emptyPageState.jsx | 65 ------------------- .../common/emptyPageState/emptyPageState.scss | 60 ----------------- .../common/emptyPageState/index.js | 17 ----- .../emptyMembersPageState.jsx | 2 +- .../emptyPageState/emptyPageState.jsx | 65 ------------------- .../emptyPageState/emptyPageState.scss | 60 ----------------- .../organization/emptyPageState/index.js | 17 ----- 7 files changed, 1 insertion(+), 285 deletions(-) delete mode 100644 app/src/pages/organization/common/emptyPageState/emptyPageState.jsx delete mode 100644 app/src/pages/organization/common/emptyPageState/emptyPageState.scss delete mode 100644 app/src/pages/organization/common/emptyPageState/index.js delete mode 100644 app/src/pages/organization/emptyPageState/emptyPageState.jsx delete mode 100644 app/src/pages/organization/emptyPageState/emptyPageState.scss delete mode 100644 app/src/pages/organization/emptyPageState/index.js diff --git a/app/src/pages/organization/common/emptyPageState/emptyPageState.jsx b/app/src/pages/organization/common/emptyPageState/emptyPageState.jsx deleted file mode 100644 index 4b05cb9322..0000000000 --- a/app/src/pages/organization/common/emptyPageState/emptyPageState.jsx +++ /dev/null @@ -1,65 +0,0 @@ -/*! - * 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 classNames from 'classnames/bind'; -import { Button } from '@reportportal/ui-kit'; -import PropTypes from 'prop-types'; -import Parser from 'html-react-parser'; -import styles from './emptyPageState.scss'; - -const cx = classNames.bind(styles); - -export const EmptyPageState = ({ - hasPermission, - label, - description, - icon, - buttonTitle, - emptyIcon, - onClick, -}) => ( -
-
{Parser(emptyIcon)}
-
- {label} -

{description}

- {hasPermission && ( - - )} -
-
-); - -EmptyPageState.propTypes = { - hasPermission: PropTypes.bool, - label: PropTypes.string, - description: PropTypes.string, - buttonTitle: PropTypes.string, - icon: PropTypes.element, - onClick: PropTypes.func, - emptyIcon: PropTypes.element.isRequired, -}; - -EmptyPageState.defaultProps = { - hasPermission: false, - label: '', - description: '', - buttonTitle: '', - icon: null, - onClick: () => {}, -}; diff --git a/app/src/pages/organization/common/emptyPageState/emptyPageState.scss b/app/src/pages/organization/common/emptyPageState/emptyPageState.scss deleted file mode 100644 index 14235bf256..0000000000 --- a/app/src/pages/organization/common/emptyPageState/emptyPageState.scss +++ /dev/null @@ -1,60 +0,0 @@ -/*! - * 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. - */ - -.empty-page-state { - width: 100%; - flex: 1; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - - .content { - width: 417px; - height: 157px; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - - .label { - font-family: $FONT-REGULAR; - font-size: 20px; - line-height: 31px; - color: $COLOR--e-400; - text-align: center; - margin-bottom: 16px; - } - - .description { - font-family: $FONT-REGULAR; - font-size: 13px; - line-height: 20px; - color: $COLOR--e-400; - text-align: center; - } - } -} - -.empty-icon { - svg { - width: 168px; - } -} - -.button { - margin-top: 16px; -} diff --git a/app/src/pages/organization/common/emptyPageState/index.js b/app/src/pages/organization/common/emptyPageState/index.js deleted file mode 100644 index 6a2d0c1810..0000000000 --- a/app/src/pages/organization/common/emptyPageState/index.js +++ /dev/null @@ -1,17 +0,0 @@ -/*! - * 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 { EmptyPageState } from './emptyPageState'; diff --git a/app/src/pages/organization/common/membersPage/emptyMembersPageState/emptyMembersPageState.jsx b/app/src/pages/organization/common/membersPage/emptyMembersPageState/emptyMembersPageState.jsx index ebbac07243..23825e4cd7 100644 --- a/app/src/pages/organization/common/membersPage/emptyMembersPageState/emptyMembersPageState.jsx +++ b/app/src/pages/organization/common/membersPage/emptyMembersPageState/emptyMembersPageState.jsx @@ -2,7 +2,7 @@ import { useIntl } from 'react-intl'; import { BubblesLoader } from '@reportportal/ui-kit'; import PropTypes from 'prop-types'; import classNames from 'classnames/bind'; -import { EmptyPageState } from '../../../../common/emptyPageState'; +import { EmptyPageState } from 'pages/common/emptyPageState'; import { messages } from '../membersPageHeader/messages'; import EmptyIcon from './img/empty-members-icon-inline.svg'; import styles from './emptyMembersPageState.scss'; diff --git a/app/src/pages/organization/emptyPageState/emptyPageState.jsx b/app/src/pages/organization/emptyPageState/emptyPageState.jsx deleted file mode 100644 index 4b05cb9322..0000000000 --- a/app/src/pages/organization/emptyPageState/emptyPageState.jsx +++ /dev/null @@ -1,65 +0,0 @@ -/*! - * 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 classNames from 'classnames/bind'; -import { Button } from '@reportportal/ui-kit'; -import PropTypes from 'prop-types'; -import Parser from 'html-react-parser'; -import styles from './emptyPageState.scss'; - -const cx = classNames.bind(styles); - -export const EmptyPageState = ({ - hasPermission, - label, - description, - icon, - buttonTitle, - emptyIcon, - onClick, -}) => ( -
-
{Parser(emptyIcon)}
-
- {label} -

{description}

- {hasPermission && ( - - )} -
-
-); - -EmptyPageState.propTypes = { - hasPermission: PropTypes.bool, - label: PropTypes.string, - description: PropTypes.string, - buttonTitle: PropTypes.string, - icon: PropTypes.element, - onClick: PropTypes.func, - emptyIcon: PropTypes.element.isRequired, -}; - -EmptyPageState.defaultProps = { - hasPermission: false, - label: '', - description: '', - buttonTitle: '', - icon: null, - onClick: () => {}, -}; diff --git a/app/src/pages/organization/emptyPageState/emptyPageState.scss b/app/src/pages/organization/emptyPageState/emptyPageState.scss deleted file mode 100644 index 14235bf256..0000000000 --- a/app/src/pages/organization/emptyPageState/emptyPageState.scss +++ /dev/null @@ -1,60 +0,0 @@ -/*! - * 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. - */ - -.empty-page-state { - width: 100%; - flex: 1; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - - .content { - width: 417px; - height: 157px; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - - .label { - font-family: $FONT-REGULAR; - font-size: 20px; - line-height: 31px; - color: $COLOR--e-400; - text-align: center; - margin-bottom: 16px; - } - - .description { - font-family: $FONT-REGULAR; - font-size: 13px; - line-height: 20px; - color: $COLOR--e-400; - text-align: center; - } - } -} - -.empty-icon { - svg { - width: 168px; - } -} - -.button { - margin-top: 16px; -} diff --git a/app/src/pages/organization/emptyPageState/index.js b/app/src/pages/organization/emptyPageState/index.js deleted file mode 100644 index 6a2d0c1810..0000000000 --- a/app/src/pages/organization/emptyPageState/index.js +++ /dev/null @@ -1,17 +0,0 @@ -/*! - * 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 { EmptyPageState } from './emptyPageState'; From d4465863ba71dd309cb0bd2fd2ed98e5d67bcbe6 Mon Sep 17 00:00:00 2001 From: maria-hambardzumian Date: Fri, 11 Oct 2024 16:38:39 +0400 Subject: [PATCH 5/6] EPMRPP-92003 || code review fix -1 --- app/src/controllers/instance/organizations/sagas.js | 5 +---- app/src/controllers/organization/sagas.js | 11 +++++++++-- .../organizationUsersPage/organizationUsersPage.jsx | 1 + .../organizationUsersPageHeader.jsx | 8 ++++---- .../organization/projectTeamPage/projectTeamPage.jsx | 2 +- .../projectTeamPageHeader/projectTeamPageHeader.jsx | 8 ++++---- 6 files changed, 20 insertions(+), 15 deletions(-) diff --git a/app/src/controllers/instance/organizations/sagas.js b/app/src/controllers/instance/organizations/sagas.js index 513c44bcf3..88dad58d28 100644 --- a/app/src/controllers/instance/organizations/sagas.js +++ b/app/src/controllers/instance/organizations/sagas.js @@ -18,9 +18,6 @@ import { takeEvery, all, put } from 'redux-saga/effects'; import { URLS } from 'common/urls'; import { showDefaultErrorNotification } from 'controllers/notification'; import { fetchDataAction } from 'controllers/fetch'; -import { organizationSagas } from 'controllers/organization'; -import { projectsSagas } from 'controllers/organization/projects'; -import { usersSagas } from 'controllers/organization/users'; import { FETCH_ORGANIZATIONS, NAMESPACE } from './constants'; function* fetchOrganizations() { @@ -36,5 +33,5 @@ function* watchFetchOrganizations() { } export function* organizationsSagas() { - yield all([watchFetchOrganizations(), organizationSagas(), projectsSagas(), usersSagas()]); + yield all([watchFetchOrganizations()]); } diff --git a/app/src/controllers/organization/sagas.js b/app/src/controllers/organization/sagas.js index 68c0e6dbdf..e78f2d7327 100644 --- a/app/src/controllers/organization/sagas.js +++ b/app/src/controllers/organization/sagas.js @@ -20,9 +20,10 @@ import { redirect } from 'redux-first-router'; import { ORGANIZATIONS_PAGE } from 'controllers/pages'; import { URLS } from 'common/urls'; import { showDefaultErrorNotification } from 'controllers/notification'; -import { fetchOrganizationProjectsAction } from 'controllers/organization/projects'; +import { fetchOrganizationProjectsAction, projectsSagas } from './projects'; import { FETCH_ORGANIZATION_BY_SLUG, PREPARE_ACTIVE_ORGANIZATION_PROJECTS } from './constants'; import { activeOrganizationSelector } from './selectors'; +import { usersSagas } from './users'; function* fetchOrganizationBySlug({ payload: slug }) { try { @@ -58,5 +59,11 @@ function* watchFetchOrganizationBySlug() { } export function* organizationSagas() { - yield all([watchFetchOrganizationProjects(), watchFetchOrganizationBySlug()]); + yield all([ + watchFetchOrganizationProjects(), + watchFetchOrganizationBySlug(), + organizationSagas(), + projectsSagas(), + usersSagas(), + ]); } diff --git a/app/src/pages/organization/organizationUsersPage/organizationUsersPage.jsx b/app/src/pages/organization/organizationUsersPage/organizationUsersPage.jsx index db425b52ef..5e8f000809 100644 --- a/app/src/pages/organization/organizationUsersPage/organizationUsersPage.jsx +++ b/app/src/pages/organization/organizationUsersPage/organizationUsersPage.jsx @@ -24,6 +24,7 @@ import { EmptyMembersPageState as EmptyUsersPageState } from '../common/membersP import { OrganizationUsersPageHeader } from './organizationUsersPageHeader'; const cx = classNames.bind(styles); + export const OrganizationUsersPage = () => { const users = useSelector(usersSelector); const isUsersLoading = useSelector(loadingSelector); diff --git a/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/organizationUsersPageHeader.jsx b/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/organizationUsersPageHeader.jsx index eeb303377d..de1084197f 100644 --- a/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/organizationUsersPageHeader.jsx +++ b/app/src/pages/organization/organizationUsersPage/organizationUsersPageHeader/organizationUsersPageHeader.jsx @@ -25,7 +25,7 @@ import { MembersPageHeader } from '../../common/membersPage/membersPageHeader'; const cx = classNames.bind(styles); -export const OrganizationUsersPageHeader = ({ isNotEmpty, showInviteUserModal }) => { +export const OrganizationUsersPageHeader = ({ isNotEmpty, onInvite }) => { const { formatMessage } = useIntl(); return ( @@ -38,7 +38,7 @@ export const OrganizationUsersPageHeader = ({ isNotEmpty, showInviteUserModal })
- @@ -50,10 +50,10 @@ export const OrganizationUsersPageHeader = ({ isNotEmpty, showInviteUserModal }) OrganizationUsersPageHeader.propTypes = { isNotEmpty: PropTypes.bool, - showInviteUserModal: PropTypes.func, + onInvite: PropTypes.func, }; OrganizationUsersPageHeader.defaultProps = { isNotEmpty: false, - showInviteUserModal: () => {}, + onInvite: () => {}, }; diff --git a/app/src/pages/organization/projectTeamPage/projectTeamPage.jsx b/app/src/pages/organization/projectTeamPage/projectTeamPage.jsx index 43f5d656a1..3981c2439e 100644 --- a/app/src/pages/organization/projectTeamPage/projectTeamPage.jsx +++ b/app/src/pages/organization/projectTeamPage/projectTeamPage.jsx @@ -55,7 +55,7 @@ export const ProjectTeamPage = () => { {isEmptyMembers ? ( { +export const ProjectTeamPageHeader = ({ hasPermission, isNotEmpty, onInvite }) => { const { formatMessage } = useIntl(); return ( @@ -42,7 +42,7 @@ export const ProjectTeamPageHeader = ({ hasPermission, isNotEmpty, showInviteUse {Parser(filterIcon)}
{hasPermission && ( - )} @@ -56,11 +56,11 @@ export const ProjectTeamPageHeader = ({ hasPermission, isNotEmpty, showInviteUse ProjectTeamPageHeader.propTypes = { hasPermission: PropTypes.bool, isNotEmpty: PropTypes.bool, - showInviteUserModal: PropTypes.func, + onInvite: PropTypes.func, }; ProjectTeamPageHeader.defaultProps = { hasPermission: false, isNotEmpty: false, - showInviteUserModal: () => {}, + onInvite: () => {}, }; From f61681f627a30cc7f55daf62d3e413e92c214f72 Mon Sep 17 00:00:00 2001 From: maria-hambardzumian Date: Fri, 11 Oct 2024 19:31:32 +0400 Subject: [PATCH 6/6] EPMRPP-92003 || code review fix -2 --- app/src/controllers/organization/sagas.js | 1 - app/src/store/rootSaga.js | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/controllers/organization/sagas.js b/app/src/controllers/organization/sagas.js index e78f2d7327..3fedbdfbf2 100644 --- a/app/src/controllers/organization/sagas.js +++ b/app/src/controllers/organization/sagas.js @@ -62,7 +62,6 @@ export function* organizationSagas() { yield all([ watchFetchOrganizationProjects(), watchFetchOrganizationBySlug(), - organizationSagas(), projectsSagas(), usersSagas(), ]); diff --git a/app/src/store/rootSaga.js b/app/src/store/rootSaga.js index 70e84c979e..42644c59b8 100644 --- a/app/src/store/rootSaga.js +++ b/app/src/store/rootSaga.js @@ -36,6 +36,7 @@ import { pageSagas } from 'controllers/pages'; import { pluginSagas } from 'controllers/plugins'; import { uniqueErrorsSagas } from 'controllers/uniqueErrors'; import { organizationsSagas } from 'controllers/instance/organizations'; +import { organizationSagas } from 'controllers/organization'; const sagas = [ notificationSagas, @@ -54,6 +55,7 @@ const sagas = [ instanceSagas, userSagas, organizationsSagas, + organizationSagas, projectSagas, initialDataSagas, pageSagas,