diff --git a/app/localization/translated/be.json b/app/localization/translated/be.json index f52d8b9c63..7ebdba3348 100644 --- a/app/localization/translated/be.json +++ b/app/localization/translated/be.json @@ -1492,6 +1492,12 @@ "NotificationsEnableForm.toggleNotificationsNote": "Адпраўляць апавяшчэнні па электроннай пошце аб заканчэнні запуску", "NotificationsEnableForm.typeDescription": "Выберыце {type} спіс атрымальнікаў для кожнага правіла, каб адпраўляць апавяшчэнні аб запуску ", "NotificationsTab.updateProjectNotificationsConfigurationSuccess": "Налады апавяшчэнняў былі паспяхова абноўлены!", + "OrganizationProjectsPage.allOrganizations": "Усе арганізацыі", + "OrganizationProjectsPage.createProject": "Стварыць праект", + "OrganizationProjectsPage.noProjectsListWithPermission": "Стварыце новы праект, каб пачаць сваё падарожжа ReportPortal", + "OrganizationProjectsPage.noProjectsListWithoutPermission": "Спіс даступных вам праектаў зараз пусты.
Звярніцеся да кіраўніка вашай арганізацыі для атрымання падрабязнай інфармацыі.", + "OrganizationProjectsPage.noProjectsWithPermission": "Праектаў пакуль няма", + "OrganizationProjectsPage.noProjectsWithoutPermission": "Праектаў пакуль няма", "OrganizationsControl.organization": "Арганізацыя", "OrganizationsItem.open": "адкрыць", "OrganizationsPopover.allOrganizations": "Усе арганізацыі", @@ -1790,15 +1796,14 @@ "ServiceModal.companyNamePlaceholder": "Увядзіце назву кампаніі", "ServiceModal.consentToProcessing": "Я даю згоду на апрацоўку маёй асабістай інфармацыі кампаніяй EPAM Systems, Inc. (“EPAM”) у адпаведнасці з Палітыкай прыватнасці", "ServiceModal.emailLabel": "Email", - "ServiceModal.emailPlaceholder": "example@mail.com", "ServiceModal.firstNameLabel": "Імя", "ServiceModal.firstNamePlaceholder": "Увядзіце сваё імя", "ServiceModal.lastNameLabel": "Прозвішча", "ServiceModal.lastNamePlaceholder": "Увядзіце сваё прозвішча", "ServiceModal.requestSent": "Запыт адпраўлены", + "ServiceModal.requestSentFail": "Запыт не быў адпраўлены", "ServiceModal.sendRequest": "Адправіць запыт", "ServiceModal.subscribeToNews": "Падпісвайцеся на навіны ReportPortal", - "ServiceModal.validEmailHint": "Калі ласка, увядзіце сапраўдны адрас электроннай пошты", "ServiceVersionItem.newVersion": "Даступны новыя версіі: {newVersion}", "ServiceVersionsBlock.currentVersion": "Бягучая версія", "ServiceVersionsBlock.deprecatedVersion": "Даступна новая версія", diff --git a/app/localization/translated/ru.json b/app/localization/translated/ru.json index b20b97969e..7551e1d6d5 100644 --- a/app/localization/translated/ru.json +++ b/app/localization/translated/ru.json @@ -1492,6 +1492,12 @@ "NotificationsEnableForm.toggleNotificationsNote": "Отправлять уведомления по электронной почте о завершении запуска", "NotificationsEnableForm.typeDescription": "Выберите список получателей {type} для каждого правила для отправки уведомлений, связанных с запуском.", "NotificationsTab.updateProjectNotificationsConfigurationSuccess": "Настройки уведомлений были успешно обновлены!", + "OrganizationProjectsPage.allOrganizations": "Все организации", + "OrganizationProjectsPage.createProject": "Создать проект", + "OrganizationProjectsPage.noProjectsListWithPermission": "Создайте новый проект, чтобы начать путешествие по ReportPortal.", + "OrganizationProjectsPage.noProjectsListWithoutPermission": "Список доступных вам проектов на данный момент пуст.
За подробностями обращайтесь к менеджеру вашей организации.", + "OrganizationProjectsPage.noProjectsWithPermission": "Пока нет проектов", + "OrganizationProjectsPage.noProjectsWithoutPermission": "Пока нет доступных проектов", "OrganizationsControl.organization": "Организация", "OrganizationsItem.open": "открыть", "OrganizationsPopover.allOrganizations": "Все организации", @@ -1790,15 +1796,14 @@ "ServiceModal.companyNamePlaceholder": "Введите название компании", "ServiceModal.consentToProcessing": "Я даю согласие на обработку моей личной информации компанией EPAM Systems, Inc. («EPAM») в соответствии с Политикой конфиденциальности.", "ServiceModal.emailLabel": "Email", - "ServiceModal.emailPlaceholder": "example@mail.com", "ServiceModal.firstNameLabel": "Имя", "ServiceModal.firstNamePlaceholder": "Введите свое имя", "ServiceModal.lastNameLabel": "Фамилия", "ServiceModal.lastNamePlaceholder": "Введите свою фамилию", "ServiceModal.requestSent": "Запрос был отправлен", + "ServiceModal.requestSentFail": "Не удалось отправить запрос", "ServiceModal.sendRequest": "Послать запрос", "ServiceModal.subscribeToNews": "Подпишитесь на новости ReportPortal", - "ServiceModal.validEmailHint": "Пожалуйста, введите действительный адрес электронной почты", "ServiceVersionItem.newVersion": "Доступна новая версия: {newVersion}", "ServiceVersionsBlock.currentVersion": "Текущая версия", "ServiceVersionsBlock.deprecatedVersion": "Доступна новая версия", diff --git a/app/localization/translated/uk.json b/app/localization/translated/uk.json index 3525702cc5..ca38fa6d76 100644 --- a/app/localization/translated/uk.json +++ b/app/localization/translated/uk.json @@ -1492,6 +1492,12 @@ "NotificationsEnableForm.toggleNotificationsNote": "Надсилайте сповіщення електронною поштою про завершення запуску", "NotificationsEnableForm.typeDescription": "Виберіть {type} список одержувачів для кожного правила, щоб надсилати сповіщення про запуск ", "NotificationsTab.updateProjectNotificationsConfigurationSuccess": "Налаштування сповіщень були успішно оновлені!", + "OrganizationProjectsPage.allOrganizations": "Всі організації", + "OrganizationProjectsPage.createProject": "Створити проект", + "OrganizationProjectsPage.noProjectsListWithPermission": "Створіть новий проект, щоб почати свою подорож ReportPortal", + "OrganizationProjectsPage.noProjectsListWithoutPermission": "Список доступних вам проектів наразі порожній.
Зверніться до менеджера вашої організації для отримання деталей.", + "OrganizationProjectsPage.noProjectsWithPermission": "Проектів ще немає", + "OrganizationProjectsPage.noProjectsWithoutPermission": "Ще немає доступних проектів", "OrganizationsControl.organization": "Організація", "OrganizationsItem.open": "відкрити", "OrganizationsPopover.allOrganizations": "Всі організації", @@ -1790,15 +1796,14 @@ "ServiceModal.companyNamePlaceholder": "Введіть назву компанії", "ServiceModal.consentToProcessing": "Я даю згоду на обробку моєї особистої інформації EPAM Systems, Inc. («EPAM»), як зазначено в Політиці конфіденційності", "ServiceModal.emailLabel": "Email", - "ServiceModal.emailPlaceholder": "example@mail.com", "ServiceModal.firstNameLabel": "Ім'я", "ServiceModal.firstNamePlaceholder": "Введіть своє ім'я", "ServiceModal.lastNameLabel": "Прізвище", "ServiceModal.lastNamePlaceholder": "Введіть своє прізвище", "ServiceModal.requestSent": "Запит надіслано", + "ServiceModal.requestSentFail": "Запит не відправлено", "ServiceModal.sendRequest": "Відправляти запит", "ServiceModal.subscribeToNews": "Підпишіться на новини ReportPortal", - "ServiceModal.validEmailHint": "Введіть дійсну адресу електронної пошти", "ServiceVersionItem.newVersion": "Доступна новая версия: {newVersion}", "ServiceVersionsBlock.currentVersion": "Поточна версія", "ServiceVersionsBlock.deprecatedVersion": "Доступна нова версія", diff --git a/app/localization/translated/zh.json b/app/localization/translated/zh.json index 4ff3e4787e..0c5c493fc0 100644 --- a/app/localization/translated/zh.json +++ b/app/localization/translated/zh.json @@ -1492,6 +1492,12 @@ "NotificationsEnableForm.toggleNotificationsNote": "在启测试任务成时发送电子邮件通知", "NotificationsEnableForm.typeDescription": "为每个规则选择 {type} 收件人列表以发送启动相关通知", "NotificationsTab.updateProjectNotificationsConfigurationSuccess": "通知设置已更新!", + "OrganizationProjectsPage.allOrganizations": "所有组织", + "OrganizationProjectsPage.createProject": "创建项目", + "OrganizationProjectsPage.noProjectsListWithPermission": "创建新项目以开始您的 ReportPortal 之旅", + "OrganizationProjectsPage.noProjectsListWithoutPermission": "您可用的项目列表目前为空。
请联系您组织的经理了解详情。", + "OrganizationProjectsPage.noProjectsWithPermission": "尚无项目", + "OrganizationProjectsPage.noProjectsWithoutPermission": "尚无可用项目", "OrganizationsControl.organization": "组织", "OrganizationsItem.open": "open", "OrganizationsPopover.allOrganizations": "All organizations", @@ -1790,15 +1796,14 @@ "ServiceModal.companyNamePlaceholder": "输入公司名称", "ServiceModal.consentToProcessing": "我同意 EPAM Systems, Inc.(“EPAM”)按照隐私政策中的规定处理我的个人信息", "ServiceModal.emailLabel": "Email", - "ServiceModal.emailPlaceholder": "example@mail.com", "ServiceModal.firstNameLabel": "名", "ServiceModal.firstNamePlaceholder": "输入您的名字", "ServiceModal.lastNameLabel": "姓", "ServiceModal.lastNamePlaceholder": "输入您的姓氏", "ServiceModal.requestSent": "请求已发送", + "ServiceModal.requestSentFail": "请求发送失败", "ServiceModal.sendRequest": "发送请求", "ServiceModal.subscribeToNews": "订阅 ReportPortal 新闻", - "ServiceModal.validEmailHint": "请输入有效的电子邮件", "ServiceVersionItem.newVersion": "新版本可用:{newVersion}", "ServiceVersionsBlock.currentVersion": "当前版本", "ServiceVersionsBlock.deprecatedVersion": "有新版本可用。", diff --git a/app/src/common/img/newIcons/filters-outline-inline.svg b/app/src/common/img/newIcons/filters-outline-inline.svg new file mode 100644 index 0000000000..d0cb45fbdc --- /dev/null +++ b/app/src/common/img/newIcons/filters-outline-inline.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/src/common/img/newIcons/search-outline-inline.svg b/app/src/common/img/newIcons/search-outline-inline.svg new file mode 100644 index 0000000000..d4f7dd6913 --- /dev/null +++ b/app/src/common/img/newIcons/search-outline-inline.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/src/common/urls.js b/app/src/common/urls.js index 9f4abd5b91..785bf1fb24 100644 --- a/app/src/common/urls.js +++ b/app/src/common/urls.js @@ -127,8 +127,10 @@ export const URLS = { apiKeys: (userId) => `${urlCommonBase}users/${userId}/api-keys`, apiKeyById: (userId, apiKeyId) => `${urlCommonBase}users/${userId}/api-keys/${apiKeyId}`, - organizationList: (name) => `${urlBase}organizations${getQueryParams({ name })}`, - organizationById: (organizationId) => `${urlBase}organizations/${organizationId}`, + organizationList: (name) => `${urlCommonBase}organizations${getQueryParams({ name })}`, + organizationById: (organizationId) => `${urlCommonBase}organizations/${organizationId}`, + organizationProjects: (organizationId) => + `${urlCommonBase}organizations/${organizationId}/projects`, projectByName: (projectKey) => `${urlBase}project/${projectKey}`, project: (ids = []) => `${urlBase}project?ids=${ids.join(',')}`, diff --git a/app/src/componentLibrary/breadcrumbs/breadcrumb/breadcrumb.scss b/app/src/componentLibrary/breadcrumbs/breadcrumb/breadcrumb.scss index 731654189c..1000082ba7 100644 --- a/app/src/componentLibrary/breadcrumbs/breadcrumb/breadcrumb.scss +++ b/app/src/componentLibrary/breadcrumbs/breadcrumb/breadcrumb.scss @@ -28,12 +28,21 @@ overflow: hidden; font-size: 13px; font-family: $FONT-REGULAR; - color: $COLOR--e-300; + color: $COLOR--e-400; text-decoration: none; user-select: none; -webkit-user-select: none; } +.breadcrumb:last-child { + .breadcrumb-text { + color: $COLOR--e-200; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } +} + .link .breadcrumb-text { &:hover { color: $COLOR--topaz-hover-2; diff --git a/app/src/controllers/organizations/organization/actionCreators.js b/app/src/controllers/organizations/organization/actionCreators.js new file mode 100644 index 0000000000..5a015f2529 --- /dev/null +++ b/app/src/controllers/organizations/organization/actionCreators.js @@ -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. + */ + +import { FETCH_ORGANIZATION_PROJECTS } from './constants'; + +export const fetchOrganizationProjectsAction = (payload) => ({ + type: FETCH_ORGANIZATION_PROJECTS, + payload, +}); diff --git a/app/src/controllers/organizations/organization/constants.js b/app/src/controllers/organizations/organization/constants.js new file mode 100644 index 0000000000..8bf4ccb7f1 --- /dev/null +++ b/app/src/controllers/organizations/organization/constants.js @@ -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. + */ + +export const PROJECTS_NAMESPACE = 'projects'; + +export const ACTIVE_ORGANIZATION_NAMESPACE = 'activeOrganization'; + +export const FETCH_ORGANIZATION_PROJECTS = 'fetchOrganizationProjects'; diff --git a/app/src/controllers/organizations/organization/index.js b/app/src/controllers/organizations/organization/index.js new file mode 100644 index 0000000000..c5d1645a8c --- /dev/null +++ b/app/src/controllers/organizations/organization/index.js @@ -0,0 +1,20 @@ +/* + * 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 { fetchOrganizationProjectsAction } from './actionCreators'; +export { organizationReducer } from './reducer'; +export { organizationProjectsSelector, activeOrganizationSelector } from './selectors'; +export { organizationSagas } from './sagas'; diff --git a/app/src/controllers/organizations/organization/reducer.js b/app/src/controllers/organizations/organization/reducer.js new file mode 100644 index 0000000000..00ee8d0e9d --- /dev/null +++ b/app/src/controllers/organizations/organization/reducer.js @@ -0,0 +1,27 @@ +/* + * 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 { ACTIVE_ORGANIZATION_NAMESPACE, PROJECTS_NAMESPACE } from './constants'; + +export const activeOrganizationReducer = (state, { type = '', payload = {} }) => { + return type === ACTIVE_ORGANIZATION_NAMESPACE ? payload : null; +}; +export const organizationReducer = combineReducers({ + projects: fetchReducer(PROJECTS_NAMESPACE, { contentPath: 'content' }), + activeOrganization: activeOrganizationReducer, +}); diff --git a/app/src/controllers/organizations/organization/sagas.js b/app/src/controllers/organizations/organization/sagas.js new file mode 100644 index 0000000000..228a42d6a6 --- /dev/null +++ b/app/src/controllers/organizations/organization/sagas.js @@ -0,0 +1,58 @@ +/* + * 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 { takeEvery, all, put, select, take } from 'redux-saga/effects'; +import { createFetchPredicate } from 'controllers/fetch'; +import { redirect } from 'redux-first-router'; +import { PROJECTS_PAGE } from 'controllers/pages'; +import { organizationsListSelector } from '../selectors'; +import { fetchOrganizationsAction } from '../actionCreators'; +import { FETCH_ORGANIZATION_PROJECTS, ACTIVE_ORGANIZATION_NAMESPACE } from './constants'; +import { activeOrganizationSelector } from './selectors'; +import { NAMESPACE } from '../constants'; + +function* fetchOrganizationProjects({ payload: { organizationSlug } }) { + yield put(fetchOrganizationsAction()); + yield take(createFetchPredicate(NAMESPACE)); + const organizations = yield select(organizationsListSelector); + const activeOrganization = yield select(activeOrganizationSelector); + try { + if (!activeOrganization) { + const organization = organizations.find((org) => org.slug === organizationSlug); + if (!organization) { + throw new Error('Organization not found'); + } + yield put({ type: ACTIVE_ORGANIZATION_NAMESPACE, payload: organization }); + } + + // TODO: Uncomment this line after implementation of the organizationProjects in backend + // yield put(fetchDataAction(PROJECTS_NAMESPACE)(URLS.organizationProjects(orgsSlug))); + } catch (error) { + yield put( + redirect({ + type: PROJECTS_PAGE, + }), + ); + } +} + +function* watchFetchOrganizationProjects() { + yield takeEvery(FETCH_ORGANIZATION_PROJECTS, fetchOrganizationProjects); +} + +export function* organizationSagas() { + yield all([watchFetchOrganizationProjects()]); +} diff --git a/app/src/controllers/organizations/organization/selectors.js b/app/src/controllers/organizations/organization/selectors.js new file mode 100644 index 0000000000..0b0db89ba7 --- /dev/null +++ b/app/src/controllers/organizations/organization/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 { organizationsSelector } from 'controllers/organizations/selectors'; + +const organizationSelector = (state) => organizationsSelector(state).organization || {}; + +export const activeOrganizationSelector = (state) => organizationSelector(state).activeOrganization; + +export const organizationProjectsSelector = (state) => organizationSelector(state).projects || []; diff --git a/app/src/controllers/organizations/reducer.js b/app/src/controllers/organizations/reducer.js index de8a250353..f72e3b2589 100644 --- a/app/src/controllers/organizations/reducer.js +++ b/app/src/controllers/organizations/reducer.js @@ -17,9 +17,11 @@ import { combineReducers } from 'redux'; import { fetchReducer } from 'controllers/fetch'; import { loadingReducer } from 'controllers/loading'; +import { organizationReducer } from 'controllers/organizations/organization/reducer'; import { NAMESPACE } from './constants'; export const organizationsReducer = combineReducers({ list: fetchReducer(NAMESPACE, { contentPath: 'content' }), listLoading: loadingReducer(NAMESPACE), + organization: organizationReducer, }); diff --git a/app/src/controllers/organizations/sagas.js b/app/src/controllers/organizations/sagas.js index 88dad58d28..c4236a9073 100644 --- a/app/src/controllers/organizations/sagas.js +++ b/app/src/controllers/organizations/sagas.js @@ -18,6 +18,7 @@ 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/organizations/organization/sagas'; import { FETCH_ORGANIZATIONS, NAMESPACE } from './constants'; function* fetchOrganizations() { @@ -33,5 +34,5 @@ function* watchFetchOrganizations() { } export function* organizationsSagas() { - yield all([watchFetchOrganizations()]); + yield all([watchFetchOrganizations(), organizationSagas()]); } diff --git a/app/src/controllers/organizations/selectors.js b/app/src/controllers/organizations/selectors.js index 01e74453d7..5ea88f415d 100644 --- a/app/src/controllers/organizations/selectors.js +++ b/app/src/controllers/organizations/selectors.js @@ -14,7 +14,7 @@ * limitations under the License. */ -const organizationsSelector = (state) => state.organizations || {}; +export const organizationsSelector = (state) => state.organizations || {}; export const organizationsListSelector = (state) => organizationsSelector(state).list || []; diff --git a/app/src/controllers/pages/constants.js b/app/src/controllers/pages/constants.js index 5aeaa6d7a5..2881f9b45b 100644 --- a/app/src/controllers/pages/constants.js +++ b/app/src/controllers/pages/constants.js @@ -33,6 +33,7 @@ export const PLUGINS_TAB_PAGE = 'PLUGINS_TAB_PAGE'; 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 PROJECT_PAGE = 'PROJECT_PAGE'; export const PROJECT_DASHBOARD_PAGE = 'PROJECT_DASHBOARD_PAGE'; export const PROJECT_DASHBOARD_ITEM_PAGE = 'PROJECT_DASHBOARD_ITEM_PAGE'; @@ -66,6 +67,7 @@ export const PROJECT_PLUGIN_PAGE = 'PROJECT_PLUGIN_PAGE'; export const pageNames = { [NOT_FOUND]: NOT_FOUND, ADMINISTRATE_PAGE, + ORGANIZATION_PROJECTS_PAGE, PROJECTS_PAGE, PROJECT_DETAILS_PAGE, ALL_USERS_PAGE, diff --git a/app/src/layouts/common/appSidebar/helpAndService/modal/requestSupportModal.scss b/app/src/layouts/common/appSidebar/helpAndService/modal/requestSupportModal.scss index f6c1fa20af..7f983e68a1 100644 --- a/app/src/layouts/common/appSidebar/helpAndService/modal/requestSupportModal.scss +++ b/app/src/layouts/common/appSidebar/helpAndService/modal/requestSupportModal.scss @@ -15,7 +15,7 @@ */ .form-fields { - display: flex; + display: flex; flex-direction: column; gap: 16px; transform: translateY(-8px); @@ -36,6 +36,6 @@ } } -.check-item{ +.check-item { align-items: start; } diff --git a/app/src/pages/inside/projectSettingsPageContainer/content/notifications/modals/addEditNotificationModal/addEditNotificationModal.scss b/app/src/pages/inside/projectSettingsPageContainer/content/notifications/modals/addEditNotificationModal/addEditNotificationModal.scss index fff24dfab2..58bfd8d082 100644 --- a/app/src/pages/inside/projectSettingsPageContainer/content/notifications/modals/addEditNotificationModal/addEditNotificationModal.scss +++ b/app/src/pages/inside/projectSettingsPageContainer/content/notifications/modals/addEditNotificationModal/addEditNotificationModal.scss @@ -57,7 +57,7 @@ } } -.dynamicField{ +.dynamicField { margin-top: 16px; margin-bottom: 16px; } diff --git a/app/src/pages/organization/organizationProjectsPage/emptyProjectsState/emptyProjectsState.jsx b/app/src/pages/organization/organizationProjectsPage/emptyProjectsState/emptyProjectsState.jsx new file mode 100644 index 0000000000..38e77491de --- /dev/null +++ b/app/src/pages/organization/organizationProjectsPage/emptyProjectsState/emptyProjectsState.jsx @@ -0,0 +1,58 @@ +/*! + * 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 { useIntl } from 'react-intl'; +import classNames from 'classnames/bind'; +import plusIcon from 'common/img/plus-button-inline.svg'; +import { Button } from 'componentLibrary/button'; +import PropTypes from 'prop-types'; +import Parser from 'html-react-parser'; +import styles from './emptyProjectsState.scss'; +import { messages } from '../messages'; +import emptyProjectsStateImage from './img/emptyProjectsState.png'; + +const cx = classNames.bind(styles); + +export const EmptyProjectsState = ({ hasPermission }) => { + const { formatMessage } = useIntl(); + const permissionSuffix = hasPermission ? 'WithPermission' : 'WithoutPermission'; + return ( +
+ {'empty-projects-state'} +
+ + {formatMessage(messages[`noProjects${permissionSuffix}`])} + +

+ {Parser(formatMessage(messages[`noProjectsList${permissionSuffix}`]))} +

+ {hasPermission && ( + + )} +
+
+ ); +}; + +EmptyProjectsState.propTypes = { + hasPermission: PropTypes.bool, +}; + +EmptyProjectsState.defaultProps = { + hasPermission: false, +}; diff --git a/app/src/pages/organization/organizationProjectsPage/emptyProjectsState/emptyProjectsState.scss b/app/src/pages/organization/organizationProjectsPage/emptyProjectsState/emptyProjectsState.scss new file mode 100644 index 0000000000..339ea38540 --- /dev/null +++ b/app/src/pages/organization/organizationProjectsPage/emptyProjectsState/emptyProjectsState.scss @@ -0,0 +1,74 @@ +/*! + * Copyright 2024 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.empty-projects-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; + } + } +} + +.button { + background-color: $COLOR--topaz; + border: 1px solid $COLOR--topaz; + color: $COLOR--white-two; + height: 36px; + padding: 7px 16px; + margin-top: 16px; + + &:hover:not(.disabled), + &:focus:not(.disabled) { + border: 1px solid $COLOR--topaz-hover-2; + background-color: $COLOR--topaz-hover-2; + color: $COLOR--white-two; + + svg * { + fill: $COLOR--white-two; + } + } + + svg * { + fill: $COLOR--white-two; + } +} diff --git a/app/src/pages/organization/organizationProjectsPage/emptyProjectsState/img/emptyProjectsState.png b/app/src/pages/organization/organizationProjectsPage/emptyProjectsState/img/emptyProjectsState.png new file mode 100644 index 0000000000..fa678fb3c3 Binary files /dev/null and b/app/src/pages/organization/organizationProjectsPage/emptyProjectsState/img/emptyProjectsState.png differ diff --git a/app/src/pages/organization/organizationProjectsPage/emptyProjectsState/index.js b/app/src/pages/organization/organizationProjectsPage/emptyProjectsState/index.js new file mode 100644 index 0000000000..66ba102b62 --- /dev/null +++ b/app/src/pages/organization/organizationProjectsPage/emptyProjectsState/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 { EmptyProjectsState } from './emptyProjectsState'; diff --git a/app/src/pages/organization/organizationProjectsPage/header/index.js b/app/src/pages/organization/organizationProjectsPage/header/index.js new file mode 100644 index 0000000000..0fe92956d7 --- /dev/null +++ b/app/src/pages/organization/organizationProjectsPage/header/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 { ProjectsPageHeader } from './projectsPageHeader'; diff --git a/app/src/pages/organization/organizationProjectsPage/header/projectsPageHeader.jsx b/app/src/pages/organization/organizationProjectsPage/header/projectsPageHeader.jsx new file mode 100644 index 0000000000..bf323b5197 --- /dev/null +++ b/app/src/pages/organization/organizationProjectsPage/header/projectsPageHeader.jsx @@ -0,0 +1,80 @@ +/* + * 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 { useIntl } from 'react-intl'; +import { useSelector } from 'react-redux'; +import Parser from 'html-react-parser'; +import classNames from 'classnames/bind'; +import { PROJECTS_PAGE } from 'controllers/pages'; +import { activeOrganizationSelector } from 'controllers/organizations/organization'; +import searchIcon from 'common/img/newIcons/search-outline-inline.svg'; +import filterIcon from 'common/img/newIcons/filters-outline-inline.svg'; +import plusIcon from 'common/img/plus-button-inline.svg'; +import { Breadcrumbs } from 'componentLibrary/breadcrumbs'; +import { Button } from 'componentLibrary/button'; +import { messages } from '../messages'; +import styles from './projectsPageHeader.scss'; + +const cx = classNames.bind(styles); + +export const ProjectsPageHeader = ({ hasPermission }) => { + const { formatMessage } = useIntl(); + const organization = useSelector(activeOrganizationSelector); + const orgName = organization.name; + const isNotEmpty = organization.relationships?.projects?.meta.count > 0; + + const breadcrumbs = [ + { + title: formatMessage(messages.allOrganizations), + link: { type: PROJECTS_PAGE }, + }, + { + title: orgName, + }, + ]; + + return ( +
+
+ +
+
+ {orgName} +
+ {isNotEmpty && ( +
+ {Parser(searchIcon)} + {Parser(filterIcon)} +
+ )} + {isNotEmpty && hasPermission && ( + + )} +
+
+
+ ); +}; +ProjectsPageHeader.propTypes = { + hasPermission: PropTypes.bool, +}; +ProjectsPageHeader.defaultProps = { + hasPermission: false, +}; diff --git a/app/src/pages/organization/organizationProjectsPage/header/projectsPageHeader.scss b/app/src/pages/organization/organizationProjectsPage/header/projectsPageHeader.scss new file mode 100644 index 0000000000..a762c29e26 --- /dev/null +++ b/app/src/pages/organization/organizationProjectsPage/header/projectsPageHeader.scss @@ -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. + */ + +.projects-page-header-container { + padding: 16px 32px; + border-bottom: 1px solid $COLOR--e-100; + background: $COLOR--bg-000; + box-sizing: border-box; +} + +.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; +} + +.top-breadcrumbs { + height: 32px; +} + +.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; + } + + &.filters-icon { + width: 57px; + height: 36px; + } + + svg { + width: 16px; + height: 16px; + } + } + } +} +.button { + border: $COLOR--topaz solid 1px; + height: 36px; + padding: 7px 16px; +} diff --git a/app/src/pages/organization/organizationProjectsPage/index.js b/app/src/pages/organization/organizationProjectsPage/index.js new file mode 100644 index 0000000000..9b7f593415 --- /dev/null +++ b/app/src/pages/organization/organizationProjectsPage/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 { OrganizationProjectsPage } from './organizationProjectsPage'; diff --git a/app/src/pages/organization/organizationProjectsPage/messages.js b/app/src/pages/organization/organizationProjectsPage/messages.js new file mode 100644 index 0000000000..8c0ff63172 --- /dev/null +++ b/app/src/pages/organization/organizationProjectsPage/messages.js @@ -0,0 +1,45 @@ +/*! + * 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 { defineMessages } from 'react-intl'; + +export const messages = defineMessages({ + allOrganizations: { + id: 'OrganizationProjectsPage.allOrganizations', + defaultMessage: 'All organizations', + }, + createProject: { + id: 'OrganizationProjectsPage.createProject', + defaultMessage: 'Create project', + }, + noProjectsWithoutPermission: { + id: 'OrganizationProjectsPage.noProjectsWithoutPermission', + defaultMessage: 'No projects available yet', + }, + noProjectsWithPermission: { + id: 'OrganizationProjectsPage.noProjectsWithPermission', + defaultMessage: 'No projects yet', + }, + noProjectsListWithoutPermission: { + id: 'OrganizationProjectsPage.noProjectsListWithoutPermission', + defaultMessage: + 'The list of available to you projects is currently empty.
Contact your Organization’s manager for details.', + }, + noProjectsListWithPermission: { + id: 'OrganizationProjectsPage.noProjectsListWithPermission', + defaultMessage: 'Create a new project to begin your ReportPortal journey', + }, +}); diff --git a/app/src/pages/organization/organizationProjectsPage/organizationProjectsPage.jsx b/app/src/pages/organization/organizationProjectsPage/organizationProjectsPage.jsx new file mode 100644 index 0000000000..3185931f8f --- /dev/null +++ b/app/src/pages/organization/organizationProjectsPage/organizationProjectsPage.jsx @@ -0,0 +1,49 @@ +/*! + * 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 { userRolesSelector } from 'controllers/user'; +import { canCreateProject } from 'common/utils/permissions'; +import classNames from 'classnames/bind'; +import { organizationsListLoadingSelector } from 'controllers/organizations'; +import { SpinningPreloader } from 'components/preloaders/spinningPreloader'; +import { organizationProjectsSelector } from 'controllers/organizations/organization'; +import { ProjectsPageHeader } from './header'; +import { EmptyProjectsState } from './emptyProjectsState'; +import styles from './organizationProjectsPage.scss'; + +const cx = classNames.bind(styles); + +export const OrganizationProjectsPage = () => { + const userRoles = useSelector(userRolesSelector); + const hasPermission = canCreateProject(userRoles); + const orgProjects = useSelector(organizationProjectsSelector); + const organizationLoading = useSelector(organizationsListLoadingSelector); + const isProjectsEmpty = orgProjects.length === 0; + + return ( + <> + {organizationLoading ? ( + + ) : ( +
+ + {isProjectsEmpty && } +
+ )} + + ); +}; diff --git a/app/src/pages/organization/organizationProjectsPage/organizationProjectsPage.scss b/app/src/pages/organization/organizationProjectsPage/organizationProjectsPage.scss new file mode 100644 index 0000000000..01c03c97ea --- /dev/null +++ b/app/src/pages/organization/organizationProjectsPage/organizationProjectsPage.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-projects-container { + display: flex; + flex-direction: column; + height: 100%; +} diff --git a/app/src/routes/constants.js b/app/src/routes/constants.js index 0ddcd18f6f..6e63c9881a 100644 --- a/app/src/routes/constants.js +++ b/app/src/routes/constants.js @@ -59,6 +59,7 @@ import { AdminUiExtensionPage } from 'pages/admin/adminUiExtensionPage'; import { AdminUiExtensionPageLayout } from 'layouts/adminLayout/adminUiExtensionPageLayout'; import { AccountRemovedPage } from 'pages/outside/accountRemovedPage'; import { ProjectUiExtensionPage } from 'pages/inside/projectUiExtensionPage'; +import { OrganizationProjectsPage } from 'pages/organization/organizationProjectsPage'; export const ANONYMOUS_ACCESS = 'anonymous'; export const ADMIN_ACCESS = 'admin'; @@ -77,6 +78,11 @@ export const pageRendering = { USER_PROFILE_PAGE: { component: ProfilePage, layout: AppLayout }, [USER_PROFILE_SUB_PAGE]: { component: ProfilePage, layout: AppLayout }, API_PAGE: { component: ApiPage, layout: AppLayout }, + ORGANIZATION_PROJECTS_PAGE: { + component: OrganizationProjectsPage, + layout: AppLayout, + rawContent: true, + }, PROJECT_DASHBOARD_PAGE: { component: DashboardPage, layout: AppLayout }, PROJECT_DASHBOARD_ITEM_PAGE: { component: DashboardItemPage, layout: AppLayout }, PROJECT_DASHBOARD_PRINT_PAGE: { component: DashboardPrintPage, layout: EmptyLayout }, diff --git a/app/src/routes/routesMap.js b/app/src/routes/routesMap.js index 5cbaf4763b..8b3789fdc5 100644 --- a/app/src/routes/routesMap.js +++ b/app/src/routes/routesMap.js @@ -94,6 +94,8 @@ import { PROJECT_ASSIGNMENT_ROUTE, } from 'common/constants/userProfileRoutes'; import { parseQueryToFilterEntityAction } from 'controllers/filter/actionCreators'; +import { ORGANIZATION_PROJECTS_PAGE } from 'controllers/pages/constants'; +import { fetchOrganizationProjectsAction } from 'controllers/organizations/organization/actionCreators'; import { pageRendering, ANONYMOUS_ACCESS, ADMIN_ACCESS } from './constants'; const redirectRoute = (path, createNewAction, onRedirect = () => {}) => ({ @@ -162,6 +164,16 @@ const routesMap = { ), [PLUGINS_TAB_PAGE]: `/administrate/plugins/:pluginsTab(${INSTALLED}|${STORE})`, + [ORGANIZATION_PROJECTS_PAGE]: { + path: '/organizations/:organizationSlug/projects', + thunk: (dispatch, getState) => { + const { + location: { payload }, + } = getState(); + dispatch(fetchOrganizationProjectsAction(payload)); + }, + }, + [PROJECT_PAGE]: { path: '/organizations/:organizationSlug?/projects/:projectSlug', thunk: (dispatch, getState) => {