TP-52973 | merge conflict resolve

This commit is contained in:
AyushRanjan
2024-01-22 00:17:05 +05:30
parent 5aa9976928
commit bd43bc6213
20 changed files with 1101 additions and 1777 deletions

View File

@@ -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<any>;
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<ActivityLogProps> = ({ incidentLog, totalLog }) => {
return (
<div>
{incidentLog?.data?.logs && incidentLog.data.logs.length > 0 && (

View File

@@ -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;
}

View File

@@ -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<any>();
const [isLoading, setIsLoading] = useState<boolean>(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 (
<div className={styles['content-wrapper']}>
<Typography variant="h5">{IncidentConstants.incidentSummary}</Typography>
<hr className={commonStyles['divider']} />
<div>
<Typography variant="h4" className={styles['content-info']}>
{IncidentConstants.title}:{' '}
<Typography variant="p3">{incidentData?.title}</Typography>
</Typography>
</div>
<div className={styles['description-details']}>
<Typography variant="h4">{IncidentConstants.description}:</Typography>
<Typography variant="p3">{incidentData?.description}</Typography>
</div>
<Typography variant="h4">{IncidentConstants.status}:</Typography>
<div>
<KeyValueLabel
className={styles['key-value-pair']}
dataArray={[
{
key: 'Created At',
value: returnFormattedDate(incidentData?.createdAt),
},
{
key: 'Created By',
value: incidentData?.createdBy,
},
{
key: 'Updated At',
value: returnFormattedDate(incidentData?.updatedAt),
},
{
key: 'Updated By',
value: incidentData?.updatedBy,
},
]}
/>
</div>
<div className={styles['description-details']}>
<Typography variant="h4">{IncidentConstants.team}:</Typography>
{isLoading ? (
<div className={styles['loader']}>
<LoadingIcon size="lg" />
</div>
) : (
data?.map(participant => (
<div
key={participant?.id}
className={styles['team-details-wrapper']}
>
<div className={styles['participant-detail']}>
<Avatar
size={20}
alt={participant?.real_name?.[0]?.toUpperCase()}
/>{' '}
<Typography variant="p3">{participant?.real_name}</Typography>
</div>
</div>
))
)}
</div>
</div>
);
};
export default Content;

View File

@@ -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<DescriptionContentProps> = ({
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<HTMLInputElement>): 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<HTMLInputElement>,
): 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: <LoadingIcon />,
});
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: <LoadingIcon />,
});
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 => (
<div key={participant?.id} className={styles['team-details-wrapper']}>
<div className={styles['participant-detail']}>
<Avatar size={20} isImage src={participant?.image} /> &nbsp;&nbsp;
<Typography variant="h5">{participant?.name}</Typography>
</div>
</div>
))
) : (
<div className={styles['team-details-wrapper']}>-</div>
);
};
const returnOthers = (): JSX.Element => {
return incidentParticipants?.others?.length ? (
incidentParticipants?.others?.map(participant => (
return list?.length ? (
list.map(participant => (
<div key={participant?.id} className={styles['team-details-wrapper']}>
<div className={styles['participant-detail']}>
<Avatar size={20} isImage src={participant?.image} /> &nbsp;&nbsp;
@@ -273,116 +133,8 @@ const DescriptionContent: React.FC<DescriptionContentProps> = ({
</a>
</div>
)}
<div className={styles['description-content-jira']}>
<div className={styles['flex-row']}>
<JiraLogo />
&nbsp;
<Typography variant="h4" color="var(--navi-color-gray-c2)">
JIRA ticket(s) &nbsp;&nbsp;&nbsp;&nbsp;
</Typography>
</div>
<div>
<div className={styles['flex-row']}>
<div
className={styles['first-row-link']}
onClick={handleLinkedTicketsClick}
>
<Typography
variant="h4"
color="var(--navi-color-gray-c2)"
className={styles['typo-style']}
>
{state.jiraLinks?.filter(link => link !== '').length || 0}{' '}
linked tickets&nbsp;&nbsp;
</Typography>
{state.jiraLinks?.filter(link => link !== '').length > 0 && (
<>
{state.showLinkedTickets && (
<ArrowUpSolidIcon width={8} height={8} />
)}
{!state.showLinkedTickets && (
<ArrowDownSolidIcon width={8} height={8} />
)}
</>
)}
</div>
<div className={styles['vertical-line']} />
<div
className={`${styles['link-ticket-style']} ${
state.showInput ? styles['link-ticket-opacity'] : ''
}`}
onClick={handleLinkJiraClick}
>
<LinkIcon />
&nbsp;
<Typography variant="h4" color="var(--navi-color-blue-base)">
Link JIRA ticket
</Typography>
</div>
</div>
<div>
{state.showLinkedTickets && (
<div>
{state.jiraLinks
.filter(jiraLink => jiraLink !== '')
?.map((jiraLink, index) => (
<div key={index} className={styles['link-row-style']}>
<div>
<div className={styles['jira-link-row']}>
<a
href={jiraLink}
target="_blank"
rel="noreferrer"
className={styles['jira-link']}
>
<Typography
variant="h4"
color="var(--navi-color-blue-base)"
>
{truncateText(jiraLink)}
</Typography>
</a>
</div>
</div>
&nbsp;&nbsp;
<div className={styles['copyicon-style']}>
<CopyIcon onClick={() => handleCopyClick(jiraLink)} />
</div>
<div className={styles['deleteicon-style']}>
<DeleteIconOutlined
onClick={() => handleDeleteIconClick(jiraLink)}
width={20}
height={20}
color="var(--navi-color-gray-c3)"
/>
</div>
</div>
))}
</div>
)}
</div>
{state.showInput && (
<div className={styles['jira-input-row']}>
<div className={styles['row-input-field']}>
<BorderLessInput
inputLabel="Enter ticket link"
onChange={handleLinkChange}
onKeyDown={handleInputKeyDown}
hideClearIcon
error={state.errorText}
hintMsg={state.helperText}
fullWidth={true}
maxLength={50}
/>
</div>
<div className={styles['closeicon-style']}>
<CloseIcon onClick={handleCloseIconClick} />
</div>
</div>
)}
</div>
</div>
</div>
<JiraLinks />
<div className={styles['horizontal-line']} />
<div className={styles['description-participants']}>
<Typography variant="h6" color="var(--navi-color-gray-c3)">
@@ -392,14 +144,14 @@ const DescriptionContent: React.FC<DescriptionContentProps> = ({
<Typography variant="h6" color="var(--navi-color-gray-c3)">
Team
</Typography>
{returnParticipants()}
{renderParticipantList('participants')}
</div>
<div className={styles['description-participants']}>
<div className={styles['description-team']}>
<Typography variant="h6" color="var(--navi-color-gray-c3)">
Others
</Typography>
{returnOthers()}
{renderParticipantList('others')}
</div>
</div>
</div>

View File

@@ -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;
}

View File

@@ -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<DrawerModeProps> = ({ incidentId, slackChannel }) => {
const [severity, setSeverity] = useState<any>();
const [status, setStatus] = useState<any>();
const [team, setTeam] = useState<any>();
const [headerData, setHeaderData] = useState<any>({});
const [incidentDetails, setIncidentDetails] = useState<any>({});
const [incidentParticipants, setIncidentParticipants] = useState<any>([]);
const [isLoading, setIsLoading] = useState<boolean>(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 (
<div>
<Typography variant="h4">Update Tags: </Typography>
<div className={styles['severity-details-wrapper']}>
<Typography variant="h5">Severity: </Typography>
<Filter
title={severity?.label ? severity?.label : 'Severity'}
options={headerData?.severities}
onSelectionChange={val => {
setSeverity(val);
updateIncident({
id: incidentId,
severityId: !Array.isArray(val)
? `${val?.value}`
: val?.[0]?.value,
});
}}
isSingleSelect
filterClass={styles['filter-wrapper']}
/>
</div>
<div className={styles['severity-details-wrapper']}>
<Typography variant="h5">Status: </Typography>
<Filter
title={status?.label ? status?.label : 'Status'}
options={headerData?.incidentStatuses}
onSelectionChange={val => {
setStatus(val);
updateIncident({
id: incidentId,
status: !Array.isArray(val) ? `${val?.value}` : val?.[0]?.value,
});
}}
isSingleSelect
filterClass={styles['filter-wrapper']}
/>
</div>
<div className={styles['severity-details-wrapper']}>
<Typography variant="h5">Team: </Typography>
<Filter
title={team?.label ? team?.label : 'Team'}
options={headerData?.teams}
onSelectionChange={val => {
setTeam(val);
updateIncident({
id: incidentId,
teamId: !Array.isArray(val) ? `${val?.value}` : val?.[0]?.value,
});
}}
isSingleSelect
filterClass={styles['filter-wrapper']}
/>
</div>
</div>
);
};
const returnContent = () => {
return (
<div>
<div className={styles['content-info']}>
<Typography variant="h4">{IncidentConstants.title}:</Typography>
<Typography variant="p4">{incidentDetails?.title}</Typography>
</div>
<div className={styles['description-details']}>
<Typography variant="h4">{IncidentConstants.description}:</Typography>
<Typography variant="p4" className={styles['description']}>
{incidentDetails?.description || '-'}
</Typography>
</div>
<div className={styles['description-details']}>
<Typography variant="h4">{IncidentConstants.channel}:</Typography>
<Typography variant="p4" className={styles['slack-channel']}>
<SlackIcon />
<a
href={`https://go-navi.slack.com/archives/${incidentDetails?.slackChannel}`}
target="_blank"
rel="noreferrer"
>
{incidentDetails?.incidentName || '-'}
</a>
</Typography>
</div>
<div className={styles['description-details']}>
<Typography variant="h4">{IncidentConstants.incidentId}:</Typography>
<Typography variant="p4" className={styles['description']}>
{incidentDetails?.id}
</Typography>
</div>
</div>
);
};
const returnParticipants = () => {
return incidentParticipants?.length ? (
incidentParticipants?.map(participant => (
<div key={participant?.id} className={styles['team-details-wrapper']}>
<div className={styles['participant-detail']}>
<Avatar size={20} isImage src={participant?.image} />{' '}
<Typography variant="p3">{participant?.name}</Typography>
</div>
</div>
))
) : (
<div className={styles['team-details-wrapper']}>-</div>
);
};
if (isLoading) {
return (
<div className={styles['loader-container']}>
<LoadingIcon size="md" />
</div>
);
}
return (
<div>
{returnContent()}
<hr className={cx(commonStyles['divider'], styles['content-divider'])} />
{returnHeaderDetails()}
<hr className={cx(commonStyles['divider'], styles['content-divider'])} />
<Typography variant="h4">Participants:</Typography>
{returnParticipants()}
</div>
);
};
export default DrawerMode;

View File

@@ -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<HTMLDivElement>;
const refSeverity = useOutsideClick({
callback: handleSevClickOutside,
}) as MutableRefObject<HTMLDivElement>;
const refTeam = useOutsideClick({
callback: handleTeamClickOutside,
}) as MutableRefObject<HTMLDivElement>;
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: <LoadingIcon />,
});
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: <LoadingIcon />,
});
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: <LoadingIcon />,
});
const value = Array.isArray(selectedOption)
? selectedOption[0].value
: selectedOption.value;
updateIncident({
id: parseInt(incidentId, 10),
teamId: value.toString(),
});
}
};
return (
<div>
<div className={styles['log-update-dropdown']}>
<div className={styles['dropdown-severity']}>
<div>
<Typography variant="p5" color="var(--navi-color-gray-c2">
Severity
</Typography>
</div>
<div>
<div className={combinedSevClassNames} onClick={handleSevClick}>
<div>
<Typography
variant="p4"
color={
isUserParticipantList
? 'var(--navi-color-gray-c1)'
: 'var(--navi-color-gray-c3)'
}
>
{severityMap && state.severity?.value
? severityMap[state.severity.value]
: '-'}
</Typography>
</div>
<div className={styles['arrowdown-style']}>
<ArrowDownIcon />
</div>
</div>
<div className={styles['severity-selectpicker']}>
{state.isSeverityPickerOpen && (
<div className={styles['severity-selectpicker-style']}>
<SelectPicker
multiSelect={false}
onSelectionChange={selectedOption =>
handleOpenConfirmationDailog(selectedOption, SeverityType)
}
options={updatedSeverities}
selectedValue={state.severity?.value.toString()}
/>
</div>
)}
</div>
</div>
</div>
<div className={styles['dropdown-status']}>
<div>
<Typography variant="p5" color="var(--navi-color-gray-c2)">
Status
</Typography>
</div>
<div ref={refStatus}>
<div
className={combinedStatusClassNames}
onClick={handleStatusClick}
>
<div>
<Typography
variant="p4"
color={
isUserParticipantList
? 'var(--navi-color-gray-c1)'
: 'var(--navi-color-gray-c3)'
}
>
{incidentStatusMap && state.status?.value
? incidentStatusMap[state.status.value]
: '-'}
</Typography>
</div>
<div>
<ArrowDownIcon className={styles['arrowdown-style']} />
</div>
</div>
<div className={styles['status-selectpicker']}>
{state.isStatusPickerOpen && (
<div className={styles['status-selectpicker-style']}>
<SelectPicker
multiSelect={false}
onSelectionChange={selectedOption =>
handleOpenConfirmationDailog(selectedOption, StatusType)
}
options={updatedStatuses}
selectedValue={state.status?.value.toString()}
/>
</div>
)}
</div>
</div>
</div>
<div className={styles['dropdown-team']}>
<div>
<Typography variant="p5" color="var(--navi-color-gray-c2)">
Team
</Typography>
</div>
<div ref={refTeam}>
<div className={combinedTeamClassNames} onClick={handleTeamClick}>
<div>
<Typography
variant="p4"
color={
isUserParticipantList
? 'var(--navi-color-gray-c1)'
: 'var(--navi-color-gray-c3)'
}
>
{teamsMap && state.team?.value
? teamsMap[state.team.value]
: '-'}
</Typography>
</div>
<div>
<ArrowDownIcon className={styles['arrowdown-style']} />
</div>
</div>
<div className={styles['team-selectpicker']}>
{state.isTeamPickerOpen && (
<div className={styles['team-selectpicker-style']}>
<SelectPicker
multiSelect={false}
onSelectionChange={selectedOption =>
handleOpenConfirmationDailog(selectedOption, TeamType)
}
options={updatedTeams}
selectedValue={state.team?.value.toString()}
/>
</div>
)}
</div>
</div>
</div>
</div>
</div>
);
};
export default Dropdowns;

View File

@@ -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<HeaderProps> = ({ 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 (
<div>
<Button variant="text" onClick={handleBacktoDashboard}>
@@ -38,7 +40,7 @@ const Header: FC<HeaderProps> = ({ incidentName, title }) => {
<div className={styles['incident-info']}>
<div className={styles['incident-info-text']}>
<Typography variant="h3">
{incidentName} : {title}
{incidentData?.incidentName} : {incidentData?.title}
</Typography>
</div>
<div className={styles['incident-info-icon']}>

View File

@@ -0,0 +1,301 @@
import { FC, useReducer, useEffect } from 'react';
import { truncateText, linkSanitization } from '../utils';
import styles from '../DescriptionContent/DescriptionContent.module.scss';
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 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 {
ActionType,
initialState,
reducer,
JIRA_VALIDATION,
} from '../DescriptionContent/DescriptionContentProps';
import {
LINK_JIRA_INCIDENT,
UNLINK_JIRA_INCIDENT,
FETCH_INCIDENT_DATA,
} from '../constants';
const JiraLinks: 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]);
//move to constant or utils
const handleApiError = (error: any): void => {
const errorMessage =
error?.response?.data?.error?.message || 'An error occurred.';
toast.error(errorMessage);
};
//use from hooks below three functions
const startIncidentSearch = (): void => {
const endPoint = FETCH_INCIDENT_DATA(id);
ApiService.get(endPoint)
.then(response => {
reduxDispatch(setIncidentData(response?.data?.data));
dispatch({
type: ActionType.SET_JIRA_LINKS,
payload: response?.data?.data?.jiraLinks || [],
});
})
.catch(handleApiError);
};
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 => {
//move to constants
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<HTMLInputElement>): void => {
const inputValue = e.target.value;
dispatch({ type: ActionType.SET_INPUT_VALUE, payload: inputValue });
validateLink(inputValue);
};
const handleInputKeyDown = (
event: React.KeyboardEvent<HTMLInputElement>,
): 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: <LoadingIcon />,
});
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: <LoadingIcon />,
});
removeJiraLink(payload);
};
return (
<div>
<div className={styles['description-content-jira']}>
<div className={styles['flex-row']}>
<JiraLogo />
&nbsp;
<Typography variant="h4" color="var(--navi-color-gray-c2)">
JIRA ticket(s) &nbsp;&nbsp;&nbsp;&nbsp;
</Typography>
</div>
<div>
<div className={styles['flex-row']}>
<div
className={styles['first-row-link']}
onClick={handleLinkedTicketsClick}
>
<Typography
variant="h4"
color="var(--navi-color-gray-c2)"
className={styles['typo-style']}
>
{state.jiraLinks?.filter(link => link !== '').length || 0}{' '}
linked tickets&nbsp;&nbsp;
</Typography>
{state.jiraLinks?.filter(link => link !== '').length > 0 && (
<>
{state.showLinkedTickets && (
<ArrowUpSolidIcon width={8} height={8} />
)}
{!state.showLinkedTickets && (
<ArrowDownSolidIcon width={8} height={8} />
)}
</>
)}
</div>
<div className={styles['vertical-line']} />
<div
className={`${styles['link-ticket-style']} ${
state.showInput ? styles['link-ticket-opacity'] : ''
}`}
onClick={handleLinkJiraClick}
>
<LinkIcon />
&nbsp;
<Typography variant="h4" color="var(--navi-color-blue-base)">
Link JIRA ticket
</Typography>
</div>
</div>
<div>
{state.showLinkedTickets && (
<div>
{state.jiraLinks
.filter(jiraLink => jiraLink !== '')
?.map((jiraLink, index) => (
<div key={index} className={styles['link-row-style']}>
<div>
<div className={styles['jira-link-row']}>
<a
href={jiraLink}
target="_blank"
rel="noreferrer"
className={styles['jira-link']}
>
<Typography
variant="h4"
color="var(--navi-color-blue-base)"
>
{truncateText(jiraLink)}
</Typography>
</a>
</div>
</div>
&nbsp;&nbsp;
<div className={styles['copyicon-style']}>
<CopyIcon onClick={() => handleCopyClick(jiraLink)} />
</div>
<div className={styles['deleteicon-style']}>
<DeleteIconOutlined
onClick={() => handleDeleteIconClick(jiraLink)}
width={20}
height={20}
color="var(--navi-color-gray-c3)"
/>
</div>
</div>
))}
</div>
)}
</div>
{state.showInput && (
<div className={styles['jira-input-row']}>
<div className={styles['row-input-field']}>
<BorderLessInput
inputLabel="Enter ticket link"
onChange={handleLinkChange}
onKeyDown={handleInputKeyDown}
hideClearIcon
error={state.errorText}
hintMsg={state.helperText}
fullWidth={true}
maxLength={50}
/>
</div>
<div className={styles['closeicon-style']}>
<CloseIcon onClick={handleCloseIconClick} />
</div>
</div>
)}
</div>
</div>
</div>
);
};
export default JiraLinks;

View File

@@ -1,16 +0,0 @@
.meta-info-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: 65%;
// Remove once UI is available
padding-top: 24px;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}

View File

@@ -1,17 +0,0 @@
import { FC } from 'react';
import Typography from '@navi/web-ui/lib/primitives/Typography';
import UnderConstructionIllustration from '@src/assets/UnderConstructionIllustration';
import styles from './MetaInfo.module.scss';
const MetaInfo: FC = () => {
return (
<div className={styles['meta-info-wrapper']}>
<Typography variant="h3">More Info will be updated soon!!</Typography>
<UnderConstructionIllustration width={500} height={500} />
</div>
);
};
export default MetaInfo;

View File

@@ -1,5 +1,3 @@
import exp from 'constants';
export const mapSeverityToTagColor = severity => {
switch (severity) {
case 'Sev-0':

View File

@@ -0,0 +1,55 @@
import { FC } from 'react';
import { useSelector } from 'react-redux';
import styles from '../Incidents.module.scss';
import { Tooltip, Typography } from '@navi/web-ui/lib/primitives';
import { AlertOutlineIcon } from '@navi/web-ui/lib/icons';
import Dropdowns from '../Dropdowns';
const UpdateIncidentBox: FC = () => {
//add type here and color to centralize
const incidentData = useSelector(
(state: any) => state.incidentLog.incidentData,
);
const incidentParticipants = useSelector(
(state: any) => state.incidentLog.incidentParticipants,
);
// 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,
);
return (
<div>
<div className={styles['log-update-text']}>
<Typography variant="h6" color="var(--navi-color-gray-c3)">
UPDATE INCIDENT
</Typography>
{!isUserParticipantList && (
<div className={styles['info-icon']}>
<Tooltip
text="Only Slack channel participants can update this incident."
withPointer
position={'right'}
>
<div className={styles['alert-icon']}>
<AlertOutlineIcon />
</div>
</Tooltip>
</div>
)}
</div>
<Dropdowns />
</div>
);
};
export default UpdateIncidentBox;

File diff suppressed because it is too large Load Diff

View File

@@ -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;

View File

@@ -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;
};

View File

@@ -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;

View File

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