TP-24975: added the drawer view for the incident rundown
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Houston</title>
|
||||
<title>Houston UI</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { FC } from 'react';
|
||||
import { FC, useState, useRef } from 'react';
|
||||
import cx from 'classnames';
|
||||
|
||||
import { AgTable, Pagination } from '@navi/web-ui/lib/components';
|
||||
import { Tag } from '@navi/web-ui/lib/primitives';
|
||||
import { Tag, Drawer } from '@navi/web-ui/lib/primitives';
|
||||
|
||||
import { returnFormattedDate } from '@src/services/globalUtils';
|
||||
import DrawerMode from '@src/Pages/Incidents/DrawerMode/index';
|
||||
|
||||
import styles from '../SearchResultsTable.module.scss';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { getSeverityColor } from '../constants';
|
||||
@@ -31,7 +33,8 @@ const SearchResultsTable: FC<SearchResultTableProps> = ({
|
||||
handlePageSizeChange,
|
||||
}) => {
|
||||
const { pageNumber, totalElements } = pageDetails;
|
||||
|
||||
const [drawerState, setDrawerState] = useState<boolean>(false);
|
||||
const incidentData = useRef<any>({});
|
||||
const navigate = useNavigate();
|
||||
|
||||
const rowData = currentPageData?.map((item: any, ind: number) => {
|
||||
@@ -52,19 +55,16 @@ const SearchResultsTable: FC<SearchResultTableProps> = ({
|
||||
createdAt: returnFormattedDate(item?.createdAt),
|
||||
updatedBy: item?.updatedBy,
|
||||
updatedAt: returnFormattedDate(item?.updatedAt),
|
||||
slackChannel: item?.slackChannel,
|
||||
};
|
||||
});
|
||||
|
||||
const columnData = [
|
||||
{
|
||||
field: 'sNo',
|
||||
suppressSizeToFit: true,
|
||||
width: 90,
|
||||
suppressMovable: true,
|
||||
},
|
||||
{
|
||||
field: 'id',
|
||||
headerName: 'Incident Id',
|
||||
suppressSizeToFit: true,
|
||||
width: 120,
|
||||
suppressMovable: true,
|
||||
},
|
||||
{
|
||||
@@ -118,10 +118,23 @@ const SearchResultsTable: FC<SearchResultTableProps> = ({
|
||||
if (params?.api?.sizeColumnsToFit) params.api.sizeColumnsToFit();
|
||||
};
|
||||
|
||||
const handleRowClick = (event: any) => {
|
||||
if (event?.data) {
|
||||
navigate(`/incident/${event.data?.id}`);
|
||||
}
|
||||
// const handleRowClick = (event: any) => {
|
||||
// if (event?.data) {
|
||||
// navigate(`/incident/${event.data?.id}`);
|
||||
// }
|
||||
// };
|
||||
|
||||
const handleCloseDrawer = () => {
|
||||
incidentData.current = {};
|
||||
setDrawerState(false);
|
||||
};
|
||||
|
||||
const handleOpenDrawer = rowData => {
|
||||
incidentData.current = {
|
||||
incidentId: rowData?.data?.id,
|
||||
slackChannel: rowData?.data?.slackChannel,
|
||||
};
|
||||
setDrawerState(true);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -152,10 +165,21 @@ const SearchResultsTable: FC<SearchResultTableProps> = ({
|
||||
suppressCellFocus
|
||||
defaultColDef={defaultColDef}
|
||||
onGridSizeChanged={onGridSizeChanged}
|
||||
onRowClicked={handleRowClick}
|
||||
onRowClicked={handleOpenDrawer}
|
||||
suppressColumnMoveAnimation
|
||||
detailRowAutoHeight={true}
|
||||
/>
|
||||
<Drawer
|
||||
open={drawerState}
|
||||
onClose={handleCloseDrawer}
|
||||
className={styles['drawer-wrapper']}
|
||||
headerText="Incident Rundown"
|
||||
>
|
||||
<DrawerMode
|
||||
incidentId={incidentData?.current?.incidentId}
|
||||
slackChannel={incidentData?.current?.slackChannel}
|
||||
/>
|
||||
</Drawer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
45
src/Pages/Incidents/DrawerMode/DrawerMode.module.scss
Normal file
45
src/Pages/Incidents/DrawerMode/DrawerMode.module.scss
Normal file
@@ -0,0 +1,45 @@
|
||||
.severity-details-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0 8px;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.content-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0 4px;
|
||||
}
|
||||
|
||||
.description-details {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0 4px;
|
||||
margin: 12px 0 16px 0;
|
||||
|
||||
.description {
|
||||
margin-top: -2px;
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
231
src/Pages/Incidents/DrawerMode/index.tsx
Normal file
231
src/Pages/Incidents/DrawerMode/index.tsx
Normal file
@@ -0,0 +1,231 @@
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
|
||||
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 {
|
||||
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);
|
||||
};
|
||||
|
||||
const fetchIncidentDetails = (): void => {
|
||||
const endPoint = FETCH_INCIDENT_DATA(incidentId);
|
||||
ApiService.get(endPoint)
|
||||
.then(response => {
|
||||
setIncidentDetails(response?.data?.data);
|
||||
initFilters(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(incidentDetails).length &&
|
||||
Object.keys(headerData) &&
|
||||
incidentParticipants.length
|
||||
) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [incidentDetails, headerData, incidentParticipants]);
|
||||
|
||||
const updateIncident = () => {
|
||||
const endPoint = UPDATE_INCIDENT;
|
||||
ApiService.post(endPoint, {
|
||||
id: incidentId,
|
||||
teamId: team?.value,
|
||||
status: status?.value,
|
||||
severityId: severity?.value,
|
||||
})
|
||||
.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();
|
||||
}}
|
||||
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();
|
||||
}}
|
||||
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();
|
||||
}}
|
||||
isSingleSelect
|
||||
filterClass={styles['filter-wrapper']}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const returnContent = () => {
|
||||
return (
|
||||
<div>
|
||||
<div className={styles['content-info']}>
|
||||
<Typography variant="h4">{IncidentConstants.title}:</Typography>
|
||||
<Typography variant="p3">{incidentDetails?.title}</Typography>
|
||||
</div>
|
||||
<div className={styles['description-details']}>
|
||||
<Typography variant="h4">{IncidentConstants.description}:</Typography>
|
||||
<Typography variant="p3" className={styles['description']}>
|
||||
{incidentDetails?.description}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const returnParticipants = () => {
|
||||
return 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>
|
||||
));
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return <LoadingIcon size="md" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{returnContent()}
|
||||
<hr className={commonStyles['divider']} />
|
||||
{returnHeaderDetails()}
|
||||
<hr className={commonStyles['divider']} />
|
||||
<Typography variant="h4">Participants:</Typography>
|
||||
{returnParticipants()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DrawerMode;
|
||||
@@ -11,12 +11,12 @@ export const IncidentConstants = {
|
||||
team: 'Team',
|
||||
};
|
||||
|
||||
export const FETCH_SINGLE_INCIDENT_DATA = (payload: any): string => {
|
||||
export const FETCH_INCIDENT_DATA = (payload: any): string => {
|
||||
return `${window?.config?.BASE_API_URL}/incidents/${payload}`;
|
||||
};
|
||||
|
||||
export const FETCH_PARTICIPANTS_DATA = (payload: string): string => {
|
||||
return `${window?.config?.BASE_API_URL}/users?channelId=${payload}`;
|
||||
return `${window?.config?.BASE_API_URL}/users?channel_id=${payload}`;
|
||||
};
|
||||
|
||||
export const FETCH_HEADER_DETAILS = `${window?.config?.BASE_API_URL}/incidents/header`;
|
||||
|
||||
@@ -8,7 +8,7 @@ import Content from './Content';
|
||||
import Header from './Header';
|
||||
import MetaInfo from './MetaInfo';
|
||||
|
||||
import { FETCH_SINGLE_INCIDENT_DATA, IncidentConstants } from './constants';
|
||||
import { FETCH_INCIDENT_DATA, IncidentConstants } from './constants';
|
||||
import { ApiService } from '@src/services/api';
|
||||
|
||||
import styles from './Incidents.module.scss';
|
||||
@@ -24,7 +24,7 @@ const Incident: FC = () => {
|
||||
const incidentId = IncidentMatch?.params?.incidentId || '';
|
||||
|
||||
const startIncidentSearch = (): void => {
|
||||
const endPoint = FETCH_SINGLE_INCIDENT_DATA(incidentId);
|
||||
const endPoint = FETCH_INCIDENT_DATA(incidentId);
|
||||
ApiService.get(endPoint)
|
||||
.then(response => {
|
||||
setData(response?.data?.data);
|
||||
|
||||
@@ -9,12 +9,15 @@ export class ApiService {
|
||||
public static getInstance(): ApiService {
|
||||
if (!ApiService.instance) {
|
||||
ApiService.instance = new ApiService();
|
||||
// TODO: Uncomment this backend is sending Access-Control-Allow-Headers: x-session-token
|
||||
// ApiService.instance.service.interceptors.request.use((req: any) => {
|
||||
// req.headers['X-Session-Token'] =
|
||||
// localStorage.getItem('react-token') || '';
|
||||
// return req;
|
||||
// });
|
||||
const userData = localStorage.getItem('user-data');
|
||||
ApiService.instance.service.interceptors.request.use((req: any) => {
|
||||
req.headers['X-Session-Token'] =
|
||||
localStorage.getItem('react-token') || '';
|
||||
req.headers['X-User-Email'] = userData
|
||||
? JSON.parse(userData)?.emailId
|
||||
: null;
|
||||
return req;
|
||||
});
|
||||
}
|
||||
|
||||
return ApiService.instance;
|
||||
|
||||
Reference in New Issue
Block a user