From 8ef4f2aa994f7948e49191540d58cf676ce39286 Mon Sep 17 00:00:00 2001 From: Siarhei Iukou Date: Mon, 2 Dec 2024 11:56:06 +0300 Subject: [PATCH 01/10] EPMRPP-91556 || Filter organization --- app/localization/translated/be.json | 27 ++++ app/localization/translated/es.json | 27 ++++ app/localization/translated/ru.json | 27 ++++ app/localization/translated/uk.json | 27 ++++ app/localization/translated/zh.json | 27 ++++ app/src/common/constants/localization.js | 4 + .../img/newIcons/filter-filled-inline.svg | 3 + app/src/common/urls.js | 3 + .../buttons/filterButton/constants.js | 51 +++++++ .../buttons/filterButton/filterButton.jsx | 119 ++++++++++++++++ .../buttons/filterButton/filterButton.scss | 128 ++++++++++++++++++ .../filterContent/filterContent.jsx | 111 +++++++++++++++ .../filterContent/filterContent.scss | 37 +++++ .../filterContent/filterInput/filterInput.jsx | 75 ++++++++++ .../filterInput/filterInput.scss | 41 ++++++ .../filterContent/filterInput/index.js | 17 +++ .../filterButton/filterContent/index.js | 17 +++ .../filterButton/filterContent/messages.js | 24 ++++ .../components/buttons/filterButton/index.jsx | 26 ++++ .../buttons/filterButton/messages.js | 64 +++++++++ .../containers/filterEntitiesURLContainer.jsx | 66 ++++++++- .../filterEntities/containers/index.js | 2 +- app/src/controllers/instance/events/utils.js | 4 +- .../instance/organizations/actionCreators.js | 6 +- .../instance/organizations/constants.js | 1 + .../instance/organizations/index.js | 4 +- .../instance/organizations/sagas.js | 21 ++- .../instance/organizations/selectors.js | 14 +- .../organization/projects/actionCreators.js | 11 +- .../organization/projects/constants.js | 1 + .../organization/projects/index.js | 10 +- .../organization/projects/sagas.js | 33 ++++- .../organization/projects/selectors.js | 14 +- app/src/controllers/pages/selectors.js | 26 +++- app/src/controllers/sorting/constants.js | 1 + .../addEditNotificationModal.jsx | 6 +- .../organizationsPage/organizationsPage.jsx | 5 +- .../organizationsFilter/index.js | 17 +++ .../organizationsFilter/messages.js | 52 +++++++ .../organizationsFilter.jsx | 105 ++++++++++++++ .../organizationsPageHeader.jsx | 17 ++- .../organizationProjectsPage.jsx | 6 +- .../projectsFilter/index.js | 17 +++ .../projectsFilter/messages.js | 52 +++++++ .../projectsFilter/projectsFilter.jsx | 105 ++++++++++++++ .../projectsPageHeader/projectsPageHeader.jsx | 25 +++- 46 files changed, 1437 insertions(+), 39 deletions(-) create mode 100644 app/src/common/img/newIcons/filter-filled-inline.svg create mode 100644 app/src/components/buttons/filterButton/constants.js create mode 100644 app/src/components/buttons/filterButton/filterButton.jsx create mode 100644 app/src/components/buttons/filterButton/filterButton.scss create mode 100644 app/src/components/buttons/filterButton/filterContent/filterContent.jsx create mode 100644 app/src/components/buttons/filterButton/filterContent/filterContent.scss create mode 100644 app/src/components/buttons/filterButton/filterContent/filterInput/filterInput.jsx create mode 100644 app/src/components/buttons/filterButton/filterContent/filterInput/filterInput.scss create mode 100644 app/src/components/buttons/filterButton/filterContent/filterInput/index.js create mode 100644 app/src/components/buttons/filterButton/filterContent/index.js create mode 100644 app/src/components/buttons/filterButton/filterContent/messages.js create mode 100644 app/src/components/buttons/filterButton/index.jsx create mode 100644 app/src/components/buttons/filterButton/messages.js create mode 100644 app/src/pages/instance/organizationsPage/organizationsPageHeader/organizationsFilter/index.js create mode 100644 app/src/pages/instance/organizationsPage/organizationsPageHeader/organizationsFilter/messages.js create mode 100644 app/src/pages/instance/organizationsPage/organizationsPageHeader/organizationsFilter/organizationsFilter.jsx create mode 100644 app/src/pages/organization/organizationProjectsPage/projectsPageHeader/projectsFilter/index.js create mode 100644 app/src/pages/organization/organizationProjectsPage/projectsPageHeader/projectsFilter/messages.js create mode 100644 app/src/pages/organization/organizationProjectsPage/projectsPageHeader/projectsFilter/projectsFilter.jsx diff --git a/app/localization/translated/be.json b/app/localization/translated/be.json index f449811b43..4917cb690d 100644 --- a/app/localization/translated/be.json +++ b/app/localization/translated/be.json @@ -774,6 +774,17 @@ "Filter.name": "Назва", "Filter.namePlaceholder": "Увядзіце назву фільтра", "FilterAdd.addTitle": "Дадаць новы фільтр", + "FilterButton.any": "Any", + "FilterButton.contains": "Contains", + "FilterButton.equals": "Equals", + "FilterButton.greaterOrEqual": "Greater or equal", + "FilterButton.lessOrEqual": "Less or equal", + "FilterButton.last2days": "Last 2 days", + "FilterButton.last7days": "Last 7 days", + "FilterButton.last30days": "Last 30 days", + "FilterButton.notContains": "Not contains", + "FilterButton.notEqual": "Not equal", + "FilterButton.today": "Today", "FilterEdit.editTitle": "Рэдагаваць фільтр", "FilterNameById.statistics$defects$automation_bug": "Automation Bug", "FilterNameById.statistics$defects$no_defect": "No Defect", @@ -1566,6 +1577,14 @@ "OrganizationsControl.all": "Усе", "OrganizationsControl.allOrganizations": "Усе арганізацыі", "OrganizationsControl.organization": "Арганізацыя", + "OrganizationsFilter.lastRunDate": "Last Run Date", + "OrganizationsFilter.lastRunDatePlaceholder": "Any", + "OrganizationsFilter.launches": "Launches", + "OrganizationsFilter.launchesPlaceholder": "Enter the number of launches", + "OrganizationsFilter.name": "Organization Name", + "OrganizationsFilter.namePlaceholder": "Enter part of the name", + "OrganizationsFilter.users": "Users", + "OrganizationsFilter.usersPlaceholder": "Enter the number of members", "OrganizationsItem.open": "адкрыць", "OrganizationsPage.title": "Усе арганізацыі", "OrganizationsPage.description": "Спіс даступных вам арганізацый у дадзены момант пусты. Калі ласка, звяжыцеся са сваім адміністратарам, каб атрымаць прызначэнне ва ўжо існуючую арганізацыю.", @@ -1794,6 +1813,14 @@ "ProjectsGrid.nameCol": "Назва", "ProjectsGrid.organizationCol": "Арганізацыя", "ProjectsGrid.projectTypeCol": "Тып праекта", + "ProjectsFilter.lastRunDate": "Last Run Date", + "ProjectsFilter.lastRunDatePlaceholder": "Any", + "ProjectsFilter.launches": "Launches", + "ProjectsFilter.launchesPlaceholder": "Enter the number of launches", + "ProjectsFilter.name": "Project Name", + "ProjectsFilter.namePlaceholder": "Enter part of the name", + "ProjectsFilter.users": "Teammates", + "ProjectsFilter.usersPlaceholder": "Enter the number of members", "ProjectsPage.addProject": "Дадаць Праект", "ProjectsPage.addProjectSuccess": "Праект ''{name}'' быў паспяхова створаны", "ProjectsPage.addProjectTitle": "Дадаць Праект", diff --git a/app/localization/translated/es.json b/app/localization/translated/es.json index 6f5adf1795..fa269b445d 100644 --- a/app/localization/translated/es.json +++ b/app/localization/translated/es.json @@ -773,6 +773,17 @@ "Filter.name": "Nombre", "Filter.namePlaceholder": "Introduce el nombre del filtro", "FilterAdd.addTitle": "Agregar nuevo filtro", + "FilterButton.any": "Any", + "FilterButton.contains": "Contains", + "FilterButton.equals": "Equals", + "FilterButton.greaterOrEqual": "Greater or equal", + "FilterButton.lessOrEqual": "Less or equal", + "FilterButton.last2days": "Last 2 days", + "FilterButton.last7days": "Last 7 days", + "FilterButton.last30days": "Last 30 days", + "FilterButton.notContains": "Not contains", + "FilterButton.notEqual": "Not equal", + "FilterButton.today": "Today", "FilterEdit.editTitle": "Editar filtro", "FilterNameById.statistics$defects$automation_bug": "Error de automatización", "FilterNameById.statistics$defects$no_defect": "Sin defecto", @@ -1564,6 +1575,14 @@ "OrganizationsControl.all": "All", "OrganizationsControl.allOrganizations": "All organizations", "OrganizationsControl.organization": "Organization", + "OrganizationsFilter.lastRunDate": "Last Run Date", + "OrganizationsFilter.lastRunDatePlaceholder": "Any", + "OrganizationsFilter.launches": "Launches", + "OrganizationsFilter.launchesPlaceholder": "Enter the number of launches", + "OrganizationsFilter.name": "Organization Name", + "OrganizationsFilter.namePlaceholder": "Enter part of the name", + "OrganizationsFilter.users": "Users", + "OrganizationsFilter.usersPlaceholder": "Enter the number of members", "OrganizationsItem.open": "open", "OrganizationsPage.title": "All Organizations", "OrganizationsPage.description": "The list of organizations available to you is currently empty. Please contact your Administrator to be assigned to an existing one.", @@ -1792,6 +1811,14 @@ "ProjectsGrid.nameCol": "Nombre", "ProjectsGrid.organizationCol": "Organización", "ProjectsGrid.projectTypeCol": "Tipo de proyecto", + "ProjectsFilter.lastRunDate": "Last Run Date", + "ProjectsFilter.lastRunDatePlaceholder": "Any", + "ProjectsFilter.launches": "Launches", + "ProjectsFilter.launchesPlaceholder": "Enter the number of launches", + "ProjectsFilter.name": "Project Name", + "ProjectsFilter.namePlaceholder": "Enter part of the name", + "ProjectsFilter.users": "Teammates", + "ProjectsFilter.usersPlaceholder": "Enter the number of members", "ProjectsPage.addProject": "Crear Proyecto", "ProjectsPage.addProjectSuccess": "El proyecto ''{name}'' ha sido creado exitosamente", "ProjectsPage.addProjectTitle": "Agregar Proyecto", diff --git a/app/localization/translated/ru.json b/app/localization/translated/ru.json index d003a9d17b..ccf2fe66a1 100644 --- a/app/localization/translated/ru.json +++ b/app/localization/translated/ru.json @@ -774,6 +774,17 @@ "Filter.name": "Имя", "Filter.namePlaceholder": "Ввести имя фильтра", "FilterAdd.addTitle": "Добавить новый фильтр", + "FilterButton.any": "Any", + "FilterButton.contains": "Contains", + "FilterButton.equals": "Equals", + "FilterButton.greaterOrEqual": "Greater or equal", + "FilterButton.lessOrEqual": "Less or equal", + "FilterButton.last2days": "Last 2 days", + "FilterButton.last7days": "Last 7 days", + "FilterButton.last30days": "Last 30 days", + "FilterButton.notContains": "Not contains", + "FilterButton.notEqual": "Not equal", + "FilterButton.today": "Today", "FilterEdit.editTitle": "Редактировать фильтр", "FilterNameById.statistics$defects$automation_bug": "Automation Bug", "FilterNameById.statistics$defects$no_defect": "No Defect", @@ -1561,6 +1572,14 @@ "OrganizationsControl.all": "Все", "OrganizationsControl.allOrganizations": "Все организации", "OrganizationsControl.organization": "Организация", + "OrganizationsFilter.lastRunDate": "Last Run Date", + "OrganizationsFilter.lastRunDatePlaceholder": "Any", + "OrganizationsFilter.launches": "Launches", + "OrganizationsFilter.launchesPlaceholder": "Enter the number of launches", + "OrganizationsFilter.name": "Organization Name", + "OrganizationsFilter.namePlaceholder": "Enter part of the name", + "OrganizationsFilter.users": "Users", + "OrganizationsFilter.usersPlaceholder": "Enter the number of members", "OrganizationsItem.open": "открыть", "OrganizationsPage.title": "Все организации", "OrganizationsPage.description": "Список доступных вам организаций в данный момент пуст. Пожалуйста, свяжитесь со своим администратором, чтобы получить назначение в уже существующую организацию.", @@ -1789,6 +1808,14 @@ "ProjectsGrid.nameCol": "Название", "ProjectsGrid.organizationCol": "Организация", "ProjectsGrid.projectTypeCol": "Тип проекта", + "ProjectsFilter.lastRunDate": "Last Run Date", + "ProjectsFilter.lastRunDatePlaceholder": "Any", + "ProjectsFilter.launches": "Launches", + "ProjectsFilter.launchesPlaceholder": "Enter the number of launches", + "ProjectsFilter.name": "Project Name", + "ProjectsFilter.namePlaceholder": "Enter part of the name", + "ProjectsFilter.users": "Teammates", + "ProjectsFilter.usersPlaceholder": "Enter the number of members", "ProjectsPage.addProject": "Создать Проект", "ProjectsPage.addProjectSuccess": "Проект ''{name}'' успешно создан", "ProjectsPage.addProjectTitle": "Добавить Проект", diff --git a/app/localization/translated/uk.json b/app/localization/translated/uk.json index 6a55d43530..6dc19735a0 100644 --- a/app/localization/translated/uk.json +++ b/app/localization/translated/uk.json @@ -774,6 +774,17 @@ "Filter.name": "Ім’я", "Filter.namePlaceholder": "Ввести ім’я фільтра", "FilterAdd.addTitle": "Додати новий фільтр", + "FilterButton.any": "Any", + "FilterButton.contains": "Contains", + "FilterButton.equals": "Equals", + "FilterButton.greaterOrEqual": "Greater or equal", + "FilterButton.lessOrEqual": "Less or equal", + "FilterButton.last2days": "Last 2 days", + "FilterButton.last7days": "Last 7 days", + "FilterButton.last30days": "Last 30 days", + "FilterButton.notContains": "Not contains", + "FilterButton.notEqual": "Not equal", + "FilterButton.today": "Today", "FilterEdit.editTitle": "Редагувати фільтр", "FilterNameById.statistics$defects$automation_bug": "Помилка Автоматизації", "FilterNameById.statistics$defects$no_defect": "Ніякої Дефект", @@ -1563,6 +1574,14 @@ "OrganizationsControl.all": "Всі", "OrganizationsControl.allOrganizations": "Всі організації", "OrganizationsControl.organization": "Організація", + "OrganizationsFilter.lastRunDate": "Last Run Date", + "OrganizationsFilter.lastRunDatePlaceholder": "Any", + "OrganizationsFilter.launches": "Launches", + "OrganizationsFilter.launchesPlaceholder": "Enter the number of launches", + "OrganizationsFilter.name": "Organization Name", + "OrganizationsFilter.namePlaceholder": "Enter part of the name", + "OrganizationsFilter.users": "Users", + "OrganizationsFilter.usersPlaceholder": "Enter the number of members", "OrganizationsItem.open": "відкрити", "OrganizationsPage.title": "Всі організації", "OrganizationsPage.description": "Список доступних вам організацій в даний момент порожній. Будь ласка, зв'яжіться зі своїм адміністратором, щоб отримати призначення в уже існуючу організацію.", @@ -1791,6 +1810,14 @@ "ProjectsGrid.nameCol": "Назва", "ProjectsGrid.organizationCol": "Організація", "ProjectsGrid.projectTypeCol": "Тип проекту", + "ProjectsFilter.lastRunDate": "Last Run Date", + "ProjectsFilter.lastRunDatePlaceholder": "Any", + "ProjectsFilter.launches": "Launches", + "ProjectsFilter.launchesPlaceholder": "Enter the number of launches", + "ProjectsFilter.name": "Project Name", + "ProjectsFilter.namePlaceholder": "Enter part of the name", + "ProjectsFilter.users": "Teammates", + "ProjectsFilter.usersPlaceholder": "Enter the number of members", "ProjectsPage.addProject": "Створити Проект", "ProjectsPage.addProjectSuccess": "Проект ''{name}'' успешно создан", "ProjectsPage.addProjectTitle": "Проект Додати", diff --git a/app/localization/translated/zh.json b/app/localization/translated/zh.json index e70470bdf9..960ff22afc 100644 --- a/app/localization/translated/zh.json +++ b/app/localization/translated/zh.json @@ -774,6 +774,17 @@ "Filter.name": "名称", "Filter.namePlaceholder": "请输入过滤器名称", "FilterAdd.addTitle": "添加新过滤器", + "FilterButton.any": "Any", + "FilterButton.contains": "Contains", + "FilterButton.equals": "Equals", + "FilterButton.greaterOrEqual": "Greater or equal", + "FilterButton.lessOrEqual": "Less or equal", + "FilterButton.last2days": "Last 2 days", + "FilterButton.last7days": "Last 7 days", + "FilterButton.last30days": "Last 30 days", + "FilterButton.notContains": "Not contains", + "FilterButton.notEqual": "Not equal", + "FilterButton.today": "Today", "FilterEdit.editTitle": "编辑过滤器", "FilterNameById.statistics$defects$automation_bug": "自动化错误", "FilterNameById.statistics$defects$no_defect": "无缺陷", @@ -1563,6 +1574,14 @@ "OrganizationsControl.all": "All", "OrganizationsControl.allOrganizations": "All organizations", "OrganizationsControl.organization": "组织", + "OrganizationsFilter.lastRunDate": "Last Run Date", + "OrganizationsFilter.lastRunDatePlaceholder": "Any", + "OrganizationsFilter.launches": "Launches", + "OrganizationsFilter.launchesPlaceholder": "Enter the number of launches", + "OrganizationsFilter.name": "Organization Name", + "OrganizationsFilter.namePlaceholder": "Enter part of the name", + "OrganizationsFilter.users": "Users", + "OrganizationsFilter.usersPlaceholder": "Enter the number of members", "OrganizationsItem.open": "open", "OrganizationsPage.title": "All Organizations", "OrganizationsPage.description": "The list of organizations available to you is currently empty. Please contact your Administrator to be assigned to an existing one.", @@ -1791,6 +1810,14 @@ "ProjectsGrid.nameCol": "名称", "ProjectsGrid.organizationCol": "组织", "ProjectsGrid.projectTypeCol": "项目类型", + "ProjectsFilter.lastRunDate": "Last Run Date", + "ProjectsFilter.lastRunDatePlaceholder": "Any", + "ProjectsFilter.launches": "Launches", + "ProjectsFilter.launchesPlaceholder": "Enter the number of launches", + "ProjectsFilter.name": "Project Name", + "ProjectsFilter.namePlaceholder": "Enter part of the name", + "ProjectsFilter.users": "Teammates", + "ProjectsFilter.usersPlaceholder": "Enter the number of members", "ProjectsPage.addProject": "Create Project", "ProjectsPage.addProjectSuccess": "项目“{name}”创建成功", "ProjectsPage.addProjectTitle": "创建项目", diff --git a/app/src/common/constants/localization.js b/app/src/common/constants/localization.js index a1b668373a..83d7beee75 100644 --- a/app/src/common/constants/localization.js +++ b/app/src/common/constants/localization.js @@ -31,6 +31,10 @@ export const COMMON_LOCALE_KEYS = defineMessages({ id: 'Common.cancel', defaultMessage: 'Cancel', }, + APPLY: { + id: 'Common.apply', + defaultMessage: 'Apply', + }, RENAME: { id: 'Common.rename', defaultMessage: 'Rename', diff --git a/app/src/common/img/newIcons/filter-filled-inline.svg b/app/src/common/img/newIcons/filter-filled-inline.svg new file mode 100644 index 0000000000..16871d0ff7 --- /dev/null +++ b/app/src/common/img/newIcons/filter-filled-inline.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/src/common/urls.js b/app/src/common/urls.js index b9144f87ce..7e635dacb6 100644 --- a/app/src/common/urls.js +++ b/app/src/common/urls.js @@ -127,8 +127,11 @@ export const URLS = { organizationList: (preferencesObj = {}) => `${urlCommonBase}organizations${getQueryParams(preferencesObj)}`, + organizationSearch: () => `${urlCommonBase}organizations/searches`, organizationProjects: (organizationId, preferencesObj = {}) => `${urlCommonBase}organizations/${organizationId}/projects${getQueryParams(preferencesObj)}`, + filterOrganizationProjects: (organizationId) => + `${urlCommonBase}organizations/${organizationId}/projects/searches`, organizationUsers: (organizationId, preferencesObj = {}) => `${urlCommonBase}organizations/${organizationId}/users${getQueryParams(preferencesObj)}`, projectDelete: ({ organizationId, projectId }) => diff --git a/app/src/components/buttons/filterButton/constants.js b/app/src/components/buttons/filterButton/constants.js new file mode 100644 index 0000000000..84d5ffea99 --- /dev/null +++ b/app/src/components/buttons/filterButton/constants.js @@ -0,0 +1,51 @@ +/* + * 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 { + CONDITION_CNT, + CONDITION_EQ, + CONDITION_GREATER_EQ, + CONDITION_LESS_EQ, + CONDITION_NOT_CNT_EVENTS, + CONDITION_NOT_EQ, +} from 'components/filterEntities/constants'; +import { messages } from './messages'; + +export const LAST_RUN_DATE_FILTER_NAME = 'last_launch_occurred'; +export const LAUNCHES_FILTER_NAME = 'launches'; +export const TEAMMATES_FILTER_NAME = 'users'; +export const FILTER_NAME = 'name'; + +export const getTimeRange = (formatMessage) => [ + { label: formatMessage(messages.any), value: '' }, + { label: formatMessage(messages.today), value: 'today' }, + { label: formatMessage(messages.last2days), value: 'last2days' }, + { label: formatMessage(messages.last7days), value: 'last7days' }, + { label: formatMessage(messages.last30days), value: 'last30days' }, +]; + +export const getRangeComparisons = (formatMessage) => [ + { label: formatMessage(messages.equals), value: CONDITION_EQ }, + { label: formatMessage(messages.greaterOrEqual), value: CONDITION_GREATER_EQ }, + { label: formatMessage(messages.lessOrEqual), value: CONDITION_LESS_EQ }, +]; + +export const getContainmentComparisons = (formatMessage) => [ + { label: formatMessage(messages.equals), value: CONDITION_EQ }, + { label: formatMessage(messages.notEqual), value: CONDITION_NOT_EQ }, + { label: formatMessage(messages.contains), value: CONDITION_CNT }, + { label: formatMessage(messages.notContains), value: CONDITION_NOT_CNT_EVENTS }, +]; diff --git a/app/src/components/buttons/filterButton/filterButton.jsx b/app/src/components/buttons/filterButton/filterButton.jsx new file mode 100644 index 0000000000..e9daa81935 --- /dev/null +++ b/app/src/components/buttons/filterButton/filterButton.jsx @@ -0,0 +1,119 @@ +/* + * 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 { useEffect, useState } from 'react'; +import { useDispatch } from 'react-redux'; +import PropTypes from 'prop-types'; +import classNames from 'classnames/bind'; +import { Popover } from '@reportportal/ui-kit'; +import Parser from 'html-react-parser'; +import filterIcon from 'common/img/newIcons/filters-outline-inline.svg'; +import filterFilledIcon from 'common/img/newIcons/filter-filled-inline.svg'; +import { FilterContent } from './filterContent'; +import styles from './filterButton.scss'; + +const cx = classNames.bind(styles); + +export const FilterButton = ({ + definedFilters = {}, + onFilterChange, + appliedFiltersCount, + setAppliedFiltersCount, + defaultFilters, + filteredAction, +}) => { + const [isOpen, setIsOpen] = useState(false); + const dispatch = useDispatch(); + const [filters, setFilters] = useState(defaultFilters); + const [initialFilters, setInitialFilters] = useState(defaultFilters); + + useEffect(() => { + dispatch(filteredAction()); + }, []); + + useEffect(() => { + if (Object.keys(definedFilters).length) { + let definedAppliedFiltersCount = 0; + const updatedFilters = { ...filters }; + Object.keys(definedFilters).forEach((filterKey) => { + updatedFilters[filterKey] = { + ...updatedFilters[filterKey], + condition: definedFilters[filterKey].condition, + value: definedFilters[filterKey].value, + }; + definedAppliedFiltersCount += definedFilters[filterKey].value ? 1 : 0; + }); + setFilters(updatedFilters); + setInitialFilters(updatedFilters); + setAppliedFiltersCount(definedAppliedFiltersCount); + } else { + setFilters(defaultFilters); + setInitialFilters(defaultFilters); + setAppliedFiltersCount(0); + } + }, [definedFilters]); + + return ( + + } + placement="bottom-end" + className={cx('filter-popover')} + isOpened={isOpen} + setIsOpened={setIsOpen} + > +
+ + {appliedFiltersCount ? Parser(filterFilledIcon) : Parser(filterIcon)} + + {appliedFiltersCount ? ( + {appliedFiltersCount} + ) : null} +
+
+ ); +}; + +FilterButton.propTypes = { + definedFilters: PropTypes.objectOf( + PropTypes.shape({ + filter_key: PropTypes.string, + value: PropTypes.string, + condition: PropTypes.string, + }), + ), + onFilterChange: PropTypes.func, + appliedFiltersCount: PropTypes.number, + setAppliedFiltersCount: PropTypes.func, + defaultFilters: PropTypes.object, + filteredAction: PropTypes.func.isRequired, +}; diff --git a/app/src/components/buttons/filterButton/filterButton.scss b/app/src/components/buttons/filterButton/filterButton.scss new file mode 100644 index 0000000000..84f9baa600 --- /dev/null +++ b/app/src/components/buttons/filterButton/filterButton.scss @@ -0,0 +1,128 @@ +/* + * 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. + */ + +.filters-icon-container { + height: 36px; + width: 40px; + border-radius: 20px; + display: flex; + align-items: center; + justify-content: center; + + i.filter-icon { + display: block; + + svg { + width: 16px; + height: 16px; + + path { + fill: $COLOR--e-300; + } + } + } + + &:focus-visible { + outline: 2px solid $COLOR--topaz; + } + + &:hover { + i.filter-icon { + svg { + path { + fill: $COLOR--e-400; + } + } + } + } + + &.opened { + i.filter-icon { + svg { + path { + fill: $COLOR--topaz-pressed; + } + } + } + + &:hover { + i.filter-icon { + svg { + path { + fill: $COLOR--topaz-hover-2; + } + } + } + } + } + + &.with-applied { + gap: 8px; + padding: 0 12px; + background-color: $COLOR--bg-200; + + i.filter-icon { + svg { + path { + fill: $COLOR--topaz-2; + stroke: $COLOR--topaz-2; + } + } + } + + .filters-count { + font-size: 13px; + line-height: 20px; + color: $COLOR--topaz-2; + font-family: $FONT-ROBOTO-MEDIUM; + } + + &:hover { + i.filter-icon { + svg { + path { + fill: $COLOR--topaz-hover-2; + stroke: $COLOR--topaz-hover-2; + } + } + } + + .filters-count { + color: $COLOR--topaz-hover-2; + } + } + + &.opened { + i.filter-icon { + svg { + path { + fill: $COLOR--topaz-pressed; + stroke: $COLOR--topaz-pressed; + } + } + } + + .filters-count { + color: $COLOR--topaz-pressed; + } + } + } +} + +.filter-popover { + max-width: 480px; + width: 480px; +} diff --git a/app/src/components/buttons/filterButton/filterContent/filterContent.jsx b/app/src/components/buttons/filterButton/filterContent/filterContent.jsx new file mode 100644 index 0000000000..87bafb0576 --- /dev/null +++ b/app/src/components/buttons/filterButton/filterContent/filterContent.jsx @@ -0,0 +1,111 @@ +/* + * 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 { useIntl } from 'react-intl'; +import { useDispatch } from 'react-redux'; +import PropTypes from 'prop-types'; +import isEqual from 'fast-deep-equal'; +import { COMMON_LOCALE_KEYS } from 'common/constants/localization'; +import { FilterInput } from './filterInput'; +import { messages } from './messages'; +import styles from './filterContent.scss'; + +const cx = classNames.bind(styles); + +export const FilterContent = ({ + setIsOpen, + setAppliedFiltersCount, + onFilterChange, + defaultFilters, + filters, + setFilters, + initialFilters, + filteredAction, +}) => { + const { formatMessage } = useIntl(); + const dispatch = useDispatch(); + + const closePopover = () => { + setIsOpen(false); + setFilters(initialFilters); + }; + + const clearAllFilters = () => { + setFilters(defaultFilters); + }; + + const handleApply = () => { + let appliedFiltersCount = 0; + + const fields = Object.values(filters).reduce((acc, { filterName, value, condition }) => { + acc[filterName] = { + value, + condition, + }; + appliedFiltersCount += value ? 1 : 0; + + return acc; + }, {}); + onFilterChange(fields); + setAppliedFiltersCount(appliedFiltersCount); + dispatch(filteredAction()); + setIsOpen(false); + }; + + const isDefaultFilters = isEqual(defaultFilters, filters); + const isDefinedFilters = isEqual(initialFilters, filters); + + return ( +
+
+ {Object.values(filters).map((filter) => ( + + ))} +
+
+ +
+ + +
+
+
+ ); +}; + +FilterContent.propTypes = { + setIsOpen: PropTypes.func.isRequired, + setAppliedFiltersCount: PropTypes.func.isRequired, + onFilterChange: PropTypes.func.isRequired, + defaultFilters: PropTypes.object.isRequired, + initialFilters: PropTypes.array.isRequired, + filters: PropTypes.array.isRequired, + setFilters: PropTypes.func.isRequired, + filteredAction: PropTypes.func.isRequired, +}; diff --git a/app/src/components/buttons/filterButton/filterContent/filterContent.scss b/app/src/components/buttons/filterButton/filterContent/filterContent.scss new file mode 100644 index 0000000000..909fba1f48 --- /dev/null +++ b/app/src/components/buttons/filterButton/filterContent/filterContent.scss @@ -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. + */ + +.filter-popover-content { + display: flex; + flex-direction: column; + gap: 32px; +} + +.filter-items { + display: flex; + flex-direction: column; + gap: 16px; +} + +.actions { + display: flex; + justify-content: space-between; + + .controls { + display: flex; + gap: 8px; + } +} diff --git a/app/src/components/buttons/filterButton/filterContent/filterInput/filterInput.jsx b/app/src/components/buttons/filterButton/filterContent/filterInput/filterInput.jsx new file mode 100644 index 0000000000..c506f50cd5 --- /dev/null +++ b/app/src/components/buttons/filterButton/filterContent/filterInput/filterInput.jsx @@ -0,0 +1,75 @@ +/* + * 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 { Dropdown, FieldText } from '@reportportal/ui-kit'; +import PropTypes from 'prop-types'; +import styles from './filterInput.scss'; + +const cx = classNames.bind(styles); + +export const FilterInput = ({ filter, setFilters }) => { + const { filterName, options, value, condition, placeholder, title, withField } = filter; + + return ( +
+ {title} +
+ { + setFilters((prevFilters) => ({ + ...prevFilters, + [filterName]: { + ...filter, + ...(withField ? { condition: newValue } : { value: newValue }), + }, + })); + }} + isListWidthLimited + className={cx({ dropdown: withField })} + placeholder={placeholder} + /> + {withField && ( + { + setFilters((prevFilters) => ({ + ...prevFilters, + [filterName]: { ...filter, value: target.value }, + })); + }} + onClear={() => { + setFilters((prevFilters) => ({ + ...prevFilters, + [filterName]: { ...filter, value: '' }, + })); + }} + clearable + /> + )} +
+
+ ); +}; + +FilterInput.propTypes = { + filter: PropTypes.object, + setFilters: PropTypes.func, +}; diff --git a/app/src/components/buttons/filterButton/filterContent/filterInput/filterInput.scss b/app/src/components/buttons/filterButton/filterContent/filterInput/filterInput.scss new file mode 100644 index 0000000000..19c1268ccc --- /dev/null +++ b/app/src/components/buttons/filterButton/filterContent/filterInput/filterInput.scss @@ -0,0 +1,41 @@ +/* + * 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. + */ + +.filter-item { + display: flex; + flex-direction: column; + gap: 4px; + + .label { + font-size: 13px; + line-height: 20px; + color: $COLOR--almost-black; + font-family: $FONT-ROBOTO-MEDIUM; + } + + .container { + display: flex; + gap: 8px; + + .dropdown { + width: 156px; + } + + .input-field { + flex: 1; + } + } +} diff --git a/app/src/components/buttons/filterButton/filterContent/filterInput/index.js b/app/src/components/buttons/filterButton/filterContent/filterInput/index.js new file mode 100644 index 0000000000..92bef2de3a --- /dev/null +++ b/app/src/components/buttons/filterButton/filterContent/filterInput/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 { FilterInput } from './filterInput'; diff --git a/app/src/components/buttons/filterButton/filterContent/index.js b/app/src/components/buttons/filterButton/filterContent/index.js new file mode 100644 index 0000000000..16b3e405f8 --- /dev/null +++ b/app/src/components/buttons/filterButton/filterContent/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 { FilterContent } from './filterContent'; diff --git a/app/src/components/buttons/filterButton/filterContent/messages.js b/app/src/components/buttons/filterButton/filterContent/messages.js new file mode 100644 index 0000000000..d798aa5f93 --- /dev/null +++ b/app/src/components/buttons/filterButton/filterContent/messages.js @@ -0,0 +1,24 @@ +/* + * 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({ + clearAllFilters: { + id: 'ProjectsFilterPopover.clearAllFilters', + defaultMessage: 'Clear all filters', + }, +}); diff --git a/app/src/components/buttons/filterButton/index.jsx b/app/src/components/buttons/filterButton/index.jsx new file mode 100644 index 0000000000..47eaeee42e --- /dev/null +++ b/app/src/components/buttons/filterButton/index.jsx @@ -0,0 +1,26 @@ +/* + * 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 { FilterButton } from './filterButton'; +export { + LAST_RUN_DATE_FILTER_NAME, + LAUNCHES_FILTER_NAME, + TEAMMATES_FILTER_NAME, + FILTER_NAME, + getContainmentComparisons, + getRangeComparisons, + getTimeRange, +} from './constants'; diff --git a/app/src/components/buttons/filterButton/messages.js b/app/src/components/buttons/filterButton/messages.js new file mode 100644 index 0000000000..1bc497131a --- /dev/null +++ b/app/src/components/buttons/filterButton/messages.js @@ -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 { defineMessages } from 'react-intl'; + +export const messages = defineMessages({ + any: { + id: 'FilterButton.any', + defaultMessage: 'Any', + }, + today: { + id: 'FilterButton.today', + defaultMessage: 'Today', + }, + last2days: { + id: 'FilterButton.last2days', + defaultMessage: 'Last 2 days', + }, + last7days: { + id: 'FilterButton.last7days', + defaultMessage: 'Last 7 days', + }, + last30days: { + id: 'FilterButton.last30days', + defaultMessage: 'Last 30 days', + }, + equals: { + id: 'FilterButton.equals', + defaultMessage: 'Equals', + }, + greaterOrEqual: { + id: 'FilterButton.greaterOrEqual', + defaultMessage: 'Greater or equal', + }, + lessOrEqual: { + id: 'FilterButton.lessOrEqual', + defaultMessage: 'Less or equal', + }, + notEqual: { + id: 'FilterButton.notEqual', + defaultMessage: 'Not equal', + }, + contains: { + id: 'FilterButton.contains', + defaultMessage: 'Contains', + }, + notContains: { + id: 'FilterButton.notContains', + defaultMessage: 'Not contains', + }, +}); diff --git a/app/src/components/filterEntities/containers/filterEntitiesURLContainer.jsx b/app/src/components/filterEntities/containers/filterEntitiesURLContainer.jsx index bf8838a32f..cb6a34a1b5 100644 --- a/app/src/components/filterEntities/containers/filterEntitiesURLContainer.jsx +++ b/app/src/components/filterEntities/containers/filterEntitiesURLContainer.jsx @@ -24,7 +24,6 @@ import { collectFilterEntities, createFilterQuery } from './utils'; const FilterEntitiesURL = ({ entities = {}, updateFilters = () => {}, - render, debounced = true, debounceTime = 1000, defaultPagination, @@ -37,6 +36,7 @@ const FilterEntitiesURL = ({ } const filterQuery = createFilterQuery(newEntities, entities, prefixQueryKey); if (!isEmptyObject(filterQuery)) { + console.log('filterQuery', filterQuery); updateFilters(filterQuery, defaultPagination[PAGE_KEY]); } }, @@ -48,22 +48,78 @@ const FilterEntitiesURL = ({ debounceTime, ]); - return render({ + return { entities, onChange: debounced ? debouncedHandleChange : handleChange, - }); + }; }; FilterEntitiesURL.propTypes = { entities: PropTypes.object, updateFilters: PropTypes.func, - render: PropTypes.func.isRequired, debounced: PropTypes.bool, debounceTime: PropTypes.number, defaultPagination: PropTypes.any.isRequired, prefixQueryKey: PropTypes.string, }; +const filterEntitiesURLForContainer = ({ + entities = {}, + updateFilters = () => {}, + debounced = true, + debounceTime = 1000, + defaultPagination, + prefixQueryKey, + render, +}) => { + const filterEntitiesURLParams = FilterEntitiesURL({ + entities, + updateFilters, + debounced, + debounceTime, + defaultPagination, + prefixQueryKey, + }); + return render(filterEntitiesURLParams); +}; + +export const withFilterEntitiesURL = (namespace, prefixQueryKey) => (WrappedComponent) => { + const filterEntitiesURL = (props) => { + const { + entities, + defaultPagination, + updateFilters, + debounced, + debounceTime, + ...restProps + } = props; + + const { entities: filteredEntities, onChange } = FilterEntitiesURL({ + entities, + updateFilters, + debounced, + debounceTime, + defaultPagination, + prefixQueryKey, + }); + + return ( + + ); + }; + + return connectRouter( + (query) => ({ + entities: collectFilterEntities(query, prefixQueryKey), + defaultPagination: defaultPaginationSelector(), + }), + { + updateFilters: (query, page) => ({ ...query, [PAGE_KEY]: page }), + }, + { namespace }, + )(filterEntitiesURL); +}; + const createFilterEntitiesURLContainer = (prefixQueryKey) => connectRouter( (query) => ({ @@ -73,6 +129,6 @@ const createFilterEntitiesURLContainer = (prefixQueryKey) => { updateFilters: (query, page) => ({ ...query, [PAGE_KEY]: page }), }, - )(FilterEntitiesURL); + )(filterEntitiesURLForContainer); export const FilterEntitiesURLContainer = createFilterEntitiesURLContainer(); diff --git a/app/src/components/filterEntities/containers/index.js b/app/src/components/filterEntities/containers/index.js index c887364d38..3894dad8c7 100644 --- a/app/src/components/filterEntities/containers/index.js +++ b/app/src/components/filterEntities/containers/index.js @@ -15,4 +15,4 @@ */ export { FilterEntitiesContainer } from './filterEntitiesContainer'; -export { FilterEntitiesURLContainer } from './filterEntitiesURLContainer'; +export { FilterEntitiesURLContainer, withFilterEntitiesURL } from './filterEntitiesURLContainer'; diff --git a/app/src/controllers/instance/events/utils.js b/app/src/controllers/instance/events/utils.js index 5e3825cb94..2d40807b47 100644 --- a/app/src/controllers/instance/events/utils.js +++ b/app/src/controllers/instance/events/utils.js @@ -57,5 +57,7 @@ export const getAppliedFilters = (filters, projectKey) => { }; }); - return { search_criterias: [...appliedFilters, projectIdFilterParam] }; + return { + search_criterias: [...appliedFilters, ...(projectKey ? [projectIdFilterParam] : [])], + }; }; diff --git a/app/src/controllers/instance/organizations/actionCreators.js b/app/src/controllers/instance/organizations/actionCreators.js index 46a7ea66f6..01501d14f0 100644 --- a/app/src/controllers/instance/organizations/actionCreators.js +++ b/app/src/controllers/instance/organizations/actionCreators.js @@ -14,8 +14,12 @@ * limitations under the License. */ -import { FETCH_ORGANIZATIONS } from './constants'; +import { FETCH_ORGANIZATIONS, FILTERED_ORGANIZATIONS } from './constants'; export const fetchOrganizationsAction = () => ({ type: FETCH_ORGANIZATIONS, }); + +export const fetchFilteredOrganizationsAction = () => ({ + type: FILTERED_ORGANIZATIONS, +}); diff --git a/app/src/controllers/instance/organizations/constants.js b/app/src/controllers/instance/organizations/constants.js index 1e93837fdd..b94fa0ace9 100644 --- a/app/src/controllers/instance/organizations/constants.js +++ b/app/src/controllers/instance/organizations/constants.js @@ -19,6 +19,7 @@ import { PAGE_KEY, SIZE_KEY } from 'controllers/pagination'; export const NAMESPACE = 'organizations'; export const FETCH_ORGANIZATIONS = 'fetchOrganizations'; +export const FILTERED_ORGANIZATIONS = 'filteredOrganizations'; export const DEFAULT_PAGE_SIZE_OPTIONS = [10, 20, 50, 100]; export const DEFAULT_LIMITATION = 20; export const initialPaginationState = { diff --git a/app/src/controllers/instance/organizations/index.js b/app/src/controllers/instance/organizations/index.js index b89052a05d..5b645458b9 100644 --- a/app/src/controllers/instance/organizations/index.js +++ b/app/src/controllers/instance/organizations/index.js @@ -14,8 +14,8 @@ * limitations under the License. */ -export { FETCH_ORGANIZATIONS } from './constants'; -export { fetchOrganizationsAction } from './actionCreators'; +export { FETCH_ORGANIZATIONS, FILTERED_ORGANIZATIONS } from './constants'; +export { fetchOrganizationsAction, fetchFilteredOrganizationsAction } from './actionCreators'; export { organizationsReducer } from './reducer'; export { organizationsSelector, diff --git a/app/src/controllers/instance/organizations/sagas.js b/app/src/controllers/instance/organizations/sagas.js index 398cd30330..64414b40a5 100644 --- a/app/src/controllers/instance/organizations/sagas.js +++ b/app/src/controllers/instance/organizations/sagas.js @@ -18,8 +18,8 @@ import { takeEvery, all, put, select } from 'redux-saga/effects'; import { URLS } from 'common/urls'; import { showDefaultErrorNotification } from 'controllers/notification'; import { fetchDataAction } from 'controllers/fetch'; -import { querySelector } from './selectors'; -import { FETCH_ORGANIZATIONS, NAMESPACE } from './constants'; +import { filterQuerySelector, querySelector } from './selectors'; +import { FETCH_ORGANIZATIONS, FILTERED_ORGANIZATIONS, NAMESPACE } from './constants'; function* fetchOrganizations() { try { @@ -35,6 +35,21 @@ function* watchFetchOrganizations() { yield takeEvery(FETCH_ORGANIZATIONS, fetchOrganizations); } +function* fetchFilteredOrganizations() { + const filtersParams = yield select(filterQuerySelector); + + yield put( + fetchDataAction(NAMESPACE)(URLS.organizationSearch(), { + method: 'post', + data: filtersParams, + }), + ); +} + +function* watchFetchFilteredProjects() { + yield takeEvery(FILTERED_ORGANIZATIONS, fetchFilteredOrganizations); +} + export function* organizationsSagas() { - yield all([watchFetchOrganizations()]); + yield all([watchFetchOrganizations(), watchFetchFilteredProjects()]); } diff --git a/app/src/controllers/instance/organizations/selectors.js b/app/src/controllers/instance/organizations/selectors.js index 13c96e87e3..67178e429c 100644 --- a/app/src/controllers/instance/organizations/selectors.js +++ b/app/src/controllers/instance/organizations/selectors.js @@ -14,9 +14,12 @@ * limitations under the License. */ -import { createAlternativeQueryParametersSelector } from 'controllers/pages/selectors'; +import { + createAlternativeQueryParametersSelector, + createFilterQuerySelector, +} from 'controllers/pages/selectors'; import { SORTING_ASC } from 'controllers/sorting'; -import { NAMESPACE, DEFAULT_PAGINATION, SORTING_KEY } from './constants'; +import { NAMESPACE, DEFAULT_PAGINATION, SORTING_KEY, FILTERED_ORGANIZATIONS } from './constants'; export const organizationsSelector = (state) => state.organizations || {}; @@ -33,3 +36,10 @@ export const querySelector = createAlternativeQueryParametersSelector({ sortingKey: SORTING_KEY, namespace: NAMESPACE, }); + +export const filterQuerySelector = createFilterQuerySelector({ + defaultPagination: DEFAULT_PAGINATION, + defaultSorting: SORTING_ASC, + sortingKey: SORTING_KEY, + namespace: FILTERED_ORGANIZATIONS, +}); diff --git a/app/src/controllers/organization/projects/actionCreators.js b/app/src/controllers/organization/projects/actionCreators.js index d6117f8c2a..bd0622bdec 100644 --- a/app/src/controllers/organization/projects/actionCreators.js +++ b/app/src/controllers/organization/projects/actionCreators.js @@ -14,7 +14,12 @@ * limitations under the License. */ -import { CREATE_PROJECT, DELETE_PROJECT, FETCH_ORGANIZATION_PROJECTS } from './constants'; +import { + CREATE_PROJECT, + DELETE_PROJECT, + FETCH_ORGANIZATION_PROJECTS, + FILTERED_PROJECTS, +} from './constants'; export const fetchOrganizationProjectsAction = (params) => { return { @@ -32,3 +37,7 @@ export const deleteProjectAction = (project) => ({ type: DELETE_PROJECT, payload: project, }); + +export const fetchFilteredProjectAction = () => ({ + type: FILTERED_PROJECTS, +}); diff --git a/app/src/controllers/organization/projects/constants.js b/app/src/controllers/organization/projects/constants.js index c9f1abcb85..10e2f8f5a4 100644 --- a/app/src/controllers/organization/projects/constants.js +++ b/app/src/controllers/organization/projects/constants.js @@ -18,6 +18,7 @@ import { PAGE_KEY, SIZE_KEY } from 'controllers/pagination'; import { formatSortingString, SORTING_ASC } from 'controllers/sorting'; export const FETCH_ORGANIZATION_PROJECTS = 'fetchOrganizationProjects'; +export const FILTERED_PROJECTS = 'filteredProjects'; export const NAMESPACE = 'organizationProjects'; export const CREATE_PROJECT = 'createProject'; export const DELETE_PROJECT = 'deleteProject'; diff --git a/app/src/controllers/organization/projects/index.js b/app/src/controllers/organization/projects/index.js index 1c5865f409..1a2addab43 100644 --- a/app/src/controllers/organization/projects/index.js +++ b/app/src/controllers/organization/projects/index.js @@ -14,9 +14,14 @@ * limitations under the License. */ -export { fetchOrganizationProjectsAction } from './actionCreators'; +export { fetchOrganizationProjectsAction, fetchFilteredProjectAction } from './actionCreators'; export { projectsReducer } from './reducer'; -export { projectsPaginationSelector, projectsSelector, loadingSelector } from './selectors'; +export { + projectsPaginationSelector, + projectsSelector, + loadingSelector, + filterQuerySelector, +} from './selectors'; export { projectsSagas } from './sagas'; export { DEFAULT_LIMITATION, @@ -26,4 +31,5 @@ export { DEFAULT_QUERY_PARAMS, FETCH_ORGANIZATION_PROJECTS, SORTING_KEY, + FILTERED_PROJECTS, } from './constants'; diff --git a/app/src/controllers/organization/projects/sagas.js b/app/src/controllers/organization/projects/sagas.js index e06d9044fe..8faf97bff2 100644 --- a/app/src/controllers/organization/projects/sagas.js +++ b/app/src/controllers/organization/projects/sagas.js @@ -20,17 +20,18 @@ import { URLS } from 'common/urls'; import { fetch } from 'common/utils'; import { hideModalAction } from 'controllers/modal'; import { NOTIFICATION_TYPES, showNotification } from 'controllers/notification'; -import { fetchOrganizationBySlugAction } from '..'; -import { querySelector } from './selectors'; -import { activeOrganizationSelector } from '../selectors'; -import { fetchOrganizationProjectsAction } from './actionCreators'; import { CREATE_PROJECT, FETCH_ORGANIZATION_PROJECTS, ERROR_CODES, NAMESPACE, DELETE_PROJECT, + FILTERED_PROJECTS, } from './constants'; +import { fetchOrganizationBySlugAction } from '..'; +import { filterQuerySelector, querySelector } from './selectors'; +import { activeOrganizationIdSelector, activeOrganizationSelector } from '../selectors'; +import { fetchOrganizationProjectsAction } from './actionCreators'; function* fetchOrganizationProjects({ payload: organizationId }) { const query = yield select(querySelector); @@ -116,6 +117,28 @@ function* deleteProject({ payload: { projectId, projectName } }) { function* watchDeleteProject() { yield takeEvery(DELETE_PROJECT, deleteProject); } + +function* fetchFilteredProjects() { + const activeOrganizationId = yield select(activeOrganizationIdSelector); + const filtersParams = yield select(filterQuerySelector); + + yield put( + fetchDataAction(NAMESPACE)(URLS.filterOrganizationProjects(activeOrganizationId), { + method: 'post', + data: filtersParams, + }), + ); +} + +function* watchFetchFilteredProjects() { + yield takeEvery(FILTERED_PROJECTS, fetchFilteredProjects); +} + export function* projectsSagas() { - yield all([watchFetchProjects(), watchCreateProject(), watchDeleteProject()]); + yield all([ + watchFetchProjects(), + watchCreateProject(), + watchDeleteProject(), + watchFetchFilteredProjects(), + ]); } diff --git a/app/src/controllers/organization/projects/selectors.js b/app/src/controllers/organization/projects/selectors.js index f88be72964..94f1b3e635 100644 --- a/app/src/controllers/organization/projects/selectors.js +++ b/app/src/controllers/organization/projects/selectors.js @@ -14,10 +14,13 @@ * limitations under the License. */ -import { createAlternativeQueryParametersSelector } from 'controllers/pages/selectors'; +import { + createAlternativeQueryParametersSelector, + createFilterQuerySelector, +} from 'controllers/pages/selectors'; import { SORTING_ASC } from 'controllers/sorting'; -import { DEFAULT_PAGINATION, NAMESPACE, SORTING_KEY } from './constants'; import { organizationSelector } from '../selectors'; +import { DEFAULT_PAGINATION, FILTERED_PROJECTS, NAMESPACE, SORTING_KEY } from './constants'; const domainSelector = (state) => organizationSelector(state).projects || {}; @@ -31,3 +34,10 @@ export const querySelector = createAlternativeQueryParametersSelector({ sortingKey: SORTING_KEY, namespace: NAMESPACE, }); + +export const filterQuerySelector = createFilterQuerySelector({ + defaultPagination: DEFAULT_PAGINATION, + defaultSorting: SORTING_ASC, + sortingKey: SORTING_KEY, + namespace: FILTERED_PROJECTS, +}); diff --git a/app/src/controllers/pages/selectors.js b/app/src/controllers/pages/selectors.js index efd7de301e..bef6de15cb 100644 --- a/app/src/controllers/pages/selectors.js +++ b/app/src/controllers/pages/selectors.js @@ -17,7 +17,7 @@ import { createSelector } from 'reselect'; import { extractNamespacedQuery } from 'common/utils/routingUtils'; import { DEFAULT_PAGINATION, SIZE_KEY, PAGE_KEY } from 'controllers/pagination/constants'; -import { SORTING_KEY } from 'controllers/sorting/constants'; +import { SORTING_KEY, SORTING_ORDER_KEY } from 'controllers/sorting/constants'; import { getStorageItem } from 'common/utils/storageUtils'; import { activeProjectSelector, @@ -31,6 +31,7 @@ import { ADMINISTRATOR } from 'common/constants/accountRoles'; import { MANAGER } from 'common/constants/projectRoles'; import { getAlternativePaginationAndSortParams } from 'controllers/pagination'; import { findAssignedProjectByOrganization } from 'common/utils'; +import { getAppliedFilters } from 'controllers/instance/events/utils'; import { pageNames, NO_PAGE } from './constants'; import { stringToArray } from './utils'; @@ -148,11 +149,32 @@ export const createAlternativeQueryParametersSelector = ({ sortingKey, namespace, }), - ({ [SIZE_KEY]: limit, [SORTING_KEY]: sort, [PAGE_KEY]: pageNumber, ...rest }) => { + ({ [SIZE_KEY]: limit, [SORTING_ORDER_KEY]: sort, [PAGE_KEY]: pageNumber, ...rest }) => { return { ...getAlternativePaginationAndSortParams(sort, limit, pageNumber), ...rest }; }, ); +export const createFilterQuerySelector = ({ + defaultPagination, + defaultSorting, + sortingKey, + namespace, +} = {}) => + createSelector( + createQueryParametersSelector({ + defaultPagination, + defaultSorting, + sortingKey, + namespace, + }), + ({ [SIZE_KEY]: limit, [SORTING_ORDER_KEY]: sort, [PAGE_KEY]: pageNumber, ...rest }) => { + return { + ...getAlternativePaginationAndSortParams(sort, limit, pageNumber), + search_criteria: getAppliedFilters(rest)?.search_criterias, + }; + }, + ); + export const launchIdSelector = (state) => { const testItemIds = testItemIdsArraySelector(state); return testItemIds?.[0]; diff --git a/app/src/controllers/sorting/constants.js b/app/src/controllers/sorting/constants.js index 985b404dc6..ebd451eb17 100644 --- a/app/src/controllers/sorting/constants.js +++ b/app/src/controllers/sorting/constants.js @@ -17,3 +17,4 @@ export const SORTING_ASC = 'ASC'; export const SORTING_DESC = 'DESC'; export const SORTING_KEY = 'page.sort'; +export const SORTING_ORDER_KEY = 'order'; diff --git a/app/src/pages/inside/projectSettingsPageContainer/content/notifications/modals/addEditNotificationModal/addEditNotificationModal.jsx b/app/src/pages/inside/projectSettingsPageContainer/content/notifications/modals/addEditNotificationModal/addEditNotificationModal.jsx index 7b5473d9f4..505c99a860 100644 --- a/app/src/pages/inside/projectSettingsPageContainer/content/notifications/modals/addEditNotificationModal/addEditNotificationModal.jsx +++ b/app/src/pages/inside/projectSettingsPageContainer/content/notifications/modals/addEditNotificationModal/addEditNotificationModal.jsx @@ -37,7 +37,7 @@ import { EMAIL } from 'common/constants/pluginNames'; import { FieldTextFlex } from 'componentLibrary/fieldTextFlex'; import { ruleField } from 'pages/inside/projectSettingsPageContainer/content/notifications/propTypes'; import { fetchProjectAction } from 'controllers/project/actionCreators'; -import { projectIdSelector } from 'controllers/pages'; +import { projectKeySelector } from 'controllers/project'; import { capitalizeWord } from '../util'; import { RecipientsContainer } from './recipientsContainer'; import { LaunchNamesContainer } from './launchNamesContainer'; @@ -215,14 +215,14 @@ const AddEditNotificationModal = ({ }) => { const { formatMessage } = useIntl(); const dispatch = useDispatch(); - const projectId = useSelector(projectIdSelector); + const projectKey = useSelector(projectKeySelector); const [isEditorShown, setShowEditor] = React.useState(data.notification.attributes.length > 0); const attributesValue = useSelector((state) => attributesValueSelector(state, ATTRIBUTES_FIELD_KEY)) ?? []; useEffect(() => { initialize(data.notification); - dispatch(fetchProjectAction(projectId, false)); + dispatch(fetchProjectAction(projectKey, false)); }, []); const caseOptions = [ diff --git a/app/src/pages/instance/organizationsPage/organizationsPage.jsx b/app/src/pages/instance/organizationsPage/organizationsPage.jsx index 57f0568501..394ce1c0db 100644 --- a/app/src/pages/instance/organizationsPage/organizationsPage.jsx +++ b/app/src/pages/instance/organizationsPage/organizationsPage.jsx @@ -49,6 +49,7 @@ export const OrganizationsPage = () => { const isOrganizationsLoading = useSelector(organizationsListLoadingSelector); const userId = useSelector(userIdSelector); const [searchValue, setSearchValue] = useState(null); + const [appliedFiltersCount, setAppliedFiltersCount] = useState(0); const isEmptyOrganizations = !isOrganizationsLoading && organizationsList.length === 0; const [isOpenTableView, setIsOpenTableView] = useState( getStorageItem(`${userId}_settings`)?.organizationsPanel === TABLE_VIEW, @@ -73,7 +74,7 @@ export const OrganizationsPage = () => { ); } - return searchValue === null ? ( + return searchValue === null && appliedFiltersCount === 0 ? ( { openPanelView={openPanelView} openTableView={openTableView} isOpenTableView={isOpenTableView} + appliedFiltersCount={appliedFiltersCount} + setAppliedFiltersCount={setAppliedFiltersCount} /> {isEmptyOrganizations ? ( getEmptyPageState() diff --git a/app/src/pages/instance/organizationsPage/organizationsPageHeader/organizationsFilter/index.js b/app/src/pages/instance/organizationsPage/organizationsPageHeader/organizationsFilter/index.js new file mode 100644 index 0000000000..36998207a6 --- /dev/null +++ b/app/src/pages/instance/organizationsPage/organizationsPageHeader/organizationsFilter/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 { OrganizationsFilter } from './organizationsFilter'; diff --git a/app/src/pages/instance/organizationsPage/organizationsPageHeader/organizationsFilter/messages.js b/app/src/pages/instance/organizationsPage/organizationsPageHeader/organizationsFilter/messages.js new file mode 100644 index 0000000000..43a51fc83e --- /dev/null +++ b/app/src/pages/instance/organizationsPage/organizationsPageHeader/organizationsFilter/messages.js @@ -0,0 +1,52 @@ +/* + * 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({ + lastRunDate: { + id: 'OrganizationsFilter.lastRunDate', + defaultMessage: 'Last Run Date', + }, + lastRunDatePlaceholder: { + id: 'OrganizationsFilter.lastRunDatePlaceholder', + defaultMessage: 'Any', + }, + launches: { + id: 'OrganizationsFilter.launches', + defaultMessage: 'Launches', + }, + launchesPlaceholder: { + id: 'OrganizationsFilter.launchesPlaceholder', + defaultMessage: 'Enter the number of launches', + }, + users: { + id: 'OrganizationsFilter.users', + defaultMessage: 'Users', + }, + usersPlaceholder: { + id: 'OrganizationsFilter.usersPlaceholder', + defaultMessage: 'Enter the number of members', + }, + name: { + id: 'OrganizationsFilter.name', + defaultMessage: 'Organization name', + }, + namePlaceholder: { + id: 'OrganizationsFilter.namePlaceholder', + defaultMessage: 'Enter part of the name', + }, +}); diff --git a/app/src/pages/instance/organizationsPage/organizationsPageHeader/organizationsFilter/organizationsFilter.jsx b/app/src/pages/instance/organizationsPage/organizationsPageHeader/organizationsFilter/organizationsFilter.jsx new file mode 100644 index 0000000000..d4f24f8bb4 --- /dev/null +++ b/app/src/pages/instance/organizationsPage/organizationsPageHeader/organizationsFilter/organizationsFilter.jsx @@ -0,0 +1,105 @@ +/* + * 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 PropTypes from 'prop-types'; +import { + LAUNCHES_FILTER_NAME, + FILTER_NAME, + TEAMMATES_FILTER_NAME, + LAST_RUN_DATE_FILTER_NAME, + getContainmentComparisons, + getRangeComparisons, + getTimeRange, +} from 'components/buttons/filterButton'; +import { FilterButton } from 'components/buttons/filterButton/filterButton'; +import { fetchFilteredOrganizationsAction } from 'controllers/instance/organizations'; +import { messages } from './messages'; + +export const OrganizationsFilter = ({ + entities, + onFilterChange, + appliedFiltersCount, + setAppliedFiltersCount, +}) => { + const { formatMessage } = useIntl(); + + const timeRange = getTimeRange(formatMessage); + const rangeComparisons = getRangeComparisons(formatMessage); + const containmentComparisons = getContainmentComparisons(formatMessage); + + const filters = { + [LAST_RUN_DATE_FILTER_NAME]: { + filterName: LAST_RUN_DATE_FILTER_NAME, + value: timeRange[0].value, + title: formatMessage(messages.lastRunDate), + options: timeRange, + placeholder: formatMessage(messages.lastRunDatePlaceholder), + }, + [LAUNCHES_FILTER_NAME]: { + filterName: LAUNCHES_FILTER_NAME, + value: '', + title: formatMessage(messages.launches), + placeholder: formatMessage(messages.launchesPlaceholder), + options: rangeComparisons, + condition: rangeComparisons[0].value, + withField: true, + }, + [TEAMMATES_FILTER_NAME]: { + filterName: TEAMMATES_FILTER_NAME, + value: '', + title: formatMessage(messages.users), + placeholder: formatMessage(messages.usersPlaceholder), + options: rangeComparisons, + condition: rangeComparisons[0].value, + withField: true, + }, + [FILTER_NAME]: { + filterName: FILTER_NAME, + value: '', + title: formatMessage(messages.name), + placeholder: formatMessage(messages.namePlaceholder), + options: containmentComparisons, + condition: containmentComparisons[0].value, + withField: true, + }, + }; + + return ( + + ); +}; + +OrganizationsFilter.propTypes = { + entities: PropTypes.objectOf( + PropTypes.shape({ + filter_key: PropTypes.string, + value: PropTypes.string, + condition: PropTypes.string, + }), + ), + onFilterChange: PropTypes.func, + appliedFiltersCount: PropTypes.number, + setAppliedFiltersCount: PropTypes.func, + defaultFilters: PropTypes.object, +}; diff --git a/app/src/pages/instance/organizationsPage/organizationsPageHeader/organizationsPageHeader.jsx b/app/src/pages/instance/organizationsPage/organizationsPageHeader/organizationsPageHeader.jsx index 9ffd5e96aa..4a36dc96a1 100644 --- a/app/src/pages/instance/organizationsPage/organizationsPageHeader/organizationsPageHeader.jsx +++ b/app/src/pages/instance/organizationsPage/organizationsPageHeader/organizationsPageHeader.jsx @@ -18,18 +18,20 @@ import React from 'react'; import PropTypes from 'prop-types'; import Parser from 'html-react-parser'; import classNames from 'classnames/bind'; +import { BaseIconButton } from '@reportportal/ui-kit'; import filterIcon from 'common/img/newIcons/filters-outline-inline.svg'; import { useIntl } from 'react-intl'; import { SearchField } from 'components/fields/searchField'; import { SEARCH_KEY } from 'controllers/organization/projects/constants'; import { withFilter } from 'controllers/filter'; -import { NAMESPACE } from 'controllers/instance/organizations/constants'; +import { FILTERED_ORGANIZATIONS, NAMESPACE } from 'controllers/instance/organizations/constants'; import { useSelector } from 'react-redux'; import { organizationsListLoadingSelector } from 'controllers/instance/organizations'; import { ORGANIZATION_PAGE_EVENTS } from 'components/main/analytics/events/ga4Events/organizationsPageEvents'; -import { BaseIconButton } from '@reportportal/ui-kit'; +import { withFilterEntitiesURL } from 'components/filterEntities/containers'; import PanelViewIcon from '../img/panel-view-inline.svg'; import TableViewIcon from '../img/table-view-inline.svg'; +import { OrganizationsFilter } from './organizationsFilter'; import { messages } from '../messages'; import styles from './organizationsPageHeader.scss'; @@ -39,6 +41,8 @@ const SearchFieldWithFilter = withFilter({ filterKey: SEARCH_KEY, namespace: NAM SearchField, ); +const FiltersFields = withFilterEntitiesURL(FILTERED_ORGANIZATIONS)(OrganizationsFilter); + export const OrganizationsPageHeader = ({ isEmpty, searchValue, @@ -46,6 +50,8 @@ export const OrganizationsPageHeader = ({ openPanelView, openTableView, isOpenTableView, + appliedFiltersCount, + setAppliedFiltersCount, }) => { const { formatMessage } = useIntl(); const projectsLoading = useSelector(organizationsListLoadingSelector); @@ -65,6 +71,11 @@ export const OrganizationsPageHeader = ({ event={ORGANIZATION_PAGE_EVENTS.SEARCH_ORGANIZATION_FIELD} /> {Parser(filterIcon)} + { const isProjectsEmpty = !projectsLoading && projects.length === 0; const [searchValue, setSearchValue] = useState(null); + const [appliedFiltersCount, setAppliedFiltersCount] = useState(0); const showCreateProjectModal = () => { dispatch( @@ -98,7 +99,8 @@ export const OrganizationProjectsPage = () => { ); } - return searchValue === null ? ( + + return searchValue === null && appliedFiltersCount === 0 ? ( { onCreateProject={showCreateProjectModal} searchValue={searchValue} setSearchValue={setSearchValue} + appliedFiltersCount={appliedFiltersCount} + setAppliedFiltersCount={setAppliedFiltersCount} /> {isProjectsEmpty ? ( getEmptyPageState() diff --git a/app/src/pages/organization/organizationProjectsPage/projectsPageHeader/projectsFilter/index.js b/app/src/pages/organization/organizationProjectsPage/projectsPageHeader/projectsFilter/index.js new file mode 100644 index 0000000000..e788469ec0 --- /dev/null +++ b/app/src/pages/organization/organizationProjectsPage/projectsPageHeader/projectsFilter/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 { ProjectsFilter } from './projectsFilter'; diff --git a/app/src/pages/organization/organizationProjectsPage/projectsPageHeader/projectsFilter/messages.js b/app/src/pages/organization/organizationProjectsPage/projectsPageHeader/projectsFilter/messages.js new file mode 100644 index 0000000000..9911e14050 --- /dev/null +++ b/app/src/pages/organization/organizationProjectsPage/projectsPageHeader/projectsFilter/messages.js @@ -0,0 +1,52 @@ +/* + * 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({ + lastRunDate: { + id: 'ProjectsFilter.lastRunDate', + defaultMessage: 'Last Run Date', + }, + lastRunDatePlaceholder: { + id: 'ProjectsFilter.lastRunDatePlaceholder', + defaultMessage: 'Any', + }, + launches: { + id: 'ProjectsFilter.launches', + defaultMessage: 'Launches', + }, + launchesPlaceholder: { + id: 'ProjectsFilter.launchesPlaceholder', + defaultMessage: 'Enter the number of launches', + }, + users: { + id: 'ProjectsFilter.users', + defaultMessage: 'Teammates', + }, + usersPlaceholder: { + id: 'ProjectsFilter.usersPlaceholder', + defaultMessage: 'Enter the number of members', + }, + name: { + id: 'ProjectsFilter.name', + defaultMessage: 'Project Name', + }, + namePlaceholder: { + id: 'ProjectsFilter.namePlaceholder', + defaultMessage: 'Enter part of the name', + }, +}); diff --git a/app/src/pages/organization/organizationProjectsPage/projectsPageHeader/projectsFilter/projectsFilter.jsx b/app/src/pages/organization/organizationProjectsPage/projectsPageHeader/projectsFilter/projectsFilter.jsx new file mode 100644 index 0000000000..a629a38706 --- /dev/null +++ b/app/src/pages/organization/organizationProjectsPage/projectsPageHeader/projectsFilter/projectsFilter.jsx @@ -0,0 +1,105 @@ +/* + * 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 PropTypes from 'prop-types'; +import { + LAUNCHES_FILTER_NAME, + FILTER_NAME, + TEAMMATES_FILTER_NAME, + LAST_RUN_DATE_FILTER_NAME, + getContainmentComparisons, + getRangeComparisons, + getTimeRange, +} from 'components/buttons/filterButton'; +import { FilterButton } from 'components/buttons/filterButton/filterButton'; +import { fetchFilteredProjectAction } from 'controllers/organization/projects'; +import { messages } from './messages'; + +export const ProjectsFilter = ({ + entities, + onFilterChange, + appliedFiltersCount, + setAppliedFiltersCount, +}) => { + const { formatMessage } = useIntl(); + + const timeRange = getTimeRange(formatMessage); + const rangeComparisons = getRangeComparisons(formatMessage); + const containmentComparisons = getContainmentComparisons(formatMessage); + + const filters = { + [LAST_RUN_DATE_FILTER_NAME]: { + filterName: LAST_RUN_DATE_FILTER_NAME, + value: timeRange[0].value, + title: formatMessage(messages.lastRunDate), + options: timeRange, + placeholder: formatMessage(messages.lastRunDatePlaceholder), + }, + [LAUNCHES_FILTER_NAME]: { + filterName: LAUNCHES_FILTER_NAME, + value: '', + title: formatMessage(messages.launches), + placeholder: formatMessage(messages.launchesPlaceholder), + options: rangeComparisons, + condition: rangeComparisons[0].value, + withField: true, + }, + [TEAMMATES_FILTER_NAME]: { + filterName: TEAMMATES_FILTER_NAME, + value: '', + title: formatMessage(messages.users), + placeholder: formatMessage(messages.usersPlaceholder), + options: rangeComparisons, + condition: rangeComparisons[0].value, + withField: true, + }, + [FILTER_NAME]: { + filterName: FILTER_NAME, + value: '', + title: formatMessage(messages.name), + placeholder: formatMessage(messages.namePlaceholder), + options: containmentComparisons, + condition: containmentComparisons[0].value, + withField: true, + }, + }; + + return ( + + ); +}; + +ProjectsFilter.propTypes = { + entities: PropTypes.objectOf( + PropTypes.shape({ + filter_key: PropTypes.string, + value: PropTypes.string, + condition: PropTypes.string, + }), + ), + onFilterChange: PropTypes.func, + appliedFiltersCount: PropTypes.number, + setAppliedFiltersCount: PropTypes.func, + defaultFilters: PropTypes.object, +}; diff --git a/app/src/pages/organization/organizationProjectsPage/projectsPageHeader/projectsPageHeader.jsx b/app/src/pages/organization/organizationProjectsPage/projectsPageHeader/projectsPageHeader.jsx index ab181c6aff..f17e851613 100644 --- a/app/src/pages/organization/organizationProjectsPage/projectsPageHeader/projectsPageHeader.jsx +++ b/app/src/pages/organization/organizationProjectsPage/projectsPageHeader/projectsPageHeader.jsx @@ -21,17 +21,22 @@ import Parser from 'html-react-parser'; import { Button, PlusIcon } from '@reportportal/ui-kit'; import classNames from 'classnames/bind'; import { ORGANIZATIONS_PAGE } from 'controllers/pages'; -import filterIcon from 'common/img/newIcons/filters-outline-inline.svg'; import { Breadcrumbs } from 'componentLibrary/breadcrumbs'; import { activeOrganizationSelector } from 'controllers/organization'; import { loadingSelector } from 'controllers/organization/projects'; import { SearchField } from 'components/fields/searchField'; -import { SEARCH_KEY, NAMESPACE } from 'controllers/organization/projects/constants'; +import { + SEARCH_KEY, + NAMESPACE, + FILTERED_PROJECTS, +} from 'controllers/organization/projects/constants'; import { withFilter } from 'controllers/filter'; +import { withFilterEntitiesURL } from 'components/filterEntities/containers'; import projectsIcon from './img/projects-inline.svg'; -import styles from './projectsPageHeader.scss'; -import { messages } from '../messages'; import userIcon from './img/user-inline.svg'; +import { ProjectsFilter } from './projectsFilter'; +import { messages } from '../messages'; +import styles from './projectsPageHeader.scss'; const cx = classNames.bind(styles); @@ -39,11 +44,15 @@ const SearchFieldWithFilter = withFilter({ filterKey: SEARCH_KEY, namespace: NAM SearchField, ); +const FiltersFields = withFilterEntitiesURL(FILTERED_PROJECTS)(ProjectsFilter); + export const ProjectsPageHeader = ({ hasPermission, onCreateProject, searchValue, setSearchValue, + appliedFiltersCount, + setAppliedFiltersCount, }) => { const { formatMessage } = useIntl(); const organization = useSelector(activeOrganizationSelector); @@ -95,7 +104,11 @@ export const ProjectsPageHeader = ({ setSearchValue={setSearchValue} placeholder={formatMessage(messages.searchPlaceholder)} /> - {Parser(filterIcon)} + )} {isNotEmpty && hasPermission && ( @@ -114,6 +127,8 @@ ProjectsPageHeader.propTypes = { onCreateProject: PropTypes.func.isRequired, searchValue: PropTypes.string || null, setSearchValue: PropTypes.func.isRequired, + appliedFiltersCount: PropTypes.number, + setAppliedFiltersCount: PropTypes.func, }; ProjectsPageHeader.defaultProps = { From b7bf220b64811a606433445c2b6c02daec55d606 Mon Sep 17 00:00:00 2001 From: Siarhei Iukou Date: Mon, 9 Dec 2024 15:05:28 +0300 Subject: [PATCH 02/10] EPMRPP-91556 || Code Review fix - 1 --- app/localization/translated/be.json | 56 ++++++++------- app/localization/translated/es.json | 2 + app/localization/translated/ru.json | 56 ++++++++------- app/localization/translated/uk.json | 54 +++++++------- app/localization/translated/zh.json | 2 + app/src/common/urls.js | 30 ++++---- .../containers/filterEntitiesURLContainer.jsx | 1 - .../filterButton/constants.js | 0 .../filterButton/filterButton.jsx | 5 +- .../filterButton/filterButton.scss | 0 .../filterContent/filterContent.jsx | 15 ++-- .../filterContent/filterContent.scss | 3 +- .../filterContent/filterInput/filterInput.jsx | 71 +++++++++++-------- .../filterInput/filterInput.scss | 19 +++++ .../filterContent/filterInput/index.js | 0 .../filterButton/filterContent/index.js | 0 .../filterButton/filterContent/messages.js | 0 .../{buttons => main}/filterButton/index.jsx | 1 + .../filterButton/messages.js | 4 ++ .../instance/organizations/actionCreators.js | 4 +- .../instance/organizations/constants.js | 2 +- .../instance/organizations/index.js | 2 +- .../instance/organizations/sagas.js | 4 +- .../instance/organizations/selectors.js | 4 +- .../organization/projects/actionCreators.js | 4 +- .../organization/projects/constants.js | 2 +- .../organization/projects/index.js | 2 +- .../organization/projects/sagas.js | 6 +- .../organization/projects/selectors.js | 4 +- .../organizationsFilter.jsx | 11 ++- .../organizationsPageHeader.jsx | 8 +-- .../projectsFilter/projectsFilter.jsx | 8 ++- .../projectsPageHeader/projectsPageHeader.jsx | 8 +-- 33 files changed, 219 insertions(+), 169 deletions(-) rename app/src/components/{buttons => main}/filterButton/constants.js (100%) rename app/src/components/{buttons => main}/filterButton/filterButton.jsx (97%) rename app/src/components/{buttons => main}/filterButton/filterButton.scss (100%) rename app/src/components/{buttons => main}/filterButton/filterContent/filterContent.jsx (91%) rename app/src/components/{buttons => main}/filterButton/filterContent/filterContent.scss (98%) rename app/src/components/{buttons => main}/filterButton/filterContent/filterInput/filterInput.jsx (55%) rename app/src/components/{buttons => main}/filterButton/filterContent/filterInput/filterInput.scss (79%) rename app/src/components/{buttons => main}/filterButton/filterContent/filterInput/index.js (100%) rename app/src/components/{buttons => main}/filterButton/filterContent/index.js (100%) rename app/src/components/{buttons => main}/filterButton/filterContent/messages.js (100%) rename app/src/components/{buttons => main}/filterButton/index.jsx (95%) rename app/src/components/{buttons => main}/filterButton/messages.js (94%) diff --git a/app/localization/translated/be.json b/app/localization/translated/be.json index 4917cb690d..cb95531ab0 100644 --- a/app/localization/translated/be.json +++ b/app/localization/translated/be.json @@ -774,17 +774,18 @@ "Filter.name": "Назва", "Filter.namePlaceholder": "Увядзіце назву фільтра", "FilterAdd.addTitle": "Дадаць новы фільтр", - "FilterButton.any": "Any", - "FilterButton.contains": "Contains", - "FilterButton.equals": "Equals", - "FilterButton.greaterOrEqual": "Greater or equal", - "FilterButton.lessOrEqual": "Less or equal", - "FilterButton.last2days": "Last 2 days", - "FilterButton.last7days": "Last 7 days", - "FilterButton.last30days": "Last 30 days", - "FilterButton.notContains": "Not contains", - "FilterButton.notEqual": "Not equal", - "FilterButton.today": "Today", + "FilterButton.any": "Нейкі", + "FilterButton.contains": "Змяшчае", + "FilterButton.equals": "Раўняецца", + "FilterButton.greaterOrEqual": "Больш або роўна", + "FilterButton.helpText": "Дазволеныя толькі лічбы", + "FilterButton.lessOrEqual": "Менш або роўна", + "FilterButton.last2days": "Апошнія 2 дні", + "FilterButton.last7days": "Апошнія 7 дзён", + "FilterButton.last30days": "Апошнія 30 дзён", + "FilterButton.notContains": "Не змяшчае", + "FilterButton.notEqual": "Не роўна", + "FilterButton.today": "Сёння", "FilterEdit.editTitle": "Рэдагаваць фільтр", "FilterNameById.statistics$defects$automation_bug": "Automation Bug", "FilterNameById.statistics$defects$no_defect": "No Defect", @@ -1577,14 +1578,14 @@ "OrganizationsControl.all": "Усе", "OrganizationsControl.allOrganizations": "Усе арганізацыі", "OrganizationsControl.organization": "Арганізацыя", - "OrganizationsFilter.lastRunDate": "Last Run Date", - "OrganizationsFilter.lastRunDatePlaceholder": "Any", - "OrganizationsFilter.launches": "Launches", - "OrganizationsFilter.launchesPlaceholder": "Enter the number of launches", - "OrganizationsFilter.name": "Organization Name", - "OrganizationsFilter.namePlaceholder": "Enter part of the name", - "OrganizationsFilter.users": "Users", - "OrganizationsFilter.usersPlaceholder": "Enter the number of members", + "OrganizationsFilter.lastRunDate": "Дата апошняга запуску", + "OrganizationsFilter.lastRunDatePlaceholder": "Любая", + "OrganizationsFilter.launches": "Запускі", + "OrganizationsFilter.launchesPlaceholder": "Увядзіце колькасць запускаў", + "OrganizationsFilter.name": "Назва Арганізацыі", + "OrganizationsFilter.namePlaceholder": "Увядзіце частку імя", + "OrganizationsFilter.users": "Карыстальнік", + "OrganizationsFilter.usersPlaceholder": "Увядзіце колькасць удзельнікаў", "OrganizationsItem.open": "адкрыць", "OrganizationsPage.title": "Усе арганізацыі", "OrganizationsPage.description": "Спіс даступных вам арганізацый у дадзены момант пусты. Калі ласка, звяжыцеся са сваім адміністратарам, каб атрымаць прызначэнне ва ўжо існуючую арганізацыю.", @@ -1813,14 +1814,15 @@ "ProjectsGrid.nameCol": "Назва", "ProjectsGrid.organizationCol": "Арганізацыя", "ProjectsGrid.projectTypeCol": "Тып праекта", - "ProjectsFilter.lastRunDate": "Last Run Date", - "ProjectsFilter.lastRunDatePlaceholder": "Any", - "ProjectsFilter.launches": "Launches", - "ProjectsFilter.launchesPlaceholder": "Enter the number of launches", - "ProjectsFilter.name": "Project Name", - "ProjectsFilter.namePlaceholder": "Enter part of the name", - "ProjectsFilter.users": "Teammates", - "ProjectsFilter.usersPlaceholder": "Enter the number of members", + "ProjectsFilter.lastRunDate": "Дата апошняга запуску", + "ProjectsFilter.lastRunDatePlaceholder": "Любая", + "ProjectsFilter.launches": "Запускі", + "ProjectsFilter.launchesPlaceholder": "Увядзіце колькасць запускаў", + "ProjectsFilter.name": "Назва Праекта", + "ProjectsFilter.namePlaceholder": "Увядзіце частку імя", + "ProjectsFilter.users": "Таварышы па камандзе", + "ProjectsFilter.usersPlaceholder": "Увядзіце колькасць удзельнікаў", + "ProjectsFilterPopover.clearAllFilters": "Ачысціць усе фільтры", "ProjectsPage.addProject": "Дадаць Праект", "ProjectsPage.addProjectSuccess": "Праект ''{name}'' быў паспяхова створаны", "ProjectsPage.addProjectTitle": "Дадаць Праект", diff --git a/app/localization/translated/es.json b/app/localization/translated/es.json index fa269b445d..4a458b4943 100644 --- a/app/localization/translated/es.json +++ b/app/localization/translated/es.json @@ -777,6 +777,7 @@ "FilterButton.contains": "Contains", "FilterButton.equals": "Equals", "FilterButton.greaterOrEqual": "Greater or equal", + "FilterButton.helpText": "Only digits are allowed", "FilterButton.lessOrEqual": "Less or equal", "FilterButton.last2days": "Last 2 days", "FilterButton.last7days": "Last 7 days", @@ -1819,6 +1820,7 @@ "ProjectsFilter.namePlaceholder": "Enter part of the name", "ProjectsFilter.users": "Teammates", "ProjectsFilter.usersPlaceholder": "Enter the number of members", + "ProjectsFilterPopover.clearAllFilters": "Clear all filters", "ProjectsPage.addProject": "Crear Proyecto", "ProjectsPage.addProjectSuccess": "El proyecto ''{name}'' ha sido creado exitosamente", "ProjectsPage.addProjectTitle": "Agregar Proyecto", diff --git a/app/localization/translated/ru.json b/app/localization/translated/ru.json index ccf2fe66a1..bf0ec639a1 100644 --- a/app/localization/translated/ru.json +++ b/app/localization/translated/ru.json @@ -774,17 +774,18 @@ "Filter.name": "Имя", "Filter.namePlaceholder": "Ввести имя фильтра", "FilterAdd.addTitle": "Добавить новый фильтр", - "FilterButton.any": "Any", - "FilterButton.contains": "Contains", - "FilterButton.equals": "Equals", - "FilterButton.greaterOrEqual": "Greater or equal", - "FilterButton.lessOrEqual": "Less or equal", - "FilterButton.last2days": "Last 2 days", - "FilterButton.last7days": "Last 7 days", - "FilterButton.last30days": "Last 30 days", - "FilterButton.notContains": "Not contains", - "FilterButton.notEqual": "Not equal", - "FilterButton.today": "Today", + "FilterButton.any": "Любой", + "FilterButton.contains": "Содержит", + "FilterButton.equals": "Равняется", + "FilterButton.greaterOrEqual": "Больше или равно", + "FilterButton.helpText": "Разрешены только цифры", + "FilterButton.lessOrEqual": "Меньше или равно", + "FilterButton.last2days": "Последние 2 дня", + "FilterButton.last7days": "Последние 7 дней", + "FilterButton.last30days": "Последние 30 дней", + "FilterButton.notContains": "Не содержит", + "FilterButton.notEqual": "Не равны", + "FilterButton.today": "Сегодня", "FilterEdit.editTitle": "Редактировать фильтр", "FilterNameById.statistics$defects$automation_bug": "Automation Bug", "FilterNameById.statistics$defects$no_defect": "No Defect", @@ -1572,14 +1573,14 @@ "OrganizationsControl.all": "Все", "OrganizationsControl.allOrganizations": "Все организации", "OrganizationsControl.organization": "Организация", - "OrganizationsFilter.lastRunDate": "Last Run Date", - "OrganizationsFilter.lastRunDatePlaceholder": "Any", - "OrganizationsFilter.launches": "Launches", - "OrganizationsFilter.launchesPlaceholder": "Enter the number of launches", - "OrganizationsFilter.name": "Organization Name", - "OrganizationsFilter.namePlaceholder": "Enter part of the name", - "OrganizationsFilter.users": "Users", - "OrganizationsFilter.usersPlaceholder": "Enter the number of members", + "OrganizationsFilter.lastRunDate": "Дата последнего запуска", + "OrganizationsFilter.lastRunDatePlaceholder": "Любой", + "OrganizationsFilter.launches": "Запуски", + "OrganizationsFilter.launchesPlaceholder": "Введите количество запусков", + "OrganizationsFilter.name": "Название Организации", + "OrganizationsFilter.namePlaceholder": "Введите часть имени", + "OrganizationsFilter.users": "Пользователи", + "OrganizationsFilter.usersPlaceholder": "Введите количество участников", "OrganizationsItem.open": "открыть", "OrganizationsPage.title": "Все организации", "OrganizationsPage.description": "Список доступных вам организаций в данный момент пуст. Пожалуйста, свяжитесь со своим администратором, чтобы получить назначение в уже существующую организацию.", @@ -1808,14 +1809,15 @@ "ProjectsGrid.nameCol": "Название", "ProjectsGrid.organizationCol": "Организация", "ProjectsGrid.projectTypeCol": "Тип проекта", - "ProjectsFilter.lastRunDate": "Last Run Date", - "ProjectsFilter.lastRunDatePlaceholder": "Any", - "ProjectsFilter.launches": "Launches", - "ProjectsFilter.launchesPlaceholder": "Enter the number of launches", - "ProjectsFilter.name": "Project Name", - "ProjectsFilter.namePlaceholder": "Enter part of the name", - "ProjectsFilter.users": "Teammates", - "ProjectsFilter.usersPlaceholder": "Enter the number of members", + "ProjectsFilter.lastRunDate": "Дата последнего запуска", + "ProjectsFilter.lastRunDatePlaceholder": "Любой", + "ProjectsFilter.launches": "Запуски", + "ProjectsFilter.launchesPlaceholder": "Введите количество запусков", + "ProjectsFilter.name": "Название проекта", + "ProjectsFilter.namePlaceholder": "Введите часть имени", + "ProjectsFilter.users": "Товарищи по команде", + "ProjectsFilter.usersPlaceholder": "Введите количество участников", + "ProjectsFilterPopover.clearAllFilters": "Очистить все фильтры", "ProjectsPage.addProject": "Создать Проект", "ProjectsPage.addProjectSuccess": "Проект ''{name}'' успешно создан", "ProjectsPage.addProjectTitle": "Добавить Проект", diff --git a/app/localization/translated/uk.json b/app/localization/translated/uk.json index 6dc19735a0..30421986d4 100644 --- a/app/localization/translated/uk.json +++ b/app/localization/translated/uk.json @@ -774,17 +774,18 @@ "Filter.name": "Ім’я", "Filter.namePlaceholder": "Ввести ім’я фільтра", "FilterAdd.addTitle": "Додати новий фільтр", - "FilterButton.any": "Any", - "FilterButton.contains": "Contains", - "FilterButton.equals": "Equals", - "FilterButton.greaterOrEqual": "Greater or equal", - "FilterButton.lessOrEqual": "Less or equal", - "FilterButton.last2days": "Last 2 days", - "FilterButton.last7days": "Last 7 days", - "FilterButton.last30days": "Last 30 days", - "FilterButton.notContains": "Not contains", - "FilterButton.notEqual": "Not equal", - "FilterButton.today": "Today", + "FilterButton.any": "Будь-який", + "FilterButton.contains": "Містити", + "FilterButton.equals": "Дорівнювати", + "FilterButton.greaterOrEqual": "Більше або дорівнює", + "FilterButton.helpText": "Дозволені лише цифри", + "FilterButton.lessOrEqual": "Менше або дорівнює", + "FilterButton.last2days": "Останні 2 дні", + "FilterButton.last7days": "Останні 7 днів", + "FilterButton.last30days": "Останні 30 днів", + "FilterButton.notContains": "Не містить", + "FilterButton.notEqual": "Не рівні", + "FilterButton.today": "Сьогодні", "FilterEdit.editTitle": "Редагувати фільтр", "FilterNameById.statistics$defects$automation_bug": "Помилка Автоматизації", "FilterNameById.statistics$defects$no_defect": "Ніякої Дефект", @@ -1575,13 +1576,13 @@ "OrganizationsControl.allOrganizations": "Всі організації", "OrganizationsControl.organization": "Організація", "OrganizationsFilter.lastRunDate": "Last Run Date", - "OrganizationsFilter.lastRunDatePlaceholder": "Any", - "OrganizationsFilter.launches": "Launches", - "OrganizationsFilter.launchesPlaceholder": "Enter the number of launches", - "OrganizationsFilter.name": "Organization Name", - "OrganizationsFilter.namePlaceholder": "Enter part of the name", - "OrganizationsFilter.users": "Users", - "OrganizationsFilter.usersPlaceholder": "Enter the number of members", + "OrganizationsFilter.lastRunDatePlaceholder": "Будь-який", + "OrganizationsFilter.launches": "Запуски", + "OrganizationsFilter.launchesPlaceholder": "Введіть кількість запусків", + "OrganizationsFilter.name": "Назва організації", + "OrganizationsFilter.namePlaceholder": "Введіть частину імені", + "OrganizationsFilter.users": "Користувачі", + "OrganizationsFilter.usersPlaceholder": "Введіть кількість учасників", "OrganizationsItem.open": "відкрити", "OrganizationsPage.title": "Всі організації", "OrganizationsPage.description": "Список доступних вам організацій в даний момент порожній. Будь ласка, зв'яжіться зі своїм адміністратором, щоб отримати призначення в уже існуючу організацію.", @@ -1810,14 +1811,15 @@ "ProjectsGrid.nameCol": "Назва", "ProjectsGrid.organizationCol": "Організація", "ProjectsGrid.projectTypeCol": "Тип проекту", - "ProjectsFilter.lastRunDate": "Last Run Date", - "ProjectsFilter.lastRunDatePlaceholder": "Any", - "ProjectsFilter.launches": "Launches", - "ProjectsFilter.launchesPlaceholder": "Enter the number of launches", - "ProjectsFilter.name": "Project Name", - "ProjectsFilter.namePlaceholder": "Enter part of the name", - "ProjectsFilter.users": "Teammates", - "ProjectsFilter.usersPlaceholder": "Enter the number of members", + "ProjectsFilter.lastRunDate": "Дата останнього запуску", + "ProjectsFilter.lastRunDatePlaceholder": "Будь-який", + "ProjectsFilter.launches": "Запуски", + "ProjectsFilter.launchesPlaceholder": "Введіть кількість запусків", + "ProjectsFilter.name": "Назва проекту", + "ProjectsFilter.namePlaceholder": "Введіть частину імені", + "ProjectsFilter.users": "Товариші по команді", + "ProjectsFilter.usersPlaceholder": "Введіть кількість учасників", + "ProjectsFilterPopover.clearAllFilters": "Очистити всі фільтри", "ProjectsPage.addProject": "Створити Проект", "ProjectsPage.addProjectSuccess": "Проект ''{name}'' успешно создан", "ProjectsPage.addProjectTitle": "Проект Додати", diff --git a/app/localization/translated/zh.json b/app/localization/translated/zh.json index 960ff22afc..ca5048a7f2 100644 --- a/app/localization/translated/zh.json +++ b/app/localization/translated/zh.json @@ -778,6 +778,7 @@ "FilterButton.contains": "Contains", "FilterButton.equals": "Equals", "FilterButton.greaterOrEqual": "Greater or equal", + "FilterButton.helpText": "Only digits are allowed", "FilterButton.lessOrEqual": "Less or equal", "FilterButton.last2days": "Last 2 days", "FilterButton.last7days": "Last 7 days", @@ -1818,6 +1819,7 @@ "ProjectsFilter.namePlaceholder": "Enter part of the name", "ProjectsFilter.users": "Teammates", "ProjectsFilter.usersPlaceholder": "Enter the number of members", + "ProjectsFilterPopover.clearAllFilters": "Clear all filters", "ProjectsPage.addProject": "Create Project", "ProjectsPage.addProjectSuccess": "项目“{name}”创建成功", "ProjectsPage.addProjectTitle": "创建项目", diff --git a/app/src/common/urls.js b/app/src/common/urls.js index 7e635dacb6..37ac093efc 100644 --- a/app/src/common/urls.js +++ b/app/src/common/urls.js @@ -122,15 +122,15 @@ export const URLS = { login: () => `${uatBase}sso/oauth/token`, sessionToken: () => `${uatBase}sso/me`, - apiKeys: (userId) => `${urlCommonBase}users/${userId}/api-keys`, - apiKeyById: (userId, apiKeyId) => `${urlCommonBase}users/${userId}/api-keys/${apiKeyId}`, + apiKeys: (userId) => `${urlBase}users/${userId}/api-keys`, + apiKeyById: (userId, apiKeyId) => `${urlBase}users/${userId}/api-keys/${apiKeyId}`, organizationList: (preferencesObj = {}) => `${urlCommonBase}organizations${getQueryParams(preferencesObj)}`, - organizationSearch: () => `${urlCommonBase}organizations/searches`, + organizationSearches: () => `${urlCommonBase}organizations/searches`, organizationProjects: (organizationId, preferencesObj = {}) => `${urlCommonBase}organizations/${organizationId}/projects${getQueryParams(preferencesObj)}`, - filterOrganizationProjects: (organizationId) => + organizationProjectsSearches: (organizationId) => `${urlCommonBase}organizations/${organizationId}/projects/searches`, organizationUsers: (organizationId, preferencesObj = {}) => `${urlCommonBase}organizations/${organizationId}/users${getQueryParams(preferencesObj)}`, @@ -155,7 +155,7 @@ export const URLS = { term: searchTerm, })}`, searchUsers: (term) => - `${urlCommonBase}users/search${getQueryParams({ + `${urlBase}users/search${getQueryParams({ term, })}`, projectAddPattern: (projectKey) => `${urlBase}${projectKey}/settings/pattern`, @@ -245,17 +245,17 @@ export const URLS = { })}`, logSearch: (projectKey, itemId) => `${urlBase}${projectKey}/log/search/${itemId}`, bulkLastLogs: (projectKey) => `${urlBase}${projectKey}/log/under`, - users: (ids = []) => `${urlCommonBase}users?ids=${ids.join(',')}`, - userRegistration: () => `${urlCommonBase}users/registration`, - userValidateRegistrationInfo: () => `${urlCommonBase}users/registration/info`, - userPasswordReset: () => `${urlCommonBase}users/password/reset`, - userPasswordResetToken: (token) => `${urlCommonBase}users/password/reset/${token}`, - userPasswordRestore: () => `${urlCommonBase}users/password/restore`, - userChangePassword: () => `${urlCommonBase}users/password/change`, + users: (ids = []) => `${urlBase}users?ids=${ids.join(',')}`, + userRegistration: () => `${urlBase}users/registration`, + userValidateRegistrationInfo: () => `${urlBase}users/registration/info`, + userPasswordReset: () => `${urlBase}users/password/reset`, + userPasswordResetToken: (token) => `${urlBase}users/password/reset/${token}`, + userPasswordRestore: () => `${urlBase}users/password/restore`, + userChangePassword: () => `${urlBase}users/password/change`, userSynchronize: (type) => `${uatBase}sso/me/${type}/synchronize`, - userInfo: (userId) => `${urlCommonBase}users/${userId}`, + userInfo: (userId) => `${urlBase}users/${userId}`, userInviteInternal: (projectKey) => `${urlBase}project/${projectKey}/assign`, - userInviteExternal: () => `${urlCommonBase}users/bid`, + userInviteExternal: () => `${urlBase}users/bid`, userUnassign: (projectKey) => `${urlBase}project/${projectKey}/unassign`, generateDemoData: (projectKey) => `${urlBase}demo/${projectKey}/generate`, @@ -268,7 +268,7 @@ export const URLS = { events: () => `${urlBase}activities/searches`, searchEventsBySubjectName: (projectName) => (searchTerm = '') => `${urlBase}activities/${projectName}/subjectName?filter.cnt.subjectName=${searchTerm}`, - allUsers: () => `${urlCommonBase}users/all`, + allUsers: () => `${urlBase}users/all`, exportUsers: (filterEntities) => `${urlCommonBase}users/export${getQueryParams({ diff --git a/app/src/components/filterEntities/containers/filterEntitiesURLContainer.jsx b/app/src/components/filterEntities/containers/filterEntitiesURLContainer.jsx index cb6a34a1b5..c45db0a6e1 100644 --- a/app/src/components/filterEntities/containers/filterEntitiesURLContainer.jsx +++ b/app/src/components/filterEntities/containers/filterEntitiesURLContainer.jsx @@ -36,7 +36,6 @@ const FilterEntitiesURL = ({ } const filterQuery = createFilterQuery(newEntities, entities, prefixQueryKey); if (!isEmptyObject(filterQuery)) { - console.log('filterQuery', filterQuery); updateFilters(filterQuery, defaultPagination[PAGE_KEY]); } }, diff --git a/app/src/components/buttons/filterButton/constants.js b/app/src/components/main/filterButton/constants.js similarity index 100% rename from app/src/components/buttons/filterButton/constants.js rename to app/src/components/main/filterButton/constants.js diff --git a/app/src/components/buttons/filterButton/filterButton.jsx b/app/src/components/main/filterButton/filterButton.jsx similarity index 97% rename from app/src/components/buttons/filterButton/filterButton.jsx rename to app/src/components/main/filterButton/filterButton.jsx index e9daa81935..2bbc115040 100644 --- a/app/src/components/buttons/filterButton/filterButton.jsx +++ b/app/src/components/main/filterButton/filterButton.jsx @@ -15,7 +15,6 @@ */ import { useEffect, useState } from 'react'; -import { useDispatch } from 'react-redux'; import PropTypes from 'prop-types'; import classNames from 'classnames/bind'; import { Popover } from '@reportportal/ui-kit'; @@ -36,12 +35,12 @@ export const FilterButton = ({ filteredAction, }) => { const [isOpen, setIsOpen] = useState(false); - const dispatch = useDispatch(); const [filters, setFilters] = useState(defaultFilters); const [initialFilters, setInitialFilters] = useState(defaultFilters); useEffect(() => { - dispatch(filteredAction()); + setAppliedFiltersCount(Object.keys(definedFilters).length); + filteredAction(); }, []); useEffect(() => { diff --git a/app/src/components/buttons/filterButton/filterButton.scss b/app/src/components/main/filterButton/filterButton.scss similarity index 100% rename from app/src/components/buttons/filterButton/filterButton.scss rename to app/src/components/main/filterButton/filterButton.scss diff --git a/app/src/components/buttons/filterButton/filterContent/filterContent.jsx b/app/src/components/main/filterButton/filterContent/filterContent.jsx similarity index 91% rename from app/src/components/buttons/filterButton/filterContent/filterContent.jsx rename to app/src/components/main/filterButton/filterContent/filterContent.jsx index 87bafb0576..1cfbf97fd6 100644 --- a/app/src/components/buttons/filterButton/filterContent/filterContent.jsx +++ b/app/src/components/main/filterButton/filterContent/filterContent.jsx @@ -17,10 +17,10 @@ import classNames from 'classnames/bind'; import { Button } from '@reportportal/ui-kit'; import { useIntl } from 'react-intl'; -import { useDispatch } from 'react-redux'; import PropTypes from 'prop-types'; import isEqual from 'fast-deep-equal'; import { COMMON_LOCALE_KEYS } from 'common/constants/localization'; +import { useMemo } from 'react'; import { FilterInput } from './filterInput'; import { messages } from './messages'; import styles from './filterContent.scss'; @@ -38,7 +38,6 @@ export const FilterContent = ({ filteredAction, }) => { const { formatMessage } = useIntl(); - const dispatch = useDispatch(); const closePopover = () => { setIsOpen(false); @@ -63,12 +62,18 @@ export const FilterContent = ({ }, {}); onFilterChange(fields); setAppliedFiltersCount(appliedFiltersCount); - dispatch(filteredAction()); + filteredAction(); setIsOpen(false); }; - const isDefaultFilters = isEqual(defaultFilters, filters); - const isDefinedFilters = isEqual(initialFilters, filters); + const isDefaultFilters = useMemo(() => isEqual(defaultFilters, filters), [ + defaultFilters, + filters, + ]); + const isDefinedFilters = useMemo(() => isEqual(initialFilters, filters), [ + initialFilters, + filters, + ]); return (
diff --git a/app/src/components/buttons/filterButton/filterContent/filterContent.scss b/app/src/components/main/filterButton/filterContent/filterContent.scss similarity index 98% rename from app/src/components/buttons/filterButton/filterContent/filterContent.scss rename to app/src/components/main/filterButton/filterContent/filterContent.scss index 909fba1f48..b760c08945 100644 --- a/app/src/components/buttons/filterButton/filterContent/filterContent.scss +++ b/app/src/components/main/filterButton/filterContent/filterContent.scss @@ -17,13 +17,12 @@ .filter-popover-content { display: flex; flex-direction: column; - gap: 32px; + gap: 16px; } .filter-items { display: flex; flex-direction: column; - gap: 16px; } .actions { diff --git a/app/src/components/buttons/filterButton/filterContent/filterInput/filterInput.jsx b/app/src/components/main/filterButton/filterContent/filterInput/filterInput.jsx similarity index 55% rename from app/src/components/buttons/filterButton/filterContent/filterInput/filterInput.jsx rename to app/src/components/main/filterButton/filterContent/filterInput/filterInput.jsx index c506f50cd5..9ded5538bf 100644 --- a/app/src/components/buttons/filterButton/filterContent/filterInput/filterInput.jsx +++ b/app/src/components/main/filterButton/filterContent/filterInput/filterInput.jsx @@ -22,47 +22,60 @@ import styles from './filterInput.scss'; const cx = classNames.bind(styles); export const FilterInput = ({ filter, setFilters }) => { - const { filterName, options, value, condition, placeholder, title, withField } = filter; + const { filterName, options, value, condition, placeholder, title, withField, helpText } = filter; + + const onChangeOption = (newValue) => { + setFilters((prevFilters) => ({ + ...prevFilters, + [filterName]: { + ...filter, + ...(withField ? { condition: newValue } : { value: newValue }), + }, + })); + }; + + const onTextFieldChange = ({ target }) => { + if (helpText && !Number(target.value)) { + return; + } + + setFilters((prevFilters) => ({ + ...prevFilters, + [filterName]: { ...filter, value: target.value }, + })); + }; + + const onClear = () => { + setFilters((prevFilters) => ({ + ...prevFilters, + [filterName]: { ...filter, value: '' }, + })); + }; return ( -
+
{title}
{ - setFilters((prevFilters) => ({ - ...prevFilters, - [filterName]: { - ...filter, - ...(withField ? { condition: newValue } : { value: newValue }), - }, - })); - }} + onChange={onChangeOption} isListWidthLimited className={cx({ dropdown: withField })} placeholder={placeholder} /> {withField && ( - { - setFilters((prevFilters) => ({ - ...prevFilters, - [filterName]: { ...filter, value: target.value }, - })); - }} - onClear={() => { - setFilters((prevFilters) => ({ - ...prevFilters, - [filterName]: { ...filter, value: '' }, - })); - }} - clearable - /> +
+ +
)}
diff --git a/app/src/components/buttons/filterButton/filterContent/filterInput/filterInput.scss b/app/src/components/main/filterButton/filterContent/filterInput/filterInput.scss similarity index 79% rename from app/src/components/buttons/filterButton/filterContent/filterInput/filterInput.scss rename to app/src/components/main/filterButton/filterContent/filterInput/filterInput.scss index 19c1268ccc..4c37692b73 100644 --- a/app/src/components/buttons/filterButton/filterContent/filterInput/filterInput.scss +++ b/app/src/components/main/filterButton/filterContent/filterInput/filterInput.scss @@ -18,6 +18,7 @@ display: flex; flex-direction: column; gap: 4px; + margin-bottom: 16px; .label { font-size: 13px; @@ -28,9 +29,11 @@ .container { display: flex; + width: 100%; gap: 8px; .dropdown { + min-width: 156px; width: 156px; } @@ -39,3 +42,19 @@ } } } + +.with-help-text { + margin-bottom: 0; +} + +.input-field-container { + width: 100%; + + &:last-child span { + color: $COLOR--e-400; + } + + div.input-field { + width: 100%; + } +} diff --git a/app/src/components/buttons/filterButton/filterContent/filterInput/index.js b/app/src/components/main/filterButton/filterContent/filterInput/index.js similarity index 100% rename from app/src/components/buttons/filterButton/filterContent/filterInput/index.js rename to app/src/components/main/filterButton/filterContent/filterInput/index.js diff --git a/app/src/components/buttons/filterButton/filterContent/index.js b/app/src/components/main/filterButton/filterContent/index.js similarity index 100% rename from app/src/components/buttons/filterButton/filterContent/index.js rename to app/src/components/main/filterButton/filterContent/index.js diff --git a/app/src/components/buttons/filterButton/filterContent/messages.js b/app/src/components/main/filterButton/filterContent/messages.js similarity index 100% rename from app/src/components/buttons/filterButton/filterContent/messages.js rename to app/src/components/main/filterButton/filterContent/messages.js diff --git a/app/src/components/buttons/filterButton/index.jsx b/app/src/components/main/filterButton/index.jsx similarity index 95% rename from app/src/components/buttons/filterButton/index.jsx rename to app/src/components/main/filterButton/index.jsx index 47eaeee42e..446d829301 100644 --- a/app/src/components/buttons/filterButton/index.jsx +++ b/app/src/components/main/filterButton/index.jsx @@ -24,3 +24,4 @@ export { getRangeComparisons, getTimeRange, } from './constants'; +export { messages } from './messages'; diff --git a/app/src/components/buttons/filterButton/messages.js b/app/src/components/main/filterButton/messages.js similarity index 94% rename from app/src/components/buttons/filterButton/messages.js rename to app/src/components/main/filterButton/messages.js index 1bc497131a..e7333803e5 100644 --- a/app/src/components/buttons/filterButton/messages.js +++ b/app/src/components/main/filterButton/messages.js @@ -61,4 +61,8 @@ export const messages = defineMessages({ id: 'FilterButton.notContains', defaultMessage: 'Not contains', }, + helpText: { + id: 'FilterButton.helpText', + defaultMessage: 'Only digits are allowed', + }, }); diff --git a/app/src/controllers/instance/organizations/actionCreators.js b/app/src/controllers/instance/organizations/actionCreators.js index 01501d14f0..44eb3ea8f9 100644 --- a/app/src/controllers/instance/organizations/actionCreators.js +++ b/app/src/controllers/instance/organizations/actionCreators.js @@ -14,12 +14,12 @@ * limitations under the License. */ -import { FETCH_ORGANIZATIONS, FILTERED_ORGANIZATIONS } from './constants'; +import { FETCH_ORGANIZATIONS, FETCH_FILTERED_ORGANIZATIONS } from './constants'; export const fetchOrganizationsAction = () => ({ type: FETCH_ORGANIZATIONS, }); export const fetchFilteredOrganizationsAction = () => ({ - type: FILTERED_ORGANIZATIONS, + type: FETCH_FILTERED_ORGANIZATIONS, }); diff --git a/app/src/controllers/instance/organizations/constants.js b/app/src/controllers/instance/organizations/constants.js index b94fa0ace9..a69e335d49 100644 --- a/app/src/controllers/instance/organizations/constants.js +++ b/app/src/controllers/instance/organizations/constants.js @@ -19,7 +19,7 @@ import { PAGE_KEY, SIZE_KEY } from 'controllers/pagination'; export const NAMESPACE = 'organizations'; export const FETCH_ORGANIZATIONS = 'fetchOrganizations'; -export const FILTERED_ORGANIZATIONS = 'filteredOrganizations'; +export const FETCH_FILTERED_ORGANIZATIONS = 'fetchFilteredOrganizations'; export const DEFAULT_PAGE_SIZE_OPTIONS = [10, 20, 50, 100]; export const DEFAULT_LIMITATION = 20; export const initialPaginationState = { diff --git a/app/src/controllers/instance/organizations/index.js b/app/src/controllers/instance/organizations/index.js index 5b645458b9..1763ffeded 100644 --- a/app/src/controllers/instance/organizations/index.js +++ b/app/src/controllers/instance/organizations/index.js @@ -14,7 +14,7 @@ * limitations under the License. */ -export { FETCH_ORGANIZATIONS, FILTERED_ORGANIZATIONS } from './constants'; +export { FETCH_ORGANIZATIONS, FETCH_FILTERED_ORGANIZATIONS } from './constants'; export { fetchOrganizationsAction, fetchFilteredOrganizationsAction } from './actionCreators'; export { organizationsReducer } from './reducer'; export { diff --git a/app/src/controllers/instance/organizations/sagas.js b/app/src/controllers/instance/organizations/sagas.js index 64414b40a5..47111dcca4 100644 --- a/app/src/controllers/instance/organizations/sagas.js +++ b/app/src/controllers/instance/organizations/sagas.js @@ -19,7 +19,7 @@ import { URLS } from 'common/urls'; import { showDefaultErrorNotification } from 'controllers/notification'; import { fetchDataAction } from 'controllers/fetch'; import { filterQuerySelector, querySelector } from './selectors'; -import { FETCH_ORGANIZATIONS, FILTERED_ORGANIZATIONS, NAMESPACE } from './constants'; +import { FETCH_ORGANIZATIONS, FETCH_FILTERED_ORGANIZATIONS, NAMESPACE } from './constants'; function* fetchOrganizations() { try { @@ -47,7 +47,7 @@ function* fetchFilteredOrganizations() { } function* watchFetchFilteredProjects() { - yield takeEvery(FILTERED_ORGANIZATIONS, fetchFilteredOrganizations); + yield takeEvery(FETCH_FILTERED_ORGANIZATIONS, fetchFilteredOrganizations); } export function* organizationsSagas() { diff --git a/app/src/controllers/instance/organizations/selectors.js b/app/src/controllers/instance/organizations/selectors.js index 67178e429c..20a5cb200d 100644 --- a/app/src/controllers/instance/organizations/selectors.js +++ b/app/src/controllers/instance/organizations/selectors.js @@ -19,7 +19,7 @@ import { createFilterQuerySelector, } from 'controllers/pages/selectors'; import { SORTING_ASC } from 'controllers/sorting'; -import { NAMESPACE, DEFAULT_PAGINATION, SORTING_KEY, FILTERED_ORGANIZATIONS } from './constants'; +import { NAMESPACE, DEFAULT_PAGINATION, SORTING_KEY } from './constants'; export const organizationsSelector = (state) => state.organizations || {}; @@ -41,5 +41,5 @@ export const filterQuerySelector = createFilterQuerySelector({ defaultPagination: DEFAULT_PAGINATION, defaultSorting: SORTING_ASC, sortingKey: SORTING_KEY, - namespace: FILTERED_ORGANIZATIONS, + namespace: NAMESPACE, }); diff --git a/app/src/controllers/organization/projects/actionCreators.js b/app/src/controllers/organization/projects/actionCreators.js index bd0622bdec..5c2fd07c9b 100644 --- a/app/src/controllers/organization/projects/actionCreators.js +++ b/app/src/controllers/organization/projects/actionCreators.js @@ -18,7 +18,7 @@ import { CREATE_PROJECT, DELETE_PROJECT, FETCH_ORGANIZATION_PROJECTS, - FILTERED_PROJECTS, + FETCH_FILTERED_PROJECTS, } from './constants'; export const fetchOrganizationProjectsAction = (params) => { @@ -39,5 +39,5 @@ export const deleteProjectAction = (project) => ({ }); export const fetchFilteredProjectAction = () => ({ - type: FILTERED_PROJECTS, + type: FETCH_FILTERED_PROJECTS, }); diff --git a/app/src/controllers/organization/projects/constants.js b/app/src/controllers/organization/projects/constants.js index 10e2f8f5a4..5ced36e308 100644 --- a/app/src/controllers/organization/projects/constants.js +++ b/app/src/controllers/organization/projects/constants.js @@ -18,7 +18,7 @@ import { PAGE_KEY, SIZE_KEY } from 'controllers/pagination'; import { formatSortingString, SORTING_ASC } from 'controllers/sorting'; export const FETCH_ORGANIZATION_PROJECTS = 'fetchOrganizationProjects'; -export const FILTERED_PROJECTS = 'filteredProjects'; +export const FETCH_FILTERED_PROJECTS = 'fetchFilteredProjects'; export const NAMESPACE = 'organizationProjects'; export const CREATE_PROJECT = 'createProject'; export const DELETE_PROJECT = 'deleteProject'; diff --git a/app/src/controllers/organization/projects/index.js b/app/src/controllers/organization/projects/index.js index 1a2addab43..b113ec77e4 100644 --- a/app/src/controllers/organization/projects/index.js +++ b/app/src/controllers/organization/projects/index.js @@ -31,5 +31,5 @@ export { DEFAULT_QUERY_PARAMS, FETCH_ORGANIZATION_PROJECTS, SORTING_KEY, - FILTERED_PROJECTS, + FETCH_FILTERED_PROJECTS, } from './constants'; diff --git a/app/src/controllers/organization/projects/sagas.js b/app/src/controllers/organization/projects/sagas.js index 8faf97bff2..2e6610f8ff 100644 --- a/app/src/controllers/organization/projects/sagas.js +++ b/app/src/controllers/organization/projects/sagas.js @@ -26,7 +26,7 @@ import { ERROR_CODES, NAMESPACE, DELETE_PROJECT, - FILTERED_PROJECTS, + FETCH_FILTERED_PROJECTS, } from './constants'; import { fetchOrganizationBySlugAction } from '..'; import { filterQuerySelector, querySelector } from './selectors'; @@ -123,7 +123,7 @@ function* fetchFilteredProjects() { const filtersParams = yield select(filterQuerySelector); yield put( - fetchDataAction(NAMESPACE)(URLS.filterOrganizationProjects(activeOrganizationId), { + fetchDataAction(NAMESPACE)(URLS.organizationProjectsSearches(activeOrganizationId), { method: 'post', data: filtersParams, }), @@ -131,7 +131,7 @@ function* fetchFilteredProjects() { } function* watchFetchFilteredProjects() { - yield takeEvery(FILTERED_PROJECTS, fetchFilteredProjects); + yield takeEvery(FETCH_FILTERED_PROJECTS, fetchFilteredProjects); } export function* projectsSagas() { diff --git a/app/src/controllers/organization/projects/selectors.js b/app/src/controllers/organization/projects/selectors.js index 94f1b3e635..6388158b05 100644 --- a/app/src/controllers/organization/projects/selectors.js +++ b/app/src/controllers/organization/projects/selectors.js @@ -20,7 +20,7 @@ import { } from 'controllers/pages/selectors'; import { SORTING_ASC } from 'controllers/sorting'; import { organizationSelector } from '../selectors'; -import { DEFAULT_PAGINATION, FILTERED_PROJECTS, NAMESPACE, SORTING_KEY } from './constants'; +import { DEFAULT_PAGINATION, NAMESPACE, SORTING_KEY } from './constants'; const domainSelector = (state) => organizationSelector(state).projects || {}; @@ -39,5 +39,5 @@ export const filterQuerySelector = createFilterQuerySelector({ defaultPagination: DEFAULT_PAGINATION, defaultSorting: SORTING_ASC, sortingKey: SORTING_KEY, - namespace: FILTERED_PROJECTS, + namespace: NAMESPACE, }); diff --git a/app/src/pages/instance/organizationsPage/organizationsPageHeader/organizationsFilter/organizationsFilter.jsx b/app/src/pages/instance/organizationsPage/organizationsPageHeader/organizationsFilter/organizationsFilter.jsx index d4f24f8bb4..66fdb0275f 100644 --- a/app/src/pages/instance/organizationsPage/organizationsPageHeader/organizationsFilter/organizationsFilter.jsx +++ b/app/src/pages/instance/organizationsPage/organizationsPageHeader/organizationsFilter/organizationsFilter.jsx @@ -16,6 +16,7 @@ import { useIntl } from 'react-intl'; import PropTypes from 'prop-types'; +import { useDispatch } from 'react-redux'; import { LAUNCHES_FILTER_NAME, FILTER_NAME, @@ -24,8 +25,9 @@ import { getContainmentComparisons, getRangeComparisons, getTimeRange, -} from 'components/buttons/filterButton'; -import { FilterButton } from 'components/buttons/filterButton/filterButton'; + messages as helpMessage, +} from 'components/main/filterButton'; +import { FilterButton } from 'components/main/filterButton/filterButton'; import { fetchFilteredOrganizationsAction } from 'controllers/instance/organizations'; import { messages } from './messages'; @@ -36,6 +38,7 @@ export const OrganizationsFilter = ({ setAppliedFiltersCount, }) => { const { formatMessage } = useIntl(); + const dispatch = useDispatch(); const timeRange = getTimeRange(formatMessage); const rangeComparisons = getRangeComparisons(formatMessage); @@ -56,6 +59,7 @@ export const OrganizationsFilter = ({ placeholder: formatMessage(messages.launchesPlaceholder), options: rangeComparisons, condition: rangeComparisons[0].value, + helpText: formatMessage(helpMessage.helpText), withField: true, }, [TEAMMATES_FILTER_NAME]: { @@ -65,6 +69,7 @@ export const OrganizationsFilter = ({ placeholder: formatMessage(messages.usersPlaceholder), options: rangeComparisons, condition: rangeComparisons[0].value, + helpText: formatMessage(helpMessage.helpText), withField: true, }, [FILTER_NAME]: { @@ -85,7 +90,7 @@ export const OrganizationsFilter = ({ setAppliedFiltersCount={setAppliedFiltersCount} definedFilters={entities} onFilterChange={onFilterChange} - filteredAction={fetchFilteredOrganizationsAction} + filteredAction={() => dispatch(fetchFilteredOrganizationsAction())} /> ); }; diff --git a/app/src/pages/instance/organizationsPage/organizationsPageHeader/organizationsPageHeader.jsx b/app/src/pages/instance/organizationsPage/organizationsPageHeader/organizationsPageHeader.jsx index 4a36dc96a1..ce3692bae0 100644 --- a/app/src/pages/instance/organizationsPage/organizationsPageHeader/organizationsPageHeader.jsx +++ b/app/src/pages/instance/organizationsPage/organizationsPageHeader/organizationsPageHeader.jsx @@ -15,17 +15,16 @@ */ import React from 'react'; +import { useSelector } from 'react-redux'; import PropTypes from 'prop-types'; import Parser from 'html-react-parser'; import classNames from 'classnames/bind'; import { BaseIconButton } from '@reportportal/ui-kit'; -import filterIcon from 'common/img/newIcons/filters-outline-inline.svg'; import { useIntl } from 'react-intl'; import { SearchField } from 'components/fields/searchField'; import { SEARCH_KEY } from 'controllers/organization/projects/constants'; import { withFilter } from 'controllers/filter'; -import { FILTERED_ORGANIZATIONS, NAMESPACE } from 'controllers/instance/organizations/constants'; -import { useSelector } from 'react-redux'; +import { NAMESPACE } from 'controllers/instance/organizations/constants'; import { organizationsListLoadingSelector } from 'controllers/instance/organizations'; import { ORGANIZATION_PAGE_EVENTS } from 'components/main/analytics/events/ga4Events/organizationsPageEvents'; import { withFilterEntitiesURL } from 'components/filterEntities/containers'; @@ -41,7 +40,7 @@ const SearchFieldWithFilter = withFilter({ filterKey: SEARCH_KEY, namespace: NAM SearchField, ); -const FiltersFields = withFilterEntitiesURL(FILTERED_ORGANIZATIONS)(OrganizationsFilter); +const FiltersFields = withFilterEntitiesURL(NAMESPACE)(OrganizationsFilter); export const OrganizationsPageHeader = ({ isEmpty, @@ -70,7 +69,6 @@ export const OrganizationsPageHeader = ({ placeholder={formatMessage(messages.searchPlaceholder)} event={ORGANIZATION_PAGE_EVENTS.SEARCH_ORGANIZATION_FIELD} /> - {Parser(filterIcon)} { const { formatMessage } = useIntl(); + const dispatch = useDispatch(); const timeRange = getTimeRange(formatMessage); const rangeComparisons = getRangeComparisons(formatMessage); @@ -85,7 +87,7 @@ export const ProjectsFilter = ({ setAppliedFiltersCount={setAppliedFiltersCount} definedFilters={entities} onFilterChange={onFilterChange} - filteredAction={fetchFilteredProjectAction} + filteredAction={() => dispatch(fetchFilteredProjectAction())} /> ); }; diff --git a/app/src/pages/organization/organizationProjectsPage/projectsPageHeader/projectsPageHeader.jsx b/app/src/pages/organization/organizationProjectsPage/projectsPageHeader/projectsPageHeader.jsx index f17e851613..1840c40299 100644 --- a/app/src/pages/organization/organizationProjectsPage/projectsPageHeader/projectsPageHeader.jsx +++ b/app/src/pages/organization/organizationProjectsPage/projectsPageHeader/projectsPageHeader.jsx @@ -25,11 +25,7 @@ import { Breadcrumbs } from 'componentLibrary/breadcrumbs'; import { activeOrganizationSelector } from 'controllers/organization'; import { loadingSelector } from 'controllers/organization/projects'; import { SearchField } from 'components/fields/searchField'; -import { - SEARCH_KEY, - NAMESPACE, - FILTERED_PROJECTS, -} from 'controllers/organization/projects/constants'; +import { SEARCH_KEY, NAMESPACE } from 'controllers/organization/projects/constants'; import { withFilter } from 'controllers/filter'; import { withFilterEntitiesURL } from 'components/filterEntities/containers'; import projectsIcon from './img/projects-inline.svg'; @@ -44,7 +40,7 @@ const SearchFieldWithFilter = withFilter({ filterKey: SEARCH_KEY, namespace: NAM SearchField, ); -const FiltersFields = withFilterEntitiesURL(FILTERED_PROJECTS)(ProjectsFilter); +const FiltersFields = withFilterEntitiesURL(NAMESPACE)(ProjectsFilter); export const ProjectsPageHeader = ({ hasPermission, From 3a35d4e7e7d74f50e5dff55c7fa6c1be7cfd4e46 Mon Sep 17 00:00:00 2001 From: Siarhei Iukou Date: Mon, 9 Dec 2024 15:32:02 +0300 Subject: [PATCH 03/10] EPMRPP-9155 || fix url name --- app/src/controllers/instance/organizations/sagas.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/controllers/instance/organizations/sagas.js b/app/src/controllers/instance/organizations/sagas.js index 47111dcca4..524fe0f794 100644 --- a/app/src/controllers/instance/organizations/sagas.js +++ b/app/src/controllers/instance/organizations/sagas.js @@ -39,7 +39,7 @@ function* fetchFilteredOrganizations() { const filtersParams = yield select(filterQuerySelector); yield put( - fetchDataAction(NAMESPACE)(URLS.organizationSearch(), { + fetchDataAction(NAMESPACE)(URLS.organizationSearches(), { method: 'post', data: filtersParams, }), From ea0c634a6ae9f0638d0efb8b76575949759d2ccf Mon Sep 17 00:00:00 2001 From: Siarhei Iukou Date: Mon, 9 Dec 2024 15:54:00 +0300 Subject: [PATCH 04/10] EPMRPP-91556 || change conditions --- app/src/components/main/filterButton/constants.js | 14 +++++++------- .../projectsFilter/projectsFilter.jsx | 2 ++ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/app/src/components/main/filterButton/constants.js b/app/src/components/main/filterButton/constants.js index 84d5ffea99..c824da52be 100644 --- a/app/src/components/main/filterButton/constants.js +++ b/app/src/components/main/filterButton/constants.js @@ -38,14 +38,14 @@ export const getTimeRange = (formatMessage) => [ ]; export const getRangeComparisons = (formatMessage) => [ - { label: formatMessage(messages.equals), value: CONDITION_EQ }, - { label: formatMessage(messages.greaterOrEqual), value: CONDITION_GREATER_EQ }, - { label: formatMessage(messages.lessOrEqual), value: CONDITION_LESS_EQ }, + { label: formatMessage(messages.equals), value: CONDITION_EQ.toUpperCase() }, + { label: formatMessage(messages.greaterOrEqual), value: CONDITION_GREATER_EQ.toUpperCase() }, + { label: formatMessage(messages.lessOrEqual), value: CONDITION_LESS_EQ.toUpperCase() }, ]; export const getContainmentComparisons = (formatMessage) => [ - { label: formatMessage(messages.equals), value: CONDITION_EQ }, - { label: formatMessage(messages.notEqual), value: CONDITION_NOT_EQ }, - { label: formatMessage(messages.contains), value: CONDITION_CNT }, - { label: formatMessage(messages.notContains), value: CONDITION_NOT_CNT_EVENTS }, + { label: formatMessage(messages.equals), value: CONDITION_EQ.toUpperCase() }, + { label: formatMessage(messages.notEqual), value: CONDITION_NOT_EQ.toUpperCase() }, + { label: formatMessage(messages.contains), value: CONDITION_CNT.toUpperCase() }, + { label: formatMessage(messages.notContains), value: CONDITION_NOT_CNT_EVENTS.toUpperCase() }, ]; diff --git a/app/src/pages/organization/organizationProjectsPage/projectsPageHeader/projectsFilter/projectsFilter.jsx b/app/src/pages/organization/organizationProjectsPage/projectsPageHeader/projectsFilter/projectsFilter.jsx index e3a931d34a..b5ad5296b4 100644 --- a/app/src/pages/organization/organizationProjectsPage/projectsPageHeader/projectsFilter/projectsFilter.jsx +++ b/app/src/pages/organization/organizationProjectsPage/projectsPageHeader/projectsFilter/projectsFilter.jsx @@ -28,6 +28,7 @@ import { } from 'components/main/filterButton'; import { FilterButton } from 'components/main/filterButton/filterButton'; import { fetchFilteredProjectAction } from 'controllers/organization/projects'; +import { CONDITION_BETWEEN } from 'components/filterEntities/constants'; import { messages } from './messages'; export const ProjectsFilter = ({ @@ -49,6 +50,7 @@ export const ProjectsFilter = ({ value: timeRange[0].value, title: formatMessage(messages.lastRunDate), options: timeRange, + condition: CONDITION_BETWEEN.toUpperCase(), placeholder: formatMessage(messages.lastRunDatePlaceholder), }, [LAUNCHES_FILTER_NAME]: { From 9e4fb47adce4f51263f0f861b3e1988ddc57adbc Mon Sep 17 00:00:00 2001 From: Siarhei Iukou Date: Mon, 9 Dec 2024 15:55:57 +0300 Subject: [PATCH 05/10] EPMRPP-91556 || add help text in organization projects filter --- .../projectsPageHeader/projectsFilter/projectsFilter.jsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/pages/organization/organizationProjectsPage/projectsPageHeader/projectsFilter/projectsFilter.jsx b/app/src/pages/organization/organizationProjectsPage/projectsPageHeader/projectsFilter/projectsFilter.jsx index b5ad5296b4..5c0d918cf1 100644 --- a/app/src/pages/organization/organizationProjectsPage/projectsPageHeader/projectsFilter/projectsFilter.jsx +++ b/app/src/pages/organization/organizationProjectsPage/projectsPageHeader/projectsFilter/projectsFilter.jsx @@ -25,6 +25,7 @@ import { getContainmentComparisons, getRangeComparisons, getTimeRange, + messages as helpMessage, } from 'components/main/filterButton'; import { FilterButton } from 'components/main/filterButton/filterButton'; import { fetchFilteredProjectAction } from 'controllers/organization/projects'; @@ -60,6 +61,7 @@ export const ProjectsFilter = ({ placeholder: formatMessage(messages.launchesPlaceholder), options: rangeComparisons, condition: rangeComparisons[0].value, + helpText: formatMessage(helpMessage.helpText), withField: true, }, [TEAMMATES_FILTER_NAME]: { @@ -69,6 +71,7 @@ export const ProjectsFilter = ({ placeholder: formatMessage(messages.usersPlaceholder), options: rangeComparisons, condition: rangeComparisons[0].value, + helpText: formatMessage(helpMessage.helpText), withField: true, }, [FILTER_NAME]: { From 2f5cb388644baa88451fa19aef0e87da6c1712ef Mon Sep 17 00:00:00 2001 From: Siarhei Iukou Date: Tue, 10 Dec 2024 07:18:45 +0300 Subject: [PATCH 06/10] EPMRPP-91556 || fix last run date --- app/src/components/filterEntities/utils.js | 48 +++++++++++++++++++ .../instance/organizations/sagas.js | 3 +- .../organization/projects/sagas.js | 2 + .../organizationsFilter.jsx | 2 + 4 files changed, 54 insertions(+), 1 deletion(-) diff --git a/app/src/components/filterEntities/utils.js b/app/src/components/filterEntities/utils.js index 7524271626..4fdaf8ad61 100644 --- a/app/src/components/filterEntities/utils.js +++ b/app/src/components/filterEntities/utils.js @@ -14,6 +14,10 @@ * limitations under the License. */ +import moment from 'moment/moment'; +import { getMinutesFromTimestamp } from 'common/utils'; +import { LAST_RUN_DATE_FILTER_NAME } from 'components/main/filterButton'; + export function bindDefaultValue(key, options = {}) { const { filterValues } = this.props; if (key in filterValues) { @@ -25,3 +29,47 @@ export function bindDefaultValue(key, options = {}) { ...options, }; } + +const getFormattedDate = (value) => { + const utcString = moment().format('ZZ'); + const calculateStartDate = (days) => + moment() + .startOf('day') + .subtract(days - 1, 'days') + .valueOf(); + const endOfToday = moment() + .add(1, 'days') + .startOf('day') + .valueOf(); + let start = null; + switch (value) { + case 'today': + start = calculateStartDate(1); + break; + case 'last2days': + start = calculateStartDate(2); + break; + case 'last7days': + start = calculateStartDate(7); + break; + case 'last30days': + start = calculateStartDate(30); + break; + default: + break; + } + return `${getMinutesFromTimestamp(start)};${getMinutesFromTimestamp(endOfToday)};${utcString}`; +}; + +export const updateFormatDate = (filtersParams) => { + const { search_criteria: searchCriteria } = filtersParams; + + const lastRunDateFilterIndex = Object.values(searchCriteria).findIndex( + (el) => el.filter_key === LAST_RUN_DATE_FILTER_NAME, + ); + if (lastRunDateFilterIndex !== -1) { + searchCriteria[lastRunDateFilterIndex].value = getFormattedDate( + searchCriteria[lastRunDateFilterIndex].value, + ); + } +}; diff --git a/app/src/controllers/instance/organizations/sagas.js b/app/src/controllers/instance/organizations/sagas.js index 524fe0f794..5e8513b6e8 100644 --- a/app/src/controllers/instance/organizations/sagas.js +++ b/app/src/controllers/instance/organizations/sagas.js @@ -18,6 +18,7 @@ import { takeEvery, all, put, select } from 'redux-saga/effects'; import { URLS } from 'common/urls'; import { showDefaultErrorNotification } from 'controllers/notification'; import { fetchDataAction } from 'controllers/fetch'; +import { updateFormatDate } from 'components/filterEntities/utils'; import { filterQuerySelector, querySelector } from './selectors'; import { FETCH_ORGANIZATIONS, FETCH_FILTERED_ORGANIZATIONS, NAMESPACE } from './constants'; @@ -37,7 +38,7 @@ function* watchFetchOrganizations() { function* fetchFilteredOrganizations() { const filtersParams = yield select(filterQuerySelector); - + updateFormatDate(filtersParams); yield put( fetchDataAction(NAMESPACE)(URLS.organizationSearches(), { method: 'post', diff --git a/app/src/controllers/organization/projects/sagas.js b/app/src/controllers/organization/projects/sagas.js index 2e6610f8ff..4359f244d2 100644 --- a/app/src/controllers/organization/projects/sagas.js +++ b/app/src/controllers/organization/projects/sagas.js @@ -20,6 +20,7 @@ import { URLS } from 'common/urls'; import { fetch } from 'common/utils'; import { hideModalAction } from 'controllers/modal'; import { NOTIFICATION_TYPES, showNotification } from 'controllers/notification'; +import { updateFormatDate } from 'components/filterEntities/utils'; import { CREATE_PROJECT, FETCH_ORGANIZATION_PROJECTS, @@ -121,6 +122,7 @@ function* watchDeleteProject() { function* fetchFilteredProjects() { const activeOrganizationId = yield select(activeOrganizationIdSelector); const filtersParams = yield select(filterQuerySelector); + updateFormatDate(filtersParams); yield put( fetchDataAction(NAMESPACE)(URLS.organizationProjectsSearches(activeOrganizationId), { diff --git a/app/src/pages/instance/organizationsPage/organizationsPageHeader/organizationsFilter/organizationsFilter.jsx b/app/src/pages/instance/organizationsPage/organizationsPageHeader/organizationsFilter/organizationsFilter.jsx index 66fdb0275f..2eb3126ca6 100644 --- a/app/src/pages/instance/organizationsPage/organizationsPageHeader/organizationsFilter/organizationsFilter.jsx +++ b/app/src/pages/instance/organizationsPage/organizationsPageHeader/organizationsFilter/organizationsFilter.jsx @@ -29,6 +29,7 @@ import { } from 'components/main/filterButton'; import { FilterButton } from 'components/main/filterButton/filterButton'; import { fetchFilteredOrganizationsAction } from 'controllers/instance/organizations'; +import { CONDITION_BETWEEN } from 'components/filterEntities/constants'; import { messages } from './messages'; export const OrganizationsFilter = ({ @@ -50,6 +51,7 @@ export const OrganizationsFilter = ({ value: timeRange[0].value, title: formatMessage(messages.lastRunDate), options: timeRange, + condition: CONDITION_BETWEEN.toUpperCase(), placeholder: formatMessage(messages.lastRunDatePlaceholder), }, [LAUNCHES_FILTER_NAME]: { From c4cfe6300adeda8dc4e87d7e28ac94aae8477325 Mon Sep 17 00:00:00 2001 From: Siarhei Iukou Date: Tue, 10 Dec 2024 09:03:03 +0300 Subject: [PATCH 07/10] EPMRPP-91556 || Code Review fix - 2 --- app/package-lock.json | 8 ++++---- app/package.json | 2 +- app/src/common/img/newIcons/filter-filled-inline.svg | 3 --- app/src/common/img/newIcons/filters-outline-inline.svg | 3 --- app/src/components/main/filterButton/filterButton.jsx | 7 ++----- app/src/controllers/pages/selectors.js | 9 ++++++--- .../projectTeamPageHeader/projectTeamPageHeader.jsx | 8 ++++---- 7 files changed, 17 insertions(+), 23 deletions(-) delete mode 100644 app/src/common/img/newIcons/filter-filled-inline.svg delete mode 100644 app/src/common/img/newIcons/filters-outline-inline.svg diff --git a/app/package-lock.json b/app/package-lock.json index c4d471cb76..e490c72df6 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -12,7 +12,7 @@ "@formatjs/intl-pluralrules": "1.3.9", "@formatjs/intl-relativetimeformat": "4.5.1", "@formatjs/intl-utils": "1.6.0", - "@reportportal/ui-kit": "^0.0.1-alpha.34", + "@reportportal/ui-kit": "^0.0.1-alpha.35", "axios": "1.6.4", "c3": "0.7.20", "chart.js": "2.9.4", @@ -3677,9 +3677,9 @@ "integrity": "sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA==" }, "node_modules/@reportportal/ui-kit": { - "version": "0.0.1-alpha.34", - "resolved": "https://registry.npmjs.org/@reportportal/ui-kit/-/ui-kit-0.0.1-alpha.34.tgz", - "integrity": "sha512-VQfwBAeZ9otmrYTpERBOoGhYMkncnA4Upchw1LH7AflFP26YGlN8Z/nSnTcB5kbqnRDGAFn8/UF2Z7G3/Q09WQ==", + "version": "0.0.1-alpha.35", + "resolved": "https://registry.npmjs.org/@reportportal/ui-kit/-/ui-kit-0.0.1-alpha.35.tgz", + "integrity": "sha512-Ru2VkZfy3KAP62JC4sU1uYYA3EORL6JcaUPFVnNks6Fn+ezPEIdKfI9AAQn0/d1PKVxZT8qx7jvlL+IkNjA5Pg==", "dependencies": { "@floating-ui/react": "^0.26.16", "@floating-ui/react-dom": "^2.0.1", diff --git a/app/package.json b/app/package.json index 37b89eed27..7aebfdbde6 100644 --- a/app/package.json +++ b/app/package.json @@ -25,7 +25,7 @@ "@formatjs/intl-pluralrules": "1.3.9", "@formatjs/intl-relativetimeformat": "4.5.1", "@formatjs/intl-utils": "1.6.0", - "@reportportal/ui-kit": "^0.0.1-alpha.34", + "@reportportal/ui-kit": "^0.0.1-alpha.35", "axios": "1.6.4", "c3": "0.7.20", "chart.js": "2.9.4", diff --git a/app/src/common/img/newIcons/filter-filled-inline.svg b/app/src/common/img/newIcons/filter-filled-inline.svg deleted file mode 100644 index 16871d0ff7..0000000000 --- a/app/src/common/img/newIcons/filter-filled-inline.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/src/common/img/newIcons/filters-outline-inline.svg b/app/src/common/img/newIcons/filters-outline-inline.svg deleted file mode 100644 index d0cb45fbdc..0000000000 --- a/app/src/common/img/newIcons/filters-outline-inline.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/src/components/main/filterButton/filterButton.jsx b/app/src/components/main/filterButton/filterButton.jsx index 2bbc115040..92c8b34cc1 100644 --- a/app/src/components/main/filterButton/filterButton.jsx +++ b/app/src/components/main/filterButton/filterButton.jsx @@ -17,10 +17,7 @@ import { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames/bind'; -import { Popover } from '@reportportal/ui-kit'; -import Parser from 'html-react-parser'; -import filterIcon from 'common/img/newIcons/filters-outline-inline.svg'; -import filterFilledIcon from 'common/img/newIcons/filter-filled-inline.svg'; +import { Popover, FilterOutlineIcon, FilterFilledIcon } from '@reportportal/ui-kit'; import { FilterContent } from './filterContent'; import styles from './filterButton.scss'; @@ -92,7 +89,7 @@ export const FilterButton = ({ tabIndex={0} > - {appliedFiltersCount ? Parser(filterFilledIcon) : Parser(filterIcon)} + {appliedFiltersCount ? : } {appliedFiltersCount ? ( {appliedFiltersCount} diff --git a/app/src/controllers/pages/selectors.js b/app/src/controllers/pages/selectors.js index bef6de15cb..94578e6d00 100644 --- a/app/src/controllers/pages/selectors.js +++ b/app/src/controllers/pages/selectors.js @@ -161,15 +161,18 @@ export const createFilterQuerySelector = ({ namespace, } = {}) => createSelector( - createQueryParametersSelector({ + createAlternativeQueryParametersSelector({ defaultPagination, defaultSorting, sortingKey, namespace, }), - ({ [SIZE_KEY]: limit, [SORTING_ORDER_KEY]: sort, [PAGE_KEY]: pageNumber, ...rest }) => { + ({ limit, sort, offset, order, ...rest }) => { return { - ...getAlternativePaginationAndSortParams(sort, limit, pageNumber), + limit, + sort, + offset, + order, search_criteria: getAppliedFilters(rest)?.search_criterias, }; }, diff --git a/app/src/pages/organization/projectTeamPage/projectTeamPageHeader/projectTeamPageHeader.jsx b/app/src/pages/organization/projectTeamPage/projectTeamPageHeader/projectTeamPageHeader.jsx index f943909ce2..a5c3b96ed4 100644 --- a/app/src/pages/organization/projectTeamPage/projectTeamPageHeader/projectTeamPageHeader.jsx +++ b/app/src/pages/organization/projectTeamPage/projectTeamPageHeader/projectTeamPageHeader.jsx @@ -17,15 +17,13 @@ import React from 'react'; import { useSelector } from 'react-redux'; import PropTypes from 'prop-types'; -import Parser from 'html-react-parser'; import classNames from 'classnames/bind'; -import { Button } from '@reportportal/ui-kit'; +import { Button, FilterOutlineIcon } from '@reportportal/ui-kit'; import { useIntl } from 'react-intl'; import { projectMembersSelector } from 'controllers/project'; import { SearchField } from 'components/fields/searchField'; import { NAMESPACE, SEARCH_KEY } from 'controllers/members/constants'; import { withFilter } from 'controllers/filter'; -import filterIcon from 'common/img/newIcons/filters-outline-inline.svg'; import { PROJECT_PAGE_EVENTS } from 'components/main/analytics/events/ga4Events/projectPageEvents'; import { messages } from '../../common/membersPage/membersPageHeader/messages'; import { MembersPageHeader } from '../../common/membersPage/membersPageHeader'; @@ -61,7 +59,9 @@ export const ProjectTeamPageHeader = ({ placeholder={formatMessage(messages.searchPlaceholder)} event={PROJECT_PAGE_EVENTS.SEARCH_PROJECT_TEAM_FIELD} /> - {Parser(filterIcon)} + + +
{hasPermission && (