From bd43bc62137240c19e027f1b44c970f3fb667a79 Mon Sep 17 00:00:00 2001 From: AyushRanjan Date: Mon, 22 Jan 2024 00:17:05 +0530 Subject: [PATCH 01/14] TP-52973 | merge conflict resolve --- src/Pages/Incidents/ActivityLog/index.tsx | 17 +- .../Incidents/Content/Content.module.scss | 41 - src/Pages/Incidents/Content/index.tsx | 118 --- .../Incidents/DescriptionContent/index.tsx | 312 +----- .../DrawerMode/DrawerMode.module.scss | 70 -- src/Pages/Incidents/DrawerMode/index.tsx | 262 ----- src/Pages/Incidents/Dropdowns/index.tsx | 448 ++++++++ src/Pages/Incidents/Header/index.tsx | 18 +- .../Incidents/JiraLinks/JiraLinks.module.scss | 0 src/Pages/Incidents/JiraLinks/index.tsx | 301 ++++++ .../Incidents/MetaInfo/MetaInfo.module.scss | 16 - src/Pages/Incidents/MetaInfo/index.tsx | 17 - .../Incidents/StepperAssets/constants.ts | 2 - .../UpdateIncidentBox.module.scss | 0 .../Incidents/UpdateIncidentBox/index.tsx | 55 + src/Pages/Incidents/index.tsx | 965 +----------------- src/Pages/Incidents/useIncidentApis.tsx | 138 +++ src/Pages/Incidents/utils.ts | 20 +- src/slices/IncidentSlice.tsx | 76 ++ src/store/index.tsx | 2 + 20 files changed, 1101 insertions(+), 1777 deletions(-) delete mode 100644 src/Pages/Incidents/Content/Content.module.scss delete mode 100644 src/Pages/Incidents/Content/index.tsx delete mode 100644 src/Pages/Incidents/DrawerMode/DrawerMode.module.scss delete mode 100644 src/Pages/Incidents/DrawerMode/index.tsx create mode 100644 src/Pages/Incidents/Dropdowns/index.tsx create mode 100644 src/Pages/Incidents/JiraLinks/JiraLinks.module.scss create mode 100644 src/Pages/Incidents/JiraLinks/index.tsx delete mode 100644 src/Pages/Incidents/MetaInfo/MetaInfo.module.scss delete mode 100644 src/Pages/Incidents/MetaInfo/index.tsx create mode 100644 src/Pages/Incidents/UpdateIncidentBox/UpdateIncidentBox.module.scss create mode 100644 src/Pages/Incidents/UpdateIncidentBox/index.tsx create mode 100644 src/Pages/Incidents/useIncidentApis.tsx create mode 100644 src/slices/IncidentSlice.tsx diff --git a/src/Pages/Incidents/ActivityLog/index.tsx b/src/Pages/Incidents/ActivityLog/index.tsx index 3d0cd48..24611fd 100644 --- a/src/Pages/Incidents/ActivityLog/index.tsx +++ b/src/Pages/Incidents/ActivityLog/index.tsx @@ -3,18 +3,15 @@ import Typography from '@navi/web-ui/lib/primitives/Typography'; import IncidentCreated from '../StepperAssets/IncidentCreated'; import IncidentChange from '../StepperAssets/IncidentChange'; import styles from './ActivityLog.module.scss'; +import { useDispatch, useSelector } from 'react-redux'; -interface ActivityLogProps { - incidentLog: { - data: { - logs: Array; - has_creation: boolean; - }; - }; - totalLog: number; -} +const ActivityLog: FC = () => { + const incidentLog = useSelector( + (state: any) => state.incidentLog.incidentLogData, + ); + const totalLog = incidentLog?.data?.logs?.length; + console.log('logss', incidentLog); -const ActivityLog: FC = ({ incidentLog, totalLog }) => { return (
{incidentLog?.data?.logs && incidentLog.data.logs.length > 0 && ( diff --git a/src/Pages/Incidents/Content/Content.module.scss b/src/Pages/Incidents/Content/Content.module.scss deleted file mode 100644 index e0c1681..0000000 --- a/src/Pages/Incidents/Content/Content.module.scss +++ /dev/null @@ -1,41 +0,0 @@ -.content-wrapper { - background: #fff; - box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.1), 0px 2px 1px rgba(0, 0, 0, 0.06), - 0px 1px 1px rgba(0, 0, 0, 0.08); - border-radius: 8px; - padding: 12px; - margin-top: 16px; - width: 35%; -} - -.description-details { - margin: 12px 0 16px 0; -} - -.participant-detail { - display: flex; - align-items: center; - gap: 4px; -} - -.team-details-wrapper { - display: flex; - justify-content: space-between; - align-items: center; - margin-top: 12px; -} - -.loader { - margin-top: 12px; - text-align: center; -} - -.content-info { - display: flex; - align-items: center; - gap: 2px; -} - -.key-value-pair { - margin-top: 6px; -} diff --git a/src/Pages/Incidents/Content/index.tsx b/src/Pages/Incidents/Content/index.tsx deleted file mode 100644 index 3293785..0000000 --- a/src/Pages/Incidents/Content/index.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import { useEffect, useState } from 'react'; - -import Avatar from '@navi/web-ui/lib/primitives/Avatar'; -import KeyValueLabel from '@navi/web-ui/lib/primitives/KeyValueLabel'; -import Typography from '@navi/web-ui/lib/primitives/Typography'; -import { toast } from '@navi/web-ui/lib/primitives/Toast'; -import LoadingIcon from '@navi/web-ui/lib/icons/LoadingIcon'; - -import { ApiService } from '@src/services/api'; -import { returnFormattedDate } from '@src/services/globalUtils'; -import { - ContentProps, - FETCH_PARTICIPANTS_DATA, - IncidentConstants, -} from '../constants'; -import styles from './Content.module.scss'; -import commonStyles from '../Incidents.module.scss'; - -const Content = (props: ContentProps) => { - const { incidentData } = props; - const [data, setData] = useState(); - - const [isLoading, setIsLoading] = useState(false); - - const startParticipantsSearch = (): void => { - if (incidentData?.slackChannel) { - const endPoint = FETCH_PARTICIPANTS_DATA(incidentData?.slackChannel); - setIsLoading(true); - ApiService.get(endPoint) - .then(response => { - setIsLoading(false); - setData(response?.data?.data); - }) - .catch(error => { - const toastMessage = `${ - error?.response?.data?.error?.message - ? `${error?.response?.data?.error?.message},` - : '' - }`; - setIsLoading(false); - toast.error(toastMessage); - setData([]); - }); - } - }; - - useEffect(() => { - startParticipantsSearch(); - }, [incidentData]); - - return ( -
- {IncidentConstants.incidentSummary} -
-
- - {IncidentConstants.title}:{' '} - {incidentData?.title} - -
-
- {IncidentConstants.description}: - {incidentData?.description} -
- {IncidentConstants.status}: -
- -
- -
- {IncidentConstants.team}: - {isLoading ? ( -
- -
- ) : ( - data?.map(participant => ( -
-
- {' '} - {participant?.real_name} -
-
- )) - )} -
-
- ); -}; - -export default Content; diff --git a/src/Pages/Incidents/DescriptionContent/index.tsx b/src/Pages/Incidents/DescriptionContent/index.tsx index a4c35ec..03234fd 100644 --- a/src/Pages/Incidents/DescriptionContent/index.tsx +++ b/src/Pages/Incidents/DescriptionContent/index.tsx @@ -1,4 +1,11 @@ import React, { FC, useState, useReducer, useEffect } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import { + setIncidentData, + setIncidentLogData, + setHeaderData, + setParticipantsData, +} from '@src/slices/IncidentSlice'; import { Typography, Avatar, @@ -12,15 +19,10 @@ import { } from '@navi/web-ui/lib/icons'; import { toast } from '@navi/web-ui/lib/primitives/Toast'; import SlackIcon from '@src/assets/SlackIcon'; -import LinkIcon from '@src/assets/LinkIcon'; -import JiraLogo from '@src/assets/JiraLogo'; -import CopyIcon from '@src/assets/CopyIcon'; import LoadingIcon from '@src/assets/LoadingIcon'; import ConfluenceIcon from '@src/assets/ConfluenceIcon'; import GoToLinkIcon from '@src/assets/GoToLinkIcon'; import { ApiService } from '@src/services/api'; -import { handleCopyClick } from '@src/services/globalUtils'; -import DescriptionContentProps from './DescriptionContentProps'; import { ActionType, initialState, @@ -33,179 +35,37 @@ import { UNLINK_JIRA_INCIDENT, FETCH_INCIDENT_DATA, } from '../constants'; +import { truncateText, linkSanitization } from '../utils'; +import JiraLinks from '../JiraLinks'; -const DescriptionContent: React.FC = ({ - id, - description, - slackChannel, - incidentName, - incidentParticipants, - jiraIds, - rcaLink, -}) => { +const DescriptionContent: React.FC = () => { const [state, dispatch] = useReducer(reducer, initialState); + const { id, description, jiraIds, slackChannel, incidentName, rcaLink } = + useSelector((state: any) => state.incidentLog.incidentData); + + const incidentParticipants = useSelector( + (state: any) => state.incidentLog.participantsData, + ); + const reduxDispatch = useDispatch(); + + //move to hooks const storedEmail = localStorage.getItem('email-id'); + //import scss const goToLinkBlueColor = '#0276FE'; - useEffect(() => { - dispatch({ type: ActionType.SET_JIRA_LINKS, payload: jiraIds || [] }); - }, [jiraIds]); + //use from hooks below three functions - const handleApiError = (error: any): void => { - const errorMessage = - error?.response?.data?.error?.message || 'An error occurred.'; - toast.error(errorMessage); - }; + //move to utils - const startIncidentSearch = (): void => { - const endPoint = FETCH_INCIDENT_DATA(id); - ApiService.get(endPoint) - .then(response => { - dispatch({ - type: ActionType.SET_JIRA_LINKS, - payload: response?.data?.data?.jiraLinks || [], - }); - }) - .catch(handleApiError); - }; + const renderParticipantList = (type: string): JSX.Element => { + const list = + type === 'participants' + ? incidentParticipants?.participants + : incidentParticipants?.others; - const addJiraLink = (payload: any): void => { - handleCloseIconClick(); - dispatch({ type: ActionType.SET_SHOW_LINKED_TICKETS, payload: true }); - const endPoint = LINK_JIRA_INCIDENT; - ApiService.post(endPoint, payload) - .then(response => { - toast.success(`${response?.data?.data}`); - startIncidentSearch(); - }) - .catch(handleApiError); - }; - - const removeJiraLink = (payload: any): void => { - const endPoint = UNLINK_JIRA_INCIDENT; - ApiService.post(endPoint, payload) - .then(response => { - toast.info(`${response?.data?.data}`); - startIncidentSearch(); - }) - .catch(handleApiError); - }; - - const validateLink = (value: string): void => { - const urlPattern = /^https:\/\/navihq\.atlassian\.net\/browse\/.*/; - const invalidChars = /,|\s/; - if (value === '') { - dispatch({ type: ActionType.SET_ERROR_TEXT, payload: '' }); - dispatch({ type: ActionType.SET_HELPER_TEXT, payload: '' }); - } else { - if (urlPattern.test(value) && !invalidChars.test(value)) { - dispatch({ type: ActionType.SET_ERROR_TEXT, payload: '' }); - dispatch({ - type: ActionType.SET_HELPER_TEXT, - payload: 'Press enter to add link', - }); - } else { - dispatch({ type: ActionType.SET_HELPER_TEXT, payload: '' }); - dispatch({ - type: ActionType.SET_ERROR_TEXT, - payload: ' Invalid entry. Try entering a different URL', - }); - } - } - }; - - const handleLinkChange = (e: React.ChangeEvent): void => { - const inputValue = e.target.value; - dispatch({ type: ActionType.SET_INPUT_VALUE, payload: inputValue }); - validateLink(inputValue); - }; - - const linkSanitization = link => { - const sanitizedLinkMatch = link.match( - /(https:\/\/navihq.atlassian.net\/browse\/[^/]+)/, - ); - if (sanitizedLinkMatch && sanitizedLinkMatch[1]) { - return sanitizedLinkMatch[1]; - } - return link; - }; - - const handleInputKeyDown = ( - event: React.KeyboardEvent, - ): void => { - if (event.key === 'Enter') { - if (!state.errorText && state.inputValue.startsWith(JIRA_VALIDATION)) { - const sanitizedLink = linkSanitization(state.inputValue); - const payload = { - incident_id: id, - jira_link: sanitizedLink, - user: storedEmail, - }; - toast('Adding link. Please wait a moment.', { - icon: , - }); - addJiraLink(payload); - } - } - }; - - const handleLinkJiraClick = (): void => { - dispatch({ type: ActionType.SET_SHOW_INPUT, payload: true }); - }; - - const handleCloseIconClick = (): void => { - dispatch({ type: ActionType.SET_INPUT_VALUE, payload: '' }); - dispatch({ type: ActionType.SET_ERROR_TEXT, payload: '' }); - dispatch({ type: ActionType.SET_HELPER_TEXT, payload: '' }); - dispatch({ type: ActionType.SET_SHOW_INPUT, payload: false }); - }; - - const handleLinkedTicketsClick = (): void => { - dispatch({ - type: ActionType.SET_SHOW_LINKED_TICKETS, - payload: !state.showLinkedTickets, - }); - }; - - const handleDeleteIconClick = (jiraLinkToDelete: string): void => { - const payload = { - incident_id: id, - jira_link: jiraLinkToDelete, - user: storedEmail, - }; - toast('Removing link. Please wait a moment.', { - icon: , - }); - removeJiraLink(payload); - }; - - const truncateText = (text): string => { - const jiraTicketMatch = text.match(/\/browse\/([^/]+)/); - if (jiraTicketMatch && jiraTicketMatch[1]) { - return jiraTicketMatch[1]; - } - return text; - }; - - const returnParticipants = (): JSX.Element => { - return incidentParticipants?.participants?.length ? ( - incidentParticipants?.participants?.map(participant => ( -
-
-    - {participant?.name} -
-
- )) - ) : ( -
-
- ); - }; - - const returnOthers = (): JSX.Element => { - return incidentParticipants?.others?.length ? ( - incidentParticipants?.others?.map(participant => ( + return list?.length ? ( + list.map(participant => (
   @@ -273,116 +133,8 @@ const DescriptionContent: React.FC = ({
)} -
-
- -   - - JIRA ticket(s)      - -
-
-
-
- - {state.jiraLinks?.filter(link => link !== '').length || 0}{' '} - linked tickets   - - {state.jiraLinks?.filter(link => link !== '').length > 0 && ( - <> - {state.showLinkedTickets && ( - - )} - {!state.showLinkedTickets && ( - - )} - - )} -
-
-
- -   - - Link JIRA ticket - -
-
-
- {state.showLinkedTickets && ( -
- {state.jiraLinks - .filter(jiraLink => jiraLink !== '') - ?.map((jiraLink, index) => ( -
- -    -
- handleCopyClick(jiraLink)} /> -
-
- handleDeleteIconClick(jiraLink)} - width={20} - height={20} - color="var(--navi-color-gray-c3)" - /> -
-
- ))} -
- )} -
- {state.showInput && ( -
-
- -
-
- -
-
- )} -
-
+
@@ -392,14 +144,14 @@ const DescriptionContent: React.FC = ({ Team - {returnParticipants()} + {renderParticipantList('participants')}
Others - {returnOthers()} + {renderParticipantList('others')}
diff --git a/src/Pages/Incidents/DrawerMode/DrawerMode.module.scss b/src/Pages/Incidents/DrawerMode/DrawerMode.module.scss deleted file mode 100644 index 14cc4cc..0000000 --- a/src/Pages/Incidents/DrawerMode/DrawerMode.module.scss +++ /dev/null @@ -1,70 +0,0 @@ -.severity-details-wrapper { - display: flex; - align-items: center; - gap: 0 8px; - margin: 8px 0; -} - -.content-info { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 8px; -} - -.description-details { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 8px; - margin: 12px 0; - - .description { - margin-top: -2px; - line-height: 24px; - } -} - -.slack-channel { - display: flex; - align-items: center; - gap: 0 8px; - color: #24b4d2; - - a { - text-decoration: none; - color: var(--navi-color-blue-base); - } -} - -.meta-info { - margin: 12px 0 16px 0; - - &__title { - margin-bottom: 8px; - } -} - -.participant-detail { - display: flex; - align-items: center; - gap: 4px; -} - -.team-details-wrapper { - display: flex; - justify-content: space-between; - align-items: center; - margin-top: 12px; -} - -.loader-container { - height: 100%; - display: flex; - justify-content: center; - align-items: center; -} - -.content-divider { - margin: 16px 0; -} diff --git a/src/Pages/Incidents/DrawerMode/index.tsx b/src/Pages/Incidents/DrawerMode/index.tsx deleted file mode 100644 index 76f47bd..0000000 --- a/src/Pages/Incidents/DrawerMode/index.tsx +++ /dev/null @@ -1,262 +0,0 @@ -import { FC, useEffect, useState } from 'react'; -import cx from 'classnames'; - -import { Typography, Avatar } from '@navi/web-ui/lib/primitives'; -import Filter from '@navi/web-ui/lib/components/Filter'; -import { toast } from '@navi/web-ui/lib/primitives/Toast'; -import LoadingIcon from '@navi/web-ui/lib/icons/LoadingIcon'; - -import { ApiService } from '@src/services/api'; -import SlackIcon from '@src/assets/SlackIcon'; -import { - FETCH_HEADER_DETAILS, - FETCH_PARTICIPANTS_DATA, - FETCH_INCIDENT_DATA, - UPDATE_INCIDENT, - IncidentConstants, -} from '../constants'; -import styles from './DrawerMode.module.scss'; -import commonStyles from '../Incidents.module.scss'; - -interface DrawerModeProps { - incidentId: number; - slackChannel: string; -} - -const DrawerMode: FC = ({ incidentId, slackChannel }) => { - const [severity, setSeverity] = useState(); - const [status, setStatus] = useState(); - const [team, setTeam] = useState(); - const [headerData, setHeaderData] = useState({}); - const [incidentDetails, setIncidentDetails] = useState({}); - const [incidentParticipants, setIncidentParticipants] = useState([]); - const [isLoading, setIsLoading] = useState(false); - - const fetchHeaderDetails = (): void => { - const endPoint = FETCH_HEADER_DETAILS; - ApiService.get(endPoint) - .then(response => { - setHeaderData(response?.data?.data); - }) - .catch(error => { - const toastMessage = `${ - error?.response?.data?.error?.message - ? `${error?.response?.data?.error?.message},` - : '' - }`; - toast.error(toastMessage); - setHeaderData({}); - }); - }; - - const initFilters = (incidentDetails): void => { - const severity = headerData?.severities?.find( - item => item.value === incidentDetails?.severityId, - ); - const status = headerData?.incidentStatuses?.find( - item => item.value === incidentDetails?.status, - ); - const team = headerData?.teams?.find( - item => item.value === incidentDetails?.teamId, - ); - setSeverity(severity); - setStatus(status); - setTeam(team); - setIsLoading(false); - }; - - const fetchIncidentDetails = (): void => { - const endPoint = FETCH_INCIDENT_DATA(incidentId); - ApiService.get(endPoint) - .then(response => { - setIncidentDetails(response?.data?.data); - }) - .catch(error => { - const toastMessage = `${ - error?.response?.data?.error?.message - ? `${error?.response?.data?.error?.message},` - : '' - }`; - toast.error(toastMessage); - setIncidentDetails({}); - }); - }; - - const fetchParticipants = (): void => { - const endPoint = FETCH_PARTICIPANTS_DATA(slackChannel); - ApiService.get(endPoint) - .then(response => { - setIncidentParticipants(response?.data?.data); - }) - .catch(error => { - const toastMessage = `${ - error?.response?.data?.error?.message - ? `${error?.response?.data?.error?.message},` - : '' - }`; - toast.error(toastMessage); - setIncidentParticipants([]); - }); - }; - - useEffect(() => { - setIsLoading(true); - if (incidentId && slackChannel) { - fetchIncidentDetails(); - fetchHeaderDetails(); - fetchParticipants(); - } - }, [incidentId, slackChannel]); - - useEffect(() => { - if (Object.keys(headerData).length && Object.keys(incidentDetails).length) { - initFilters(incidentDetails); - } - }, [headerData, incidentDetails, incidentParticipants]); - - const updateIncident = payload => { - const endPoint = UPDATE_INCIDENT; - ApiService.post(endPoint, payload) - .then(response => { - toast.success('Incident Updated Successfully'); - }) - .catch(error => { - const toastMessage = `${ - error?.response?.data?.error?.message - ? `${error?.response?.data?.error?.message},` - : '' - }`; - toast.error(toastMessage); - }); - }; - - const returnHeaderDetails = () => { - return ( -
- Update Tags: -
- Severity: - { - setSeverity(val); - updateIncident({ - id: incidentId, - severityId: !Array.isArray(val) - ? `${val?.value}` - : val?.[0]?.value, - }); - }} - isSingleSelect - filterClass={styles['filter-wrapper']} - /> -
-
- Status: - { - setStatus(val); - updateIncident({ - id: incidentId, - status: !Array.isArray(val) ? `${val?.value}` : val?.[0]?.value, - }); - }} - isSingleSelect - filterClass={styles['filter-wrapper']} - /> -
-
- Team: - { - setTeam(val); - updateIncident({ - id: incidentId, - teamId: !Array.isArray(val) ? `${val?.value}` : val?.[0]?.value, - }); - }} - isSingleSelect - filterClass={styles['filter-wrapper']} - /> -
-
- ); - }; - - const returnContent = () => { - return ( -
-
- {IncidentConstants.title}: - {incidentDetails?.title} -
-
- {IncidentConstants.description}: - - {incidentDetails?.description || '-'} - -
-
- {IncidentConstants.channel}: - - - - {incidentDetails?.incidentName || '-'} - - -
-
- {IncidentConstants.incidentId}: - - {incidentDetails?.id} - -
-
- ); - }; - - const returnParticipants = () => { - return incidentParticipants?.length ? ( - incidentParticipants?.map(participant => ( -
-
- {' '} - {participant?.name} -
-
- )) - ) : ( -
-
- ); - }; - - if (isLoading) { - return ( -
- -
- ); - } - - return ( -
- {returnContent()} -
- {returnHeaderDetails()} -
- Participants: - {returnParticipants()} -
- ); -}; - -export default DrawerMode; diff --git a/src/Pages/Incidents/Dropdowns/index.tsx b/src/Pages/Incidents/Dropdowns/index.tsx new file mode 100644 index 0000000..1cf4a08 --- /dev/null +++ b/src/Pages/Incidents/Dropdowns/index.tsx @@ -0,0 +1,448 @@ +import { FC, useReducer, useState, MutableRefObject } from 'react'; +import { useMatch } from 'react-router-dom'; +import { useSelector } from 'react-redux'; +import Typography from '@navi/web-ui/lib/primitives/Typography'; +import styles from '../Incidents.module.scss'; +import classnames from 'classnames'; +import { toast } from '@navi/web-ui/lib/primitives/Toast'; +import LoadingIcon from '@src/assets/LoadingIcon'; +import { + reducer, + actionTypes, + initialState, + DUPLICATE_STATUS, + RESOLVE_STATUS, + SeverityType, + StatusType, + TeamType, + SLACK_BASE_URL, + incidentRegrex, + ResponseType, +} from '../constants'; +import { ArrowDownIcon } from '@navi/web-ui/lib/icons'; +import { SelectPicker } from '@navi/web-ui/lib/components'; +import { SelectPickerOptionProps } from '@navi/web-ui/lib/components/SelectPicker/types'; +import useIncidentApis from '../useIncidentApis'; +import { generateOptions, getUpdateTypeText } from '../utils'; +import useOutsideClick from '@src/services/hooks/useOustideClick'; + +const Dropdowns: FC = () => { + const match = useMatch({ + end: true, + path: '/incident/:incidentId', + }); + const incidentId2 = match?.params?.incidentId || ''; + + const incidentId = incidentId2.toString(); + const { updateIncident } = useIncidentApis(); + const incidentData = useSelector( + (state: any) => state.incidentLog.incidentData, + ); + const incidentParticipants = useSelector( + (state: any) => state.incidentLog.incidentParticipants, + ); + + const [state, dispatch] = useReducer(reducer, initialState); + const [open, setOpen] = useState(false); + const updatedSeverities = generateOptions(state.headerData?.severities); + const updatedStatuses = generateOptions(state.headerData?.incidentStatuses); + const updatedTeams = generateOptions(state.headerData?.teams); + + // use custom hook to fetch data + const userData = JSON.parse(localStorage.getItem('user-data') || '{}'); + const userEmail = userData?.emailId || []; + + const participants = incidentParticipants?.participants; + const others = incidentParticipants?.others; + + const participantsList = (participants || []).concat(others || []); + const isUserParticipantList = participantsList?.some( + (participant: any) => participant.email === userEmail, + ); + + const severityMap = state.headerData?.severities?.reduce((map, severity) => { + map[severity.value] = severity.label; + return map; + }, {}); + const incidentStatusMap = state.headerData?.incidentStatuses?.reduce( + (map, status) => { + map[status.value] = status.label; + return map; + }, + {}, + ); + const teamsMap = state.headerData?.teams?.reduce((map, Incidentteam) => { + map[Incidentteam.value] = Incidentteam.label; + return map; + }, {}); + + const combinedSevClassNames = classnames( + styles[isUserParticipantList ? 'dropdown-box' : 'dropdown-disabled'], + { + [styles['open-box']]: state.isSeverityPickerOpen, + [styles['open-box-disabled']]: + state.isSeverityPickerOpen && !isUserParticipantList, + }, + ); + const combinedStatusClassNames = classnames( + styles[isUserParticipantList ? 'dropdown-box' : 'dropdown-disabled'], + { + [styles['open-box']]: state.isStatusPickerOpen, + [styles['open-box-disabled']]: + state.isStatusPickerOpen && !isUserParticipantList, + }, + ); + const combinedTeamClassNames = classnames( + styles[isUserParticipantList ? 'dropdown-box' : 'dropdown-disabled'], + { + [styles['open-box']]: state.isTeamPickerOpen, + [styles['open-box-disabled']]: + state.isTeamPickerOpen && !isUserParticipantList, + }, + ); + + const handleSevClickOutside = () => { + dispatch({ + type: actionTypes.SET_IS_SEVERITY_PICKER_OPEN, + payload: false, + }); + }; + + const handleStatusClickOutside = () => { + dispatch({ + type: actionTypes.SET_IS_STATUS_PICKER_OPEN, + payload: false, + }); + }; + + const handleTeamClickOutside = () => { + dispatch({ + type: actionTypes.SET_IS_TEAM_PICKER_OPEN, + payload: false, + }); + }; + + const refStatus = useOutsideClick({ + callback: handleStatusClickOutside, + }) as MutableRefObject; + + const refSeverity = useOutsideClick({ + callback: handleSevClickOutside, + }) as MutableRefObject; + + const refTeam = useOutsideClick({ + callback: handleTeamClickOutside, + }) as MutableRefObject; + + const handleSeverityDropdownClick = (): void => { + dispatch({ + type: actionTypes.SET_IS_SEVERITY_PICKER_OPEN, + payload: !state.isSeverityPickerOpen, + }); + }; + const handleStatusDropdownClick = (): void => { + dispatch({ + type: actionTypes.SET_IS_STATUS_PICKER_OPEN, + payload: !state.isStatusPickerOpen, + }); + }; + const handleTeamDropdownClick = (): void => { + dispatch({ + type: actionTypes.SET_IS_TEAM_PICKER_OPEN, + payload: !state.isTeamPickerOpen, + }); + }; + + const handleSevClick = () => { + if (isUserParticipantList) { + handleSeverityDropdownClick(); + } else { + handleDisabledDropdownClick(); + } + }; + const handleStatusClick = () => { + if (isUserParticipantList) { + handleStatusDropdownClick(); + } else { + handleDisabledDropdownClick(); + } + }; + const handleTeamClick = () => { + if (isUserParticipantList) { + handleTeamDropdownClick(); + } else { + handleDisabledDropdownClick(); + } + }; + + const handleDisabledDropdownClick = (): void => { + setOpen(true); + }; + const closeModal = (): void => { + setOpen(false); + }; + + const handleOpenConfirmationDailog = ( + selectedOption: SelectPickerOptionProps | SelectPickerOptionProps[], + updateType: number, + ): void => { + const currentValue = + updateType === SeverityType + ? state.severity?.value + : updateType === StatusType + ? state.status?.value + : state.team?.value; + const currentState = currentValue.toString(); + const selectedvalue = Array.isArray(selectedOption) + ? selectedOption[0].value + : selectedOption.value; + if (currentState !== selectedvalue) { + dispatch({ type: actionTypes.SET_IS_STATUS_PICKER_OPEN, payload: false }); + dispatch({ + type: actionTypes.SET_IS_SEVERITY_PICKER_OPEN, + payload: false, + }); + dispatch({ type: actionTypes.SET_IS_TEAM_PICKER_OPEN, payload: false }); + if (updateType === StatusType && selectedvalue === RESOLVE_STATUS) { + handleStatusSelectionChange(selectedOption); + } else if ( + updateType === StatusType && + selectedvalue === DUPLICATE_STATUS + ) { + dispatch({ + type: actionTypes.SET_DUPLICATE_DIALOG, + payload: true, + }); + } else { + dispatch({ type: actionTypes.SET_UPDATE_TYPE, payload: updateType }); + dispatch({ + type: actionTypes.SET_SELECTED_OPTION, + payload: selectedOption, + }); + dispatch({ + type: actionTypes.SET_OPEN_CONFIRMATION_DIALOG, + payload: true, + }); + } + } + }; + + const handleCloseConfirmationDialog = () => { + if (state.updateType === SeverityType) { + handleSeveritySelectionChange(state.selectedOption); + } else { + if (state.updateType === StatusType) { + handleStatusSelectionChange(state.selectedOption); + } else { + handleTeamSelectionChange(state.selectedOption); + } + } + }; + + const handleSeveritySelectionChange = ( + selectedOption: SelectPickerOptionProps | SelectPickerOptionProps[], + ): void => { + if (selectedOption) { + toast('Updating ticket. Please wait a moment.', { + icon: , + }); + dispatch({ + type: actionTypes.SET_IS_SEVERITY_PICKER_OPEN, + payload: false, + }); + const value = Array.isArray(selectedOption) + ? selectedOption[0].value + : selectedOption.value; + updateIncident({ + id: parseInt(incidentId, 10), + severityId: value.toString(), + }); + } + }; + const handleStatusSelectionChange = ( + selectedOption: SelectPickerOptionProps | SelectPickerOptionProps[], + ): void => { + if (selectedOption) { + dispatch({ type: actionTypes.SET_IS_STATUS_PICKER_OPEN, payload: false }); + const value = Array.isArray(selectedOption) + ? selectedOption[0].value + : selectedOption.value; + if (value === RESOLVE_STATUS) { + dispatch({ + type: actionTypes.SET_DIALOG_TEXT, + payload: 'Resolve incident', + }); + dispatch({ + type: actionTypes.SET_DIALOG_BODY_TEXT, + payload: 'resolved', + }); + dispatch({ type: actionTypes.SET_OPEN_DIALOG, payload: true }); + } else { + toast('Updating ticket. Please wait a moment.', { + icon: , + }); + updateIncident({ + id: parseInt(incidentId, 10), + status: value.toString(), + }); + } + } + }; + const handleTeamSelectionChange = ( + selectedOption: SelectPickerOptionProps | SelectPickerOptionProps[], + ): void => { + if (selectedOption) { + dispatch({ type: actionTypes.SET_IS_TEAM_PICKER_OPEN, payload: false }); + toast('Updating ticket. Please wait a moment.', { + icon: , + }); + const value = Array.isArray(selectedOption) + ? selectedOption[0].value + : selectedOption.value; + updateIncident({ + id: parseInt(incidentId, 10), + teamId: value.toString(), + }); + } + }; + + return ( +
+
+
+
+ + Severity + +
+
+
+
+ + {severityMap && state.severity?.value + ? severityMap[state.severity.value] + : '-'} + +
+
+ +
+
+
+ {state.isSeverityPickerOpen && ( +
+ + handleOpenConfirmationDailog(selectedOption, SeverityType) + } + options={updatedSeverities} + selectedValue={state.severity?.value.toString()} + /> +
+ )} +
+
+
+
+
+ + Status + +
+
+
+
+ + {incidentStatusMap && state.status?.value + ? incidentStatusMap[state.status.value] + : '-'} + +
+
+ +
+
+
+ {state.isStatusPickerOpen && ( +
+ + handleOpenConfirmationDailog(selectedOption, StatusType) + } + options={updatedStatuses} + selectedValue={state.status?.value.toString()} + /> +
+ )} +
+
+
+
+
+ + Team + +
+
+
+
+ + {teamsMap && state.team?.value + ? teamsMap[state.team.value] + : '-'} + +
+ +
+ +
+
+ +
+ {state.isTeamPickerOpen && ( +
+ + handleOpenConfirmationDailog(selectedOption, TeamType) + } + options={updatedTeams} + selectedValue={state.team?.value.toString()} + /> +
+ )} +
+
+
+
+
+ ); +}; + +export default Dropdowns; diff --git a/src/Pages/Incidents/Header/index.tsx b/src/Pages/Incidents/Header/index.tsx index 2b0b146..08c0a48 100644 --- a/src/Pages/Incidents/Header/index.tsx +++ b/src/Pages/Incidents/Header/index.tsx @@ -1,5 +1,6 @@ import { FC } from 'react'; import { useNavigate } from 'react-router'; +import { useSelector } from 'react-redux'; import Typography from '@navi/web-ui/lib/primitives/Typography'; import Tooltip from '@navi/web-ui/lib/primitives/Tooltip'; import Button from '@navi/web-ui/lib/primitives/Button'; @@ -8,16 +9,17 @@ import CopyIcon from '@src/assets/CopyIcon'; import { handleCopyUrlToClipboard } from '@src/services/globalUtils'; import styles from './Header.module.scss'; -interface HeaderProps { - incidentName: string; - title: string; -} - -const Header: FC = ({ incidentName, title }) => { +const Header: FC = () => { const navigate = useNavigate(); const handleBacktoDashboard = (): void => { - navigate('/'); + navigate(-1); }; + + //add type here and color to centralize + const incidentData = useSelector( + (state: any) => state.incidentLog.incidentData, + ); + return (
- - ) : null} - {state.openDialog ? ( - { - dispatch({ - type: actionTypes.SET_OPEN_DIALOG, - payload: false, - }); - }, - }, - { - label: 'Go to slack channel', - onClick: handleGoToSlackChannel, - startAdornment: ( - - ), - }, - ]} - header={`${state.dialogText}`} - onClose={() => - dispatch({ type: actionTypes.SET_OPEN_DIALOG, payload: false }) - } - > - - We’re working on improving this feature. For the time being please - mark this incident as {`${state.dialogBodyText}`} on Slack. - - - ) : null} -
- {state.openConfirmationDialog ? ( - { - dispatch({ - type: actionTypes.SET_OPEN_CONFIRMATION_DIALOG, - payload: false, - }); - }, - }, - { - label: 'Update incident', - onClick: () => { - dispatch({ - type: actionTypes.SET_OPEN_CONFIRMATION_DIALOG, - payload: false, - }); - handleCloseConfirmationDialog(); - }, - }, - ]} - header={`Are you sure you want to update the incident?`} - onClose={() => - dispatch({ - type: actionTypes.SET_OPEN_CONFIRMATION_DIALOG, - payload: false, - }) - } - > -
- - You are updating this incident‘s - {getUpdateTypeText(state.updateType)} - from - - {getUpdateValueText()} - - to  - - {state.selectedOption?.label || '..'} - - -
-
- ) : null} -
); diff --git a/src/Pages/Incidents/useIncidentApis.tsx b/src/Pages/Incidents/useIncidentApis.tsx new file mode 100644 index 0000000..835db82 --- /dev/null +++ b/src/Pages/Incidents/useIncidentApis.tsx @@ -0,0 +1,138 @@ +import { useDispatch, useSelector } from 'react-redux'; +import { toast } from '@navi/web-ui/lib/primitives/Toast'; +import { ApiService } from '@src/services/api'; +import { + FETCH_INCIDENT_DATA, + UPDATE_INCIDENT, + FETCH_HEADER_DETAILS, + FETCH_AUDIT_LOG, + FETCH_PARTICIPANTS_DATA, + actionTypes, +} from './constants'; +// Import necessary action creators +import { + setIncidentData, + setIncidentLogData, + setHeaderData, + setParticipantsData, +} from '@src/slices/IncidentSlice'; + +const useIncidentApis = () => { + const dispatch = useDispatch(); + + const handleApiError = error => { + const toastMessage = `${ + error?.response?.data?.error?.message + ? `${error?.response?.data?.error?.message},` + : '' + }`; + toast.error(toastMessage); + }; + + const incidentData = useSelector( + (state: any) => state.incidentLog.incidentData, + ); + const slackChannel = incidentData?.slackChannel; + + const fetchIncidentLog = (incidentId): void => { + const endPoint = FETCH_AUDIT_LOG(incidentId); + ApiService.get(endPoint) + .then(response => { + dispatch(setIncidentLogData(response?.data)); + }) + .catch(error => { + const toastMessage = `${ + error?.response?.data?.error?.message + ? `${error?.response?.data?.error?.message},` + : '' + }`; + toast.error(toastMessage); + }); + }; + + const startIncidentSearch = (incidentId): void => { + const endPoint = FETCH_INCIDENT_DATA(incidentId); + ApiService.get(endPoint) + .then(response => { + dispatch(setIncidentData(response?.data?.data)); + }) + .catch(error => { + const toastMessage = `${ + error?.response?.data?.error?.message + ? `${error?.response?.data?.error?.message},` + : '' + }`; + toast.error(toastMessage); + }); + }; + + const fetchHeaderDetails = (): void => { + const endPoint = FETCH_HEADER_DETAILS; + ApiService.get(endPoint) + .then(response => { + // dispatch({ + // type: actionTypes.SET_HEADER_DATA, + // payload: response?.data?.data, + // }); + }) + .catch(error => { + const toastMessage = `${ + error?.response?.data?.error?.message + ? `${error?.response?.data?.error?.message},` + : '' + }`; + toast.error(toastMessage); + }); + }; + + const fetchParticipants = (): void => { + const endPoint = FETCH_PARTICIPANTS_DATA(slackChannel); + ApiService.get(endPoint) + .then(response => { + // dispatch({ type: actionTypes.SET_INCIDENT_PARTICIPANTS, payload: [] }); + // dispatch({ + // type: actionTypes.SET_INCIDENT_PARTICIPANTS, + // payload: response?.data?.data, + // }); + }) + .catch(error => { + const toastMessage = `${ + error?.response?.data?.error?.message + ? `${error?.response?.data?.error?.message},` + : '' + }`; + toast.error(toastMessage); + //dispatch({ type: actionTypes.SET_INCIDENT_PARTICIPANTS, payload: [] }); + }); + }; + + const updateIncident = (payload: any): void => { + const endPoint = UPDATE_INCIDENT; + ApiService.post(endPoint, payload) + .then(response => { + toast.success('Incident updated successfully'); + // startIncidentSearch(i); + //fetchIncidentLog(); + //fetchParticipants(); + }) + .catch(error => { + const toastMessage = `${ + error?.response?.data?.error?.message + ? `${error?.response?.data?.error?.message},` + : '' + }`; + toast.error(toastMessage); + //startIncidentSearch(); + }); + }; + + return { + fetchIncidentLog, + startIncidentSearch, + fetchHeaderDetails, + fetchParticipants, + updateIncident, + }; +}; + +export default useIncidentApis; diff --git a/src/Pages/Incidents/utils.ts b/src/Pages/Incidents/utils.ts index 6eeef28..7c555bc 100644 --- a/src/Pages/Incidents/utils.ts +++ b/src/Pages/Incidents/utils.ts @@ -1,5 +1,5 @@ import { SeverityType, StatusType, TeamType } from './constants'; - +import { useMatch } from 'react-router-dom'; export const getUpdateTypeText = (updateType: number): string => { switch (updateType) { case SeverityType: @@ -29,3 +29,21 @@ export const generateOptions = (data: DataItem[]): DataItemModified[] => { label: item.label, })); }; + +export const truncateText = (text): string => { + const jiraTicketMatch = text.match(/\/browse\/([^/]+)/); + if (jiraTicketMatch && jiraTicketMatch[1]) { + return jiraTicketMatch[1]; + } + return text; +}; + +export const linkSanitization = link => { + const sanitizedLinkMatch = link.match( + /(https:\/\/navihq.atlassian.net\/browse\/[^/]+)/, + ); + if (sanitizedLinkMatch && sanitizedLinkMatch[1]) { + return sanitizedLinkMatch[1]; + } + return link; +}; diff --git a/src/slices/IncidentSlice.tsx b/src/slices/IncidentSlice.tsx new file mode 100644 index 0000000..0651788 --- /dev/null +++ b/src/slices/IncidentSlice.tsx @@ -0,0 +1,76 @@ +import { createSlice } from '@reduxjs/toolkit'; + +const initialState = { + incidentLogData: { + data: { + relation_name: '', + record_id: null, + logs: [], + has_creation: false, + }, + }, + incidentData: { + id: null, + title: '', + description: '', + status: null, + statusName: '', + severityId: null, + severityName: '', + incidentName: '', + slackChannel: '', + detectionTime: null, + startTime: null, + endTime: null, + teamId: null, + teamName: '', + jiraLinks: [], + confluenceId: null, + severityTat: null, + remindMeAt: null, + enableReminder: false, + createdBy: '', + updatedBy: '', + createdAt: null, + updatedAt: null, + rcaLink: '', + // Include any additional fields that you expect to receive + }, + headerData: { + severities: [], + incidentStatuses: [], + teams: [], + }, + participantsData: { + others: [], + participants: [], + }, +}; + +const incidentLogSlice = createSlice({ + name: 'incidentLog', + initialState, + reducers: { + setIncidentLogData: (state, action) => { + state.incidentLogData = action.payload; + }, + setIncidentData: (state, action) => { + state.incidentData = action.payload; + }, + setHeaderData: (state, action) => { + state.headerData = action.payload; + }, + setParticipantsData: (state, action) => { + state.participantsData = action.payload; + }, + }, +}); + +export const { + setIncidentData, + setIncidentLogData, + setHeaderData, + setParticipantsData, +} = incidentLogSlice.actions; + +export default incidentLogSlice.reducer; diff --git a/src/store/index.tsx b/src/store/index.tsx index f226248..787c7b3 100644 --- a/src/store/index.tsx +++ b/src/store/index.tsx @@ -6,6 +6,7 @@ import dashboardReducer from '../slices/dashboardSlice'; import teamReducer1 from '../slices/team1Slice'; import jiraDashboardReducer from '../slices/jiraDashboardSlice'; +import incidentLogReducer from '@src/slices/IncidentSlice'; const store = configureStore({ reducer: { @@ -14,6 +15,7 @@ const store = configureStore({ dashboard: dashboardReducer, team1: teamReducer1, jiraDashboard: jiraDashboardReducer, + incidentLog: incidentLogReducer, }, }); From 59d8228b6a29eebfc0a3e24fa4a0910bee8daeff Mon Sep 17 00:00:00 2001 From: AyushRanjan Date: Mon, 22 Jan 2024 14:42:49 +0530 Subject: [PATCH 02/14] TP-52973 | update incident flow done --- src/Pages/Incidents/ActivityLog/index.tsx | 1 - .../Incidents/DescriptionContent/index.tsx | 3 +- src/Pages/Incidents/Dropdowns/index.tsx | 374 +++++++++++++++++- src/Pages/Incidents/index.tsx | 5 +- src/Pages/Incidents/useIncidentApis.tsx | 25 +- src/Pages/Team/partials/TeamForm.module.scss | 70 ++++ src/Pages/Team/partials/TeamForm.tsx | 2 +- src/slices/IncidentSlice.tsx | 2 +- 8 files changed, 437 insertions(+), 45 deletions(-) create mode 100644 src/Pages/Team/partials/TeamForm.module.scss diff --git a/src/Pages/Incidents/ActivityLog/index.tsx b/src/Pages/Incidents/ActivityLog/index.tsx index 24611fd..9a2ae39 100644 --- a/src/Pages/Incidents/ActivityLog/index.tsx +++ b/src/Pages/Incidents/ActivityLog/index.tsx @@ -10,7 +10,6 @@ const ActivityLog: FC = () => { (state: any) => state.incidentLog.incidentLogData, ); const totalLog = incidentLog?.data?.logs?.length; - console.log('logss', incidentLog); return (
diff --git a/src/Pages/Incidents/DescriptionContent/index.tsx b/src/Pages/Incidents/DescriptionContent/index.tsx index 03234fd..e19c4be 100644 --- a/src/Pages/Incidents/DescriptionContent/index.tsx +++ b/src/Pages/Incidents/DescriptionContent/index.tsx @@ -47,6 +47,7 @@ const DescriptionContent: React.FC = () => { const incidentParticipants = useSelector( (state: any) => state.incidentLog.participantsData, ); + // console.log('incident participants desc', incidentParticipants); const reduxDispatch = useDispatch(); //move to hooks @@ -57,7 +58,7 @@ const DescriptionContent: React.FC = () => { //use from hooks below three functions //move to utils - + // console.log('incident participants', incidentParticipants); const renderParticipantList = (type: string): JSX.Element => { const list = type === 'participants' diff --git a/src/Pages/Incidents/Dropdowns/index.tsx b/src/Pages/Incidents/Dropdowns/index.tsx index 1cf4a08..2f612fe 100644 --- a/src/Pages/Incidents/Dropdowns/index.tsx +++ b/src/Pages/Incidents/Dropdowns/index.tsx @@ -1,7 +1,6 @@ -import { FC, useReducer, useState, MutableRefObject } from 'react'; +import { FC, useEffect, useReducer, useState, MutableRefObject } from 'react'; import { useMatch } from 'react-router-dom'; import { useSelector } from 'react-redux'; -import Typography from '@navi/web-ui/lib/primitives/Typography'; import styles from '../Incidents.module.scss'; import classnames from 'classnames'; import { toast } from '@navi/web-ui/lib/primitives/Toast'; @@ -18,13 +17,23 @@ import { SLACK_BASE_URL, incidentRegrex, ResponseType, + UPDATE_INCIDENT, } from '../constants'; -import { ArrowDownIcon } from '@navi/web-ui/lib/icons'; +import { ArrowDownIcon, AlertOutlineIcon } from '@navi/web-ui/lib/icons'; import { SelectPicker } from '@navi/web-ui/lib/components'; import { SelectPickerOptionProps } from '@navi/web-ui/lib/components/SelectPicker/types'; import useIncidentApis from '../useIncidentApis'; import { generateOptions, getUpdateTypeText } from '../utils'; +import GoToLinkIcon from '@src/assets/GoToLinkIcon'; import useOutsideClick from '@src/services/hooks/useOustideClick'; +import { ApiService } from '@src/services/api'; +import { + BorderedInput, + ModalDialog, + Tooltip, + Typography, + Button, +} from '@navi/web-ui/lib/primitives'; const Dropdowns: FC = () => { const match = useMatch({ @@ -39,14 +48,54 @@ const Dropdowns: FC = () => { (state: any) => state.incidentLog.incidentData, ); const incidentParticipants = useSelector( - (state: any) => state.incidentLog.incidentParticipants, + (state: any) => state.incidentLog?.participantsData, ); + console.log('incidentParticipants', incidentParticipants); + + const headerData = useSelector((state: any) => state.incidentLog.headerData); const [state, dispatch] = useReducer(reducer, initialState); + const incidentName = state.incidentName; + console.log('sincidentName', incidentName); + const [open, setOpen] = useState(false); - const updatedSeverities = generateOptions(state.headerData?.severities); - const updatedStatuses = generateOptions(state.headerData?.incidentStatuses); - const updatedTeams = generateOptions(state.headerData?.teams); + const updatedSeverities = generateOptions(headerData?.severities); + const updatedStatuses = generateOptions(headerData?.incidentStatuses); + const updatedTeams = generateOptions(headerData?.teams); + + useEffect(() => { + dispatch({ + type: actionTypes.SET_SLACK_CHANNEL, + payload: incidentData?.slackChannel, + }); + dispatch({ + type: actionTypes.SET_SEVERITY, + payload: { + label: incidentData?.severityName, + value: incidentData?.severityId, + }, + }); + dispatch({ + type: actionTypes.SET_STATUS, + payload: { + label: incidentData?.statusName, + value: incidentData?.status, + }, + }); + + dispatch({ + type: actionTypes.SET_TEAM, + payload: { + label: incidentData?.teamName, + value: incidentData?.teamId, + }, + }); + }, [incidentData, headerData]); + + const slackChannel = incidentData?.slackChannel; + + const { fetchIncidentLog, startIncidentSearch, fetchHeaderDetails } = + useIncidentApis(); // use custom hook to fetch data const userData = JSON.parse(localStorage.getItem('user-data') || '{}'); @@ -59,19 +108,22 @@ const Dropdowns: FC = () => { const isUserParticipantList = participantsList?.some( (participant: any) => participant.email === userEmail, ); + // console.log('isUserParticipantList', isUserParticipantList); + // console.log('useremail', userEmail); + // console.log('participantsList', incidentParticipants); - const severityMap = state.headerData?.severities?.reduce((map, severity) => { + const severityMap = headerData?.severities?.reduce((map, severity) => { map[severity.value] = severity.label; return map; }, {}); - const incidentStatusMap = state.headerData?.incidentStatuses?.reduce( + const incidentStatusMap = headerData?.incidentStatuses?.reduce( (map, status) => { map[status.value] = status.label; return map; }, {}, ); - const teamsMap = state.headerData?.teams?.reduce((map, Incidentteam) => { + const teamsMap = headerData?.teams?.reduce((map, Incidentteam) => { map[Incidentteam.value] = Incidentteam.label; return map; }, {}); @@ -306,6 +358,132 @@ const Dropdowns: FC = () => { } }; + // console.log('severity', severityMap); + const currentSeverity = + severityMap && incidentData.severityId + ? severityMap[incidentData.severityId] + : '-'; + //console.log('currentSeverity', incidentData); + const currentStatus = + incidentStatusMap && incidentData.status + ? incidentStatusMap[incidentData.status] + : '-'; + const currentTeam = + teamsMap && incidentData.teamId ? teamsMap[incidentData.teamId] : '-'; + + const handleGoToSlackChannel = () => { + const slackChannelURL = `${SLACK_BASE_URL}/archives/${incidentData?.slackChannel}`; + window.open(slackChannelURL, '_blank'); + dispatch({ + type: actionTypes.SET_OPEN_DIALOG, + payload: false, + }); + }; + + const handleIncidentChange = ( + e: React.ChangeEvent, + ): void => { + const inputValue = e.target.value; + validateIncidentID(inputValue); + dispatch({ + type: actionTypes.SET_INCIDENT_NAME, + payload: inputValue, + }); + }; + const handleResetDialog = (): void => { + dispatch({ + type: actionTypes.RESET_DUPLICATE_DIALOG, + }); + }; + const validateIncidentID = (value: string): void => { + dispatch({ + type: actionTypes.SET_ERROR_MSG, + payload: !incidentRegrex.test(value) + ? 'Please enter a valid incident I.D.' + : '', + }); + }; + + const extractIncidentId = (incidentName: string): number | null => { + return (match => (match ? parseInt(match[1], 10) : null))( + incidentName.match(/_houston-(\d+)/), + ); + }; + + const duplicateOfId = extractIncidentId(incidentName); + const disable = (): boolean => { + return !incidentName || !validate(incidentName); + }; + const goToIncident = (): void => { + if (state.incidentName) { + const incidentId = extractIncidentId(state.incidentName); + window.open(`/incident/${incidentId}`, '_blank'); + } + }; + const validate = (value: string): boolean => incidentRegrex.test(value); + const isDisabled = (): boolean => { + const incidentId = extractIncidentId(state.incidentName); + return !incidentId; + }; + + const markDuplicateIncident = (): void => { + const endPoint = UPDATE_INCIDENT; + toast('Updating ticket. Please wait a moment.', { + icon: , + }); + ApiService.post(endPoint, { + id: parseInt(incidentId, 10), + status: DUPLICATE_STATUS, + duplicateOfId: duplicateOfId, + }) + .then((response: ResponseType) => { + if (response?.status === 200) { + const toastMessage = `This incident is marked as duplicate of _houston-${duplicateOfId}`; + toast.success(toastMessage); + startIncidentSearch(incidentId); + fetchIncidentLog(incidentId); + handleResetDialog(); + //fetchParticipants(slackChannel); + } + }) + .catch(error => { + const toastMessage = `${ + error?.response?.data?.error?.message + ? `${error?.response?.data?.error?.message}` + : 'Something went wrong. Please try again.' + }`; + toast.error(toastMessage); + startIncidentSearch(incidentId); + }); + }; + + const getUpdateValueText = () => { + switch (state.updateType) { + case SeverityType: + return ` ${ + severityMap && state.severity?.value + ? severityMap[state.severity.value] + : '-' + } `; + case StatusType: + return ` ${ + incidentStatusMap && state.status?.value + ? incidentStatusMap[state.status.value] + : '-' + } `; + case TeamType: + return ` ${ + teamsMap && state.team?.value ? teamsMap[state.team.value] : '-' + } `; + default: + return ` ${ + severityMap && state.severity?.value + ? severityMap[state.severity.value] + : '-' + } `; + } + }; + return (
@@ -326,9 +504,8 @@ const Dropdowns: FC = () => { : 'var(--navi-color-gray-c3)' } > - {severityMap && state.severity?.value - ? severityMap[state.severity.value] - : '-'} + {/* make the below as const */} + {currentSeverity}
@@ -371,9 +548,7 @@ const Dropdowns: FC = () => { : 'var(--navi-color-gray-c3)' } > - {incidentStatusMap && state.status?.value - ? incidentStatusMap[state.status.value] - : '-'} + {currentStatus}
@@ -413,9 +588,7 @@ const Dropdowns: FC = () => { : 'var(--navi-color-gray-c3)' } > - {teamsMap && state.team?.value - ? teamsMap[state.team.value] - : '-'} + {currentTeam}
@@ -441,6 +614,169 @@ const Dropdowns: FC = () => {
+ {open && ( + + ), + onClick: handleGoToSlackChannel, + }, + ]} + header="You are not authorised to update this incident" + onClose={closeModal} + > + + You must be a participant of this incident to update it. Please join + the Slack channel to be added a participant + + + )} + {state.openDuplicateDialog ? ( + { + markDuplicateIncident(); + }, + }, + ]} + header={`Duplicate incident`} + onClose={handleResetDialog} + > + + Once marked as duplicate, this incident will be archived after 24 + hours. + +
+ } + /> +
+ + +
+ ) : null} + {state.openDialog ? ( + { + dispatch({ + type: actionTypes.SET_OPEN_DIALOG, + payload: false, + }); + }, + }, + { + label: 'Go to slack channel', + onClick: handleGoToSlackChannel, + startAdornment: ( + + ), + }, + ]} + header={`${state.dialogText}`} + onClose={() => + dispatch({ type: actionTypes.SET_OPEN_DIALOG, payload: false }) + } + > + + We’re working on improving this feature. For the time being please + mark this incident as {`${state.dialogBodyText}`} on Slack. + + + ) : null} +
+ {state.openConfirmationDialog ? ( + { + dispatch({ + type: actionTypes.SET_OPEN_CONFIRMATION_DIALOG, + payload: false, + }); + }, + }, + { + label: 'Update incident', + onClick: () => { + dispatch({ + type: actionTypes.SET_OPEN_CONFIRMATION_DIALOG, + payload: false, + }); + handleCloseConfirmationDialog(); + }, + }, + ]} + header={`Are you sure you want to update the incident?`} + onClose={() => + dispatch({ + type: actionTypes.SET_OPEN_CONFIRMATION_DIALOG, + payload: false, + }) + } + > +
+ + You are updating this incident‘s + {getUpdateTypeText(state.updateType)} + from + + {getUpdateValueText()} + + to  + + {state.selectedOption?.label || '..'} + + +
+
+ ) : null} +
); }; diff --git a/src/Pages/Incidents/index.tsx b/src/Pages/Incidents/index.tsx index 743ce98..f975b33 100644 --- a/src/Pages/Incidents/index.tsx +++ b/src/Pages/Incidents/index.tsx @@ -22,8 +22,7 @@ const Incident: FC = () => { useEffect(() => { startIncidentSearch(incidentId); fetchHeaderDetails(); - fetchIncidentLog(incidentId); - }, []); + }, [incidentId]); return ( @@ -38,9 +37,9 @@ const Incident: FC = () => { label: 'Details', children: (
-
+
diff --git a/src/Pages/Incidents/useIncidentApis.tsx b/src/Pages/Incidents/useIncidentApis.tsx index 835db82..cea44e5 100644 --- a/src/Pages/Incidents/useIncidentApis.tsx +++ b/src/Pages/Incidents/useIncidentApis.tsx @@ -29,11 +29,6 @@ const useIncidentApis = () => { toast.error(toastMessage); }; - const incidentData = useSelector( - (state: any) => state.incidentLog.incidentData, - ); - const slackChannel = incidentData?.slackChannel; - const fetchIncidentLog = (incidentId): void => { const endPoint = FETCH_AUDIT_LOG(incidentId); ApiService.get(endPoint) @@ -55,6 +50,8 @@ const useIncidentApis = () => { ApiService.get(endPoint) .then(response => { dispatch(setIncidentData(response?.data?.data)); + fetchIncidentLog(incidentId); + fetchParticipants(response?.data?.data?.slackChannel); }) .catch(error => { const toastMessage = `${ @@ -70,10 +67,7 @@ const useIncidentApis = () => { const endPoint = FETCH_HEADER_DETAILS; ApiService.get(endPoint) .then(response => { - // dispatch({ - // type: actionTypes.SET_HEADER_DATA, - // payload: response?.data?.data, - // }); + dispatch(setHeaderData(response?.data?.data)); }) .catch(error => { const toastMessage = `${ @@ -85,15 +79,11 @@ const useIncidentApis = () => { }); }; - const fetchParticipants = (): void => { + const fetchParticipants = (slackChannel): void => { const endPoint = FETCH_PARTICIPANTS_DATA(slackChannel); ApiService.get(endPoint) .then(response => { - // dispatch({ type: actionTypes.SET_INCIDENT_PARTICIPANTS, payload: [] }); - // dispatch({ - // type: actionTypes.SET_INCIDENT_PARTICIPANTS, - // payload: response?.data?.data, - // }); + dispatch(setParticipantsData(response?.data?.data)); }) .catch(error => { const toastMessage = `${ @@ -102,7 +92,6 @@ const useIncidentApis = () => { : '' }`; toast.error(toastMessage); - //dispatch({ type: actionTypes.SET_INCIDENT_PARTICIPANTS, payload: [] }); }); }; @@ -111,9 +100,7 @@ const useIncidentApis = () => { ApiService.post(endPoint, payload) .then(response => { toast.success('Incident updated successfully'); - // startIncidentSearch(i); - //fetchIncidentLog(); - //fetchParticipants(); + startIncidentSearch(payload?.id); }) .catch(error => { const toastMessage = `${ diff --git a/src/Pages/Team/partials/TeamForm.module.scss b/src/Pages/Team/partials/TeamForm.module.scss new file mode 100644 index 0000000..14cc4cc --- /dev/null +++ b/src/Pages/Team/partials/TeamForm.module.scss @@ -0,0 +1,70 @@ +.severity-details-wrapper { + display: flex; + align-items: center; + gap: 0 8px; + margin: 8px 0; +} + +.content-info { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 8px; +} + +.description-details { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 8px; + margin: 12px 0; + + .description { + margin-top: -2px; + line-height: 24px; + } +} + +.slack-channel { + display: flex; + align-items: center; + gap: 0 8px; + color: #24b4d2; + + a { + text-decoration: none; + color: var(--navi-color-blue-base); + } +} + +.meta-info { + margin: 12px 0 16px 0; + + &__title { + margin-bottom: 8px; + } +} + +.participant-detail { + display: flex; + align-items: center; + gap: 4px; +} + +.team-details-wrapper { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 12px; +} + +.loader-container { + height: 100%; + display: flex; + justify-content: center; + align-items: center; +} + +.content-divider { + margin: 16px 0; +} diff --git a/src/Pages/Team/partials/TeamForm.tsx b/src/Pages/Team/partials/TeamForm.tsx index e2c0d1f..91c673c 100644 --- a/src/Pages/Team/partials/TeamForm.tsx +++ b/src/Pages/Team/partials/TeamForm.tsx @@ -13,7 +13,7 @@ import useClickStream from '@src/services/clickStream'; import { CLICK_STREAM_EVENT_FACTORY } from '@src/services/clickStream/constants/values'; import { ApiService } from '@src/services/api'; import MembersDetails from '@src/components/MembersDetails'; -import DrawerStyles from '@src/Pages/Incidents/DrawerMode/DrawerMode.module.scss'; +import DrawerStyles from './TeamForm.module.scss'; import styles from '../Team.module.scss'; import { ConfigProvider, Select, ThemeConfig } from 'antd'; import { getBots } from '../bots'; diff --git a/src/slices/IncidentSlice.tsx b/src/slices/IncidentSlice.tsx index 0651788..e1e0857 100644 --- a/src/slices/IncidentSlice.tsx +++ b/src/slices/IncidentSlice.tsx @@ -42,8 +42,8 @@ const initialState = { teams: [], }, participantsData: { - others: [], participants: [], + others: [], }, }; From aee164ad428643a2125189c1338d527815c4c535 Mon Sep 17 00:00:00 2001 From: AyushRanjan Date: Tue, 23 Jan 2024 11:43:02 +0530 Subject: [PATCH 03/14] TP-52973 | dailogbox component done --- .../Incidents/Dropdowns/AllDailogBox.tsx | 416 ++++++++++++++++ src/Pages/Incidents/Dropdowns/index.tsx | 445 ++++-------------- src/Pages/Incidents/useIncidentApis.tsx | 1 + src/Pages/Incidents/utils.ts | 11 + src/slices/IncidentSlice.tsx | 34 ++ 5 files changed, 551 insertions(+), 356 deletions(-) create mode 100644 src/Pages/Incidents/Dropdowns/AllDailogBox.tsx diff --git a/src/Pages/Incidents/Dropdowns/AllDailogBox.tsx b/src/Pages/Incidents/Dropdowns/AllDailogBox.tsx new file mode 100644 index 0000000..458ab68 --- /dev/null +++ b/src/Pages/Incidents/Dropdowns/AllDailogBox.tsx @@ -0,0 +1,416 @@ +import { FC, useEffect, useReducer } from 'react'; +import { useMatch } from 'react-router-dom'; +import { useSelector, useDispatch } from 'react-redux'; +import { Tabs } from 'antd'; +import { ArrowDownIcon, AlertOutlineIcon } from '@navi/web-ui/lib/icons'; +import { SelectPickerOptionProps } from '@navi/web-ui/lib/components/SelectPicker/types'; +import { + BorderedInput, + ModalDialog, + Typography, + Button, +} from '@navi/web-ui/lib/primitives'; +import { toast } from '@navi/web-ui/lib/primitives/Toast'; + +import GoToLinkIcon from '@src/assets/GoToLinkIcon'; +import styles from '../Incidents.module.scss'; +import LoadingIcon from '@src/assets/LoadingIcon'; +import { + setOpenDialognotParticipants, + setOpenDialogDuplicate, + setOpenDialogResolve, + setOpenDialogUpdate, +} from '@src/slices/IncidentSlice'; +import { ApiService } from '@src/services/api'; + +import { + actionTypes, + SLACK_BASE_URL, + reducer, + initialState, + incidentRegrex, + ResponseType, + DUPLICATE_STATUS, + UPDATE_INCIDENT, + SeverityType, + StatusType, + TeamType, + RESOLVE_STATUS, +} from '../constants'; + +import useIncidentApis from '../useIncidentApis'; + +const AllDailogBox: FC = () => { + const [state, dispatch] = useReducer(reducer, initialState); + + const openNotParticipants = useSelector( + (state: any) => state.incidentLog.openDialognotParticipants, + ); + const openDuplicate = useSelector( + (state: any) => state.incidentLog.openDialogDuplicate, + ); + const openResolve = useSelector( + (state: any) => state.incidentLog.openDialogResolve, + ); + const openUpdate = useSelector( + (state: any) => state.incidentLog.openDialogUpdate, + ); + const UpdateData = useSelector( + (state: any) => state.incidentLog.updateDetails, + ); + const selectedOption = useSelector( + (state: any) => state.incidentLog.selectedOptions, + ); + //console.log('updatedata', UpdateData); + + const incidentData = useSelector( + (state: any) => state.incidentLog.incidentData, + ); + const headerData = useSelector((state: any) => state.incidentLog.headerData); + + const severityMap = headerData?.severities?.reduce((map, severity) => { + map[severity.value] = severity.label; + return map; + }, {}); + const statusMap = headerData?.incidentStatuses?.reduce((map, status) => { + map[status.value] = status.label; + return map; + }, {}); + const teamsMap = headerData?.teams?.reduce((map, Incidentteam) => { + map[Incidentteam.value] = Incidentteam.label; + return map; + }, {}); + + const { updateIncident, fetchIncidentLog, startIncidentSearch } = + useIncidentApis(); + 0; + + const incidentId = incidentData?.id?.toString(); + console.log('incidentId', incidentId); + const reduxDispatch = useDispatch(); + const incidentName = incidentData?.incidentName; + + const handleopenNotParticipants = (): void => { + reduxDispatch(setOpenDialognotParticipants(false)); + }; + + const handleGoToSlackChannel = () => { + const slackChannelURL = `${SLACK_BASE_URL}/archives/${incidentData?.slackChannel}`; + window.open(slackChannelURL, '_blank'); + handleopenNotParticipants(); + }; + + const handleResetDialog = (): void => { + dispatch({ + type: actionTypes.RESET_DUPLICATE_DIALOG, + }); + reduxDispatch(setOpenDialogDuplicate(false)); + }; + const disable = (): boolean => { + return !incidentName || !validate(incidentName); + }; + + const extractIncidentId = (incidentName: string): number | null => { + return (match => (match ? parseInt(match[1], 10) : null))( + incidentName.match(/_houston-(\d+)/), + ); + }; + + const duplicateOfId = extractIncidentId(incidentName); + + const markDuplicateIncident = (): void => { + const endPoint = UPDATE_INCIDENT; + toast('Updating ticket. Please wait a moment.', { + icon: , + }); + ApiService.post(endPoint, { + id: parseInt(incidentId, 10), + status: DUPLICATE_STATUS, + duplicateOfId: duplicateOfId, + }) + .then((response: ResponseType) => { + if (response?.status === 200) { + const toastMessage = `This incident is marked as duplicate of _houston-${duplicateOfId}`; + toast.success(toastMessage); + startIncidentSearch(incidentId); + } + }) + .catch(error => { + const toastMessage = `${ + error?.response?.data?.error?.message + ? `${error?.response?.data?.error?.message}` + : 'Something went wrong. Please try again.' + }`; + toast.error(toastMessage); + startIncidentSearch(incidentId); + }); + }; + + const handleIncidentChange = ( + e: React.ChangeEvent, + ): void => { + const inputValue = e.target.value; + validateIncidentID(inputValue); + dispatch({ + type: actionTypes.SET_INCIDENT_NAME, + payload: inputValue, + }); + }; + + const validateIncidentID = (value: string): void => { + dispatch({ + type: actionTypes.SET_ERROR_MSG, + payload: !incidentRegrex.test(value) + ? 'Please enter a valid incident I.D.' + : '', + }); + }; + const validate = (value: string): boolean => incidentRegrex.test(value); + const isDisabled = (): boolean => { + const incidentId = extractIncidentId(state.incidentName); + return !incidentId; + }; + + const goToIncident = (): void => { + if (state.incidentName) { + const incidentId = extractIncidentId(state.incidentName); + window.open(`/incident/${incidentId}`, '_blank'); + } + }; + + const handleSeveritySelectionChange = ( + selectedOption: SelectPickerOptionProps | SelectPickerOptionProps[], + ): void => { + if (selectedOption) { + toast('Updating ticket. Please wait a moment.', { + icon: , + }); + dispatch({ + type: actionTypes.SET_IS_SEVERITY_PICKER_OPEN, + payload: false, + }); + const value = Array.isArray(selectedOption) + ? selectedOption[0].value + : selectedOption.value; + updateIncident({ + id: parseInt(incidentId, 10), + severityId: value.toString(), + }); + } + }; + const handleStatusSelectionChange = ( + selectedOption: SelectPickerOptionProps | SelectPickerOptionProps[], + ): void => { + if (selectedOption) { + dispatch({ type: actionTypes.SET_IS_STATUS_PICKER_OPEN, payload: false }); + const value = Array.isArray(selectedOption) + ? selectedOption[0].value + : selectedOption.value; + if (value === RESOLVE_STATUS) { + dispatch({ + type: actionTypes.SET_DIALOG_TEXT, + payload: 'Resolve incident', + }); + dispatch({ + type: actionTypes.SET_DIALOG_BODY_TEXT, + payload: 'resolved', + }); + + reduxDispatch(setOpenDialogResolve(true)); + } else { + toast('Updating ticket. Please wait a moment.', { + icon: , + }); + updateIncident({ + id: parseInt(incidentId, 10), + status: value.toString(), + }); + } + } + }; + const handleTeamSelectionChange = ( + selectedOption: SelectPickerOptionProps | SelectPickerOptionProps[], + ): void => { + console.log('selectedOption', selectedOption); + if (selectedOption) { + dispatch({ type: actionTypes.SET_IS_TEAM_PICKER_OPEN, payload: false }); + toast('Updating ticket. Please wait a moment.', { + icon: , + }); + const value = Array.isArray(selectedOption) + ? selectedOption[0].value + : selectedOption.value; + updateIncident({ + id: parseInt(incidentId, 10), + teamId: value.toString(), + }); + } + }; + + const handleCloseConfirmationDialog = () => { + if (UpdateData?.type === SeverityType) { + handleSeveritySelectionChange(selectedOption); + } else { + if (UpdateData?.type === StatusType) { + handleStatusSelectionChange(selectedOption); + } else { + handleTeamSelectionChange(selectedOption); + } + } + }; + + return ( +
+ {openNotParticipants && ( + + ), + onClick: handleGoToSlackChannel, + }, + ]} + header="You are not authorised to update this incident" + onClose={handleopenNotParticipants} + > + + You must be a participant of this incident to update it. Please join + the Slack channel to be added a participant + + + )} + {openDuplicate ? ( + { + markDuplicateIncident(); + }, + }, + ]} + header={`Duplicate incident`} + onClose={handleResetDialog} + > + + Once marked as duplicate, this incident will be archived after 24 + hours. + +
+ } + /> +
+ + +
+ ) : null} + {openResolve ? ( + { + reduxDispatch(setOpenDialogResolve(false)); + }, + }, + { + label: 'Go to slack channel', + onClick: handleGoToSlackChannel, + startAdornment: ( + + ), + }, + ]} + header={`Resolve incident`} + onClose={() => reduxDispatch(setOpenDialogResolve(false))} + > + + We’re working on improving this feature. For the time being please + mark this incident as resolved on Slack. + + + ) : null} +
+ {openUpdate ? ( + { + reduxDispatch(setOpenDialogUpdate(false)); + }, + }, + { + label: 'Update incident', + onClick: () => { + reduxDispatch(setOpenDialogUpdate(false)); + handleCloseConfirmationDialog(); + }, + }, + ]} + header={`Are you sure you want to update the incident?`} + onClose={() => reduxDispatch(setOpenDialogUpdate(false))} + > +
+ + You are updating this incident‘s + {UpdateData?.type || '..'} + from + + {UpdateData?.from || '..'} + + to  + + {UpdateData?.to?.label || '..'} + + +
+
+ ) : null} +
+
+ ); +}; + +export default AllDailogBox; diff --git a/src/Pages/Incidents/Dropdowns/index.tsx b/src/Pages/Incidents/Dropdowns/index.tsx index 2f612fe..f47006a 100644 --- a/src/Pages/Incidents/Dropdowns/index.tsx +++ b/src/Pages/Incidents/Dropdowns/index.tsx @@ -1,10 +1,36 @@ import { FC, useEffect, useReducer, useState, MutableRefObject } from 'react'; import { useMatch } from 'react-router-dom'; -import { useSelector } from 'react-redux'; -import styles from '../Incidents.module.scss'; +import { useSelector, useDispatch } from 'react-redux'; import classnames from 'classnames'; +import { + BorderedInput, + ModalDialog, + Typography, + Button, +} from '@navi/web-ui/lib/primitives'; import { toast } from '@navi/web-ui/lib/primitives/Toast'; +import { ArrowDownIcon, AlertOutlineIcon } from '@navi/web-ui/lib/icons'; +import { SelectPicker } from '@navi/web-ui/lib/components'; +import { SelectPickerOptionProps } from '@navi/web-ui/lib/components/SelectPicker/types'; import LoadingIcon from '@src/assets/LoadingIcon'; +import useOutsideClick from '@src/services/hooks/useOustideClick'; +import { getUpdateTypeText } from '../utils'; +import { ApiService } from '@src/services/api'; +import { + setIncidentData, + setIncidentLogData, + setHeaderData, + setParticipantsData, + setOpenDialogDuplicate, + setOpenDialogResolve, + setOpenDialogUpdate, + setOpenDialognotParticipants, + setUpdateDetails, + setSelectedOptions, +} from '@src/slices/IncidentSlice'; +import AllDailogBox from './AllDailogBox'; +import useIncidentApis from '../useIncidentApis'; +import { generateOptions, getCurrentData, isUserParticipant } from '../utils'; import { reducer, actionTypes, @@ -19,46 +45,25 @@ import { ResponseType, UPDATE_INCIDENT, } from '../constants'; -import { ArrowDownIcon, AlertOutlineIcon } from '@navi/web-ui/lib/icons'; -import { SelectPicker } from '@navi/web-ui/lib/components'; -import { SelectPickerOptionProps } from '@navi/web-ui/lib/components/SelectPicker/types'; -import useIncidentApis from '../useIncidentApis'; -import { generateOptions, getUpdateTypeText } from '../utils'; -import GoToLinkIcon from '@src/assets/GoToLinkIcon'; -import useOutsideClick from '@src/services/hooks/useOustideClick'; -import { ApiService } from '@src/services/api'; -import { - BorderedInput, - ModalDialog, - Tooltip, - Typography, - Button, -} from '@navi/web-ui/lib/primitives'; +import styles from '../Incidents.module.scss'; const Dropdowns: FC = () => { - const match = useMatch({ - end: true, - path: '/incident/:incidentId', - }); - const incidentId2 = match?.params?.incidentId || ''; - - const incidentId = incidentId2.toString(); - const { updateIncident } = useIncidentApis(); + const { updateIncident, fetchIncidentLog, startIncidentSearch } = + useIncidentApis(); + const reduxDispatch = useDispatch(); const incidentData = useSelector( (state: any) => state.incidentLog.incidentData, ); const incidentParticipants = useSelector( (state: any) => state.incidentLog?.participantsData, ); - console.log('incidentParticipants', incidentParticipants); - const headerData = useSelector((state: any) => state.incidentLog.headerData); + const incidentId = incidentData?.incidentId?.toString(); + const [state, dispatch] = useReducer(reducer, initialState); const incidentName = state.incidentName; - console.log('sincidentName', incidentName); - const [open, setOpen] = useState(false); const updatedSeverities = generateOptions(headerData?.severities); const updatedStatuses = generateOptions(headerData?.incidentStatuses); const updatedTeams = generateOptions(headerData?.teams); @@ -82,7 +87,6 @@ const Dropdowns: FC = () => { value: incidentData?.status, }, }); - dispatch({ type: actionTypes.SET_TEAM, payload: { @@ -90,39 +94,24 @@ const Dropdowns: FC = () => { value: incidentData?.teamId, }, }); - }, [incidentData, headerData]); + }, [incidentData]); - const slackChannel = incidentData?.slackChannel; - - const { fetchIncidentLog, startIncidentSearch, fetchHeaderDetails } = - useIncidentApis(); - - // use custom hook to fetch data const userData = JSON.parse(localStorage.getItem('user-data') || '{}'); const userEmail = userData?.emailId || []; - const participants = incidentParticipants?.participants; - const others = incidentParticipants?.others; - - const participantsList = (participants || []).concat(others || []); - const isUserParticipantList = participantsList?.some( - (participant: any) => participant.email === userEmail, + const isUserParticipantList = isUserParticipant( + userEmail, + incidentParticipants, ); - // console.log('isUserParticipantList', isUserParticipantList); - // console.log('useremail', userEmail); - // console.log('participantsList', incidentParticipants); const severityMap = headerData?.severities?.reduce((map, severity) => { map[severity.value] = severity.label; return map; }, {}); - const incidentStatusMap = headerData?.incidentStatuses?.reduce( - (map, status) => { - map[status.value] = status.label; - return map; - }, - {}, - ); + const statusMap = headerData?.incidentStatuses?.reduce((map, status) => { + map[status.value] = status.label; + return map; + }, {}); const teamsMap = headerData?.teams?.reduce((map, Incidentteam) => { map[Incidentteam.value] = Incidentteam.label; return map; @@ -228,10 +217,32 @@ const Dropdowns: FC = () => { }; const handleDisabledDropdownClick = (): void => { - setOpen(true); + reduxDispatch(setOpenDialognotParticipants(true)); }; - const closeModal = (): void => { - setOpen(false); + + const getUpdateValueText = updateType => { + switch (updateType) { + case SeverityType: + return ` ${ + severityMap && state.severity?.value + ? severityMap[state.severity.value] + : '-' + } `; + case StatusType: + return ` ${ + statusMap && state.status?.value ? statusMap[state.status.value] : '-' + } `; + case TeamType: + return ` ${ + teamsMap && state.team?.value ? teamsMap[state.team.value] : '-' + } `; + default: + return ` ${ + severityMap && state.severity?.value + ? severityMap[state.severity.value] + : '-' + } `; + } }; const handleOpenConfirmationDailog = ( @@ -265,16 +276,20 @@ const Dropdowns: FC = () => { type: actionTypes.SET_DUPLICATE_DIALOG, payload: true, }); + reduxDispatch(setOpenDialogDuplicate(true)); } else { - dispatch({ type: actionTypes.SET_UPDATE_TYPE, payload: updateType }); - dispatch({ - type: actionTypes.SET_SELECTED_OPTION, - payload: selectedOption, - }); - dispatch({ - type: actionTypes.SET_OPEN_CONFIRMATION_DIALOG, - payload: true, - }); + console.log('type', updateType); + console.log('to', selectedOption); + console.log('from', getUpdateValueText); + reduxDispatch( + setUpdateDetails({ + type: getUpdateTypeText(updateType), + to: selectedOption, + from: getUpdateValueText(updateType), + }), + ); + reduxDispatch(setSelectedOptions(selectedOption)); + reduxDispatch(setOpenDialogUpdate(true)); } } }; @@ -328,7 +343,8 @@ const Dropdowns: FC = () => { type: actionTypes.SET_DIALOG_BODY_TEXT, payload: 'resolved', }); - dispatch({ type: actionTypes.SET_OPEN_DIALOG, payload: true }); + + reduxDispatch(setOpenDialogResolve(true)); } else { toast('Updating ticket. Please wait a moment.', { icon: , @@ -358,131 +374,9 @@ const Dropdowns: FC = () => { } }; - // console.log('severity', severityMap); - const currentSeverity = - severityMap && incidentData.severityId - ? severityMap[incidentData.severityId] - : '-'; - //console.log('currentSeverity', incidentData); - const currentStatus = - incidentStatusMap && incidentData.status - ? incidentStatusMap[incidentData.status] - : '-'; - const currentTeam = - teamsMap && incidentData.teamId ? teamsMap[incidentData.teamId] : '-'; - - const handleGoToSlackChannel = () => { - const slackChannelURL = `${SLACK_BASE_URL}/archives/${incidentData?.slackChannel}`; - window.open(slackChannelURL, '_blank'); - dispatch({ - type: actionTypes.SET_OPEN_DIALOG, - payload: false, - }); - }; - - const handleIncidentChange = ( - e: React.ChangeEvent, - ): void => { - const inputValue = e.target.value; - validateIncidentID(inputValue); - dispatch({ - type: actionTypes.SET_INCIDENT_NAME, - payload: inputValue, - }); - }; - const handleResetDialog = (): void => { - dispatch({ - type: actionTypes.RESET_DUPLICATE_DIALOG, - }); - }; - const validateIncidentID = (value: string): void => { - dispatch({ - type: actionTypes.SET_ERROR_MSG, - payload: !incidentRegrex.test(value) - ? 'Please enter a valid incident I.D.' - : '', - }); - }; - - const extractIncidentId = (incidentName: string): number | null => { - return (match => (match ? parseInt(match[1], 10) : null))( - incidentName.match(/_houston-(\d+)/), - ); - }; - - const duplicateOfId = extractIncidentId(incidentName); - const disable = (): boolean => { - return !incidentName || !validate(incidentName); - }; - const goToIncident = (): void => { - if (state.incidentName) { - const incidentId = extractIncidentId(state.incidentName); - window.open(`/incident/${incidentId}`, '_blank'); - } - }; - const validate = (value: string): boolean => incidentRegrex.test(value); - const isDisabled = (): boolean => { - const incidentId = extractIncidentId(state.incidentName); - return !incidentId; - }; - - const markDuplicateIncident = (): void => { - const endPoint = UPDATE_INCIDENT; - toast('Updating ticket. Please wait a moment.', { - icon: , - }); - ApiService.post(endPoint, { - id: parseInt(incidentId, 10), - status: DUPLICATE_STATUS, - duplicateOfId: duplicateOfId, - }) - .then((response: ResponseType) => { - if (response?.status === 200) { - const toastMessage = `This incident is marked as duplicate of _houston-${duplicateOfId}`; - toast.success(toastMessage); - startIncidentSearch(incidentId); - fetchIncidentLog(incidentId); - handleResetDialog(); - //fetchParticipants(slackChannel); - } - }) - .catch(error => { - const toastMessage = `${ - error?.response?.data?.error?.message - ? `${error?.response?.data?.error?.message}` - : 'Something went wrong. Please try again.' - }`; - toast.error(toastMessage); - startIncidentSearch(incidentId); - }); - }; - - const getUpdateValueText = () => { - switch (state.updateType) { - case SeverityType: - return ` ${ - severityMap && state.severity?.value - ? severityMap[state.severity.value] - : '-' - } `; - case StatusType: - return ` ${ - incidentStatusMap && state.status?.value - ? incidentStatusMap[state.status.value] - : '-' - } `; - case TeamType: - return ` ${ - teamsMap && state.team?.value ? teamsMap[state.team.value] : '-' - } `; - default: - return ` ${ - severityMap && state.severity?.value - ? severityMap[state.severity.value] - : '-' - } `; - } - }; + const currentSev = getCurrentData(incidentData, severityMap, 'severityId'); + const currentStatus = getCurrentData(incidentData, statusMap, 'status'); + const currentTeam = getCurrentData(incidentData, teamsMap, 'teamId'); return (
@@ -504,8 +398,7 @@ const Dropdowns: FC = () => { : 'var(--navi-color-gray-c3)' } > - {/* make the below as const */} - {currentSeverity} + {currentSev}
@@ -528,6 +421,7 @@ const Dropdowns: FC = () => {
+
@@ -571,6 +465,7 @@ const Dropdowns: FC = () => {
+
@@ -614,169 +509,7 @@ const Dropdowns: FC = () => {
- {open && ( - - ), - onClick: handleGoToSlackChannel, - }, - ]} - header="You are not authorised to update this incident" - onClose={closeModal} - > - - You must be a participant of this incident to update it. Please join - the Slack channel to be added a participant - - - )} - {state.openDuplicateDialog ? ( - { - markDuplicateIncident(); - }, - }, - ]} - header={`Duplicate incident`} - onClose={handleResetDialog} - > - - Once marked as duplicate, this incident will be archived after 24 - hours. - -
- } - /> -
- - -
- ) : null} - {state.openDialog ? ( - { - dispatch({ - type: actionTypes.SET_OPEN_DIALOG, - payload: false, - }); - }, - }, - { - label: 'Go to slack channel', - onClick: handleGoToSlackChannel, - startAdornment: ( - - ), - }, - ]} - header={`${state.dialogText}`} - onClose={() => - dispatch({ type: actionTypes.SET_OPEN_DIALOG, payload: false }) - } - > - - We’re working on improving this feature. For the time being please - mark this incident as {`${state.dialogBodyText}`} on Slack. - - - ) : null} -
- {state.openConfirmationDialog ? ( - { - dispatch({ - type: actionTypes.SET_OPEN_CONFIRMATION_DIALOG, - payload: false, - }); - }, - }, - { - label: 'Update incident', - onClick: () => { - dispatch({ - type: actionTypes.SET_OPEN_CONFIRMATION_DIALOG, - payload: false, - }); - handleCloseConfirmationDialog(); - }, - }, - ]} - header={`Are you sure you want to update the incident?`} - onClose={() => - dispatch({ - type: actionTypes.SET_OPEN_CONFIRMATION_DIALOG, - payload: false, - }) - } - > -
- - You are updating this incident‘s - {getUpdateTypeText(state.updateType)} - from - - {getUpdateValueText()} - - to  - - {state.selectedOption?.label || '..'} - - -
-
- ) : null} -
+ ); }; diff --git a/src/Pages/Incidents/useIncidentApis.tsx b/src/Pages/Incidents/useIncidentApis.tsx index cea44e5..cb07516 100644 --- a/src/Pages/Incidents/useIncidentApis.tsx +++ b/src/Pages/Incidents/useIncidentApis.tsx @@ -97,6 +97,7 @@ const useIncidentApis = () => { const updateIncident = (payload: any): void => { const endPoint = UPDATE_INCIDENT; + console.log('payload', payload); ApiService.post(endPoint, payload) .then(response => { toast.success('Incident updated successfully'); diff --git a/src/Pages/Incidents/utils.ts b/src/Pages/Incidents/utils.ts index 7c555bc..01ef62c 100644 --- a/src/Pages/Incidents/utils.ts +++ b/src/Pages/Incidents/utils.ts @@ -47,3 +47,14 @@ export const linkSanitization = link => { } return link; }; + +export const isUserParticipant = (userEmail, incidentParticipants) => { + const participants = incidentParticipants?.participants; + const others = incidentParticipants?.others; + const participantsList = (participants || []).concat(others || []); + return participantsList?.some(participant => participant.email === userEmail); +}; + +export const getCurrentData = (data, dataMap, key) => { + return dataMap && data[key] ? dataMap[data[key]] : '-'; +}; diff --git a/src/slices/IncidentSlice.tsx b/src/slices/IncidentSlice.tsx index e1e0857..957bbd8 100644 --- a/src/slices/IncidentSlice.tsx +++ b/src/slices/IncidentSlice.tsx @@ -45,6 +45,16 @@ const initialState = { participants: [], others: [], }, + updateDetails: { + type: undefined, + from: undefined, + to: undefined, + }, + openDialogUpdate: false, + openDialogDuplicate: false, + openDialogResolve: false, + openDialognotParticipants: false, + selectedOptions: undefined, }; const incidentLogSlice = createSlice({ @@ -63,6 +73,24 @@ const incidentLogSlice = createSlice({ setParticipantsData: (state, action) => { state.participantsData = action.payload; }, + setOpenDialogUpdate: (state, action) => { + state.openDialogUpdate = action.payload; + }, + setOpenDialogDuplicate: (state, action) => { + state.openDialogDuplicate = action.payload; + }, + setOpenDialogResolve: (state, action) => { + state.openDialogResolve = action.payload; + }, + setOpenDialognotParticipants: (state, action) => { + state.openDialognotParticipants = action.payload; + }, + setUpdateDetails: (state, action) => { + state.updateDetails = { ...state.updateDetails, ...action.payload }; + }, + setSelectedOptions: (state, action) => { + state.selectedOptions = action.payload; + }, }, }); @@ -71,6 +99,12 @@ export const { setIncidentLogData, setHeaderData, setParticipantsData, + setOpenDialogUpdate, + setOpenDialogDuplicate, + setOpenDialogResolve, + setOpenDialognotParticipants, + setUpdateDetails, + setSelectedOptions, } = incidentLogSlice.actions; export default incidentLogSlice.reducer; From 89041c0b273b460c2e2455bc52aa4ed8db0bc9ee Mon Sep 17 00:00:00 2001 From: AyushRanjan Date: Tue, 23 Jan 2024 23:11:59 +0530 Subject: [PATCH 04/14] TP-52973 | minor refinement --- src/Pages/Incidents/ActivityLog/index.tsx | 13 +- .../Incidents/DescriptionContent/index.tsx | 61 +-- .../Incidents/Dropdowns/AllDailogBox.tsx | 49 +- src/Pages/Incidents/Dropdowns/index.tsx | 430 +++++++----------- src/Pages/Incidents/Header/index.tsx | 9 +- .../Incidents/JiraLinks/JiraLinks.module.scss | 0 src/Pages/Incidents/JiraLinks/index.tsx | 66 +-- .../UpdateIncidentBox.module.scss | 0 .../Incidents/UpdateIncidentBox/index.tsx | 26 +- src/Pages/Incidents/index.tsx | 15 +- src/Pages/Incidents/useIncidentApis.tsx | 2 +- src/Pages/Incidents/utils.ts | 8 +- 12 files changed, 227 insertions(+), 452 deletions(-) delete mode 100644 src/Pages/Incidents/JiraLinks/JiraLinks.module.scss delete mode 100644 src/Pages/Incidents/UpdateIncidentBox/UpdateIncidentBox.module.scss diff --git a/src/Pages/Incidents/ActivityLog/index.tsx b/src/Pages/Incidents/ActivityLog/index.tsx index 9a2ae39..af51e21 100644 --- a/src/Pages/Incidents/ActivityLog/index.tsx +++ b/src/Pages/Incidents/ActivityLog/index.tsx @@ -1,15 +1,24 @@ import { FC } from 'react'; +import { useSelector } from 'react-redux'; import Typography from '@navi/web-ui/lib/primitives/Typography'; import IncidentCreated from '../StepperAssets/IncidentCreated'; import IncidentChange from '../StepperAssets/IncidentChange'; import styles from './ActivityLog.module.scss'; -import { useDispatch, useSelector } from 'react-redux'; const ActivityLog: FC = () => { + //To do: add type here const incidentLog = useSelector( (state: any) => state.incidentLog.incidentLogData, ); - const totalLog = incidentLog?.data?.logs?.length; + const lastChangeIndex = (incidentLog?.data?.logs || []) + .slice() + .reverse() + .findIndex(log => + log.changes.some(change => + ['Status', 'SeverityId', 'TeamId'].includes(change.attribute), + ), + ); + const totalLog = incidentLog?.data?.logs?.length - lastChangeIndex; return (
diff --git a/src/Pages/Incidents/DescriptionContent/index.tsx b/src/Pages/Incidents/DescriptionContent/index.tsx index e19c4be..b17038f 100644 --- a/src/Pages/Incidents/DescriptionContent/index.tsx +++ b/src/Pages/Incidents/DescriptionContent/index.tsx @@ -1,70 +1,25 @@ -import React, { FC, useState, useReducer, useEffect } from 'react'; -import { useSelector, useDispatch } from 'react-redux'; -import { - setIncidentData, - setIncidentLogData, - setHeaderData, - setParticipantsData, -} from '@src/slices/IncidentSlice'; -import { - Typography, - Avatar, - BorderLessInput, -} from '@navi/web-ui/lib/primitives'; -import { - CloseIcon, - DeleteIconOutlined, - ArrowUpSolidIcon, - ArrowDownSolidIcon, -} from '@navi/web-ui/lib/icons'; -import { toast } from '@navi/web-ui/lib/primitives/Toast'; +import React from 'react'; +import { useSelector } from 'react-redux'; +import { Typography, Avatar } from '@navi/web-ui/lib/primitives'; import SlackIcon from '@src/assets/SlackIcon'; -import LoadingIcon from '@src/assets/LoadingIcon'; import ConfluenceIcon from '@src/assets/ConfluenceIcon'; import GoToLinkIcon from '@src/assets/GoToLinkIcon'; -import { ApiService } from '@src/services/api'; -import { - ActionType, - initialState, - reducer, - JIRA_VALIDATION, -} from './DescriptionContentProps'; -import styles from './DescriptionContent.module.scss'; -import { - LINK_JIRA_INCIDENT, - UNLINK_JIRA_INCIDENT, - FETCH_INCIDENT_DATA, -} from '../constants'; -import { truncateText, linkSanitization } from '../utils'; import JiraLinks from '../JiraLinks'; +import styles from './DescriptionContent.module.scss'; const DescriptionContent: React.FC = () => { - const [state, dispatch] = useReducer(reducer, initialState); - - const { id, description, jiraIds, slackChannel, incidentName, rcaLink } = - useSelector((state: any) => state.incidentLog.incidentData); - + const { description, slackChannel, incidentName, rcaLink } = useSelector( + (state: any) => state.incidentLog.incidentData, + ); const incidentParticipants = useSelector( (state: any) => state.incidentLog.participantsData, ); - // console.log('incident participants desc', incidentParticipants); - const reduxDispatch = useDispatch(); - //move to hooks - const storedEmail = localStorage.getItem('email-id'); - //import scss - const goToLinkBlueColor = '#0276FE'; - - //use from hooks below three functions - - //move to utils - // console.log('incident participants', incidentParticipants); const renderParticipantList = (type: string): JSX.Element => { const list = type === 'participants' ? incidentParticipants?.participants : incidentParticipants?.others; - return list?.length ? ( list.map(participant => (
@@ -127,7 +82,7 @@ const DescriptionContent: React.FC = () => { rel="noreferrer" className={styles['rca-text']} > - + Go to document diff --git a/src/Pages/Incidents/Dropdowns/AllDailogBox.tsx b/src/Pages/Incidents/Dropdowns/AllDailogBox.tsx index 458ab68..43bd972 100644 --- a/src/Pages/Incidents/Dropdowns/AllDailogBox.tsx +++ b/src/Pages/Incidents/Dropdowns/AllDailogBox.tsx @@ -1,8 +1,6 @@ -import { FC, useEffect, useReducer } from 'react'; -import { useMatch } from 'react-router-dom'; +import { FC, useReducer } from 'react'; import { useSelector, useDispatch } from 'react-redux'; -import { Tabs } from 'antd'; -import { ArrowDownIcon, AlertOutlineIcon } from '@navi/web-ui/lib/icons'; +import { AlertOutlineIcon } from '@navi/web-ui/lib/icons'; import { SelectPickerOptionProps } from '@navi/web-ui/lib/components/SelectPicker/types'; import { BorderedInput, @@ -11,9 +9,7 @@ import { Button, } from '@navi/web-ui/lib/primitives'; import { toast } from '@navi/web-ui/lib/primitives/Toast'; - import GoToLinkIcon from '@src/assets/GoToLinkIcon'; -import styles from '../Incidents.module.scss'; import LoadingIcon from '@src/assets/LoadingIcon'; import { setOpenDialognotParticipants, @@ -22,7 +18,6 @@ import { setOpenDialogUpdate, } from '@src/slices/IncidentSlice'; import { ApiService } from '@src/services/api'; - import { actionTypes, SLACK_BASE_URL, @@ -32,17 +27,13 @@ import { ResponseType, DUPLICATE_STATUS, UPDATE_INCIDENT, - SeverityType, - StatusType, - TeamType, RESOLVE_STATUS, } from '../constants'; - import useIncidentApis from '../useIncidentApis'; +import styles from '../Incidents.module.scss'; const AllDailogBox: FC = () => { const [state, dispatch] = useReducer(reducer, initialState); - const openNotParticipants = useSelector( (state: any) => state.incidentLog.openDialognotParticipants, ); @@ -61,32 +52,13 @@ const AllDailogBox: FC = () => { const selectedOption = useSelector( (state: any) => state.incidentLog.selectedOptions, ); - //console.log('updatedata', UpdateData); - const incidentData = useSelector( (state: any) => state.incidentLog.incidentData, ); const headerData = useSelector((state: any) => state.incidentLog.headerData); - const severityMap = headerData?.severities?.reduce((map, severity) => { - map[severity.value] = severity.label; - return map; - }, {}); - const statusMap = headerData?.incidentStatuses?.reduce((map, status) => { - map[status.value] = status.label; - return map; - }, {}); - const teamsMap = headerData?.teams?.reduce((map, Incidentteam) => { - map[Incidentteam.value] = Incidentteam.label; - return map; - }, {}); - - const { updateIncident, fetchIncidentLog, startIncidentSearch } = - useIncidentApis(); - 0; - + const { updateIncident, startIncidentSearch } = useIncidentApis(); const incidentId = incidentData?.id?.toString(); - console.log('incidentId', incidentId); const reduxDispatch = useDispatch(); const incidentName = incidentData?.incidentName; @@ -94,7 +66,7 @@ const AllDailogBox: FC = () => { reduxDispatch(setOpenDialognotParticipants(false)); }; - const handleGoToSlackChannel = () => { + const handleGoToSlackChannel = (): void => { const slackChannelURL = `${SLACK_BASE_URL}/archives/${incidentData?.slackChannel}`; window.open(slackChannelURL, '_blank'); handleopenNotParticipants(); @@ -231,7 +203,6 @@ const AllDailogBox: FC = () => { const handleTeamSelectionChange = ( selectedOption: SelectPickerOptionProps | SelectPickerOptionProps[], ): void => { - console.log('selectedOption', selectedOption); if (selectedOption) { dispatch({ type: actionTypes.SET_IS_TEAM_PICKER_OPEN, payload: false }); toast('Updating ticket. Please wait a moment.', { @@ -248,10 +219,10 @@ const AllDailogBox: FC = () => { }; const handleCloseConfirmationDialog = () => { - if (UpdateData?.type === SeverityType) { + if (UpdateData?.type == 'severity') { handleSeveritySelectionChange(selectedOption); } else { - if (UpdateData?.type === StatusType) { + if (UpdateData?.type == 'status') { handleStatusSelectionChange(selectedOption); } else { handleTeamSelectionChange(selectedOption); @@ -263,7 +234,6 @@ const AllDailogBox: FC = () => {
{openNotParticipants && ( { Icon={} />
- ) : null} +
+ ); + + const renderResolveDialog = (): JSX.Element => ( +
{openResolve ? ( { ) : null} -
- {openUpdate ? ( - { - reduxDispatch(setOpenDialogUpdate(false)); - }, +
+ ); + + const renderUpdateDialog = (): JSX.Element => ( +
+ {openUpdate ? ( + { + reduxDispatch(setOpenDialogUpdate(false)); }, - { - label: 'Update incident', - onClick: () => { - reduxDispatch(setOpenDialogUpdate(false)); - handleCloseConfirmationDialog(); - }, + }, + { + label: 'Update incident', + onClick: () => { + reduxDispatch(setOpenDialogUpdate(false)); + handleCloseConfirmationDialog(); }, - ]} - header={`Are you sure you want to update the incident?`} - onClose={() => reduxDispatch(setOpenDialogUpdate(false))} - > -
- - You are updating this incident‘s   - {UpdateData?.type || '..'} -   from - - {UpdateData?.from || '..'} - - to  - - {getLabelFromOption(UpdateData?.to) || '..'} - + }, + ]} + header={`Are you sure you want to update the incident?`} + onClose={() => reduxDispatch(setOpenDialogUpdate(false))} + > +
+ + You are updating this incident‘s   + {UpdateData?.type || '..'} +   from + + {UpdateData?.from || '..'} -
- - ) : null} -
+ to  + + {getLabelFromOption(UpdateData?.to) || '..'} + + +
+ + ) : null} +
+ ); + + return ( +
+ {renderNotAuthorizedDialog()} + {renderDuplicateDialog()} + {renderResolveDialog()} + {renderUpdateDialog()}
); }; diff --git a/src/Pages/Incidents/Dropdowns/index.tsx b/src/Pages/Incidents/Dropdowns/index.tsx index 041f428..4c93729 100644 --- a/src/Pages/Incidents/Dropdowns/index.tsx +++ b/src/Pages/Incidents/Dropdowns/index.tsx @@ -150,6 +150,21 @@ const Dropdowns: FC = () => { reduxDispatch(setOpenDialognotParticipants(true)); }; + const handleDispatch = ( + updateType: number, + selectedOption: SelectPickerOptionProps | SelectPickerOptionProps[], + ): void => { + reduxDispatch( + setUpdateDetails({ + type: getUpdateTypeText(updateType), + to: selectedOption, + from: getUpdateValueText(updateType), + }), + ); + reduxDispatch(setSelectedOptions(selectedOption)); + reduxDispatch(setOpenDialogUpdate(true)); + }; + const handleOpenConfirmationDailog = ( selectedOption: SelectPickerOptionProps | SelectPickerOptionProps[], updateType: number, @@ -165,17 +180,7 @@ const Dropdowns: FC = () => { ? selectedOption[0].value : selectedOption.value; if (currentState !== selectedvalue) { - dispatch({ type: actionTypes.SET_IS_STATUS_PICKER_OPEN, payload: false }); - dispatch({ - type: actionTypes.SET_IS_SEVERITY_PICKER_OPEN, - payload: false, - }); - dispatch({ type: actionTypes.SET_IS_TEAM_PICKER_OPEN, payload: false }); if (updateType === StatusType && selectedvalue === RESOLVE_STATUS) { - dispatch({ - type: actionTypes.SET_IS_STATUS_PICKER_OPEN, - payload: false, - }); reduxDispatch(setOpenDialogResolve(true)); } else if ( updateType === StatusType && @@ -187,15 +192,7 @@ const Dropdowns: FC = () => { }); reduxDispatch(setOpenDialogDuplicate(true)); } else { - reduxDispatch( - setUpdateDetails({ - type: getUpdateTypeText(updateType), - to: selectedOption, - from: getUpdateValueText(updateType), - }), - ); - reduxDispatch(setSelectedOptions(selectedOption)); - reduxDispatch(setOpenDialogUpdate(true)); + handleDispatch(updateType, selectedOption); } } }; diff --git a/src/Pages/Incidents/JiraLinks/index.tsx b/src/Pages/Incidents/JiraLinks/index.tsx index e635e54..f269995 100644 --- a/src/Pages/Incidents/JiraLinks/index.tsx +++ b/src/Pages/Incidents/JiraLinks/index.tsx @@ -12,7 +12,6 @@ import LinkIcon from '@src/assets/LinkIcon'; import JiraLogo from '@src/assets/JiraLogo'; import CopyIcon from '@src/assets/CopyIcon'; import LoadingIcon from '@src/assets/LoadingIcon'; -import { ApiService } from '@src/services/api'; import { handleCopyClick } from '@src/services/globalUtils'; import { IncidentPageState } from '@src/types'; import { truncateText, linkSanitization } from '../utils'; @@ -23,11 +22,6 @@ import { reducer, JIRA_VALIDATION, } from '../DescriptionContent/DescriptionContentProps'; -import { - LINK_JIRA_INCIDENT, - UNLINK_JIRA_INCIDENT, - JiraLinkPayload, -} from '../constants'; import styles from '../DescriptionContent/DescriptionContent.module.scss'; const JiraLinks: FC = () => { @@ -41,35 +35,7 @@ const JiraLinks: FC = () => { dispatch({ type: ActionType.SET_JIRA_LINKS, payload: jiraLinks || [] }); }, [jiraLinks]); - const handleApiError = (error: any): void => { - const errorMessage = - error?.response?.data?.error?.message || 'An error occurred.'; - toast.error(errorMessage); - }; - - const { startIncidentSearch } = useIncidentApis(); - - const addJiraLink = (payload: JiraLinkPayload): void => { - handleCloseIconClick(); - dispatch({ type: ActionType.SET_SHOW_LINKED_TICKETS, payload: true }); - const endPoint = LINK_JIRA_INCIDENT; - ApiService.post(endPoint, payload) - .then(response => { - toast.success(`${response?.data?.data}`); - startIncidentSearch(id); - }) - .catch(handleApiError); - }; - - const removeJiraLink = (payload: JiraLinkPayload): void => { - const endPoint = UNLINK_JIRA_INCIDENT; - ApiService.post(endPoint, payload) - .then(response => { - toast.info(`${response?.data?.data}`); - startIncidentSearch(id); - }) - .catch(handleApiError); - }; + const { addJiraLink, removeJiraLink } = useIncidentApis(); const validateLink = (value: string): void => { const urlPattern = /^https:\/\/navihq\.atlassian\.net\/browse\/.*/; @@ -114,6 +80,8 @@ const JiraLinks: FC = () => { toast('Adding link. Please wait a moment.', { icon: , }); + handleCloseIconClick(); + dispatch({ type: ActionType.SET_SHOW_LINKED_TICKETS, payload: true }); addJiraLink(payload); } } diff --git a/src/Pages/Incidents/constants.ts b/src/Pages/Incidents/constants.ts index 44f81fd..37249fc 100644 --- a/src/Pages/Incidents/constants.ts +++ b/src/Pages/Incidents/constants.ts @@ -1,5 +1,3 @@ -import exp from 'constants'; -import { TeamsData } from '../Team/constants'; import { createURL } from '@src/services/globalUtils'; export const IncidentConstants = { @@ -39,41 +37,6 @@ export const FETCH_AUDIT_LOG = (incidentId: string): string => { }; export const incidentRegrex = /^_houston-0*[1-9][0-9]*$/; -export interface ContentProps { - incidentData: any; -} - -export interface TeamResultsTableProps { - teamsData: Array; -} - -export interface HeaderProps { - incidentId: string; - incidentData: any; -} - -export interface UpdateInfoProps { - fromState: string; - byPerson: string; - updatedAt: string; - isLastItem: boolean; -} - -export interface CreatedInfoProps { - byPerson: string; - TeamAssigned: string; - updatedAt: string; - isLastItem: boolean; -} -export interface ResponseType { - data: ''; - status: number; -} -export interface JiraLinkPayload { - incident_id: number | null; - jira_link: string; - user: string; -} export const actionTypes = { SET_INCIDENT_DATA: 'SET_INCIDENT_DATA', diff --git a/src/Pages/Incidents/types.ts b/src/Pages/Incidents/types.ts new file mode 100644 index 0000000..e06263f --- /dev/null +++ b/src/Pages/Incidents/types.ts @@ -0,0 +1,51 @@ +import { UpdateIncidentType } from '@src/types'; + +export interface ContentProps { + incidentData: any; +} + +export interface TeamResultsTableProps { + teamsData: Array; +} + +export interface HeaderProps { + incidentId: string; + incidentData: any; +} + +export interface UpdateInfoProps { + fromState: string; + byPerson: string; + updatedAt: string; + isLastItem: boolean; +} + +export interface CreatedInfoProps { + byPerson: string; + TeamAssigned: string; + updatedAt: string; + isLastItem: boolean; +} +export interface ResponseType { + data: ''; + status: number; +} +export interface JiraLinkPayload { + incident_id: number | null; + jira_link: string; + user: string; +} + +export interface useIncidentApiProps { + fetchIncidentLog: (incidentId: string) => void; + startIncidentSearch: (incidentId: string) => void; + fetchHeaderDetails: () => void; + fetchParticipants: (slackChannel: string) => void; + updateIncident: (payload: UpdateIncidentType) => void; + markDuplicateIncident: ( + incidentId: string, + duplicateOfId: number | null, + ) => void; + addJiraLink: (payload: JiraLinkPayload) => void; + removeJiraLink: (payload: JiraLinkPayload) => void; +} diff --git a/src/Pages/Incidents/useIncidentApis.tsx b/src/Pages/Incidents/useIncidentApis.tsx index dc35739..ba12886 100644 --- a/src/Pages/Incidents/useIncidentApis.tsx +++ b/src/Pages/Incidents/useIncidentApis.tsx @@ -7,6 +7,15 @@ import { setHeaderData, setParticipantsData, } from '@src/slices/IncidentSlice'; +import LoadingIcon from '@src/assets/LoadingIcon'; +import { UpdateIncidentType } from '@src/types'; +import { JiraLinkPayload, ResponseType } from '@src/Pages/Incidents/types'; +import { + DUPLICATE_STATUS, + LINK_JIRA_INCIDENT, + UNLINK_JIRA_INCIDENT, +} from '@src/Pages/Incidents/constants'; +import { useIncidentApiProps } from './types'; import { FETCH_INCIDENT_DATA, UPDATE_INCIDENT, @@ -15,7 +24,7 @@ import { FETCH_PARTICIPANTS_DATA, } from './constants'; -const useIncidentApis = () => { +const useIncidentApis = (): useIncidentApiProps => { const dispatch = useDispatch(); const handleApiError = error => { @@ -27,7 +36,7 @@ const useIncidentApis = () => { toast.error(toastMessage); }; - const fetchIncidentLog = (incidentId): void => { + const fetchIncidentLog = (incidentId: string): void => { const endPoint = FETCH_AUDIT_LOG(incidentId); ApiService.get(endPoint) .then(response => { @@ -56,7 +65,7 @@ const useIncidentApis = () => { .catch(handleApiError); }; - const fetchParticipants = (slackChannel): void => { + const fetchParticipants = (slackChannel: string): void => { const endPoint = FETCH_PARTICIPANTS_DATA(slackChannel); ApiService.get(endPoint) .then(response => { @@ -65,7 +74,7 @@ const useIncidentApis = () => { .catch(handleApiError); }; - const updateIncident = (payload: any): void => { + const updateIncident = (payload: UpdateIncidentType): void => { const endPoint = UPDATE_INCIDENT; ApiService.post(endPoint, payload) .then(response => { @@ -75,12 +84,58 @@ const useIncidentApis = () => { .catch(handleApiError); }; + const markDuplicateIncident = ( + incidentId: string, + duplicateOfId: number | null, + ): void => { + const endPoint = UPDATE_INCIDENT; + toast('Updating ticket. Please wait a moment.', { + icon: , + }); + ApiService.post(endPoint, { + id: parseInt(incidentId, 10), + status: DUPLICATE_STATUS, + duplicateOfId: duplicateOfId, + }) + .then((response: ResponseType) => { + if (response?.status === 200) { + const toastMessage = `This incident is marked as duplicate of _houston-${duplicateOfId}`; + toast.success(toastMessage); + startIncidentSearch(incidentId); + } + }) + .catch(handleApiError); + }; + + const addJiraLink = (payload: JiraLinkPayload): void => { + const endPoint = LINK_JIRA_INCIDENT; + ApiService.post(endPoint, payload) + .then(response => { + toast.success(`${response?.data?.data}`); + startIncidentSearch(payload?.incident_id); + }) + .catch(handleApiError); + }; + + const removeJiraLink = (payload: JiraLinkPayload): void => { + const endPoint = UNLINK_JIRA_INCIDENT; + ApiService.post(endPoint, payload) + .then(response => { + toast.info(`${response?.data?.data}`); + startIncidentSearch(payload?.incident_id); + }) + .catch(handleApiError); + }; + return { fetchIncidentLog, startIncidentSearch, fetchHeaderDetails, fetchParticipants, updateIncident, + markDuplicateIncident, + addJiraLink, + removeJiraLink, }; }; diff --git a/src/slices/IncidentSlice.tsx b/src/slices/IncidentSlice.tsx index d101045..6203e2a 100644 --- a/src/slices/IncidentSlice.tsx +++ b/src/slices/IncidentSlice.tsx @@ -7,6 +7,7 @@ import { UpdateDetailsType, IncidentLogDataType, IncidentLogPageState, + IncidentPageState, } from '@src/types'; const initialState: IncidentLogPageState = { @@ -90,6 +91,17 @@ const incidentLogSlice = createSlice({ }, }); +export const selectIsLastChangeIndex = (state: IncidentPageState): number => { + return (state.incidentLog.incidentLogData?.data?.logs || []) + .slice() + .reverse() + .findIndex(log => + log.changes.some(change => + ['Status', 'SeverityId', 'TeamId'].includes(change.attribute), + ), + ); +}; + export const { setIncidentData, setIncidentLogData, diff --git a/src/types/index.d.ts b/src/types/index.d.ts index 9246e10..b78c47e 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -1,4 +1,5 @@ import { SelectPickerOptionProps } from '@navi/web-ui/lib/components/SelectPicker/types'; +import Severity from '@src/Pages/Severity'; export {}; interface AppConfig { @@ -169,3 +170,10 @@ export interface IncidentLogPageState { export interface IncidentPageState { incidentLog: IncidentLogPageState; } + +export interface UpdateIncidentType { + id: number; + severityId?: string; + teamId?: string; + status?: string; +} From 4ab6cca24aa33e0ec710f8859a8c253a8d223025 Mon Sep 17 00:00:00 2001 From: AyushRanjan Date: Wed, 31 Jan 2024 00:29:36 +0530 Subject: [PATCH 11/14] TP-52973 | build fix --- src/Pages/Incidents/IncidentInfo/CreatedInfo.tsx | 2 +- src/Pages/Incidents/IncidentInfo/UpdateInfo.tsx | 2 +- src/Pages/Incidents/types.ts | 1 + src/Pages/Team/partials/TeamResultsTable.tsx | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Pages/Incidents/IncidentInfo/CreatedInfo.tsx b/src/Pages/Incidents/IncidentInfo/CreatedInfo.tsx index 0f7da8b..4346a11 100644 --- a/src/Pages/Incidents/IncidentInfo/CreatedInfo.tsx +++ b/src/Pages/Incidents/IncidentInfo/CreatedInfo.tsx @@ -2,7 +2,7 @@ import { FC } from 'react'; import { Typography } from '@navi/web-ui/lib/primitives'; import { returnFormattedDate } from '@src/services/globalUtils'; import StepperLogTag from '../StepperAssets/StepperLogTag'; -import { CreatedInfoProps } from '../constants'; +import { CreatedInfoProps } from '../types'; import styles from '../Incidents.module.scss'; const renderCreatedInfo = (byPerson, TeamAssigned, updatedAt): JSX.Element => ( diff --git a/src/Pages/Incidents/IncidentInfo/UpdateInfo.tsx b/src/Pages/Incidents/IncidentInfo/UpdateInfo.tsx index 3de9c54..d81778a 100644 --- a/src/Pages/Incidents/IncidentInfo/UpdateInfo.tsx +++ b/src/Pages/Incidents/IncidentInfo/UpdateInfo.tsx @@ -1,7 +1,7 @@ import { FC } from 'react'; import Typography from '@navi/web-ui/lib/primitives/Typography'; import { returnFormattedDate } from '@src/services/globalUtils'; -import { UpdateInfoProps } from '../constants'; +import { UpdateInfoProps } from '../types'; import styles from '../Incidents.module.scss'; const renderUpdateInfo = (fromState, byPerson, updatedAt): JSX.Element => ( diff --git a/src/Pages/Incidents/types.ts b/src/Pages/Incidents/types.ts index e06263f..fc41861 100644 --- a/src/Pages/Incidents/types.ts +++ b/src/Pages/Incidents/types.ts @@ -1,3 +1,4 @@ +import { TeamsData } from '@src/Pages/Team/constants'; import { UpdateIncidentType } from '@src/types'; export interface ContentProps { diff --git a/src/Pages/Team/partials/TeamResultsTable.tsx b/src/Pages/Team/partials/TeamResultsTable.tsx index 54458c0..a4f1fed 100644 --- a/src/Pages/Team/partials/TeamResultsTable.tsx +++ b/src/Pages/Team/partials/TeamResultsTable.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import Typography from '@navi/web-ui/lib/primitives/Typography'; import { Accordion, AccordionGroup } from '@navi/web-ui/lib/primitives'; -import { TeamResultsTableProps } from '@src/Pages/Incidents/constants'; +import { TeamResultsTableProps } from '@src/Pages/Incidents/types'; import useClickStream from '@src/services/clickStream'; import { CLICK_STREAM_EVENT_FACTORY } from '@src/services/clickStream/constants/values'; import TeamForm from './TeamForm'; From 16afdd246f8a2222ae4c0797d8784e7c08328644 Mon Sep 17 00:00:00 2001 From: AyushRanjan Date: Wed, 31 Jan 2024 16:05:33 +0530 Subject: [PATCH 12/14] TP-52973 | pr comments resolve --- src/Pages/Incidents/Dropdowns/index.tsx | 57 +++++++------------------ src/Pages/Incidents/utils.ts | 56 ++++++++++++++++++++++++ src/slices/IncidentSlice.tsx | 53 ++++++++++++++++++++--- src/types/index.d.ts | 4 +- 4 files changed, 121 insertions(+), 49 deletions(-) diff --git a/src/Pages/Incidents/Dropdowns/index.tsx b/src/Pages/Incidents/Dropdowns/index.tsx index 4c93729..d3e329a 100644 --- a/src/Pages/Incidents/Dropdowns/index.tsx +++ b/src/Pages/Incidents/Dropdowns/index.tsx @@ -17,7 +17,13 @@ import { } from '@src/slices/IncidentSlice'; import { IncidentPageState } from '@src/types'; import AllDailogBox from './AllDailogBox'; -import { generateOptions, getCurrentData, getUpdateTypeText } from '../utils'; +import { + generateOptions, + getCurrentData, + getUpdateTypeText, + generateMap, + getUpdateValueText, +} from '../utils'; import { reducer, actionTypes, @@ -69,18 +75,10 @@ const Dropdowns: FC = () => { const isUserParticipantList = participantsList?.some( (participant: Participant) => participant.email === userEmail, ); - const severityMap = headerData?.severities?.reduce((map, severity) => { - map[severity.value] = severity.label; - return map; - }, {}); - const statusMap = headerData?.incidentStatuses?.reduce((map, status) => { - map[status.value] = status.label; - return map; - }, {}); - const teamsMap = headerData?.teams?.reduce((map, Incidentteam) => { - map[Incidentteam.value] = Incidentteam.label; - return map; - }, {}); + + const severityMap = generateMap(headerData?.severities || []); + const statusMap = generateMap(headerData?.incidentStatuses || []); + const teamsMap = generateMap(headerData?.teams || []); const currentSev = getCurrentData(incidentData, severityMap, 'severityId'); const currentStatus = getCurrentData(incidentData, statusMap, 'status'); @@ -158,7 +156,11 @@ const Dropdowns: FC = () => { setUpdateDetails({ type: getUpdateTypeText(updateType), to: selectedOption, - from: getUpdateValueText(updateType), + from: getUpdateValueText({ + updateType: updateType, + maps: { severityMap, statusMap, teamsMap }, + initialValues: { initialSeverity, initialStatus, initialTeam }, + }), }), ); reduxDispatch(setSelectedOptions(selectedOption)); @@ -197,33 +199,6 @@ const Dropdowns: FC = () => { } }; - const getUpdateValueText = (updateType: number): string => { - switch (updateType) { - case SeverityType: - return ` ${ - severityMap && initialSeverity?.value - ? severityMap[initialSeverity.value] - : '-' - } `; - case StatusType: - return ` ${ - statusMap && initialStatus?.value - ? statusMap[initialStatus.value] - : '-' - } `; - case TeamType: - return ` ${ - teamsMap && initialTeam?.value ? teamsMap[initialTeam.value] : '-' - } `; - default: - return ` ${ - severityMap && initialSeverity?.value - ? severityMap[initialSeverity.value] - : '-' - } `; - } - }; - const handleSevClickOutside = (): void => { dispatch({ type: actionTypes.SET_IS_SEVERITY_PICKER_OPEN, diff --git a/src/Pages/Incidents/utils.ts b/src/Pages/Incidents/utils.ts index a11ff73..c748828 100644 --- a/src/Pages/Incidents/utils.ts +++ b/src/Pages/Incidents/utils.ts @@ -22,6 +22,18 @@ type DataItemModified = { label: string; }; +interface Maps { + severityMap: { [key: number]: string }; + statusMap: { [key: number]: string }; + teamsMap: { [key: number]: string }; +} + +interface InitialValues { + initialSeverity?: { value: number | null }; + initialStatus?: { value: number | null }; + initialTeam?: { value: number | null }; +} + export const generateOptions = (data: DataItem[]): DataItemModified[] => { return (data || []).map(item => ({ value: item.value.toString(), @@ -54,3 +66,47 @@ export const getCurrentData = ( ): string => { return dataMap && data[key] ? dataMap[data[key]] : '-'; }; + +export const generateMap = (data: DataItem[]): { [key: number]: string } => { + return data.reduce((map, item) => { + map[item.value] = item.label; + return map; + }, {}); +}; + +export const getUpdateValueText = ({ + updateType, + maps, + initialValues, +}: { + updateType: number | null; + maps: Maps; + initialValues: InitialValues; +}): string => { + switch (updateType) { + case SeverityType: + return ` ${ + maps.severityMap && initialValues.initialSeverity?.value + ? maps.severityMap[initialValues.initialSeverity.value] + : '-' + } `; + case StatusType: + return ` ${ + maps.statusMap && initialValues.initialStatus?.value + ? maps.statusMap[initialValues.initialStatus.value] + : '-' + } `; + case TeamType: + return ` ${ + maps.teamsMap && initialValues.initialTeam?.value + ? maps.teamsMap[initialValues.initialTeam.value] + : '-' + } `; + default: + return ` ${ + maps.severityMap && initialValues.initialSeverity?.value + ? maps.severityMap[initialValues.initialSeverity.value] + : '-' + } `; + } +}; diff --git a/src/slices/IncidentSlice.tsx b/src/slices/IncidentSlice.tsx index 6203e2a..beaae4a 100644 --- a/src/slices/IncidentSlice.tsx +++ b/src/slices/IncidentSlice.tsx @@ -11,16 +11,57 @@ import { } from '@src/types'; const initialState: IncidentLogPageState = { - incidentLogData: {} as IncidentLogDataType, - incidentData: {} as IncidentDatatype, - headerData: {} as HeaderDatatype, - participantsData: {} as ParticipantsDatatype, - updateDetails: {} as UpdateDetailsType, + incidentLogData: { + relation_name: '', + record_id: null, + logs: [], + has_creation: false, + }, + incidentData: { + id: null, + title: '', + description: '', + status: null, + statusName: '', + severityId: null, + severityName: '', + incidentName: null, + slackChannel: '', + detectionTime: null, + startTime: null, + endTime: null, + teamId: null, + teamName: '', + jiraLinks: [], + confluenceId: null, + severityTat: null, + remindMeAt: null, + enableReminder: false, + createdBy: '', + updatedBy: '', + createdAt: null, + updatedAt: null, + rcaLink: '', + }, + headerData: { + severities: [], + incidentStatuses: [], + teams: [], + }, + participantsData: { + participants: [], + others: [], + }, + updateDetails: { + type: null, + from: null, + to: null, + }, openDialogUpdate: false, openDialogDuplicate: false, openDialogResolve: false, openDialognotParticipants: false, - selectedOptions: {} as SelectPickerOptionProps | SelectPickerOptionProps[], + selectedOptions: null, }; const incidentLogSlice = createSlice({ diff --git a/src/types/index.d.ts b/src/types/index.d.ts index b78c47e..8137953 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -126,7 +126,7 @@ export interface ParticipantsDatatype { export interface UpdateDetailsType { type: string | null; from: string | null; - to: SelectPickerOptionProps | SelectPickerOptionProps[]; + to: SelectPickerOptionProps | SelectPickerOptionProps[] | null; } export interface IncidentLogDataType { @@ -164,7 +164,7 @@ export interface IncidentLogPageState { openDialogDuplicate: boolean; openDialogResolve: boolean; openDialognotParticipants: boolean; - selectedOptions: SelectPickerOptionProps | SelectPickerOptionProps[]; + selectedOptions: SelectPickerOptionProps | SelectPickerOptionProps[] | null; } export interface IncidentPageState { From b643bd87e885a46fd792fdde3a021a3cea4b9bad Mon Sep 17 00:00:00 2001 From: AyushRanjan Date: Wed, 31 Jan 2024 16:21:34 +0530 Subject: [PATCH 13/14] TP-52973 | build fix --- src/Pages/Incidents/Dropdowns/AllDailogBox.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Pages/Incidents/Dropdowns/AllDailogBox.tsx b/src/Pages/Incidents/Dropdowns/AllDailogBox.tsx index 8620cb8..622a1bd 100644 --- a/src/Pages/Incidents/Dropdowns/AllDailogBox.tsx +++ b/src/Pages/Incidents/Dropdowns/AllDailogBox.tsx @@ -120,7 +120,7 @@ const AllDailogBox: FC = () => { }; const handleSeveritySelectionChange = ( - selectedOption: SelectPickerOptionProps | SelectPickerOptionProps[], + selectedOption: SelectPickerOptionProps | SelectPickerOptionProps[] | null, ): void => { if (selectedOption) { toast('Updating ticket. Please wait a moment.', { @@ -140,7 +140,7 @@ const AllDailogBox: FC = () => { } }; const handleStatusSelectionChange = ( - selectedOption: SelectPickerOptionProps | SelectPickerOptionProps[], + selectedOption: SelectPickerOptionProps | SelectPickerOptionProps[] | null, ): void => { if (selectedOption) { dispatch({ type: actionTypes.SET_IS_STATUS_PICKER_OPEN, payload: false }); @@ -170,7 +170,7 @@ const AllDailogBox: FC = () => { } }; const handleTeamSelectionChange = ( - selectedOption: SelectPickerOptionProps | SelectPickerOptionProps[], + selectedOption: SelectPickerOptionProps | SelectPickerOptionProps[] | null, ): void => { if (selectedOption) { dispatch({ type: actionTypes.SET_IS_TEAM_PICKER_OPEN, payload: false }); @@ -200,12 +200,12 @@ const AllDailogBox: FC = () => { }; const getLabelFromOption = ( - option: SelectPickerOptionProps | SelectPickerOptionProps[], + option: SelectPickerOptionProps | SelectPickerOptionProps[] | null, ): string | undefined => { if (Array.isArray(option)) { return option[0]?.label; } else { - return option.label; + return option?.label; } }; const renderNotAuthorizedDialog = (): JSX.Element => ( From 0eb32fe6753963dbb62e4d2506d72d949c77e92c Mon Sep 17 00:00:00 2001 From: Ayush Ranjan Date: Tue, 6 Feb 2024 15:11:08 +0530 Subject: [PATCH 14/14] TP-52973 | minor fix (#120) --- src/Pages/Incidents/Dropdowns/AllDailogBox.tsx | 5 ++--- src/Pages/Incidents/useIncidentApis.tsx | 2 ++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Pages/Incidents/Dropdowns/AllDailogBox.tsx b/src/Pages/Incidents/Dropdowns/AllDailogBox.tsx index 622a1bd..faf6669 100644 --- a/src/Pages/Incidents/Dropdowns/AllDailogBox.tsx +++ b/src/Pages/Incidents/Dropdowns/AllDailogBox.tsx @@ -56,7 +56,6 @@ const AllDailogBox: FC = () => { const { updateIncident, markDuplicateIncident } = useIncidentApis(); const incidentId = incidentData?.id?.toString() || ''; const reduxDispatch = useDispatch(); - const incidentName = incidentData?.incidentName; const handleopenNotParticipants = (): void => { reduxDispatch(setOpenDialognotParticipants(false)); @@ -73,7 +72,7 @@ const AllDailogBox: FC = () => { reduxDispatch(setOpenDialogDuplicate(false)); }; const disable = (): boolean => { - return !incidentName || !validate(incidentName); + return !state.incidentName || !validate(state.incidentName); }; const extractIncidentId = (incidentName: string | null): number | null => { @@ -85,7 +84,7 @@ const AllDailogBox: FC = () => { ); }; - const duplicateOfId = extractIncidentId(incidentName); + const duplicateOfId = extractIncidentId(state.incidentName); const handleIncidentChange = ( e: React.ChangeEvent, diff --git a/src/Pages/Incidents/useIncidentApis.tsx b/src/Pages/Incidents/useIncidentApis.tsx index ba12886..d501b3c 100644 --- a/src/Pages/Incidents/useIncidentApis.tsx +++ b/src/Pages/Incidents/useIncidentApis.tsx @@ -15,6 +15,7 @@ import { LINK_JIRA_INCIDENT, UNLINK_JIRA_INCIDENT, } from '@src/Pages/Incidents/constants'; +import { setOpenDialogDuplicate } from '@src/slices/IncidentSlice'; import { useIncidentApiProps } from './types'; import { FETCH_INCIDENT_DATA, @@ -102,6 +103,7 @@ const useIncidentApis = (): useIncidentApiProps => { const toastMessage = `This incident is marked as duplicate of _houston-${duplicateOfId}`; toast.success(toastMessage); startIncidentSearch(incidentId); + dispatch(setOpenDialogDuplicate(false)); } }) .catch(handleApiError);