INFRA-2803 | Saurabh | De-escalation justification message (#128)

* INFRA-2803 | Saurabh | Added textarea for justification message

* INFRA-2803 | Saurabh | Showing the message in activiy log

* INFRA-2803 | Saurabh | Added max character limit and footer updated with tooltip

* INFRA-2803 | Saurabh | wrapped using break word

* INFRA-2803 | Saurabh | styles in css

* INFRA-2803 | Saurabh | custom Footer class created

* INFRA-2803 | Saurabh | comments resolved
This commit is contained in:
Saurabh Bhagwan Sathe
2024-02-15 14:13:46 +05:30
committed by GitHub
parent 5d77e0613b
commit 05e1afb108
8 changed files with 140 additions and 23 deletions

View File

@@ -7,6 +7,7 @@ import {
ModalDialog,
Typography,
Button,
Tooltip,
} from '@navi/web-ui/lib/primitives';
import { toast } from '@navi/web-ui/lib/primitives/Toast';
import GoToLinkIcon from '@src/assets/GoToLinkIcon';
@@ -25,6 +26,7 @@ import {
initialState,
incidentRegrex,
RESOLVE_STATUS,
getSeverityValue,
} from '../constants';
import useIncidentApis from '../useIncidentApis';
import styles from '../Incidents.module.scss';
@@ -57,6 +59,13 @@ const AllDailogBox: FC = () => {
const incidentId = incidentData?.id?.toString() || '';
const reduxDispatch = useDispatch();
const updateDetailsFrom = getSeverityValue(UpdateData?.from?.trim() || '');
const updateDetailsTo = (
UpdateData as { to?: { value: string; label: string } }
)?.to?.value;
const showJustificationBox: boolean =
updateDetailsFrom < (updateDetailsTo || '-1') || false;
const handleopenNotParticipants = (): void => {
reduxDispatch(setOpenDialognotParticipants(false));
};
@@ -97,6 +106,15 @@ const AllDailogBox: FC = () => {
});
};
const handleReasonChange = (
e: React.ChangeEvent<HTMLTextAreaElement>,
): void => {
dispatch({
type: actionTypes.SET_REASON,
payload: e.target.value,
});
};
const validateIncidentID = (value: string): void => {
dispatch({
type: actionTypes.SET_ERROR_MSG,
@@ -105,6 +123,12 @@ const AllDailogBox: FC = () => {
: '',
});
};
const handleUpdateIncidentClick = (): void => {
reduxDispatch(setOpenDialogUpdate(false));
handleCloseConfirmationDialog();
};
const validate = (value: string): boolean => incidentRegrex.test(value);
const isDisabled = (): boolean => {
const incidentId = extractIncidentId(state.incidentName);
@@ -132,10 +156,23 @@ const AllDailogBox: FC = () => {
const value = Array.isArray(selectedOption)
? selectedOption[0].value
: selectedOption.value;
updateIncident({
id: parseInt(incidentId, 10),
severityId: value.toString(),
});
if (showJustificationBox) {
updateIncident({
id: parseInt(incidentId, 10),
severityId: value.toString(),
justification: state.reason,
});
dispatch({
type: actionTypes.SET_REASON,
payload: '',
});
} else {
updateIncident({
id: parseInt(incidentId, 10),
severityId: value.toString(),
});
}
}
};
const handleStatusSelectionChange = (
@@ -321,26 +358,26 @@ const AllDailogBox: FC = () => {
</div>
);
const customFooter = (): JSX.Element => (
<div className={styles['modal-footer']}>
{UpdateData.type === 'severity' &&
showJustificationBox &&
state.reason.length <= 0 ? (
<Tooltip text="Please enter a reason for de-escalation" position="left">
<Button disabled>Update incident</Button>
</Tooltip>
) : (
<Button onClick={handleUpdateIncidentClick}>Update incident</Button>
)}
</div>
);
const renderUpdateDialog = (): JSX.Element => (
<div>
{openUpdate ? (
<ModalDialog
open={openUpdate}
footerButtons={[
{
label: 'Cancel',
onClick: () => {
reduxDispatch(setOpenDialogUpdate(false));
},
},
{
label: 'Update incident',
onClick: () => {
reduxDispatch(setOpenDialogUpdate(false));
handleCloseConfirmationDialog();
},
},
]}
customFooter={customFooter()}
header={`Are you sure you want to update the incident?`}
onClose={() => reduxDispatch(setOpenDialogUpdate(false))}
>
@@ -365,6 +402,25 @@ const AllDailogBox: FC = () => {
{getLabelFromOption(UpdateData?.to) || '..'}
</Typography>
</Typography>
{UpdateData.type === 'severity' && showJustificationBox ? (
<div>
<textarea
placeholder="Enter reason for de-escalation"
value={state.reason}
className={styles['incident-justification-box']}
onChange={handleReasonChange}
maxLength={100}
/>
<Typography
variant="p5"
className={
styles['incident-justification-box-character-limit']
}
>
{state.reason.length} / 100 characters
</Typography>
</div>
) : null}
</div>
</ModalDialog>
) : null}

View File

@@ -4,7 +4,12 @@ import { returnFormattedDate } from '@src/services/globalUtils';
import { UpdateInfoProps } from '../types';
import styles from '../Incidents.module.scss';
const renderUpdateInfo = (fromState, byPerson, updatedAt): JSX.Element => (
const renderUpdateInfo = (
fromState,
byPerson,
updatedAt,
reason,
): JSX.Element => (
<>
<div className={styles['update-info-firstline']}>
<div className={styles['single-line-wrapper']}>
@@ -27,6 +32,14 @@ const renderUpdateInfo = (fromState, byPerson, updatedAt): JSX.Element => (
</div>
</div>
{reason !== '' && (
<div className={styles['create-slack-info']} style={{ padding: '12px' }}>
<Typography variant="p5" color="var(--navi-color-gray-c3)">
Reason : <span className={styles['reason-text']}>{reason}</span>
</Typography>
</div>
)}
<div className={styles['timestamp-wrapper']}>
<Typography variant="p5" color="var(--navi-color-gray-c3)">
at
@@ -42,14 +55,15 @@ const UpdateInfo: FC<UpdateInfoProps> = ({
byPerson,
updatedAt,
isLastItem,
reason,
}) => (
<div className={styles['created-info-wrapper']}>
{isLastItem ? (
<div className={styles['last-log-wrapper']}>
{renderUpdateInfo(fromState, byPerson, updatedAt)}
{renderUpdateInfo(fromState, byPerson, updatedAt, reason)}
</div>
) : (
renderUpdateInfo(fromState, byPerson, updatedAt)
renderUpdateInfo(fromState, byPerson, updatedAt, reason)
)}
</div>
);

View File

@@ -231,3 +231,28 @@
@include flex-center;
}
}
.incident-justification-box {
width: 400px;
resize: vertical;
height: 50px;
border-radius: 8px;
margin-top: 12px;
padding: 12px;
}
.incident-justification-box-character-limit {
color: var(--navi-color-gray-c2);
text-align: right;
width: 425px;
}
.reason-text {
color: var(--navi-color-gray-c1);
word-wrap: break-word;
}
.modal-footer {
display: flex;
justify-content: flex-end;
}

View File

@@ -80,6 +80,7 @@ const IncidentChange: FC<IncidentChangeProps> = ({
byPerson={item.user_info ? item.user_info.name : 'N/A'}
updatedAt={item.created_at}
isLastItem={isLastItem}
reason={item?.justification || ''}
/>
}
stageIcon={getStageIcon(isSevChange, isStatusChange, change) || null}

View File

@@ -62,6 +62,7 @@ export const actionTypes = {
SET_ERROR_MSG: 'SET_ERROR_MSG',
RESET_DUPLICATE_DIALOG: 'RESET_DUPLICATE_DIALOG',
SET_IS_INCIDENT_RESOLVED: 'SET_IS_INCIDENT_RESOLVED',
SET_REASON: 'SET_REASON',
};
export const reducer = (state, action) => {
@@ -117,6 +118,8 @@ export const reducer = (state, action) => {
};
case actionTypes.SET_IS_INCIDENT_RESOLVED:
return { ...state, isIncidentResolved: action.payload };
case actionTypes.SET_REASON:
return { ...state, reason: action.payload };
default:
return state;
}
@@ -145,6 +148,23 @@ export const initialState = {
openDuplicateDialog: false,
errorMsg: '',
isIncidentResolved: false,
reason: '',
};
export const getSeverityValue = (value: string): string => {
switch (value) {
case 'Sev-0':
return '1';
case 'Sev-1':
return '2';
case 'Sev-2':
return '3';
case 'Sev-3':
return '4';
default:
break;
}
return '-1';
};
export const RESOLVE_STATUS = '4';

View File

@@ -19,6 +19,7 @@ export interface UpdateInfoProps {
byPerson: string;
updatedAt: string;
isLastItem: boolean;
reason: string;
}
export interface CreatedInfoProps {

View File

@@ -32,7 +32,6 @@ const useTeamApis = (): useTeamApiProps => {
const finalSlackData = emailIds.includes(',')
? emailIds.split(',').map(item => item?.trim())
: [emailIds];
console.log(teamId, emailIds, finalSlackData);
ApiService.post(endPoint, {
id: teamId,
workEmailIds: finalSlackData,

View File

@@ -176,4 +176,5 @@ export interface UpdateIncidentType {
severityId?: string;
teamId?: string;
status?: string;
justification?: string;
}