diff --git a/app/src/common/constants/permissions.js b/app/src/common/constants/permissions.js
index 13a5017fad..c9e5a4ffc4 100644
--- a/app/src/common/constants/permissions.js
+++ b/app/src/common/constants/permissions.js
@@ -42,6 +42,9 @@ export const ACTIONS = {
FORCE_FINISH_RERUN_LAUNCH: 'FORCE_FINISH_RERUN_LAUNCH',
START_ANALYSIS: 'START_ANALYSIS',
DELETE_TEST_ITEM: 'DELETE_TEST_ITEM',
+ MAKE_DECISION: 'MAKE_DECISION',
+ CHANGE_TEST_ITEM_STATUS: 'CHANGE_TEST_ITEM_STATUS',
+ MANAGE_BTS_ISSUES: 'MANAGE_BTS_ISSUES',
MOVE_TO_DEBUG: 'MOVE_TO_DEBUG',
MERGE_LAUNCHES: 'MERGE_LAUNCHES',
WORK_WITH_FILTERS: 'WORK_WITH_FILTERS',
@@ -80,7 +83,10 @@ export const PERMISSIONS_MAP = {
[ACTIONS.START_ANALYSIS]: true,
[ACTIONS.BULK_EDIT_ITEMS]: true,
[ACTIONS.FORCE_FINISH_RERUN_LAUNCH]: true,
+ [ACTIONS.CHANGE_TEST_ITEM_STATUS]: true,
[ACTIONS.DELETE_TEST_ITEM]: true,
+ [ACTIONS.MAKE_DECISION]: true,
+ [ACTIONS.MANAGE_BTS_ISSUES]: true,
[ACTIONS.WORK_WITH_FILTERS]: true,
[ACTIONS.WORK_WITH_WIDGETS]: true,
[ACTIONS.SEE_ORGANIZATION_SETTINGS]: true,
@@ -120,6 +126,9 @@ export const PERMISSIONS_MAP = {
[ACTIONS.FORCE_FINISH_RERUN_LAUNCH]: true,
[ACTIONS.BULK_EDIT_ITEMS]: true,
[ACTIONS.DELETE_TEST_ITEM]: true,
+ [ACTIONS.CHANGE_TEST_ITEM_STATUS]: true,
+ [ACTIONS.MAKE_DECISION]: true,
+ [ACTIONS.MANAGE_BTS_ISSUES]: true,
[ACTIONS.WORK_WITH_FILTERS]: true,
[ACTIONS.WORK_WITH_WIDGETS]: true,
[ACTIONS.RENAME_PROJECT]: true,
diff --git a/app/src/common/utils/permissions/permissions.js b/app/src/common/utils/permissions/permissions.js
index 6acf8efe89..9cc74386c3 100644
--- a/app/src/common/utils/permissions/permissions.js
+++ b/app/src/common/utils/permissions/permissions.js
@@ -63,6 +63,9 @@ export const canForceFinishRerunLaunch = checkPermission(ACTIONS.FORCE_FINISH_RE
export const canStartAnalysis = checkPermission(ACTIONS.START_ANALYSIS);
export const canDeleteTestItem = checkPermission(ACTIONS.DELETE_TEST_ITEM);
export const canMoveToDebug = checkPermission(ACTIONS.MOVE_TO_DEBUG);
+export const canChangeTestItemStatus = checkPermission(ACTIONS.CHANGE_TEST_ITEM_STATUS);
+export const canMakeDecision = checkPermission(ACTIONS.MAKE_DECISION);
+export const canManageBTSIssues = checkPermission(ACTIONS.MANAGE_BTS_ISSUES);
export const canMergeLaunches = checkPermission(ACTIONS.MERGE_LAUNCHES);
export const canWorkWithFilters = checkPermission(ACTIONS.WORK_WITH_FILTERS);
export const canReadData = checkPermission(ACTIONS.READ_DATA);
diff --git a/app/src/pages/inside/common/statusDropdown/statusDropdown.jsx b/app/src/pages/inside/common/statusDropdown/statusDropdown.jsx
index d8f28bdc3d..a8d2df981b 100644
--- a/app/src/pages/inside/common/statusDropdown/statusDropdown.jsx
+++ b/app/src/pages/inside/common/statusDropdown/statusDropdown.jsx
@@ -65,6 +65,7 @@ export class StatusDropdown extends Component {
onChange: PropTypes.func,
withIndicator: PropTypes.bool,
disabled: PropTypes.bool,
+ readOnly: PropTypes.bool,
};
static defaultProps = {
@@ -75,6 +76,7 @@ export class StatusDropdown extends Component {
onChange: () => {},
withIndicator: false,
disabled: false,
+ readOnly: false,
};
updateItem = (newStatus) => {
@@ -142,25 +144,33 @@ export class StatusDropdown extends Component {
};
render() {
- const { status, withIndicator, disabled } = this.props;
+ const { status, withIndicator, disabled, readOnly } = this.props;
return (
+export const TestItemStatus = ({ status, className }) => (
+
@@ -29,8 +29,10 @@ export const TestItemStatus = ({ status }) => (
TestItemStatus.propTypes = {
status: PropTypes.string,
+ className: PropTypes.string,
};
TestItemStatus.defaultProps = {
status: '',
+ className: '',
};
diff --git a/app/src/pages/inside/logsPage/logItemInfo/defectDetails/defectDetails.jsx b/app/src/pages/inside/logsPage/logItemInfo/defectDetails/defectDetails.jsx
index fb3531527c..b094d9c91f 100644
--- a/app/src/pages/inside/logsPage/logItemInfo/defectDetails/defectDetails.jsx
+++ b/app/src/pages/inside/logsPage/logItemInfo/defectDetails/defectDetails.jsx
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-import React, { Component, Fragment } from 'react';
+import React, { useState } from 'react';
import PropTypes from 'prop-types';
-import { connect } from 'react-redux';
-import track from 'react-tracking';
+import { useDispatch, useSelector } from 'react-redux';
+import { useTracking } from 'react-tracking';
import classNames from 'classnames/bind';
-import { defineMessages, injectIntl } from 'react-intl';
+import { defineMessages, useIntl } from 'react-intl';
import Parser from 'html-react-parser';
import { GhostButton } from 'components/buttons/ghostButton';
import { getIssueTitle } from 'pages/inside/common/utils';
@@ -52,6 +52,12 @@ import {
PALabel,
} from 'pages/inside/stepPage/stepGrid/defectType/defectType';
import { TO_INVESTIGATE_LOCATOR_PREFIX } from 'common/constants/defectTypes';
+import {
+ canChangeTestItemStatus,
+ canMakeDecision,
+ canManageBTSIssues,
+} from 'common/utils/permissions/permissions';
+import { userRolesSelector } from 'controllers/pages';
import styles from './defectDetails.scss';
const cx = classNames.bind(styles);
@@ -110,62 +116,22 @@ const getUnlinkIssueEventsInfo = (place) => ({
closeIcon: LOG_PAGE_EVENTS.UNLINK_ISSUE_MODAL_EVENTS.CLOSE_ICON_UNLINK_ISSUE_MODAL,
});
-@connect(
- (state) => ({
- btsIntegrations: availableBtsIntegrationsSelector(state),
- isBtsPluginsExist: isBtsPluginsExistSelector(state),
- enabledBtsPlugins: enabledBtsPluginsSelector(state),
- }),
- {
- linkIssueAction,
- unlinkIssueAction,
- postIssueAction,
- editDefectsAction,
- updateHistoryItemIssues: updateHistoryItemIssuesAction,
- fetchHistoryItemsWithLoading: fetchHistoryItemsWithLoadingAction,
- },
-)
-@track()
-@injectIntl
-export class DefectDetails extends Component {
- static propTypes = {
- intl: PropTypes.object.isRequired,
- editDefectsAction: PropTypes.func.isRequired,
- linkIssueAction: PropTypes.func.isRequired,
- unlinkIssueAction: PropTypes.func.isRequired,
- postIssueAction: PropTypes.func.isRequired,
- btsIntegrations: PropTypes.array.isRequired,
- fetchFunc: PropTypes.func.isRequired,
- updateHistoryItemIssues: PropTypes.func.isRequired,
- debugMode: PropTypes.bool.isRequired,
- fetchHistoryItemsWithLoading: PropTypes.func.isRequired,
- tracking: PropTypes.shape({
- trackEvent: PropTypes.func,
- getTrackingData: PropTypes.func,
- }).isRequired,
- logItem: PropTypes.object,
- isBtsPluginsExist: PropTypes.bool,
- enabledBtsPlugins: PropTypes.array,
- };
- static defaultProps = {
- logItem: null,
- isBtsPluginsExist: false,
- enabledBtsPlugins: [],
- };
+export const DefectDetails = ({ fetchFunc, debugMode, logItem }) => {
+ const { formatMessage } = useIntl();
+ const { trackEvent } = useTracking();
+ const dispatch = useDispatch();
+ const btsIntegrations = useSelector(availableBtsIntegrationsSelector);
+ const isBtsPluginsExist = useSelector(isBtsPluginsExistSelector);
+ const enabledBtsPlugins = useSelector(enabledBtsPluginsSelector);
+ const [expanded, setExpanded] = useState(false);
+ const userRoles = useSelector(userRolesSelector);
- state = {
- expanded: false,
- };
-
- getIssueActionTitle = (noIssueMessage, isPostIssueUnavailable) => {
- const {
- logItem,
- intl: { formatMessage },
- btsIntegrations,
- isBtsPluginsExist,
- enabledBtsPlugins,
- } = this.props;
+ const canChangeStatus = canChangeTestItemStatus(userRoles);
+ const canSeeMakeDecision = canMakeDecision(userRoles);
+ const canWorkWithBtsIssues = canManageBTSIssues(userRoles);
+ const isDefectTypeVisible = logItem.issue?.issueType;
+ const getIssueActionTitle = (noIssueMessage, isPostIssueUnavailable) => {
if (!logItem.issue) {
return formatMessage(noIssueMessage);
}
@@ -179,21 +145,17 @@ export class DefectDetails extends Component {
);
};
- isDefectTypeVisible = () => {
- const { logItem } = this.props;
- return logItem.issue?.issueType;
- };
-
- handleLinkIssue = () => {
- this.props.tracking.trackEvent(LOG_PAGE_EVENTS.LINK_ISSUE_ACTION);
- this.props.linkIssueAction([this.props.logItem], {
- fetchFunc: this.props.fetchFunc,
- eventsInfo: getLinkIssueEventsInfo(),
- });
+ const handleLinkIssue = () => {
+ trackEvent(LOG_PAGE_EVENTS.LINK_ISSUE_ACTION);
+ dispatch(
+ linkIssueAction([logItem], {
+ fetchFunc,
+ eventsInfo: getLinkIssueEventsInfo(),
+ }),
+ );
};
- handleUnlinkTicket = (ticketId) => {
- const { logItem, fetchFunc, tracking } = this.props;
+ const handleUnlinkTicket = (ticketId) => {
const items = [
{
...logItem,
@@ -206,228 +168,241 @@ export class DefectDetails extends Component {
},
];
- tracking.trackEvent(LOG_PAGE_EVENTS.UNLINK_ISSUES_ACTION);
+ trackEvent(LOG_PAGE_EVENTS.UNLINK_ISSUES_ACTION);
- this.props.unlinkIssueAction(items, {
- fetchFunc,
- eventsInfo: getUnlinkIssueEventsInfo(),
- });
+ dispatch(
+ unlinkIssueAction(items, {
+ fetchFunc,
+ eventsInfo: getUnlinkIssueEventsInfo(),
+ }),
+ );
};
- handlePostIssue = () => {
- this.props.tracking.trackEvent(LOG_PAGE_EVENTS.POST_ISSUE_ACTION);
- this.props.postIssueAction([this.props.logItem], {
- fetchFunc: this.props.fetchFunc,
- eventsInfo: getPostIssueEventsInfo(),
- });
+ const handlePostIssue = () => {
+ trackEvent(LOG_PAGE_EVENTS.POST_ISSUE_ACTION);
+ dispatch(
+ postIssueAction([logItem], {
+ fetchFunc,
+ eventsInfo: getPostIssueEventsInfo(),
+ }),
+ );
};
- onDefectEdited = (issues) => {
- const { fetchFunc, updateHistoryItemIssues } = this.props;
-
+ const onDefectEdited = (issues) => {
if (issues) {
- updateHistoryItemIssues(issues);
+ dispatch(updateHistoryItemIssuesAction(issues));
} else {
fetchFunc();
}
};
- handleEditDefect = () => {
- const { logItem } = this.props;
+ const handleEditDefect = () => {
const MAKE_DECISION = 'make_decision';
- this.props.tracking.trackEvent(
+ trackEvent(
LOG_PAGE_EVENTS.MAKE_DECISION_MODAL_EVENTS.getOpenModalEvent(
logItem.issue.issueType.startsWith(TO_INVESTIGATE_LOCATOR_PREFIX),
),
);
- this.props.editDefectsAction([logItem], {
- fetchFunc: this.onDefectEdited,
- eventsInfo: {
- changeSearchMode: LOG_PAGE_EVENTS.CHANGE_SEARCH_MODE_EDIT_DEFECT_MODAL,
- selectAllSimilarItems: LOG_PAGE_EVENTS.SELECT_ALL_SIMILAR_ITEMS_EDIT_DEFECT_MODAL,
- selectSpecificSimilarItem: LOG_PAGE_EVENTS.SELECT_SPECIFIC_SIMILAR_ITEM_EDIT_DEFECT_MODAL,
- editDefectsEvents: LOG_PAGE_EVENTS.MAKE_DECISION_MODAL_EVENTS,
- unlinkIssueEvents: getUnlinkIssueEventsInfo(MAKE_DECISION),
- postIssueEvents: getPostIssueEventsInfo(MAKE_DECISION),
- linkIssueEvents: getLinkIssueEventsInfo(MAKE_DECISION),
- },
- });
- };
-
- toggleExpanded = () => {
- this.setState(
- (state) => ({
- expanded: !state.expanded,
+ dispatch(
+ editDefectsAction([logItem], {
+ fetchFunc: onDefectEdited,
+ eventsInfo: {
+ changeSearchMode: LOG_PAGE_EVENTS.CHANGE_SEARCH_MODE_EDIT_DEFECT_MODAL,
+ selectAllSimilarItems: LOG_PAGE_EVENTS.SELECT_ALL_SIMILAR_ITEMS_EDIT_DEFECT_MODAL,
+ selectSpecificSimilarItem: LOG_PAGE_EVENTS.SELECT_SPECIFIC_SIMILAR_ITEM_EDIT_DEFECT_MODAL,
+ editDefectsEvents: LOG_PAGE_EVENTS.MAKE_DECISION_MODAL_EVENTS,
+ unlinkIssueEvents: getUnlinkIssueEventsInfo(MAKE_DECISION),
+ postIssueEvents: getPostIssueEventsInfo(MAKE_DECISION),
+ linkIssueEvents: getLinkIssueEventsInfo(MAKE_DECISION),
+ },
}),
- () => {
- this.props.tracking.trackEvent(
- LOG_PAGE_EVENTS.getClickOnDefectDetailsTogglerEvent(this.state.expanded),
- );
- },
);
};
- onChangeStatus = (status) =>
- this.props.tracking.trackEvent(LOG_PAGE_EVENTS.getChangeItemStatusEvent(status));
+ const toggleExpanded = () => {
+ setExpanded(!expanded);
+ trackEvent(LOG_PAGE_EVENTS.getClickOnDefectDetailsTogglerEvent(expanded));
+ };
+
+ const onChangeStatus = (status) => trackEvent(LOG_PAGE_EVENTS.getChangeItemStatusEvent(status));
- onClickIssue = (pluginName) => {
- this.props.tracking.trackEvent(LOG_PAGE_EVENTS.onClickIssueTicketEvent(pluginName));
+ const onClickIssue = (pluginName) => {
+ trackEvent(LOG_PAGE_EVENTS.onClickIssueTicketEvent(pluginName));
};
- render() {
- const {
- logItem,
- btsIntegrations,
- debugMode,
- intl: { formatMessage },
- fetchHistoryItemsWithLoading,
- } = this.props;
- const { expanded } = this.state;
- const isPostIssueUnavailable = !isPostIssueActionAvailable(this.props.btsIntegrations);
+ const isPostIssueUnavailable = !isPostIssueActionAvailable(btsIntegrations);
- return (
-
- {this.isDefectTypeVisible() && (
-
-
-
- {Parser(CommentIcon)}
- {formatMessage(messages.comment)}
-
-
- {expanded ? (
-
-
-
- ) : (
+ return (
+
+ {isDefectTypeVisible && (
+
+
+
+ {Parser(CommentIcon)}
+ {formatMessage(messages.comment)}
+
+
+ {expanded ? (
+
- )}
-
-
-
-
- {expanded && (
-
-
- {Parser(BugIcon)}
- {formatMessage(messages.btsLink)}
-
-
-
-
- {!debugMode && (
- <>
-
- {formatMessage(messages.postIssue)}
-
-
- {formatMessage(messages.linkIssue)}
-
- >
- )}
-
+
+ ) : (
+
)}
- )}
-
- {this.isDefectTypeVisible() && (
-
- {expanded ? null : (
-
-
- {Parser(ArrowDownIcon)}
- {formatMessage(messages.more)}
-
-
- {Parser(BugIcon)}
- {logItem.issue.externalSystemIssues.length}
-
+
+ {expanded && (
+ <>
+
+ {Parser(BugIcon)}
+ {formatMessage(messages.btsLink)}
+
+
+
- )}
- {logItem.issue.ignoreAnalyzer &&
}
- {logItem.issue.autoAnalyzed &&
}
- {!!logItem.patternTemplates.length && (
-
- )}
-
- )}
-
-
+
+ {formatMessage(messages.postIssue)}
+
+
+ {formatMessage(messages.linkIssue)}
+
+ >
+ )}
+ >
+ )}
+
+
+ )}
+
+ {isDefectTypeVisible && (
+ <>
+ {expanded ? null : (
+
+
+
+ {Parser(BugIcon)}
+ {logItem.issue.externalSystemIssues.length}
+
+
+ )}
+ {logItem.issue.ignoreAnalyzer &&
}
+ {logItem.issue.autoAnalyzed &&
}
+ {!!logItem.patternTemplates.length && (
+
+ )}
+ >
+ )}
+
+ dispatch(fetchHistoryItemsWithLoadingAction())}
+ onChange={onChangeStatus}
+ withIndicator
+ readOnly={!canChangeStatus}
+ />
+
+ {isDefectTypeVisible && (
+
+
- {this.isDefectTypeVisible() && (
-
-
-
- )}
- {!debugMode && (
-
-
- {formatMessage(messages.makeDecision)}
-
-
- )}
- {expanded && logItem.issue && (
-
- {Parser(ArrowDownIcon)}
- {formatMessage(messages.showLess)}
-
- )}
-
+ )}
+ {!debugMode && canSeeMakeDecision && (
+
+
+ {formatMessage(messages.makeDecision)}
+
+
+ )}
+ {expanded && logItem.issue && (
+
+ )}
- );
- }
-}
+
+ );
+};
+
+DefectDetails.propTypes = {
+ fetchFunc: PropTypes.func.isRequired,
+ debugMode: PropTypes.bool.isRequired,
+ logItem: PropTypes.object,
+};
+
+DefectDetails.defaultProps = {
+ logItem: null,
+};
diff --git a/app/src/pages/inside/logsPage/logItemInfo/defectDetails/defectDetails.scss b/app/src/pages/inside/logsPage/logItemInfo/defectDetails/defectDetails.scss
index 6701e857c0..9e32cfcf69 100644
--- a/app/src/pages/inside/logsPage/logItemInfo/defectDetails/defectDetails.scss
+++ b/app/src/pages/inside/logsPage/logItemInfo/defectDetails/defectDetails.scss
@@ -118,10 +118,16 @@
line-height: 16px;
padding-right: 16px;
color: $COLOR--topaz;
+ background: none;
+ border: none;
font-size: 13px;
font-family: $FONT-SEMIBOLD;
cursor: pointer;
+ &:focus-visible {
+ outline-color: $COLOR--topaz;
+ }
+
.icon {
width: 13px;
height: 13px;
@@ -146,6 +152,7 @@
.defect-item {
border: none;
cursor: default;
+
&:hover {
border: none;
}