diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..249d179
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,18 @@
+**PR Checklist to be followed:**
+
+**Checklist:**
+
+- [ ] Imports to be ordered : React library =>Other libraries => Other Folders => Current Folder
+- [ ] Commented code should be removed(Write TODO if required)
+- [ ] Aliasing of imports is preferred
+- [ ] Don’t hard code URL
+- [ ] No camel casing in css, use hyphens (example: use-like-this)
+- [ ] Maintain consistency while writing dimensions
+- [ ] Avoid console.log
+- [ ] Use global utils if its common for many pages, if not maintain in the folder level
+- [ ] DateFormat should be followed same
+- [ ] Avoid inline styling
+- [ ] Proper TS convention ( don’t use any, instead define interface and use it)
+- [ ] Peer review
+- [ ] Font-family / Basic css shouldn’t be overridden
+- [ ] File changes shouldn’t be more than 20-30, if its more than 25 please create a draft PR and share it so it can reviewed properly.
diff --git a/src/Pages/Dashboard/Dashboard.module.scss b/src/Pages/Dashboard/Dashboard.module.scss
index 3f42bb1..c508183 100644
--- a/src/Pages/Dashboard/Dashboard.module.scss
+++ b/src/Pages/Dashboard/Dashboard.module.scss
@@ -7,3 +7,15 @@
.dashboard-wrapper {
padding: 24px;
}
+.dashboard-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 16px;
+}
+.header-wrapper {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 16px;
+}
diff --git a/src/Pages/Dashboard/constants.ts b/src/Pages/Dashboard/constants.ts
index 0675c03..ba713f3 100644
--- a/src/Pages/Dashboard/constants.ts
+++ b/src/Pages/Dashboard/constants.ts
@@ -14,6 +14,8 @@ export const FETCH_INCIDENTS_DATA = (payload: string): string => {
export const FETCH_FILTER_CONFIG = `${URL_PREFIX}/filters`;
+export const CREATE_INCIDENT = createURL('/houston/create-incident-v2');
+
export const TimeSpan = [
{
label: 'Last 24 hours',
@@ -55,3 +57,68 @@ export const getSeverityColor = (value: string) => {
break;
}
};
+export const LIMITS = {
+ title: { max: 100 },
+ description: { max: 500 },
+};
+export const actionTypes = {
+ SET_DRAWER_OPEN: 'SET_DRAWER_OPEN',
+ SET_TEAMS: 'SET_TEAMS',
+ SET_TITLE: 'SET_TITLE',
+ SET_SEVERITY: 'SET_SEVERITY',
+ SET_SELECTED_SEVERITY: 'SET_SELECTED_SEVERITY',
+ SET_DESCRIPTION: 'SET_DESCRIPTION',
+ SET_SELECTED_TEAM: 'SET_SELECTED_TEAM',
+ SET_OPEN_SELECT: 'SET_OPEN_SELECT',
+ SET_IS_TITLE_VALID: 'SET_IS_TITLE_VALID',
+ SET_IS_DESCRIPTION_VALID: 'SET_IS_DESCRIPTION_VALID',
+ CLEAR_DRAWER: 'CLEAR_DRAWER',
+};
+
+export const initialState = {
+ open: false,
+ teams: [],
+ title: '',
+ severity: [],
+ selectedSeverity: [],
+ description: '',
+ selectedTeam: [],
+ openSelect: false,
+ isTitleValid: true,
+ isDescriptionValid: true,
+};
+export const reducer = (state, action) => {
+ switch (action.type) {
+ case actionTypes.SET_TEAMS:
+ return { ...state, teams: action.payload };
+ case actionTypes.SET_TITLE:
+ return { ...state, title: action.payload };
+ case actionTypes.SET_SEVERITY:
+ return { ...state, severity: action.payload };
+ case actionTypes.SET_SELECTED_SEVERITY:
+ return { ...state, selectedSeverity: action.payload };
+ case actionTypes.SET_DESCRIPTION:
+ return { ...state, description: action.payload };
+ case actionTypes.SET_SELECTED_TEAM:
+ return { ...state, selectedTeam: action.payload };
+ case actionTypes.SET_OPEN_SELECT:
+ return { ...state, openSelect: action.payload };
+ case actionTypes.SET_IS_TITLE_VALID:
+ return { ...state, isTitleValid: action.payload };
+ case actionTypes.SET_IS_DESCRIPTION_VALID:
+ return { ...state, isDescriptionValid: action.payload };
+ case actionTypes.SET_DRAWER_OPEN:
+ return { ...state, open: action.payload };
+ case actionTypes.CLEAR_DRAWER:
+ return {
+ ...state,
+ title: '',
+ selectedSeverity: null,
+ selectedTeam: [],
+ description: '',
+ openSelect: false,
+ };
+ default:
+ return state;
+ }
+};
diff --git a/src/Pages/Dashboard/index.tsx b/src/Pages/Dashboard/index.tsx
index 32f0cb7..b0f0b03 100644
--- a/src/Pages/Dashboard/index.tsx
+++ b/src/Pages/Dashboard/index.tsx
@@ -122,7 +122,11 @@ const Dashboard: FC = () => {
return (
-
+
+
{returnTable()}
);
diff --git a/src/Pages/Dashboard/partials/CreateIncident.module.scss b/src/Pages/Dashboard/partials/CreateIncident.module.scss
new file mode 100644
index 0000000..8c240e3
--- /dev/null
+++ b/src/Pages/Dashboard/partials/CreateIncident.module.scss
@@ -0,0 +1,130 @@
+@mixin flex-center {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+@mixin wrapper-styles {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 24px;
+}
+
+@mixin button-styles {
+ cursor: pointer;
+ height: 36px;
+}
+.name-wrapper,
+.Desc-name,
+.incident-name {
+ color: var(--navi-color-gray-c2);
+}
+.name-wrapper {
+ margin-bottom: 4px;
+}
+.Desc-name {
+ margin-bottom: 160px;
+}
+
+.incident-name {
+ margin-bottom: 65px;
+}
+
+.drawer {
+ width: 550px !important;
+}
+
+.header {
+ padding: 16px !important;
+
+ > span {
+ @include flex-center;
+ }
+}
+
+.textarea {
+ min-width: 350px !important;
+ color: var(--navi-color-gray-c2);
+
+ > div > textarea {
+ box-sizing: border-box;
+ }
+}
+.create-incident {
+ background: var(--navi-color-blue-base);
+ @include button-styles;
+ width: 153px;
+ margin: 20px 0 0 40px;
+}
+
+.create,
+.cancel {
+ @include button-styles;
+ width: 231px;
+}
+
+.footer-wrapper {
+ @include flex-center;
+ margin-top: 380px;
+}
+
+.incident-wrapper,
+.Description-wrapper,
+.severity-picker,
+.Team-wrapper {
+ @include wrapper-styles;
+}
+
+.severity-wrapper {
+ width: calc(100% - 156px);
+ display: flex;
+ justify-content: flex-start;
+ align-items: center;
+ gap: 0 16px;
+ padding: 0 8px;
+ box-sizing: border-box;
+}
+
+.team-div {
+ width: 352px;
+ 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;
+ margin-top: 5px;
+}
+
+.select-picker-style {
+ position: absolute;
+ margin-top: 25px;
+ margin-left: 152px;
+ cursor: pointer;
+ z-index: 1;
+}
+.select-picker-item {
+ min-width: 352px;
+ margin-top: 2px;
+ margin-left: 14px;
+}
+
+.arrow-down {
+ display: inline;
+ width: 8px;
+ height: 8px;
+ margin-top: 4px;
+}
diff --git a/src/Pages/Dashboard/partials/CreateIncident.tsx b/src/Pages/Dashboard/partials/CreateIncident.tsx
new file mode 100644
index 0000000..4bbbde9
--- /dev/null
+++ b/src/Pages/Dashboard/partials/CreateIncident.tsx
@@ -0,0 +1,67 @@
+import { useReducer } from 'react';
+import { Button, Drawer } from '@navi/web-ui/lib/primitives';
+import { AddIcon } from '@navi/web-ui/lib/icons';
+import ErrorBoundary from '@src/components/ErrorBoundary/ErrorBoundary';
+import useClickStream from '@src/services/clickStream';
+import { CLICK_STREAM_EVENT_FACTORY } from '@src/services/clickStream/constants/values';
+import {
+ actionTypes,
+ initialState,
+ reducer,
+} from '@src/Pages/Dashboard/constants';
+import CreateIncidentForm from '@src/Pages/Dashboard/partials/CreateIncidentForm';
+import styles from './CreateIncident.module.scss';
+
+interface CreateIncidentProps {
+ startIncidentSearch: () => void;
+}
+
+const CreateIncident: React.FC = ({
+ startIncidentSearch,
+}) => {
+ const [state, dispatch] = useReducer(reducer, initialState);
+ const { fireEvent } = useClickStream();
+ const { EVENT_NAME, SCREEN_NAME } = CLICK_STREAM_EVENT_FACTORY;
+
+ const handleDrawerOpen = () => {
+ fireEvent(EVENT_NAME.Houston_Create_Incident_initiate, {
+ screen_name: SCREEN_NAME.DASHBOARD_PAGE,
+ });
+ dispatch({ type: actionTypes.SET_DRAWER_OPEN, payload: true });
+ };
+ const handleDrawerClose = () => {
+ dispatch({ type: actionTypes.SET_DRAWER_OPEN, payload: false });
+ };
+
+ return (
+
+
+ }
+ fullWidth
+ onClick={handleDrawerOpen}
+ variant="primary"
+ className={styles['create-incident']}
+ >
+ Create incident
+
+
+
+
+
+
+ );
+};
+
+export default CreateIncident;
diff --git a/src/Pages/Dashboard/partials/CreateIncidentForm.tsx b/src/Pages/Dashboard/partials/CreateIncidentForm.tsx
new file mode 100644
index 0000000..6b2019f
--- /dev/null
+++ b/src/Pages/Dashboard/partials/CreateIncidentForm.tsx
@@ -0,0 +1,289 @@
+import { useReducer, MutableRefObject, useEffect, useState } from 'react';
+import { ArrowDownSolidIcon } from '@navi/web-ui/lib/icons';
+import { SelectPicker } from '@navi/web-ui/lib/components';
+import { toast } from '@navi/web-ui/lib/primitives/Toast';
+import {
+ Button,
+ Chip,
+ TextArea,
+ Typography,
+} from '@navi/web-ui/lib/primitives';
+import { SelectPickerOptionProps } from '@navi/web-ui/lib/components/SelectPicker/types';
+import useOutsideClick from '@src/hooks/useOutsideClick';
+import LoadingIcon from '@src/assets/LoadingIcon';
+import { ApiService } from '@src/services/api';
+
+import {
+ CREATE_INCIDENT,
+ FETCH_FILTER_CONFIG,
+ LIMITS as LIMITS,
+ actionTypes,
+ initialState,
+ reducer,
+} from '@src/Pages/Dashboard/constants';
+import styles from './CreateIncident.module.scss';
+import { Team } from '@src/Pages/Dashboard/type';
+import useClickStream from '@src/services/clickStream';
+import { CLICK_STREAM_EVENT_FACTORY } from '@src/services/clickStream/constants/values';
+
+interface CreateIncidentFormProps {
+ startIncidentSearch: () => void;
+ handleDrawerClose: () => void;
+}
+const CreateIncidentForm: React.FC = ({
+ startIncidentSearch,
+ handleDrawerClose,
+}) => {
+ const [state, dispatch] = useReducer(reducer, initialState);
+ const [loading, setIsLoading] = useState(false);
+ const { fireEvent } = useClickStream();
+ const { EVENT_NAME, SCREEN_NAME } = CLICK_STREAM_EVENT_FACTORY;
+ const ref = useOutsideClick({
+ callback: handleClickOutside,
+ }) as MutableRefObject;
+ function handleClickOutside() {
+ dispatch({ type: actionTypes.SET_OPEN_SELECT, payload: false });
+ }
+ const handleDivClick = () => {
+ dispatch({ type: actionTypes.SET_OPEN_SELECT, payload: true });
+ };
+
+ const validateTitle = (value: string): void => {
+ dispatch({
+ type: actionTypes.SET_IS_TITLE_VALID,
+ payload: value.length <= LIMITS.title.max,
+ });
+ };
+ const userData = JSON.parse(localStorage.getItem('user-data') || '{}');
+ const userEmail = userData?.emailId || [];
+ const validateDescription = (value: string): void => {
+ dispatch({
+ type: actionTypes.SET_IS_DESCRIPTION_VALID,
+ payload: value.length <= LIMITS.description.max,
+ });
+ };
+
+ const handleSeverityChange = (event: React.ChangeEvent) => {
+ dispatch({ type: actionTypes.SET_SELECTED_SEVERITY, payload: event });
+ };
+ const handleTitleChange = (event: React.ChangeEvent) => {
+ const inputValue = event.target.value;
+ dispatch({ type: actionTypes.SET_TITLE, payload: inputValue });
+ validateTitle(inputValue);
+ };
+ const handleTeamChange = (
+ val: SelectPickerOptionProps | SelectPickerOptionProps[],
+ ) => {
+ dispatch({ type: actionTypes.SET_SELECTED_TEAM, payload: val });
+ dispatch({ type: actionTypes.SET_OPEN_SELECT, payload: false });
+ };
+ const handleDescriptionChange = (
+ event: React.ChangeEvent,
+ ) => {
+ const description = event.target.value;
+ dispatch({
+ type: actionTypes.SET_DESCRIPTION,
+ payload: event.target.value,
+ });
+ validateDescription(description);
+ };
+
+ const getData = () => {
+ ApiService.get(FETCH_FILTER_CONFIG)
+ .then(response => {
+ const apiResponse = response?.data?.data;
+ if (!apiResponse) {
+ toast.error('Something went wrong. Please try again later');
+ return;
+ }
+ dispatch({
+ type: actionTypes.SET_TEAMS,
+ payload: apiResponse[2]?.filter_data,
+ });
+ dispatch({
+ type: actionTypes.SET_SEVERITY,
+ payload: apiResponse[1]?.filter_data || [],
+ });
+ dispatch({
+ type: actionTypes.SET_SELECTED_SEVERITY,
+ payload: apiResponse[1]?.filter_data.map(item => item.label) || [],
+ });
+ })
+ .catch(error => {
+ const toastMessage = error?.response?.data?.error?.message
+ ? `${error?.response?.data?.error?.message},`
+ : '';
+ toast.error(toastMessage);
+ });
+ };
+ useEffect(() => {
+ getData();
+ }, []);
+
+ const getTeams = (teamsState: Team[]) => {
+ return teamsState.map(team => ({
+ label: team.label,
+ value: team.value,
+ }));
+ };
+ const clearDrawer = () => {
+ dispatch({ type: actionTypes.CLEAR_DRAWER });
+ handleDrawerClose();
+ };
+ const isDisabled = () => {
+ return (
+ !state.isTitleValid ||
+ !state.isDescriptionValid ||
+ !state.title ||
+ !state.selectedSeverity ||
+ !state.selectedTeam ||
+ !state.description
+ );
+ };
+
+ const createIncidentHandler = () => {
+ toast('Creating incident. Please wait a moment.', {
+ icon: ,
+ });
+
+ fireEvent(EVENT_NAME.Houston_Create_Incident_submit, {
+ screen_name: SCREEN_NAME.DASHBOARD_PAGE,
+ });
+
+ ApiService.post(CREATE_INCIDENT, {
+ title: state.title,
+ severity: state.selectedSeverity.value,
+ type: state.selectedTeam.value,
+ description: state.description,
+ createdBy: userEmail,
+ })
+ .then(response => {
+ setIsLoading(false);
+ toast.success('Incident created successfully');
+ clearDrawer();
+ startIncidentSearch();
+ })
+ .catch(error => {
+ const toastMessage = `${
+ error?.response?.data?.error?.message
+ ? `${error?.response?.data?.error?.message}`
+ : 'Something went wrong. Please try again later'
+ }`;
+ toast.error(toastMessage);
+ handleDrawerClose();
+ });
+ };
+
+ return (
+
+
+
+
Choose severity
+
+ {state.severity.map((severityOption: SelectPickerOptionProps) => (
+ handleSeverityChange(severityOption as any)}
+ selected={severityOption === state.selectedSeverity}
+ />
+ ))}
+
+
+
+
Choose team
+
+
+
+
+ {state.selectedTeam.length === 0
+ ? 'Select team'
+ : ` ${state.selectedTeam.label}`}
+
+
+
+
+
+ {state.openSelect && (
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default CreateIncidentForm;
diff --git a/src/Pages/Dashboard/partials/DashboardHeader.module.scss b/src/Pages/Dashboard/partials/DashboardHeader.module.scss
index 5f359b8..798342f 100644
--- a/src/Pages/Dashboard/partials/DashboardHeader.module.scss
+++ b/src/Pages/Dashboard/partials/DashboardHeader.module.scss
@@ -143,4 +143,6 @@
.filter {
margin-top: 20px;
+ margin-left: 30px;
+ padding-left: 10px;
}
diff --git a/src/Pages/Dashboard/partials/DashboardHeader.tsx b/src/Pages/Dashboard/partials/DashboardHeader.tsx
index a85c005..cbc2437 100644
--- a/src/Pages/Dashboard/partials/DashboardHeader.tsx
+++ b/src/Pages/Dashboard/partials/DashboardHeader.tsx
@@ -7,12 +7,17 @@ import { DashboardHeaderConstants } from '../constants';
import styles from './DashboardHeader.module.scss';
import SmartSearch from './SmartSearch';
import Date from './Date';
+import CreateIncident from './CreateIncident';
interface DashboardHeaderProps {
fetchIncidentData: (payload: any) => void;
+ startIncidentSearch: () => void;
}
-const DashboardHeader: FC = ({ fetchIncidentData }) => {
+const DashboardHeader: FC = ({
+ fetchIncidentData,
+ startIncidentSearch,
+}) => {
const { title } = DashboardHeaderConstants;
const [searchValue, setSearchValue] = useState('');
const navigate = useNavigate();
@@ -45,12 +50,17 @@ const DashboardHeader: FC = ({ fetchIncidentData }) => {
setSearchValue={setSearchValue}
updateURLAndFetchData={updateURLAndFetchData}
/>
+
+
+
-
diff --git a/src/Pages/Dashboard/type.ts b/src/Pages/Dashboard/type.ts
index 536cb29..884830f 100644
--- a/src/Pages/Dashboard/type.ts
+++ b/src/Pages/Dashboard/type.ts
@@ -6,3 +6,7 @@ export interface filterItem {
filter_data: any;
selection_config: SelectionType;
}
+export interface Team {
+ label: string;
+ value: string;
+}
diff --git a/src/hooks/useOutsideClick.tsx b/src/hooks/useOutsideClick.tsx
new file mode 100644
index 0000000..c98e8e3
--- /dev/null
+++ b/src/hooks/useOutsideClick.tsx
@@ -0,0 +1,27 @@
+import React from 'react';
+
+interface useOutsideClickProps {
+ callback: Function;
+}
+
+const useOutsideClick = ({ callback }: useOutsideClickProps) => {
+ const ref = React.useRef();
+
+ React.useEffect(() => {
+ const handleClick = (event: any) => {
+ if (ref.current && !ref.current.contains(event.target)) {
+ callback();
+ }
+ };
+
+ document.addEventListener('click', handleClick, true);
+
+ return () => {
+ document.removeEventListener('click', handleClick, true);
+ };
+ }, [ref]);
+
+ return ref;
+};
+
+export default useOutsideClick;
diff --git a/src/services/clickStream/constants/values.ts b/src/services/clickStream/constants/values.ts
index 1822049..d175823 100644
--- a/src/services/clickStream/constants/values.ts
+++ b/src/services/clickStream/constants/values.ts
@@ -18,6 +18,8 @@ const EVENT_NAME = {
Houston_Add_Oncall: 'Houston_Add_Oncall',
Houston_Add_Pseconcall: 'Houston_Add_Pseconcall',
Houston_Create_Team_Initiate: 'Houston_Create_Team_Initiate',
+ Houston_Create_Incident_initiate: 'Houston_Create_Incident_initiate',
+ Houston_Create_Incident_submit: 'Houston_Create_Incident_submit',
};
const SCREEN_NAME = {