diff --git a/app/src/components/inputs/inputDropdown/inputDropdown.jsx b/app/src/components/inputs/inputDropdown/inputDropdown.jsx
index 01a92171ec..4a2ab0d4e9 100644
--- a/app/src/components/inputs/inputDropdown/inputDropdown.jsx
+++ b/app/src/components/inputs/inputDropdown/inputDropdown.jsx
@@ -45,6 +45,7 @@ export class InputDropdown extends Component {
independentGroupSelection: PropTypes.bool,
customClasses: PropTypes.object,
title: PropTypes.string,
+ readOnly: PropTypes.bool,
};
static defaultProps = {
@@ -70,6 +71,7 @@ export class InputDropdown extends Component {
opened: '',
},
title: '',
+ readOnly: false,
};
state = {
opened: false,
@@ -233,48 +235,51 @@ export class InputDropdown extends Component {
error,
touched,
'mobile-disabled': mobileDisabled,
+ readonly: this.props.readOnly,
})}
- onClick={this.onClickSelectBlock}
+ onClick={this.props.readOnly ? () => {} : this.onClickSelectBlock}
>
{this.displayedValue()}
-
+ {!this.props.readOnly && }
)}
-
- {({ placement, ref, style, scheduleUpdate }) => {
- this.updatePosition = scheduleUpdate;
- return (
-
- {multiple && selectAll && (
-
- All
-
- )}
-
- {this.renderOptions()}
-
-
- );
- }}
-
+ {!this.props.readOnly && (
+
+ {({ placement, ref, style, scheduleUpdate }) => {
+ this.updatePosition = scheduleUpdate;
+ return (
+
+ {multiple && selectAll && (
+
+ All
+
+ )}
+
+ {this.renderOptions()}
+
+
+ );
+ }}
+
+ )}
);
diff --git a/app/src/components/inputs/inputDropdown/inputDropdown.scss b/app/src/components/inputs/inputDropdown/inputDropdown.scss
index 4f5ecbd10d..8903db1365 100644
--- a/app/src/components/inputs/inputDropdown/inputDropdown.scss
+++ b/app/src/components/inputs/inputDropdown/inputDropdown.scss
@@ -18,21 +18,25 @@
display: inline-block;
width: 100%;
}
+
.dropdown {
position: relative;
display: inline-block;
width: 100%;
height: 30px;
}
+
.opened {
.arrow {
transform: rotate(180deg);
}
+
.select-block {
border-color: $COLOR--topaz;
box-shadow: 0 0 2px 0 rgba($COLOR--topaz, 0.7);
}
}
+
.select-block {
display: inline-block;
width: 100%;
@@ -42,6 +46,7 @@
border: 1px solid $COLOR--gray-80;
background-color: $COLOR--white-two;
cursor: pointer;
+
&.dark-view {
border: none;
border-radius: 4px;
@@ -58,20 +63,30 @@
box-shadow: 0 0 2px 0 rgba($COLOR--orange-red, 0.7);
}
+ &.readonly {
+ cursor: default;
+ border: none;
+ padding-left: 0;
+ }
+
&.disabled {
background-color: $COLOR--gray-91;
cursor: default;
+
& .value.dark-view {
color: $COLOR--dirty-gray;
}
+
&.dark-view {
background-color: $COLOR--black-1;
}
+
&:hover {
border-color: $COLOR--gray-80;
}
}
}
+
.value {
display: inline-block;
height: 100%;
@@ -83,10 +98,12 @@
text-overflow: ellipsis;
white-space: nowrap;
color: $COLOR--charcoal-grey;
+
&.dark-view {
color: $COLOR--white-two;
}
}
+
.arrow {
position: absolute;
right: 12px;
@@ -101,10 +118,12 @@
box-sizing: border-box;
color: $COLOR--charcoal-grey;
transition: transform 200ms linear;
+
&.dark-view {
color: $COLOR--white-two;
}
}
+
.select-list {
position: absolute;
top: 100%;
@@ -119,16 +138,19 @@
box-shadow: 0 6px 12px $COLOR--gray-80;
z-index: $Z-INDEX-POPUP;
cursor: pointer;
+
&.dark-view {
background-color: $COLOR--darkmode-gray-400;
color: $COLOR--darkmode-gray-100;
box-shadow: none;
border: 0;
}
+
&.opened {
display: block;
}
}
+
.select-all-block {
width: 100%;
height: 37px;
@@ -144,6 +166,7 @@
box-sizing: border-box;
}
}
+
.select-all {
display: inline-block;
width: 100%;
@@ -159,6 +182,7 @@
background-color: $COLOR--tealish-hover;
}
}
+
.select-item {
width: 100%;
height: 30px;
@@ -168,6 +192,7 @@
background-color: $COLOR--tealish-hover;
}
}
+
.mobile-disabled {
@media (max-width: $SCREEN_XS_MAX) {
background-color: $COLOR--gray-91;
diff --git a/app/src/pages/inside/common/launchSuiteGrid/launchSuiteGrid.jsx b/app/src/pages/inside/common/launchSuiteGrid/launchSuiteGrid.jsx
index 7b1e181987..a8d2ec4a51 100644
--- a/app/src/pages/inside/common/launchSuiteGrid/launchSuiteGrid.jsx
+++ b/app/src/pages/inside/common/launchSuiteGrid/launchSuiteGrid.jsx
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 EPAM Systems
+ * 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.
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-import React, { PureComponent, Fragment } from 'react';
-import track from 'react-tracking';
-import { injectIntl } from 'react-intl';
+import React, { useCallback, useMemo } from 'react';
+import { useTracking } from 'react-tracking';
+import { useIntl } from 'react-intl';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
import {
@@ -50,6 +50,9 @@ import {
STATS_TI_TOTAL,
} from 'common/constants/statistics';
import { formatAttribute } from 'common/utils/attributeUtils';
+import { useSelector } from 'react-redux';
+import { canBulkEditItems } from 'common/utils/permissions';
+import { userRolesSelector } from 'controllers/pages';
import { Hamburger } from './hamburger';
import { ExecutionStatistics } from './executionStatistics';
import { DefectStatistics } from './defectStatistics';
@@ -66,18 +69,19 @@ HamburgerColumn.propTypes = {
className: PropTypes.string.isRequired,
};
-const NameColumn = ({ className, ...rest }) => (
+const NameColumn = ({ className, customProps: { hideEdit }, ...rest }) => (
<>
- {readOnly ? (
-
+ {readOnly && withIndicator ? (
+
) : (
)}
diff --git a/app/src/pages/inside/common/suiteTestToolbar/actionPanel/testItemActionPanel.jsx b/app/src/pages/inside/common/suiteTestToolbar/actionPanel/testItemActionPanel.jsx
index ac148f327d..4605048f22 100644
--- a/app/src/pages/inside/common/suiteTestToolbar/actionPanel/testItemActionPanel.jsx
+++ b/app/src/pages/inside/common/suiteTestToolbar/actionPanel/testItemActionPanel.jsx
@@ -14,21 +14,20 @@
* limitations under the License.
*/
-import React, { Component } from 'react';
-import track from 'react-tracking';
+import React from 'react';
+import { useTracking } from 'react-tracking';
import PropTypes from 'prop-types';
import classNames from 'classnames/bind';
-import { connect } from 'react-redux';
-import { injectIntl, FormattedMessage } from 'react-intl';
+import { useDispatch, useSelector } from 'react-redux';
+import { FormattedMessage, useIntl } from 'react-intl';
import { breadcrumbsSelector, levelSelector, restorePathAction } from 'controllers/testItem';
-import { userRolesType } from 'common/constants/projectRoles';
import { userRolesSelector } from 'controllers/pages';
import {
availableBtsIntegrationsSelector,
isBtsPluginsExistSelector,
enabledBtsPluginsSelector,
} from 'controllers/plugins';
-import { Breadcrumbs, breadcrumbDescriptorShape } from 'components/main/breadcrumbs';
+import { Breadcrumbs } from 'components/main/breadcrumbs';
import { STEP_PAGE_EVENTS } from 'components/main/analytics/events';
import { GhostButton } from 'components/buttons/ghostButton';
import { GhostMenuButton } from 'components/buttons/ghostMenuButton';
@@ -40,101 +39,55 @@ import { createStepActionDescriptors } from 'pages/inside/common/utils';
import { ParentInfo } from 'pages/inside/common/infoLine/parentInfo';
import { pageEventsMap } from 'components/main/analytics';
import { TO_INVESTIGATE_LOCATOR_PREFIX } from 'common/constants/defectTypes';
+import { canWorkWithTests } from 'common/utils/permissions/permissions';
import styles from './testItemActionPanel.scss';
const cx = classNames.bind(styles);
-@connect(
- (state) => ({
- breadcrumbs: breadcrumbsSelector(state),
- level: levelSelector(state),
- btsIntegrations: availableBtsIntegrationsSelector(state),
- userRoles: userRolesSelector(state),
- isBtsPluginsExist: isBtsPluginsExistSelector(state),
- enabledBtsPlugins: enabledBtsPluginsSelector(state),
- }),
- {
- restorePath: restorePathAction,
- },
-)
-@injectIntl
-@track()
-export class TestItemActionPanel extends Component {
- static propTypes = {
- debugMode: PropTypes.bool,
- onRefresh: PropTypes.func,
- breadcrumbs: PropTypes.arrayOf(breadcrumbDescriptorShape),
- userRoles: userRolesType,
- restorePath: PropTypes.func,
- showBreadcrumbs: PropTypes.bool,
- hasErrors: PropTypes.bool,
- intl: PropTypes.object.isRequired,
- hasValidItems: PropTypes.bool,
- level: PropTypes.string,
- onProceedValidItems: PropTypes.func,
- selectedItems: PropTypes.array,
- onEditItems: PropTypes.func,
- onEditDefects: PropTypes.func,
- onPostIssue: PropTypes.func,
- onLinkIssue: PropTypes.func,
- onUnlinkIssue: PropTypes.func,
- onIgnoreInAA: PropTypes.func,
- onIncludeInAA: PropTypes.func,
- onDelete: PropTypes.func,
- btsIntegrations: PropTypes.array,
- deleteDisabled: PropTypes.bool,
- tracking: PropTypes.shape({
- trackEvent: PropTypes.func,
- getTrackingData: PropTypes.func,
- }).isRequired,
- isBtsPluginsExist: PropTypes.bool,
- enabledBtsPlugins: PropTypes.array,
- parentItem: PropTypes.object,
+export const TestItemActionPanel = ({
+ debugMode,
+ onRefresh,
+ showBreadcrumbs,
+ hasErrors,
+ hasValidItems,
+ onProceedValidItems,
+ selectedItems,
+ onEditItems,
+ onEditDefects,
+ onPostIssue,
+ onLinkIssue,
+ onUnlinkIssue,
+ onIgnoreInAA,
+ onIncludeInAA,
+ onDelete,
+ deleteDisabled,
+ parentItem,
+}) => {
+ const breadcrumbs = useSelector(breadcrumbsSelector);
+ const level = useSelector(levelSelector);
+ const btsIntegrations = useSelector(availableBtsIntegrationsSelector);
+ const userRoles = useSelector(userRolesSelector);
+ const isBtsPluginsExist = useSelector(isBtsPluginsExistSelector);
+ const enabledBtsPlugins = useSelector(enabledBtsPluginsSelector);
+ const { formatMessage } = useIntl();
+ const { trackEvent } = useTracking();
+ const dispatch = useDispatch();
+
+ const hasAccessToActions = canWorkWithTests(userRoles);
+ const onClickRefresh = () => {
+ trackEvent(pageEventsMap[level].CLICK_REFRESH_BTN);
+ onRefresh();
};
- static defaultProps = {
- debugMode: false,
- onRefresh: () => {},
- breadcrumbs: [],
- userRoles: {},
- restorePath: () => {},
- level: '',
- showBreadcrumbs: true,
- hasErrors: false,
- hasValidItems: false,
- onProceedValidItems: () => {},
- selectedItems: [],
- onEditItems: () => {},
- onEditDefects: () => {},
- onPostIssue: () => {},
- onLinkIssue: () => {},
- onUnlinkIssue: () => {},
- onIgnoreInAA: () => {},
- onIncludeInAA: () => {},
- onDelete: () => {},
- btsIntegrations: [],
- deleteDisabled: false,
- isBtsPluginsExist: false,
- enabledBtsPlugins: [],
- parentItem: null,
- };
-
- onClickRefresh = () => {
- this.props.tracking.trackEvent(pageEventsMap[this.props.level].CLICK_REFRESH_BTN);
- this.props.onRefresh();
- };
-
- onEditDefects = () => {
- const { tracking, selectedItems, onEditDefects, hasErrors } = this.props;
-
+ const handleEditDefects = () => {
if (!hasErrors) {
const defectFromTIGroup =
selectedItems.length > 1 && selectedItems.some(({ issue }) => issue)
? undefined
: selectedItems[0].issue?.issueType.startsWith(TO_INVESTIGATE_LOCATOR_PREFIX);
- tracking.trackEvent(
- pageEventsMap[this.props.level].MAKE_DECISION_MODAL_EVENTS.getOpenModalEvent(
+ trackEvent(
+ pageEventsMap[level].MAKE_DECISION_MODAL_EVENTS.getOpenModalEvent(
defectFromTIGroup,
'actions',
),
@@ -144,34 +97,17 @@ export class TestItemActionPanel extends Component {
onEditDefects(selectedItems);
};
- onPostIssue = () => {
- const { tracking, onPostIssue } = this.props;
- tracking.trackEvent(STEP_PAGE_EVENTS.POST_ISSUE_ACTION);
+ const handlePostIssue = () => {
+ trackEvent(STEP_PAGE_EVENTS.POST_ISSUE_ACTION);
onPostIssue();
};
- onLinkIssue = () => {
- const { tracking, onLinkIssue } = this.props;
- tracking.trackEvent(STEP_PAGE_EVENTS.LINK_ISSUE_ACTION);
+ const handleLinkIssue = () => {
+ trackEvent(STEP_PAGE_EVENTS.LINK_ISSUE_ACTION);
onLinkIssue();
};
- getStepActionDescriptors = () => {
- const {
- intl: { formatMessage },
- debugMode,
- onEditItems,
- onUnlinkIssue,
- onIgnoreInAA,
- onIncludeInAA,
- onDelete,
- btsIntegrations,
- isBtsPluginsExist,
- enabledBtsPlugins,
- userRoles,
- selectedItems,
- } = this.props;
-
+ const getStepActionDescriptors = () => {
return createStepActionDescriptors({
formatMessage,
debugMode,
@@ -185,100 +121,117 @@ export class TestItemActionPanel extends Component {
enabledBtsPlugins,
userRoles,
selectedItems,
- onEditDefects: this.onEditDefects,
- onPostIssue: this.onPostIssue,
- onLinkIssue: this.onLinkIssue,
+ onEditDefects: handleEditDefects,
+ onPostIssue: handlePostIssue,
+ onLinkIssue: handleLinkIssue,
});
};
- createSuiteActionDescriptors = () => {
- const { intl, deleteDisabled, onDelete, onEditItems, userRoles } = this.props;
-
- return [
- {
- label: intl.formatMessage(COMMON_LOCALE_KEYS.EDIT_ITEMS),
- value: 'action-edit',
- hidden: !canBulkEditItems(userRoles),
- onClick: onEditItems,
- },
- {
- label: intl.formatMessage(COMMON_LOCALE_KEYS.DELETE),
- value: 'action-delete',
- hidden: deleteDisabled,
- onClick: onDelete,
- },
- ];
+ const createSuiteActionDescriptors = () => [
+ {
+ label: formatMessage(COMMON_LOCALE_KEYS.EDIT_ITEMS),
+ value: 'action-edit',
+ hidden: !canBulkEditItems(userRoles),
+ onClick: onEditItems,
+ },
+ {
+ label: formatMessage(COMMON_LOCALE_KEYS.DELETE),
+ value: 'action-delete',
+ hidden: deleteDisabled,
+ onClick: onDelete,
+ },
+ ];
+
+ const onClickActionsButton = () => {
+ trackEvent(pageEventsMap[level].CLICK_ACTIONS_BTN);
};
- checkVisibility = (levels) => levels.some((level) => this.props.level === level);
-
- onClickActionsButton = () => {
- const { tracking, level } = this.props;
- tracking.trackEvent(pageEventsMap[level].CLICK_ACTIONS_BTN);
- };
-
- render() {
- const {
- breadcrumbs,
- restorePath,
- showBreadcrumbs,
- hasErrors,
- intl,
- hasValidItems,
- onProceedValidItems,
- selectedItems,
- level,
- parentItem,
- } = this.props;
- const actionDescriptors =
- level === LEVEL_STEP ? this.getStepActionDescriptors() : this.createSuiteActionDescriptors();
-
- return (
-
- {showBreadcrumbs && (
-
- )}
- {hasErrors && (
-
- {intl.formatMessage(COMMON_LOCALE_KEYS.PROCEED_VALID_ITEMS)}
-
- )}
-
- {parentItem &&
}
+ const actionDescriptors =
+ level === LEVEL_STEP ? getStepActionDescriptors() : createSuiteActionDescriptors();
+
+ return (
+
+ {showBreadcrumbs && (
+
dispatch(restorePathAction())}
+ />
+ )}
+ {hasErrors && (
+
+ {formatMessage(COMMON_LOCALE_KEYS.PROCEED_VALID_ITEMS)}
+
+ )}
+
+ {parentItem &&
}
+ {hasAccessToActions && (
-
-
-
-
-
+ )}
+
+
+
+
- );
- }
-}
+
+ );
+};
+
+TestItemActionPanel.propTypes = {
+ debugMode: PropTypes.bool,
+ onRefresh: PropTypes.func,
+ showBreadcrumbs: PropTypes.bool,
+ hasErrors: PropTypes.bool,
+ hasValidItems: PropTypes.bool,
+ onProceedValidItems: PropTypes.func,
+ selectedItems: PropTypes.array,
+ onEditItems: PropTypes.func,
+ onEditDefects: PropTypes.func,
+ onPostIssue: PropTypes.func,
+ onLinkIssue: PropTypes.func,
+ onUnlinkIssue: PropTypes.func,
+ onIgnoreInAA: PropTypes.func,
+ onIncludeInAA: PropTypes.func,
+ onDelete: PropTypes.func,
+ deleteDisabled: PropTypes.bool,
+ parentItem: PropTypes.object,
+};
+
+TestItemActionPanel.defaultProps = {
+ debugMode: false,
+ onRefresh: () => {},
+ showBreadcrumbs: true,
+ hasErrors: false,
+ hasValidItems: false,
+ onProceedValidItems: () => {},
+ selectedItems: [],
+ onEditItems: () => {},
+ onEditDefects: () => {},
+ onPostIssue: () => {},
+ onLinkIssue: () => {},
+ onUnlinkIssue: () => {},
+ onIgnoreInAA: () => {},
+ onIncludeInAA: () => {},
+ onDelete: () => {},
+ deleteDisabled: false,
+ parentItem: null,
+};
diff --git a/app/src/pages/inside/stepPage/stepGrid/defectType/defectType.jsx b/app/src/pages/inside/stepPage/stepGrid/defectType/defectType.jsx
index 63910ea33a..ffb0adb202 100644
--- a/app/src/pages/inside/stepPage/stepGrid/defectType/defectType.jsx
+++ b/app/src/pages/inside/stepPage/stepGrid/defectType/defectType.jsx
@@ -25,6 +25,9 @@ import PencilIcon from 'common/img/pencil-icon-inline.svg';
import { DefectTypeItem } from 'pages/inside/common/defectTypeItem';
import { PatternAnalyzedLabel } from 'pages/inside/common/patternAnalyzedLabel';
import { TO_INVESTIGATE_LOCATOR_PREFIX } from 'common/constants/defectTypes';
+import { canManageBTSIssues, canWorkWithDefectTypes } from 'common/utils/permissions/permissions';
+import { useSelector } from 'react-redux';
+import { userRolesSelector } from 'controllers/pages';
import { AutoAnalyzedLabel } from './autoAnalyzedLabel';
import { IssueList } from './issueList';
import styles from './defectType.scss';
@@ -72,6 +75,9 @@ PALabel.propTypes = {
export const DefectType = ({ issue, onEdit, onRemove, patternTemplates, events, disabled }) => {
const { trackEvent } = useTracking();
+ const userRoles = useSelector(userRolesSelector);
+ const canUnlinkIssue = canManageBTSIssues(userRoles);
+ const canChangeDefectTypes = canWorkWithDefectTypes(userRoles);
const eventData = issue.issueType.startsWith(TO_INVESTIGATE_LOCATOR_PREFIX);
const onClickEdit = (event) => {
event && trackEvent(event);
@@ -92,18 +98,29 @@ export const DefectType = ({ issue, onEdit, onRemove, patternTemplates, events,
{issue.issueType && (
onClickEdit(events.onEditEvent?.(eventData))}
+ onClick={
+ disabled || !canChangeDefectTypes
+ ? null
+ : () => onClickEdit(events.onEditEvent?.(eventData))
+ }
/>
)}
- onClickEdit(events.onEditEvent?.(eventData, 'edit'))}
- >
- {Parser(PencilIcon)}
-
+ {canChangeDefectTypes && (
+ onClickEdit(events.onEditEvent?.(eventData, 'edit'))}
+ >
+ {Parser(PencilIcon)}
+
+ )}
-
+
diff --git a/app/src/pages/inside/stepPage/stepGrid/itemInfoWithRetries/itemInfoWithRetries.jsx b/app/src/pages/inside/stepPage/stepGrid/itemInfoWithRetries/itemInfoWithRetries.jsx
index 6387c100b2..c4ef6e5c9a 100644
--- a/app/src/pages/inside/stepPage/stepGrid/itemInfoWithRetries/itemInfoWithRetries.jsx
+++ b/app/src/pages/inside/stepPage/stepGrid/itemInfoWithRetries/itemInfoWithRetries.jsx
@@ -36,10 +36,12 @@ export class ItemInfoWithRetries extends Component {
trackEvent: PropTypes.func,
getTrackingData: PropTypes.func,
}).isRequired,
+ hideEdit: PropTypes.bool,
};
static defaultProps = {
expanded: false,
+ hideEdit: false,
};
constructor(props) {
diff --git a/app/src/pages/inside/stepPage/stepGrid/stepGrid.jsx b/app/src/pages/inside/stepPage/stepGrid/stepGrid.jsx
index 01fc638a39..9b64fa4859 100644
--- a/app/src/pages/inside/stepPage/stepGrid/stepGrid.jsx
+++ b/app/src/pages/inside/stepPage/stepGrid/stepGrid.jsx
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 EPAM Systems
+ * 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.
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-import React, { Component, Fragment } from 'react';
-import track from 'react-tracking';
+import React, { useMemo } from 'react';
+import { useTracking } from 'react-tracking';
import PropTypes from 'prop-types';
-import { injectIntl } from 'react-intl';
+import { useIntl } from 'react-intl';
import classNames from 'classnames/bind';
import { Grid } from 'components/main/grid';
import { AbsRelTime } from 'components/main/absRelTime';
@@ -36,10 +36,13 @@ import {
import { NoItemMessage } from 'components/main/noItemMessage';
import { formatAttribute } from 'common/utils/attributeUtils';
import { StatusDropdown } from 'pages/inside/common/statusDropdown/statusDropdown';
-import { canWorkWithDefectTypes, canWorkWithTests } from 'common/utils/permissions/permissions';
+import {
+ canBulkEditItems,
+ canWorkWithDefectTypes,
+ canWorkWithTests,
+} from 'common/utils/permissions/permissions';
import { userRolesSelector } from 'controllers/pages';
-import { userRolesType } from 'common/constants/projectRoles';
-import { connect } from 'react-redux';
+import { useSelector } from 'react-redux';
import { PredefinedFilterSwitcher } from './predefinedFilterSwitcher';
import { DefectType } from './defectType';
import { GroupHeader } from './groupHeader';
@@ -68,6 +71,7 @@ MethodTypeColumn.defaultProps = {
const NameColumn = ({ className, customProps, ...rest }) => (
{
+const StatusColumn = ({ className, value, customProps: { onChange, fetchFunc, readOnly } }) => {
const { id, status, attributes, description } = value;
return (
@@ -96,7 +100,7 @@ const StatusColumn = ({ className, value, customProps: { onChange, fetchFunc, di
description={description}
onChange={onChange}
fetchFunc={fetchFunc}
- disabled={disabled}
+ readOnly={readOnly}
/>
);
@@ -108,7 +112,7 @@ StatusColumn.propTypes = {
formatMessage: PropTypes.func.isRequired,
onChange: PropTypes.func,
fetchFunc: PropTypes.func,
- disabled: PropTypes.bool,
+ readOnly: PropTypes.bool,
}).isRequired,
};
StatusColumn.defaultProps = {
@@ -175,82 +179,57 @@ PredefinedFilterSwitcherCell.defaultProps = {
className: null,
};
-@connect((state) => ({
- userRoles: userRolesSelector(state),
-}))
-@injectIntl
-@track()
-export class StepGrid extends Component {
- static propTypes = {
- data: PropTypes.array,
- intl: PropTypes.object.isRequired,
- selectedItems: PropTypes.array,
- onItemSelect: PropTypes.func,
- onItemsSelect: PropTypes.func,
- onAllItemsSelect: PropTypes.func,
- loading: PropTypes.bool,
- listView: PropTypes.bool,
- onFilterClick: PropTypes.func,
- onEditDefect: PropTypes.func,
- onUnlinkSingleTicket: PropTypes.func,
- events: PropTypes.object,
- tracking: PropTypes.shape({
- trackEvent: PropTypes.func,
- getTrackingData: PropTypes.func,
- }).isRequired,
- onEditItem: PropTypes.func,
- onChangeSorting: PropTypes.func,
- sortingColumn: PropTypes.string,
- sortingDirection: PropTypes.string,
- rowHighlightingConfig: PropTypes.shape({
- onGridRowHighlighted: PropTypes.func,
- isGridRowHighlighted: PropTypes.bool,
- highlightedRowId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
- }),
- onStatusUpdate: PropTypes.func.isRequired,
- modifyColumnsFunc: PropTypes.func,
- userRoles: userRolesType,
- };
+export const StepGrid = ({
+ data,
+ selectedItems,
+ onItemSelect,
+ onItemsSelect,
+ onAllItemsSelect,
+ loading,
+ listView,
+ onFilterClick,
+ onEditDefect,
+ onUnlinkSingleTicket,
+ events,
+ onEditItem,
+ onChangeSorting,
+ sortingColumn,
+ sortingDirection,
+ rowHighlightingConfig,
+ onStatusUpdate,
+ modifyColumnsFunc,
+}) => {
+ const { trackEvent } = useTracking();
+ const { formatMessage } = useIntl();
+ const userRoles = useSelector(userRolesSelector);
+ const canManageTests = canWorkWithTests(userRoles);
- static defaultProps = {
- data: [],
- selectedItems: [],
- onItemSelect: () => {},
- onItemsSelect: () => {},
- onAllItemsSelect: () => {},
- loading: false,
- listView: false,
- onFilterClick: () => {},
- onEditDefect: () => {},
- onUnlinkSingleTicket: () => {},
- onEditItem: () => {},
- events: {},
- onChangeSorting: () => {},
- sortingColumn: null,
- sortingDirection: null,
- rowHighlightingConfig: PropTypes.shape({
- onGridRowHighlighted: () => {},
- isGridRowHighlighted: false,
- highlightedRowId: null,
- }),
- modifyColumnsFunc: null,
- userRoles: {},
+ const handleAttributeFilterClick = (attribute) => {
+ onFilterClick(
+ [
+ {
+ id: ENTITY_ATTRIBUTE,
+ value: {
+ filteringField: ENTITY_ATTRIBUTE,
+ condition: CONDITION_HAS,
+ value: formatAttribute(attribute),
+ },
+ },
+ ],
+ true,
+ );
+
+ if (events.CLICK_ATTRIBUTES) {
+ trackEvent(events.CLICK_ATTRIBUTES);
+ }
};
- constructor(props) {
- super(props);
- const {
- intl: { formatMessage },
- tracking,
- events,
- onUnlinkSingleTicket,
- onEditItem,
- onEditDefect,
- onStatusUpdate,
- modifyColumnsFunc,
- userRoles,
- } = props;
- this.columns = [
+ const highlightFailedItems = (value) => ({
+ failed: value.status === FAILED,
+ });
+
+ const columns = useMemo(() => {
+ const baseColumns = [
{
id: 'predefinedFilterSwitcher',
title: {
@@ -260,30 +239,25 @@ export class StepGrid extends Component {
},
{
id: ENTITY_METHOD_TYPE,
- title: {
- full: 'method type',
- },
+ title: { full: 'method type' },
sortable: true,
component: MethodTypeColumn,
- customProps: {
- formatMessage,
- },
+ customProps: { formatMessage },
withFilter: true,
filterEventInfo: events.METHOD_TYPE_FILTER,
sortingEventInfo: events.METHOD_TYPE_SORTING,
},
{
id: 'name',
- title: {
- full: 'name',
- },
+ title: { full: 'name' },
sortable: true,
component: NameColumn,
maxHeight: 170,
customProps: {
onEditItem,
- onClickAttribute: this.handleAttributeFilterClick,
+ onClickAttribute: handleAttributeFilterClick,
events,
+ hideEdit: !canBulkEditItems(userRoles),
},
withFilter: true,
filterEventInfo: events.NAME_FILTER,
@@ -291,16 +265,14 @@ export class StepGrid extends Component {
},
{
id: ENTITY_STATUS,
- title: {
- full: 'status',
- },
+ title: { full: 'status' },
sortable: true,
component: StatusColumn,
customProps: {
formatMessage,
- onChange: (status) => tracking.trackEvent(events.getChangeItemStatusEvent(status)),
+ onChange: (status) => trackEvent(events.getChangeItemStatusEvent(status)),
fetchFunc: onStatusUpdate,
- disabled: !canWorkWithTests(userRoles),
+ readOnly: !canWorkWithTests(userRoles),
},
withFilter: true,
filterEventInfo: events.STATUS_FILTER,
@@ -308,9 +280,7 @@ export class StepGrid extends Component {
},
{
id: ENTITY_START_TIME,
- title: {
- full: 'start time',
- },
+ title: { full: 'start time' },
sortable: true,
component: StartTimeColumn,
withFilter: true,
@@ -319,15 +289,11 @@ export class StepGrid extends Component {
},
{
id: ENTITY_DEFECT_TYPE,
- title: {
- full: 'defect type',
- },
+ title: { full: 'defect type' },
sortable: false,
component: DefectTypeColumn,
customProps: {
- onEdit: (data) => {
- onEditDefect(data);
- },
+ onEdit: onEditDefect,
onUnlinkSingleTicket,
events: {
onEditEvent: events.MAKE_DECISION_MODAL_EVENTS?.getOpenModalEvent,
@@ -339,77 +305,94 @@ export class StepGrid extends Component {
filterEventInfo: events.DEFECT_TYPE_FILTER,
},
];
- if (modifyColumnsFunc) {
- this.columns = modifyColumnsFunc(this.columns);
- }
- }
-
- handleAttributeFilterClick = (attribute) => {
- const { tracking, events, onFilterClick } = this.props;
-
- onFilterClick(
- [
- {
- id: ENTITY_ATTRIBUTE,
- value: {
- filteringField: ENTITY_ATTRIBUTE,
- condition: CONDITION_HAS,
- value: formatAttribute(attribute),
- },
- },
- ],
- true,
- );
- events.CLICK_ATTRIBUTES && tracking.trackEvent(events.CLICK_ATTRIBUTES);
- };
+ return modifyColumnsFunc ? modifyColumnsFunc(baseColumns) : baseColumns;
+ }, [
+ formatMessage,
+ events,
+ onEditItem,
+ onEditDefect,
+ onUnlinkSingleTicket,
+ onStatusUpdate,
+ modifyColumnsFunc,
+ userRoles,
+ trackEvent,
+ ]);
- highlightFailedItems = (value) => ({
- [cx('failed')]: value.status === FAILED,
- });
+ return (
+ <>
+
+ {!data.length && !loading && (
+
+ )}
+ >
+ );
+};
- render() {
- const {
- intl: { formatMessage },
- data,
- onItemSelect,
- onItemsSelect,
- onAllItemsSelect,
- selectedItems,
- loading,
- listView,
- onFilterClick,
- onChangeSorting,
- sortingColumn,
- sortingDirection,
- rowHighlightingConfig,
- } = this.props;
+StepGrid.propTypes = {
+ data: PropTypes.array,
+ intl: PropTypes.object.isRequired,
+ selectedItems: PropTypes.array,
+ onItemSelect: PropTypes.func,
+ onItemsSelect: PropTypes.func,
+ onAllItemsSelect: PropTypes.func,
+ loading: PropTypes.bool,
+ listView: PropTypes.bool,
+ onFilterClick: PropTypes.func,
+ onEditDefect: PropTypes.func,
+ onUnlinkSingleTicket: PropTypes.func,
+ events: PropTypes.object,
+ onEditItem: PropTypes.func,
+ onChangeSorting: PropTypes.func,
+ sortingColumn: PropTypes.string,
+ sortingDirection: PropTypes.string,
+ rowHighlightingConfig: PropTypes.shape({
+ onGridRowHighlighted: PropTypes.func,
+ isGridRowHighlighted: PropTypes.bool,
+ highlightedRowId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ }),
+ onStatusUpdate: PropTypes.func.isRequired,
+ modifyColumnsFunc: PropTypes.func,
+};
- return (
-
-
- {!data.length && !loading && (
-
- )}
-
- );
- }
-}
+StepGrid.defaultProps = {
+ data: [],
+ selectedItems: [],
+ onItemSelect: () => {},
+ onItemsSelect: () => {},
+ onAllItemsSelect: () => {},
+ loading: false,
+ listView: false,
+ onFilterClick: () => {},
+ onEditDefect: () => {},
+ onUnlinkSingleTicket: () => {},
+ onEditItem: () => {},
+ events: {},
+ onChangeSorting: () => {},
+ sortingColumn: null,
+ sortingDirection: null,
+ rowHighlightingConfig: {
+ onGridRowHighlighted: () => {},
+ isGridRowHighlighted: false,
+ highlightedRowId: null,
+ },
+ modifyColumnsFunc: null,
+};
diff --git a/app/src/pages/inside/suitesPage/suitesPage.jsx b/app/src/pages/inside/suitesPage/suitesPage.jsx
index 0cb203334a..565abaacc6 100644
--- a/app/src/pages/inside/suitesPage/suitesPage.jsx
+++ b/app/src/pages/inside/suitesPage/suitesPage.jsx
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 EPAM Systems
+ * 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.
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-import { Component } from 'react';
-import track from 'react-tracking';
+import { useEffect, useState } from 'react';
+import { useTracking } from 'react-tracking';
import PropTypes from 'prop-types';
-import { connect } from 'react-redux';
+import { useDispatch, useSelector } from 'react-redux';
import { PageLayout, PageSection } from 'layouts/pageLayout';
import { PaginationToolbar } from 'components/main/paginationToolbar';
import { SORTING_ASC, withSortingURL } from 'controllers/sorting';
@@ -36,254 +36,214 @@ import {
} from 'controllers/suite';
import { SUITE_PAGE, SUITES_PAGE_EVENTS } from 'components/main/analytics/events';
import { SuiteTestToolbar } from 'pages/inside/common/suiteTestToolbar';
-import { userIdSelector } from 'controllers/user';
import {
namespaceSelector,
fetchTestItemsAction,
parentItemSelector,
loadingSelector,
} from 'controllers/testItem';
-import { prevTestItemSelector } from 'controllers/pages';
+import { prevTestItemSelector, userRolesSelector } from 'controllers/pages';
import { ENTITY_START_TIME } from 'components/filterEntities/constants';
+import { canWorkWithTests } from 'common/utils/permissions/permissions';
-@connect(
- (state) => ({
- debugMode: debugModeSelector(state),
- userId: userIdSelector(state),
- suites: suitesSelector(state),
- selectedSuites: selectedSuitesSelector(state),
- parentItem: parentItemSelector(state),
- loading: loadingSelector(state),
- validationErrors: validationErrorsSelector(state),
- highlightItemId: prevTestItemSelector(state),
- }),
- {
- toggleSuiteSelectionAction,
- unselectAllSuitesAction,
- toggleAllSuitesAction,
- selectSuitesAction,
- fetchTestItemsAction,
- },
-)
-@withSortingURL({
- defaultFields: [ENTITY_START_TIME],
- defaultDirection: SORTING_ASC,
- namespaceSelector,
-})
-@withPagination({
- paginationSelector: suitePaginationSelector,
- namespaceSelector,
-})
-@track({ page: SUITE_PAGE })
-export class SuitesPage extends Component {
- static propTypes = {
- debugMode: PropTypes.bool.isRequired,
- deleteItems: PropTypes.func,
- onEditItem: PropTypes.func,
- onEditItems: PropTypes.func,
- suites: PropTypes.arrayOf(PropTypes.object),
- selectedSuites: PropTypes.arrayOf(PropTypes.object),
- activePage: PropTypes.number,
- itemCount: PropTypes.number,
- pageCount: PropTypes.number,
- pageSize: PropTypes.number,
- sortingColumn: PropTypes.string,
- sortingDirection: PropTypes.string,
- fetchTestItemsAction: PropTypes.func,
- onChangePage: PropTypes.func,
- onChangePageSize: PropTypes.func,
- onChangeSorting: PropTypes.func,
- toggleSuiteSelectionAction: PropTypes.func,
- unselectAllSuitesAction: PropTypes.func,
- toggleAllSuitesAction: PropTypes.func,
- selectSuitesAction: PropTypes.func,
- parentItem: PropTypes.object,
- loading: PropTypes.bool,
- validationErrors: PropTypes.object,
- onFilterAdd: PropTypes.func,
- onFilterRemove: PropTypes.func,
- onFilterValidate: PropTypes.func,
- onFilterChange: PropTypes.func,
- filterErrors: PropTypes.object,
- filterEntities: PropTypes.array,
- tracking: PropTypes.shape({
- trackEvent: PropTypes.func,
- getTrackingData: PropTypes.func,
- }).isRequired,
- highlightItemId: PropTypes.number,
- };
+// TODO: Refactor to avoid duplication
+export const SuitesPageWrapped = ({
+ deleteItems,
+ onEditItem,
+ onEditItems,
+ activePage,
+ itemCount,
+ pageCount,
+ pageSize,
+ sortingColumn,
+ sortingDirection,
+ onChangePage,
+ onChangePageSize,
+ onChangeSorting,
+ onFilterAdd,
+ onFilterRemove,
+ onFilterValidate,
+ onFilterChange,
+ filterErrors,
+ filterEntities,
+}) => {
+ const { trackEvent } = useTracking({ page: SUITE_PAGE });
+
+ const [highlightedRowId, setHighlightedRowId] = useState(null);
+ const [isGridRowHighlighted, setIsGridRowHighlighted] = useState(false);
+ const debugMode = useSelector(debugModeSelector);
+ const suites = useSelector(suitesSelector);
+ const selectedSuites = useSelector(selectedSuitesSelector);
+ const parentItem = useSelector(parentItemSelector);
+ const loading = useSelector(loadingSelector);
+ const validationErrors = useSelector(validationErrorsSelector);
+ const highlightItemId = useSelector(prevTestItemSelector);
+ const userRoles = useSelector(userRolesSelector);
+ const canManageTests = canWorkWithTests(userRoles);
+ const dispatch = useDispatch();
- static defaultProps = {
- deleteItems: () => {},
- onEditItem: () => {},
- onEditItems: () => {},
- suites: [],
- selectedSuites: [],
- activePage: DEFAULT_PAGINATION[PAGE_KEY],
- itemCount: null,
- pageCount: null,
- pageSize: DEFAULT_PAGINATION[SIZE_KEY],
- sortingColumn: null,
- sortingDirection: null,
- fetchTestItemsAction: () => {},
- onChangePage: () => {},
- onChangePageSize: () => {},
- onChangeSorting: () => {},
- toggleSuiteSelectionAction: () => {},
- unselectAllSuitesAction: () => {},
- toggleAllSuitesAction: () => {},
- selectSuitesAction: () => {},
- parentItem: null,
- loading: false,
- validationErrors: {},
- onFilterAdd: () => {},
- onFilterRemove: () => {},
- onFilterValidate: () => {},
- onFilterChange: () => {},
- filterErrors: {},
- filterEntities: [],
- highlightItemId: null,
+ // TODO - extract highlighting into custom hook
+ const onHighlightRow = (rowId) => {
+ setHighlightedRowId(rowId);
+ setIsGridRowHighlighted(true);
};
- state = {
- highlightedRowId: null,
- isGridRowHighlighted: false,
- isSauceLabsIntegrationView: false,
+ const onGridRowHighlighted = () => {
+ setIsGridRowHighlighted(false);
};
- componentDidMount() {
- const { highlightItemId } = this.props;
- if (highlightItemId) {
- this.onHighlightRow(highlightItemId);
+ const handleAllSuitesSelection = () => {
+ if (suites.length !== selectedSuites.length) {
+ trackEvent(SUITES_PAGE_EVENTS.CLICK_SELECT_ALL_ITEMS);
}
- }
+ dispatch(toggleAllSuitesAction(suites));
+ };
- componentWillUnmount() {
- if (this.props.selectedSuites.length > 0) {
- this.props.unselectAllSuitesAction();
+ const handleOneItemSelection = (value) => {
+ if (!selectedSuites.includes(value)) {
+ trackEvent(SUITES_PAGE_EVENTS.CLICK_SELECT_ONE_ITEM);
}
- }
-
- onHighlightRow = (highlightedRowId) => {
- this.setState({
- highlightedRowId,
- isGridRowHighlighted: true,
- });
+ dispatch(toggleSuiteSelectionAction(value));
};
- onGridRowHighlighted = () => {
- this.setState({
- isGridRowHighlighted: false,
- });
+ const handleItemsSelection = (items) => {
+ dispatch(selectSuitesAction(items));
};
-
- handleAllSuitesSelection = () => {
- if (this.props.suites.length !== this.props.selectedSuites.length) {
- this.props.tracking.trackEvent(SUITES_PAGE_EVENTS.CLICK_SELECT_ALL_ITEMS);
- }
- this.props.toggleAllSuitesAction(this.props.suites);
+ const handleRefresh = () => {
+ dispatch(fetchTestItemsAction());
};
-
- handleOneItemSelection = (value) => {
- if (!this.props.selectedSuites.includes(value)) {
- this.props.tracking.trackEvent(SUITES_PAGE_EVENTS.CLICK_SELECT_ONE_ITEM);
- }
- this.props.toggleSuiteSelectionAction(value);
+ const unselectAllItems = () => {
+ trackEvent(SUITES_PAGE_EVENTS.CLOSE_ICON_FOR_ALL_SELECTIONS);
+ dispatch(unselectAllSuitesAction());
};
- unselectAllItems = () => {
- this.props.tracking.trackEvent(SUITES_PAGE_EVENTS.CLOSE_ICON_FOR_ALL_SELECTIONS);
- this.props.unselectAllSuitesAction();
+ const unselectItem = (item) => {
+ trackEvent(SUITES_PAGE_EVENTS.CLOSE_ICON_SELECTED_ITEM);
+ dispatch(toggleSuiteSelectionAction(item));
};
- unselectItem = (item) => {
- this.props.tracking.trackEvent(SUITES_PAGE_EVENTS.CLOSE_ICON_SELECTED_ITEM);
- this.props.toggleSuiteSelectionAction(item);
+ const rowHighlightingConfig = {
+ onGridRowHighlighted,
+ isGridRowHighlighted,
+ highlightedRowId,
};
- render() {
- const {
- suites,
- activePage,
- itemCount,
- pageCount,
- pageSize,
- onChangePage,
- onChangePageSize,
- sortingColumn,
- sortingDirection,
- onChangeSorting,
- selectedSuites,
- parentItem,
- loading,
- debugMode,
- deleteItems,
- onEditItem,
- onEditItems,
- onFilterAdd,
- onFilterRemove,
- onFilterValidate,
- onFilterChange,
- filterErrors,
- filterEntities,
- tracking,
- } = this.props;
+ useEffect(() => {
+ if (highlightItemId) {
+ onHighlightRow(highlightItemId);
+ }
+ }, [highlightItemId]);
- const rowHighlightingConfig = {
- onGridRowHighlighted: this.onGridRowHighlighted,
- isGridRowHighlighted: this.state.isGridRowHighlighted,
- highlightedRowId: this.state.highlightedRowId,
+ useEffect(() => {
+ return () => {
+ if (selectedSuites.length > 0) {
+ dispatch(unselectAllSuitesAction());
+ }
};
+ }, [selectedSuites, dispatch]);
- return (
-
-
- deleteItems(selectedSuites)}
- onEditItems={() => onEditItems(selectedSuites)}
- errors={this.props.validationErrors}
- selectedItems={selectedSuites}
- onUnselect={this.unselectItem}
- onUnselectAll={this.unselectAllItems}
- onProceedValidItems={() => tracking.trackEvent(SUITES_PAGE_EVENTS.PROCEED_VALID_ITEMS)}
- parentItem={parentItem}
- onRefresh={this.props.fetchTestItemsAction}
- debugMode={debugMode}
- events={SUITES_PAGE_EVENTS}
- filterErrors={filterErrors}
- onFilterChange={onFilterChange}
- onFilterValidate={onFilterValidate}
- onFilterRemove={onFilterRemove}
- onFilterAdd={onFilterAdd}
- filterEntities={filterEntities}
- />
-
+
+ deleteItems(selectedSuites)}
+ onEditItems={() => onEditItems(selectedSuites)}
+ errors={validationErrors}
+ selectedItems={selectedSuites}
+ onUnselect={unselectItem}
+ onUnselectAll={unselectAllItems}
+ onProceedValidItems={() => trackEvent(SUITES_PAGE_EVENTS.PROCEED_VALID_ITEMS)}
+ parentItem={parentItem}
+ onRefresh={handleRefresh}
+ debugMode={debugMode}
+ events={SUITES_PAGE_EVENTS}
+ filterErrors={filterErrors}
+ onFilterChange={onFilterChange}
+ onFilterValidate={onFilterValidate}
+ onFilterRemove={onFilterRemove}
+ onFilterAdd={onFilterAdd}
+ filterEntities={filterEntities}
+ />
+
+ {!!pageCount && !loading && (
+
- {!!pageCount && !loading && (
-
- )}
-
-
- );
- }
-}
+ )}
+
+
+ );
+};
+
+SuitesPageWrapped.propTypes = {
+ deleteItems: PropTypes.func,
+ onEditItem: PropTypes.func,
+ onEditItems: PropTypes.func,
+ activePage: PropTypes.number,
+ itemCount: PropTypes.number,
+ pageCount: PropTypes.number,
+ pageSize: PropTypes.number,
+ sortingColumn: PropTypes.string,
+ sortingDirection: PropTypes.string,
+ onChangePage: PropTypes.func,
+ onChangePageSize: PropTypes.func,
+ onChangeSorting: PropTypes.func,
+ onFilterAdd: PropTypes.func,
+ onFilterRemove: PropTypes.func,
+ onFilterValidate: PropTypes.func,
+ onFilterChange: PropTypes.func,
+ filterErrors: PropTypes.object,
+ filterEntities: PropTypes.array,
+};
+
+SuitesPageWrapped.defaultProps = {
+ deleteItems: () => {},
+ onEditItem: () => {},
+ onEditItems: () => {},
+ activePage: DEFAULT_PAGINATION[PAGE_KEY],
+ itemCount: null,
+ pageCount: null,
+ pageSize: DEFAULT_PAGINATION[SIZE_KEY],
+ sortingColumn: null,
+ sortingDirection: null,
+ onChangePage: () => {},
+ onChangePageSize: () => {},
+ onChangeSorting: () => {},
+ onFilterAdd: () => {},
+ onFilterRemove: () => {},
+ onFilterValidate: () => {},
+ onFilterChange: () => {},
+ filterErrors: {},
+ filterEntities: [],
+};
+
+export const SuitesPage = withSortingURL({
+ defaultFields: [ENTITY_START_TIME],
+ defaultDirection: SORTING_ASC,
+ namespaceSelector,
+})(
+ withPagination({
+ paginationSelector: suitePaginationSelector,
+ namespaceSelector,
+ })(SuitesPageWrapped),
+);
diff --git a/app/src/pages/inside/testsPage/testsPage.jsx b/app/src/pages/inside/testsPage/testsPage.jsx
index 9608ed043f..338452b652 100644
--- a/app/src/pages/inside/testsPage/testsPage.jsx
+++ b/app/src/pages/inside/testsPage/testsPage.jsx
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 EPAM Systems
+ * 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.
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-import { Component } from 'react';
-import track from 'react-tracking';
+import { useEffect, useState } from 'react';
+import { useTracking } from 'react-tracking';
import PropTypes from 'prop-types';
-import { connect } from 'react-redux';
+import { useDispatch, useSelector } from 'react-redux';
import { SORTING_ASC, withSortingURL } from 'controllers/sorting';
import { PageLayout, PageSection } from 'layouts/pageLayout';
import { LaunchSuiteGrid } from 'pages/inside/common/launchSuiteGrid';
@@ -42,244 +42,210 @@ import {
parentItemSelector,
loadingSelector,
} from 'controllers/testItem';
-import { prevTestItemSelector } from 'controllers/pages';
+import { prevTestItemSelector, userRolesSelector } from 'controllers/pages';
import { ENTITY_START_TIME } from 'components/filterEntities/constants';
+import { canWorkWithTests } from 'common/utils/permissions/permissions';
-@connect(
- (state) => ({
- debugMode: debugModeSelector(state),
- validationErrors: validationErrorsSelector(state),
- tests: testsSelector(state),
- selectedTests: selectedTestsSelector(state),
- parentItem: parentItemSelector(state),
- loading: loadingSelector(state),
- highlightItemId: prevTestItemSelector(state),
- }),
- {
- toggleTestSelectionAction,
- unselectAllTestsAction,
- toggleAllTestsAction,
- selectTestsAction,
- fetchTestItemsAction,
- },
-)
-@withSortingURL({
- defaultFields: [ENTITY_START_TIME],
- defaultDirection: SORTING_ASC,
- namespaceSelector,
-})
-@withPagination({
- paginationSelector: testPaginationSelector,
- namespaceSelector,
-})
-@track()
-export class TestsPage extends Component {
- static propTypes = {
- deleteItems: PropTypes.func,
- onEditItem: PropTypes.func,
- onEditItems: PropTypes.func,
- validationErrors: PropTypes.object.isRequired,
- debugMode: PropTypes.bool.isRequired,
- tests: PropTypes.arrayOf(PropTypes.object),
- selectedTests: PropTypes.arrayOf(PropTypes.object),
- activePage: PropTypes.number,
- itemCount: PropTypes.number,
- pageCount: PropTypes.number,
- pageSize: PropTypes.number,
- sortingColumn: PropTypes.string,
- sortingDirection: PropTypes.string,
- fetchTestItemsAction: PropTypes.func,
- onChangePage: PropTypes.func,
- onChangePageSize: PropTypes.func,
- onChangeSorting: PropTypes.func,
- toggleTestSelectionAction: PropTypes.func,
- unselectAllTestsAction: PropTypes.func,
- toggleAllTestsAction: PropTypes.func,
- selectTestsAction: PropTypes.func,
- parentItem: PropTypes.object,
- loading: PropTypes.bool,
- onFilterAdd: PropTypes.func,
- onFilterRemove: PropTypes.func,
- onFilterValidate: PropTypes.func,
- onFilterChange: PropTypes.func,
- filterErrors: PropTypes.object,
- filterEntities: PropTypes.array,
- tracking: PropTypes.shape({
- trackEvent: PropTypes.func,
- getTrackingData: PropTypes.func,
- }).isRequired,
- highlightItemId: PropTypes.number,
- };
+// TODO: Refactor to avoid duplication
+export const TestsPageWrapped = ({
+ deleteItems,
+ onEditItem,
+ onEditItems,
+ activePage,
+ itemCount,
+ pageCount,
+ pageSize,
+ sortingColumn,
+ sortingDirection,
+ onChangePage,
+ onChangePageSize,
+ onChangeSorting,
+ onFilterAdd,
+ onFilterRemove,
+ onFilterValidate,
+ onFilterChange,
+ filterErrors,
+ filterEntities,
+}) => {
+ const { trackEvent } = useTracking();
+
+ const [highlightedRowId, setHighlightedRowId] = useState(null);
+ const [isGridRowHighlighted, setIsGridRowHighlighted] = useState(false);
+ const debugMode = useSelector(debugModeSelector);
+ const validationErrors = useSelector(validationErrorsSelector);
+ const tests = useSelector(testsSelector);
+ const selectedTests = useSelector(selectedTestsSelector);
+ const parentItem = useSelector(parentItemSelector);
+ const loading = useSelector(loadingSelector);
+ const highlightItemId = useSelector(prevTestItemSelector);
+ const userRoles = useSelector(userRolesSelector);
+ const canManageTests = canWorkWithTests(userRoles);
+ const dispatch = useDispatch();
- static defaultProps = {
- deleteItems: () => {},
- onEditItem: () => {},
- onEditItems: () => {},
- tests: [],
- selectedTests: [],
- activePage: DEFAULT_PAGINATION[PAGE_KEY],
- itemCount: null,
- pageCount: null,
- pageSize: DEFAULT_PAGINATION[SIZE_KEY],
- sortingColumn: null,
- sortingDirection: null,
- fetchTestItemsAction: () => {},
- onChangePage: () => {},
- onChangePageSize: () => {},
- onChangeSorting: () => {},
- toggleTestSelectionAction: () => {},
- unselectAllTestsAction: () => {},
- toggleAllTestsAction: () => {},
- selectTestsAction: () => {},
- parentItem: null,
- loading: false,
- onFilterAdd: () => {},
- onFilterRemove: () => {},
- onFilterValidate: () => {},
- onFilterChange: () => {},
- filterErrors: {},
- filterEntities: [],
- highlightItemId: null,
+ // TODO - extract highlighting into custom hook
+ const onHighlightRow = (rowId) => {
+ setHighlightedRowId(rowId);
+ setIsGridRowHighlighted(true);
};
- state = {
- highlightedRowId: null,
- isGridRowHighlighted: false,
- isSauceLabsIntegrationView: false,
+ const onGridRowHighlighted = () => {
+ setIsGridRowHighlighted(false);
};
- componentDidMount() {
- const { highlightItemId } = this.props;
- if (highlightItemId) {
- this.onHighlightRow(highlightItemId);
+ const handleAllTestsSelection = () => {
+ if (tests.length !== selectedTests.length) {
+ trackEvent(TESTS_PAGE_EVENTS.CLICK_SELECT_ALL_ITEMS);
}
- }
+ dispatch(toggleAllTestsAction(tests));
+ };
- componentWillUnmount() {
- if (this.props.selectedTests.length > 0) {
- this.props.unselectAllTestsAction();
+ const handleOneItemSelection = (value) => {
+ if (!selectedTests.includes(value)) {
+ trackEvent(TESTS_PAGE_EVENTS.CLICK_SELECT_ONE_ITEM);
}
- }
-
- onHighlightRow = (highlightedRowId) => {
- this.setState({
- highlightedRowId,
- isGridRowHighlighted: true,
- });
+ dispatch(toggleTestSelectionAction(value));
};
- onGridRowHighlighted = () => {
- this.setState({
- isGridRowHighlighted: false,
- });
+ const handleItemsSelection = (items) => {
+ dispatch(selectTestsAction(items));
};
- handleAllTestsSelection = () => {
- if (this.props.tests.length !== this.props.selectedTests.length) {
- this.props.tracking.trackEvent(TESTS_PAGE_EVENTS.CLICK_SELECT_ALL_ITEMS);
- }
- this.props.toggleAllTestsAction(this.props.tests);
+ const unselectAllItems = () => {
+ trackEvent(TESTS_PAGE_EVENTS.CLOSE_ICON_FOR_ALL_SELECTIONS);
+ dispatch(unselectAllTestsAction());
};
- handleOneItemSelection = (value) => {
- if (!this.props.selectedTests.includes(value)) {
- this.props.tracking.trackEvent(TESTS_PAGE_EVENTS.CLICK_SELECT_ONE_ITEM);
- }
- this.props.toggleTestSelectionAction(value);
+ const unselectItem = (item) => {
+ trackEvent(TESTS_PAGE_EVENTS.CLOSE_ICON_SELECTED_ITEM);
+ dispatch(toggleTestSelectionAction(item));
};
- unselectAllItems = () => {
- this.props.tracking.trackEvent(TESTS_PAGE_EVENTS.CLOSE_ICON_FOR_ALL_SELECTIONS);
- this.props.unselectAllTestsAction();
+ const rowHighlightingConfig = {
+ onGridRowHighlighted,
+ isGridRowHighlighted,
+ highlightedRowId,
};
- unselectItem = (item) => {
- this.props.tracking.trackEvent(TESTS_PAGE_EVENTS.CLOSE_ICON_SELECTED_ITEM);
- this.props.toggleTestSelectionAction(item);
+ const handleRefresh = () => {
+ dispatch(fetchTestItemsAction());
};
- render() {
- const {
- tests,
- sortingColumn,
- sortingDirection,
- onChangeSorting,
- selectedTests,
- activePage,
- itemCount,
- pageCount,
- pageSize,
- onChangePage,
- onChangePageSize,
- parentItem,
- loading,
- debugMode,
- deleteItems,
- onEditItem,
- onEditItems,
- onFilterAdd,
- onFilterRemove,
- onFilterValidate,
- onFilterChange,
- filterErrors,
- filterEntities,
- tracking,
- } = this.props;
- const rowHighlightingConfig = {
- onGridRowHighlighted: this.onGridRowHighlighted,
- isGridRowHighlighted: this.state.isGridRowHighlighted,
- highlightedRowId: this.state.highlightedRowId,
+ useEffect(() => {
+ if (highlightItemId) {
+ onHighlightRow(highlightItemId);
+ }
+ }, [highlightItemId]);
+
+ useEffect(() => {
+ return () => {
+ if (selectedTests.length > 0) {
+ dispatch(unselectAllTestsAction());
+ }
};
+ }, [selectedTests, dispatch]);
- return (
-
-
- tracking.trackEvent(TESTS_PAGE_EVENTS.PROCEED_VALID_ITEMS)}
- onRefresh={this.props.fetchTestItemsAction}
- debugMode={debugMode}
- errors={this.props.validationErrors}
- onDelete={() => deleteItems(selectedTests)}
- onEditItems={() => onEditItems(selectedTests)}
- filterEntities={filterEntities}
- filterErrors={filterErrors}
- onFilterChange={onFilterChange}
- onFilterValidate={onFilterValidate}
- onFilterRemove={onFilterRemove}
- onFilterAdd={onFilterAdd}
- events={TESTS_PAGE_EVENTS}
+ return (
+
+
+ trackEvent(TESTS_PAGE_EVENTS.PROCEED_VALID_ITEMS)}
+ onRefresh={handleRefresh}
+ debugMode={debugMode}
+ errors={validationErrors}
+ onDelete={() => deleteItems(selectedTests)}
+ onEditItems={() => onEditItems(selectedTests)}
+ filterEntities={filterEntities}
+ filterErrors={filterErrors}
+ onFilterChange={onFilterChange}
+ onFilterValidate={onFilterValidate}
+ onFilterRemove={onFilterRemove}
+ onFilterAdd={onFilterAdd}
+ events={TESTS_PAGE_EVENTS}
+ />
+
+ {!!pageCount && !loading && (
+
-
- {!!pageCount && !loading && (
-
- )}
-
-
- );
- }
-}
+ )}
+
+
+ );
+};
+
+TestsPageWrapped.propTypes = {
+ deleteItems: PropTypes.func,
+ onEditItem: PropTypes.func,
+ onEditItems: PropTypes.func,
+ activePage: PropTypes.number,
+ itemCount: PropTypes.number,
+ pageCount: PropTypes.number,
+ pageSize: PropTypes.number,
+ sortingColumn: PropTypes.string,
+ sortingDirection: PropTypes.string,
+ onChangePage: PropTypes.func,
+ onChangePageSize: PropTypes.func,
+ onChangeSorting: PropTypes.func,
+ onFilterAdd: PropTypes.func,
+ onFilterRemove: PropTypes.func,
+ onFilterValidate: PropTypes.func,
+ onFilterChange: PropTypes.func,
+ filterErrors: PropTypes.object,
+ filterEntities: PropTypes.arrayOf(PropTypes.object),
+};
+
+TestsPageWrapped.defaultProps = {
+ deleteItems: () => {},
+ onEditItem: () => {},
+ onEditItems: () => {},
+ activePage: DEFAULT_PAGINATION[PAGE_KEY],
+ itemCount: null,
+ pageCount: null,
+ pageSize: DEFAULT_PAGINATION[SIZE_KEY],
+ sortingColumn: null,
+ sortingDirection: null,
+ onChangePage: () => {},
+ onChangePageSize: () => {},
+ onChangeSorting: () => {},
+ onFilterAdd: () => {},
+ onFilterRemove: () => {},
+ onFilterValidate: () => {},
+ onFilterChange: () => {},
+ filterErrors: {},
+ filterEntities: [],
+};
+
+export const TestsPage = withSortingURL({
+ defaultFields: [ENTITY_START_TIME],
+ defaultDirection: SORTING_ASC,
+ namespaceSelector,
+})(
+ withPagination({
+ paginationSelector: testPaginationSelector,
+ namespaceSelector,
+ })(TestsPageWrapped),
+);