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, }, });