diff --git a/src/Pages/Incidents/Dropdowns/index.tsx b/src/Pages/Incidents/Dropdowns/index.tsx
index d3e329a..c8b1054 100644
--- a/src/Pages/Incidents/Dropdowns/index.tsx
+++ b/src/Pages/Incidents/Dropdowns/index.tsx
@@ -3,20 +3,23 @@ import { useSelector, useDispatch } from 'react-redux';
import classnames from 'classnames';
import { Typography } from '@navi/web-ui/lib/primitives';
import { ArrowDownIcon } from '@navi/web-ui/lib/icons';
+import { Button, Drawer } from '@navi/web-ui/lib/primitives';
import { SelectPicker } from '@navi/web-ui/lib/components';
import { SelectPickerOptionProps } from '@navi/web-ui/lib/components/SelectPicker/types';
import useOutsideClick from '@src/services/hooks/useOustideClick';
import { Participant } from '@src/types';
import {
setOpenDialogDuplicate,
- setOpenDialogResolve,
setOpenDialogUpdate,
setOpenDialognotParticipants,
setUpdateDetails,
setSelectedOptions,
} from '@src/slices/IncidentSlice';
import { IncidentPageState } from '@src/types';
+import useClickStream from '@src/services/clickStream';
+import { CLICK_STREAM_EVENT_FACTORY } from '@src/services/clickStream/constants/values';
import AllDailogBox from './AllDailogBox';
+import ResolveForm from '../ResolveForm';
import {
generateOptions,
getCurrentData,
@@ -38,10 +41,13 @@ import styles from '../Incidents.module.scss';
const Dropdowns: FC = () => {
const reduxDispatch = useDispatch();
-
+ const { fireEvent } = useClickStream();
+ const { EVENT_NAME, SCREEN_NAME } = CLICK_STREAM_EVENT_FACTORY;
const incidentData = useSelector(
(state: IncidentPageState) => state.incidentLog.incidentData,
);
+ const currentIncidentId = incidentData?.id;
+ const slackChannel = incidentData?.slackChannel;
const incidentParticipants = useSelector(
(state: IncidentPageState) => state.incidentLog.participantsData,
);
@@ -182,8 +188,18 @@ const Dropdowns: FC = () => {
? selectedOption[0].value
: selectedOption.value;
if (currentState !== selectedvalue) {
+ fireEvent(EVENT_NAME.Houston_Resolve_Incident_initiate, {
+ screen_name: SCREEN_NAME.INCIDENT_PAGE,
+ });
if (updateType === StatusType && selectedvalue === RESOLVE_STATUS) {
- reduxDispatch(setOpenDialogResolve(true));
+ dispatch({
+ type: actionTypes.SET_IS_INCIDENT_RESOLVED,
+ payload: true,
+ });
+ dispatch({
+ type: actionTypes.SET_IS_STATUS_PICKER_OPEN,
+ payload: !state.isStatusPickerOpen,
+ });
} else if (
updateType === StatusType &&
selectedvalue === DUPLICATE_STATUS
@@ -198,7 +214,12 @@ const Dropdowns: FC = () => {
}
}
};
-
+ const handleDrawerClose = () => {
+ dispatch({
+ type: actionTypes.SET_IS_INCIDENT_RESOLVED,
+ payload: false,
+ });
+ };
const handleSevClickOutside = (): void => {
dispatch({
type: actionTypes.SET_IS_SEVERITY_PICKER_OPEN,
@@ -364,6 +385,28 @@ const Dropdowns: FC = () => {
+ {state.isIncidentResolved && (
+
+ {
+ dispatch({
+ type: actionTypes.SET_IS_INCIDENT_RESOLVED,
+ payload: false,
+ });
+ }}
+ >
+
+
+
+ )}
);
};
diff --git a/src/Pages/Incidents/Incidents.module.scss b/src/Pages/Incidents/Incidents.module.scss
index 5e938cb..4acb2bb 100644
--- a/src/Pages/Incidents/Incidents.module.scss
+++ b/src/Pages/Incidents/Incidents.module.scss
@@ -208,3 +208,26 @@
font-size: 13px;
font-weight: 400;
}
+.incident-label {
+ font-size: 13px;
+ font-weight: 400;
+}
+@mixin flex-center {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+.drawer {
+ width: 444px !important;
+ height: 100% !important;
+ > div {
+ padding: 16px 16px 16px 0px !important;
+ }
+}
+.header {
+ padding: 16px !important;
+ border-bottom: 1px solid var(--navi-drawer-divider-color);
+ > span {
+ @include flex-center;
+ }
+}
diff --git a/src/Pages/Incidents/ResolveForm/ResolveForm.module.scss b/src/Pages/Incidents/ResolveForm/ResolveForm.module.scss
new file mode 100644
index 0000000..283ab2a
--- /dev/null
+++ b/src/Pages/Incidents/ResolveForm/ResolveForm.module.scss
@@ -0,0 +1,116 @@
+@import 'mixins';
+
+.Factor-wrapper,
+.jira-wrapper,
+.Team-wrapper,
+.Tag-wrapper,
+.RCA-wrapper {
+ @include wrapper-styles;
+}
+.button-wrapper {
+ @include button-styles;
+ bottom: 0;
+ position: fixed;
+ display: flex;
+ margin-bottom: 24px;
+ border-top: 1px solid var(--navi-drawer-divider-color);
+ padding-top: 20px;
+ width: 100%;
+}
+.resolve {
+ width: 184px;
+ height: 36px;
+ margin-left: 24px;
+}
+.cancel {
+ width: 184px;
+ height: 36px;
+ margin-left: 24px;
+}
+.textarea {
+ min-width: 404px !important;
+ color: var(--navi-color-gray-c2);
+
+ > div > textarea {
+ box-sizing: border-box;
+ }
+}
+.footer-wrapper {
+ position: relative;
+ box-sizing: border-box;
+ width: 100%;
+}
+
+.select-div {
+ position: relative;
+ width: 404px;
+ height: 36px;
+ justify-content: center;
+ align-items: center;
+ border: 1px solid var(--navi-color-gray-border);
+ border-radius: 8px;
+}
+
+.select-team {
+ position: relative;
+ margin-top: 10px;
+ display: flex;
+ justify-content: space-between;
+ padding: 0 10px;
+ cursor: pointer;
+}
+
+.select-picker {
+ border-radius: 4px;
+ position: fixed;
+}
+
+.select-picker-style {
+ position: absolute;
+ cursor: pointer;
+ z-index: 1;
+}
+.select-picker-item {
+ min-width: 404px;
+ margin-top: 5px;
+}
+.arrow-down {
+ width: 8px;
+ height: 8px;
+ display: flex;
+ margin-top: 4px;
+}
+.arrow-dropdown {
+ width: 8px;
+ height: 8px;
+ display: flex;
+ margin-bottom: 2px;
+}
+.autocomplete {
+ height: 36px;
+ width: 384px;
+ justify-content: center;
+ align-items: center;
+ padding: 0px 10px;
+ > div {
+ > div {
+ input {
+ display: none;
+ }
+ }
+ }
+}
+
+.form-wrapper {
+ margin: 16px 4px 0px 20px;
+}
+.factor-label {
+ margin-bottom: 8px;
+}
+.jira {
+ margin-bottom: 8px;
+}
+.add-jira-link {
+ margin-top: 8px;
+ margin-bottom: 8px;
+}
diff --git a/src/Pages/Incidents/ResolveForm/constants.ts b/src/Pages/Incidents/ResolveForm/constants.ts
new file mode 100644
index 0000000..718499f
--- /dev/null
+++ b/src/Pages/Incidents/ResolveForm/constants.ts
@@ -0,0 +1,79 @@
+import { URL_PREFIX } from '../constants';
+
+export const FETCH_RESOLVE_CONFIG = `${URL_PREFIX}/tags/resolve`;
+export const RESOLVE_INCIDENTS = `${URL_PREFIX}/incidents/resolve`;
+
+export interface Team {
+ tag_value_id: number;
+ tag_value_name: string;
+}
+export interface ResolveFormProps {
+ handleDrawerClose: () => void;
+ incidentId: number | null;
+ slackChannel: string;
+}
+export const initialState = {
+ team: [],
+ factors: [],
+ tags: [],
+ openFactorPicker: false,
+ selectedTeam: [],
+ selectedFactor: [],
+ selectedTag: [],
+ rca: '',
+ jiraLinks: [],
+};
+
+export const actionTypes = {
+ SET_TEAM: 'SET_TEAM',
+ SET_FACTORS: 'SET_FACTORS',
+ SET_TAGS: 'SET_TAGS',
+ SET_OPEN_FACTOR_PICKER: 'SET_OPEN_FACTOR_PICKER',
+ SET_SELECTED_TEAM: 'SET_SELECTED_TEAM',
+ SET_SELECTED_FACTOR: 'SET_SELECTED_FACTOR',
+ SET_SELECTED_TAG: 'SET_SELECTED_TAG',
+ SET_RCA: 'SET_RCA',
+ RESET_STATE: 'RESET_STATE',
+ ADD_JIRA_LINK: 'ADD_JIRA_LINK',
+ UPDATE_JIRA_LINK: 'UPDATE_JIRA_LINK',
+};
+export const reducer = (state, action) => {
+ switch (action.type) {
+ case actionTypes.SET_TEAM:
+ return { ...state, team: action.payload };
+ case actionTypes.SET_FACTORS:
+ return { ...state, factors: action.payload };
+ case actionTypes.SET_TAGS:
+ return { ...state, tags: action.payload };
+ case actionTypes.SET_OPEN_FACTOR_PICKER:
+ return { ...state, openFactorPicker: action.payload };
+ case actionTypes.SET_SELECTED_TEAM:
+ return { ...state, selectedTeam: action.payload };
+ case actionTypes.SET_SELECTED_FACTOR:
+ return { ...state, selectedFactor: action.payload };
+ case actionTypes.SET_SELECTED_TAG:
+ return { ...state, selectedTag: action.payload };
+ case actionTypes.SET_RCA:
+ return { ...state, rca: action.payload };
+ case actionTypes.RESET_STATE:
+ return {
+ ...state,
+ selectedTeam: [],
+ selectedFactor: [],
+ selectedTag: [],
+ rca: '',
+ jiraLinks: [''],
+ openFactorPicker: false,
+ };
+ case actionTypes.ADD_JIRA_LINK:
+ return { ...state, jiraLinks: [...state.jiraLinks, ''] };
+ case actionTypes.UPDATE_JIRA_LINK: {
+ const { index, value } = action.payload;
+ const updatedLinks = [...state.jiraLinks];
+ updatedLinks[index] = value;
+ return { ...state, jiraLinks: updatedLinks };
+ }
+ default:
+ return state;
+ }
+};
diff --git a/src/Pages/Incidents/ResolveForm/index.tsx b/src/Pages/Incidents/ResolveForm/index.tsx
new file mode 100644
index 0000000..68d0bed
--- /dev/null
+++ b/src/Pages/Incidents/ResolveForm/index.tsx
@@ -0,0 +1,325 @@
+import React, { useEffect, MutableRefObject } from 'react';
+import {
+ BorderedInput,
+ Button,
+ TextArea,
+ Typography,
+} from '@navi/web-ui/lib/primitives';
+import { toast } from '@navi/web-ui/lib/primitives/Toast';
+import { AddIcon, ArrowDownSolidIcon } from '@navi/web-ui/lib/icons';
+import { AutoComplete, SelectPicker } from '@navi/web-ui/lib/components';
+import { SelectPickerOptionProps } from '@navi/web-ui/lib/components/SelectPicker/types';
+import ErrorBoundary from '@src/components/ErrorBoundary/ErrorBoundary';
+import useOutsideClick from '@src/hooks/useOutsideClick';
+import useClickStream from '@src/services/clickStream';
+import { CLICK_STREAM_EVENT_FACTORY } from '@src/services/clickStream/constants/values';
+import { ApiService } from '@src/services/api';
+import LoadingIcon from '@src/assets/LoadingIcon';
+import { ResolveFormProps } from './constants';
+import {
+ reducer,
+ actionTypes,
+ initialState,
+ FETCH_RESOLVE_CONFIG,
+ RESOLVE_INCIDENTS,
+} from './constants';
+import styles from './ResolveForm.module.scss';
+import useIncidentApis from '../useIncidentApis';
+
+const ResolveForm: React.FC = ({
+ handleDrawerClose,
+ incidentId,
+}) => {
+ const [state, dispatch] = React.useReducer(reducer, initialState);
+ const { startIncidentSearch } = useIncidentApis();
+ const { fireEvent } = useClickStream();
+ const { EVENT_NAME, SCREEN_NAME } = CLICK_STREAM_EVENT_FACTORY;
+ const getData = () => {
+ ApiService.get(FETCH_RESOLVE_CONFIG)
+ .then(response => {
+ const apiResponse = response?.data?.data;
+ if (!apiResponse) {
+ toast.error('Something went wrong. Please try again later');
+ return;
+ }
+ dispatch({
+ type: actionTypes.SET_TEAM,
+ payload: apiResponse.tags[0].tag_values,
+ });
+ dispatch({
+ type: actionTypes.SET_FACTORS,
+ payload: apiResponse.tags[1].tag_values,
+ });
+ dispatch({
+ type: actionTypes.SET_TAGS,
+ payload: apiResponse.tags[2].tag_values,
+ });
+ })
+ .catch(error => {
+ const toastMessage = error?.response?.data?.error?.message
+ ? `${error?.response?.data?.error?.message},`
+ : '';
+ toast.error(toastMessage);
+ });
+ };
+ const business_options = state.team.map(team => ({
+ label: team.tag_value_name,
+ value: team.tag_value_id,
+ }));
+ const factor_options = state.factors.map(factor => ({
+ label: factor.tag_value_name,
+ value: factor.tag_value_id,
+ }));
+ const tag_options = state.tags.map(tag => ({
+ label: tag.tag_value_name,
+ value: tag.tag_value_id,
+ }));
+ const factor_ref = useOutsideClick({
+ callback: () => {
+ dispatch({ type: actionTypes.SET_OPEN_FACTOR_PICKER, payload: false });
+ },
+ }) as MutableRefObject;
+
+ const handleAddJiraLink = (): void => {
+ dispatch({ type: actionTypes.ADD_JIRA_LINK });
+ };
+
+ const handleJiraLinkChange = (index, value): void => {
+ dispatch({ type: actionTypes.UPDATE_JIRA_LINK, payload: { index, value } });
+ };
+
+ const handleFactorClick = (): void => {
+ dispatch({
+ type: actionTypes.SET_OPEN_FACTOR_PICKER,
+ payload: !state.openFactorPicker,
+ });
+ };
+ const handleTeamChange = (
+ val: SelectPickerOptionProps | SelectPickerOptionProps[],
+ ): void => {
+ dispatch({ type: actionTypes.SET_SELECTED_TEAM, payload: val });
+ };
+
+ const handleFactorChange = (
+ val: SelectPickerOptionProps | SelectPickerOptionProps[],
+ ): void => {
+ dispatch({ type: actionTypes.SET_SELECTED_FACTOR, payload: val });
+ dispatch({ type: actionTypes.SET_OPEN_FACTOR_PICKER, payload: false });
+ };
+ const handleTagsChange = (
+ val: SelectPickerOptionProps | SelectPickerOptionProps[],
+ ): void => {
+ dispatch({ type: actionTypes.SET_SELECTED_TAG, payload: val });
+ };
+ const handleRCAchange = (e: React.ChangeEvent): void => {
+ dispatch({ type: actionTypes.SET_RCA, payload: e.target.value });
+ };
+ const clearTags = (): void => {
+ dispatch({ type: actionTypes.SET_SELECTED_TAG, payload: [] });
+ };
+ const clearTeam = (): void => {
+ dispatch({ type: actionTypes.SET_SELECTED_TEAM, payload: [] });
+ };
+
+ const isDisabled = () => {
+ return (
+ state.selectedTeam.length === 0 ||
+ state.selectedFactor.length === 0 ||
+ !(state.rca && state.rca.trim())
+ );
+ };
+
+ const buildRequestPayload = () => {
+ const { selectedTeam, selectedFactor, selectedTag, rca, jiraLinks } = state;
+ const validJiraLinks = jiraLinks
+ .map(link => link.trim())
+ .filter(link => link !== '');
+
+ return {
+ incident_id: incidentId,
+ business_affected: selectedTeam?.map(item => item?.value) || [],
+ contributing_factors: selectedFactor?.value,
+ additional_tags: selectedTag?.map(item => item?.value) || [],
+ rca_summary: rca.trim() || '',
+ jira_links: validJiraLinks || [],
+ };
+ };
+
+ const handleResolveIncident = () => {
+ fireEvent(EVENT_NAME.Houston_Resolve_Incident_submit, {
+ screen_name: SCREEN_NAME.INCIDENT_PAGE,
+ });
+ const endPoint = RESOLVE_INCIDENTS;
+ const requestPayload = buildRequestPayload();
+ toast('Updating ticket. Please wait a moment.', {
+ icon: ,
+ });
+ ApiService.post(endPoint, requestPayload)
+ .then(response => {
+ if (response?.status === 200) {
+ const toastMessage = response?.data?.message
+ ? `${response?.data?.message}`
+ : 'Incident resolved successfully';
+ toast.success(toastMessage);
+ handleDrawerClose();
+ startIncidentSearch(incidentId?.toString() || '');
+ }
+ })
+ .catch(error => {
+ const toastMessage = `${
+ error?.response?.data?.error?.message
+ ? `${error?.response?.data?.error?.message}`
+ : 'Something went wrong. Please try again.'
+ }`;
+ toast.error(toastMessage);
+ startIncidentSearch(incidentId?.toString() || '');
+ });
+ };
+
+ useEffect(() => {
+ getData();
+ }, []);
+ return (
+
+
+
+
+
Business affected
+
+
item?.value)}
+ updateOnChipRemoval={handleTeamChange}
+ updateClearAllCallback={clearTeam}
+ variant="bordered"
+ placeholder=""
+ persistMenuOnSelect
+ containerClassName={styles['autocomplete']}
+ dropdownIconContainerClass={styles['arrow-dropdown']}
+ enableSelectedOptionSorting
+ />
+
+
+
+
+ Contributing factor
+
+
+
+
+ {state.selectedFactor.length === 0
+ ? ''
+ : ` ${state.selectedFactor.label}`}
+
+
+
+
+
+ {state.openFactorPicker && (
+
+
+
+ )}
+
+
+
+
Additional tags (optional)
+
+
item?.value)}
+ updateOnChipRemoval={handleTagsChange}
+ variant="bordered"
+ persistMenuOnSelect
+ containerClassName={styles['autocomplete']}
+ dropdownIconContainerClass={styles['arrow-dropdown']}
+ autoCompletePickerWrapper={styles['autocomplete-picker']}
+ enableSelectedOptionSorting
+ />
+
+
+
+
+ RCA
+
+
+
+
+ Jira tickets (optional)
+
+ {state.jiraLinks?.map((link, index) => (
+ handleJiraLinkChange(index, e.target.value)}
+ hintMsg="Please enter jira link"
+ />
+ ))}
+ }
+ className={styles['add-jira-link']}
+ >
+ Add Jira link
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+export default ResolveForm;
diff --git a/src/Pages/Incidents/ResolveForm/mixins.scss b/src/Pages/Incidents/ResolveForm/mixins.scss
new file mode 100644
index 0000000..90918ee
--- /dev/null
+++ b/src/Pages/Incidents/ResolveForm/mixins.scss
@@ -0,0 +1,8 @@
+@mixin wrapper-styles {
+ align-items: center;
+ margin-bottom: 24px;
+}
+@mixin button-styles {
+ cursor: pointer;
+ height: 36px;
+}
diff --git a/src/Pages/Incidents/constants.ts b/src/Pages/Incidents/constants.ts
index 37249fc..1771958 100644
--- a/src/Pages/Incidents/constants.ts
+++ b/src/Pages/Incidents/constants.ts
@@ -61,6 +61,7 @@ export const actionTypes = {
SET_INCIDENT_NAME: 'SET_INCIDENT_NAME',
SET_ERROR_MSG: 'SET_ERROR_MSG',
RESET_DUPLICATE_DIALOG: 'RESET_DUPLICATE_DIALOG',
+ SET_IS_INCIDENT_RESOLVED: 'SET_IS_INCIDENT_RESOLVED',
};
export const reducer = (state, action) => {
@@ -114,6 +115,8 @@ export const reducer = (state, action) => {
incidentName: '',
errorMsg: '',
};
+ case actionTypes.SET_IS_INCIDENT_RESOLVED:
+ return { ...state, isIncidentResolved: action.payload };
default:
return state;
}
@@ -141,6 +144,7 @@ export const initialState = {
incidentName: '',
openDuplicateDialog: false,
errorMsg: '',
+ isIncidentResolved: false,
};
export const RESOLVE_STATUS = '4';
diff --git a/src/Pages/Incidents/useIncidentApis.tsx b/src/Pages/Incidents/useIncidentApis.tsx
index d501b3c..8a2700a 100644
--- a/src/Pages/Incidents/useIncidentApis.tsx
+++ b/src/Pages/Incidents/useIncidentApis.tsx
@@ -27,7 +27,6 @@ import {
const useIncidentApis = (): useIncidentApiProps => {
const dispatch = useDispatch();
-
const handleApiError = error => {
const toastMessage = `${
error?.response?.data?.error?.message
@@ -102,6 +101,7 @@ const useIncidentApis = (): useIncidentApiProps => {
if (response?.status === 200) {
const toastMessage = `This incident is marked as duplicate of _houston-${duplicateOfId}`;
toast.success(toastMessage);
+ dispatch(setOpenDialogDuplicate(false));
startIncidentSearch(incidentId);
dispatch(setOpenDialogDuplicate(false));
}
diff --git a/src/Pages/JiraDashboard/partials/SearchResultsTable.tsx b/src/Pages/JiraDashboard/partials/SearchResultsTable.tsx
index b41f37f..03e5f36 100644
--- a/src/Pages/JiraDashboard/partials/SearchResultsTable.tsx
+++ b/src/Pages/JiraDashboard/partials/SearchResultsTable.tsx
@@ -149,6 +149,7 @@ const SearchResultsTable: FC = ({
onPageChange={handlePageNumberChange}
onPageSizeChange={handlePageSizeChange}
containerClasses={styles['search-list-table-pagination']}
+ pageSizeOptions={[10, 20, 50]}
/>
}
columnDefs={columnData}
diff --git a/src/services/clickStream/constants/values.ts b/src/services/clickStream/constants/values.ts
index 9e7c944..e0e7aa5 100644
--- a/src/services/clickStream/constants/values.ts
+++ b/src/services/clickStream/constants/values.ts
@@ -22,6 +22,8 @@ const EVENT_NAME = {
Houston_Create_Team_Initiate: 'Houston_Create_Team_Initiate',
Houston_Create_Incident_initiate: 'Houston_Create_Incident_initiate',
Houston_Create_Incident_submit: 'Houston_Create_Incident_submit',
+ Houston_Resolve_Incident_initiate: 'Houston_Resolve_Incident_initiate',
+ Houston_Resolve_Incident_submit: 'Houston_Resolve_Incident_submit',
};
const SCREEN_NAME = {