TP-52973 | merge conflict resolve
This commit is contained in:
@@ -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 && (
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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} />
|
||||
<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} />
|
||||
@@ -273,116 +133,8 @@ const DescriptionContent: React.FC<DescriptionContentProps> = ({
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
<div className={styles['description-content-jira']}>
|
||||
<div className={styles['flex-row']}>
|
||||
<JiraLogo />
|
||||
|
||||
<Typography variant="h4" color="var(--navi-color-gray-c2)">
|
||||
JIRA ticket(s)
|
||||
</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
|
||||
</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 />
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
448
src/Pages/Incidents/Dropdowns/index.tsx
Normal file
448
src/Pages/Incidents/Dropdowns/index.tsx
Normal 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;
|
||||
@@ -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']}>
|
||||
|
||||
0
src/Pages/Incidents/JiraLinks/JiraLinks.module.scss
Normal file
0
src/Pages/Incidents/JiraLinks/JiraLinks.module.scss
Normal file
301
src/Pages/Incidents/JiraLinks/index.tsx
Normal file
301
src/Pages/Incidents/JiraLinks/index.tsx
Normal 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 />
|
||||
|
||||
<Typography variant="h4" color="var(--navi-color-gray-c2)">
|
||||
JIRA ticket(s)
|
||||
</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
|
||||
</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 />
|
||||
|
||||
<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>
|
||||
|
||||
<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;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -1,5 +1,3 @@
|
||||
import exp from 'constants';
|
||||
|
||||
export const mapSeverityToTagColor = severity => {
|
||||
switch (severity) {
|
||||
case 'Sev-0':
|
||||
|
||||
55
src/Pages/Incidents/UpdateIncidentBox/index.tsx
Normal file
55
src/Pages/Incidents/UpdateIncidentBox/index.tsx
Normal 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
138
src/Pages/Incidents/useIncidentApis.tsx
Normal file
138
src/Pages/Incidents/useIncidentApis.tsx
Normal 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;
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
76
src/slices/IncidentSlice.tsx
Normal file
76
src/slices/IncidentSlice.tsx
Normal 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;
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user