diff --git a/src/App.js b/src/App.js index b3efaffc..aeb86978 100644 --- a/src/App.js +++ b/src/App.js @@ -24,6 +24,7 @@ import ReassignModalComponent from 'components/ReassignModal/ReassignModalCompon import AddResponderModalComponent from 'components/AddResponderModal/AddResponderModalComponent'; import MergeModalComponent from 'components/MergeModal/MergeModalComponent'; import ConfirmQueryModalComponent from 'components/ConfirmQueryModal/ConfirmQueryModalComponent'; +import IncidentDetailsModalComponent from 'components/IncidentDetailsModal/IncidentDetailsModalComponent'; import { refreshIncidentsAsync as refreshIncidentsAsyncConnected, @@ -238,6 +239,7 @@ const App = ({ + ); diff --git a/src/components/IncidentDetailsModal/IncidentDetailsModalComponent.js b/src/components/IncidentDetailsModal/IncidentDetailsModalComponent.js new file mode 100644 index 00000000..d299be2d --- /dev/null +++ b/src/components/IncidentDetailsModal/IncidentDetailsModalComponent.js @@ -0,0 +1,87 @@ +import { + useState, +} from 'react'; +import { + connect, +} from 'react-redux'; + +import { + Modal, Container, Row, Col, Tabs, Tab, +} from 'react-bootstrap'; + +import { + useTranslation, +} from 'react-i18next'; + +import { + toggleDisplayIncidentDetailsModal as toggleDisplayIncidentDetailsModalConnected, +} from 'redux/incident_details/actions'; + +import AlertsTableComponent from './subcomponents/AlertsTableComponent'; +import IncidentTimelineComponent from './subcomponents/IncidentTimelineComponent'; + +import './IncidentDetailsModalComponent.scss'; + +const IncidentDetailsModalComponent = ({ + toggleDisplayIncidentDetailsModal, incidentDetails, +}) => { + const { + displayIncidentDetailsModal, + } = incidentDetails; + const { + t, + } = useTranslation(); + + const [currentTab, setCurrentTab] = useState('alerts'); + + return ( +
+ + + [#99999] Incident Summary Here + + + + + High Level Details Placeholder + + + Incident Actions Placeholder + + + Incident Fields Placeholder + Notes Placeholder + + + + setCurrentTab(k)}> + + + + + + + + + + + + +
+ ); +}; + +const mapStateToProps = (state) => ({ + incidentDetails: state.incidentDetails, +}); + +const mapDispatchToProps = (dispatch) => ({ + toggleDisplayIncidentDetailsModal: () => dispatch(toggleDisplayIncidentDetailsModalConnected()), +}); + +export default connect(mapStateToProps, mapDispatchToProps)(IncidentDetailsModalComponent); diff --git a/src/components/IncidentDetailsModal/IncidentDetailsModalComponent.scss b/src/components/IncidentDetailsModal/IncidentDetailsModalComponent.scss new file mode 100644 index 00000000..95931145 --- /dev/null +++ b/src/components/IncidentDetailsModal/IncidentDetailsModalComponent.scss @@ -0,0 +1,8 @@ +/* Bootstrap */ +.modal-fullscreen { + min-width: 98%; +} + +.modal-body-fullscreen { + min-height: 89vh; +} diff --git a/src/components/IncidentDetailsModal/subcomponents/AlertsTableComponent.js b/src/components/IncidentDetailsModal/subcomponents/AlertsTableComponent.js new file mode 100644 index 00000000..c322ab45 --- /dev/null +++ b/src/components/IncidentDetailsModal/subcomponents/AlertsTableComponent.js @@ -0,0 +1,27 @@ +import { + connect, +} from 'react-redux'; + +const AlertsTableComponent = ({ + incidentDetails, +}) => { + const { + incident, + } = incidentDetails; + if (incident) console.log('hacking eslint for now'); + return ( +
+

AlertsTableComponent Placeholder

+
+ ); +}; + +const mapStateToProps = (state) => ({ + incidentDetails: state.incidentDetails, +}); + +const mapDispatchToProps = (dispatch) => ({ + placeholder: () => dispatch(() => {}), +}); + +export default connect(mapStateToProps, mapDispatchToProps)(AlertsTableComponent); diff --git a/src/components/IncidentDetailsModal/subcomponents/IncidentTimelineComponent.js b/src/components/IncidentDetailsModal/subcomponents/IncidentTimelineComponent.js new file mode 100644 index 00000000..dd0b0934 --- /dev/null +++ b/src/components/IncidentDetailsModal/subcomponents/IncidentTimelineComponent.js @@ -0,0 +1,27 @@ +import { + connect, +} from 'react-redux'; + +const IncidentTimelineComponent = ({ + incidentDetails, +}) => { + const { + incident, + } = incidentDetails; + if (incident) console.log('hacking eslint for now'); + return ( +
+

IncidentTimelineComponent Placeholder

+
+ ); +}; + +const mapStateToProps = (state) => ({ + incidentDetails: state.incidentDetails, +}); + +const mapDispatchToProps = (dispatch) => ({ + placeholder: () => dispatch(() => {}), +}); + +export default connect(mapStateToProps, mapDispatchToProps)(IncidentTimelineComponent); diff --git a/src/redux/incident_details/actions.js b/src/redux/incident_details/actions.js new file mode 100644 index 00000000..adea13a2 --- /dev/null +++ b/src/redux/incident_details/actions.js @@ -0,0 +1,17 @@ +/* eslint-disable max-len */ +// Define Action Types +export const TOGGLE_DISPLAY_INCIDENT_DETAILS_MODAL_REQUESTED = 'TOGGLE_DISPLAY_INCIDENT_DETAILS_MODAL_REQUESTED'; +export const TOGGLE_DISPLAY_INCIDENT_DETAILS_MODAL_COMPLETED = 'TOGGLE_DISPLAY_INCIDENT_DETAILS_MODAL_COMPLETED'; + +export const UPDATE_INCIDENT_DETAILS_MODAL_REQUESTED = 'UPDATE_INCIDENT_DETAILS_MODAL_REQUESTED'; +export const UPDATE_INCIDENT_DETAILS_MODAL_COMPLETED = 'UPDATE_INCIDENT_DETAILS_MODAL_COMPLETED'; + +// Define Actions +export const toggleDisplayIncidentDetailsModal = () => ({ + type: TOGGLE_DISPLAY_INCIDENT_DETAILS_MODAL_REQUESTED, +}); + +export const updateIncidentDetailsModal = (incident) => ({ + type: UPDATE_INCIDENT_DETAILS_MODAL_REQUESTED, + incident, +}); diff --git a/src/redux/incident_details/reducers.js b/src/redux/incident_details/reducers.js new file mode 100644 index 00000000..c587a46a --- /dev/null +++ b/src/redux/incident_details/reducers.js @@ -0,0 +1,44 @@ +import produce from 'immer'; + +import { + TOGGLE_DISPLAY_INCIDENT_DETAILS_MODAL_REQUESTED, + TOGGLE_DISPLAY_INCIDENT_DETAILS_MODAL_COMPLETED, + UPDATE_INCIDENT_DETAILS_MODAL_REQUESTED, + UPDATE_INCIDENT_DETAILS_MODAL_COMPLETED, +} from './actions'; + +const incidentDetails = produce( + (draft, action) => { + switch (action.type) { + case TOGGLE_DISPLAY_INCIDENT_DETAILS_MODAL_REQUESTED: + draft.status = TOGGLE_DISPLAY_INCIDENT_DETAILS_MODAL_REQUESTED; + break; + + case TOGGLE_DISPLAY_INCIDENT_DETAILS_MODAL_COMPLETED: + draft.displayIncidentDetailsModal = action.displayIncidentDetailsModal; + draft.status = TOGGLE_DISPLAY_INCIDENT_DETAILS_MODAL_COMPLETED; + break; + + case UPDATE_INCIDENT_DETAILS_MODAL_REQUESTED: + draft.status = UPDATE_INCIDENT_DETAILS_MODAL_REQUESTED; + break; + + case UPDATE_INCIDENT_DETAILS_MODAL_COMPLETED: + draft.incident = action.incident; + draft.status = UPDATE_INCIDENT_DETAILS_MODAL_COMPLETED; + break; + + default: + break; + } + }, + { + displayIncidentDetailsModal: true, + incident: null, + status: null, + fetchingData: false, + error: null, + }, +); + +export default incidentDetails; diff --git a/src/redux/incident_details/sagas.js b/src/redux/incident_details/sagas.js new file mode 100644 index 00000000..2bb79824 --- /dev/null +++ b/src/redux/incident_details/sagas.js @@ -0,0 +1,43 @@ +import { + put, select, takeLatest, +} from 'redux-saga/effects'; + +import { + TOGGLE_DISPLAY_INCIDENT_DETAILS_MODAL_REQUESTED, + TOGGLE_DISPLAY_INCIDENT_DETAILS_MODAL_COMPLETED, + UPDATE_INCIDENT_DETAILS_MODAL_REQUESTED, + UPDATE_INCIDENT_DETAILS_MODAL_COMPLETED, +} from './actions'; + +import selectIncidentDetailsData from './selectors'; + +export function* toggleDisplayIncidentDetailsModal() { + yield takeLatest( + TOGGLE_DISPLAY_INCIDENT_DETAILS_MODAL_REQUESTED, + toggleDisplayIncidentDetailsModalImpl, + ); +} + +export function* toggleDisplayIncidentDetailsModalImpl() { + const { + displayIncidentDetailsModal, + } = yield select(selectIncidentDetailsData); + yield put({ + type: TOGGLE_DISPLAY_INCIDENT_DETAILS_MODAL_COMPLETED, + displayIncidentDetailsModal: !displayIncidentDetailsModal, + }); +} + +export function* updateIncidentDetailsModal() { + yield takeLatest(UPDATE_INCIDENT_DETAILS_MODAL_REQUESTED, updateIncidentDetailsModalImpl); +} + +export function* updateIncidentDetailsModalImpl(action) { + const { + incident, + } = action; + yield put({ + type: UPDATE_INCIDENT_DETAILS_MODAL_COMPLETED, + incident, + }); +} diff --git a/src/redux/incident_details/sagas.test.js b/src/redux/incident_details/sagas.test.js new file mode 100644 index 00000000..f1676344 --- /dev/null +++ b/src/redux/incident_details/sagas.test.js @@ -0,0 +1,61 @@ +import { + select, +} from 'redux-saga/effects'; +import { + expectSaga, +} from 'redux-saga-test-plan'; + +import { + TOGGLE_DISPLAY_INCIDENT_DETAILS_MODAL_REQUESTED, + TOGGLE_DISPLAY_INCIDENT_DETAILS_MODAL_COMPLETED, + UPDATE_INCIDENT_DETAILS_MODAL_REQUESTED, + UPDATE_INCIDENT_DETAILS_MODAL_COMPLETED, +} from './actions'; +import incidentDetails from './reducers'; +import selectIncidentDetails from './selectors'; +import { + toggleDisplayIncidentDetailsModal, updateIncidentDetailsModal, +} from './sagas'; + +describe('Sagas: Incident Details', () => { + it('toggleIncidentDetailsModal', () => { + const expectedDisplayIncidentDetailsModal = true; + return expectSaga(toggleDisplayIncidentDetailsModal) + .withReducer(incidentDetails) + .provide([[select(selectIncidentDetails), { displayIncidentDetailsModal: false }]]) + .dispatch({ type: TOGGLE_DISPLAY_INCIDENT_DETAILS_MODAL_REQUESTED }) + .put({ + type: TOGGLE_DISPLAY_INCIDENT_DETAILS_MODAL_COMPLETED, + displayIncidentDetailsModal: expectedDisplayIncidentDetailsModal, + }) + .hasFinalState({ + displayIncidentDetailsModal: expectedDisplayIncidentDetailsModal, + incident: null, + status: TOGGLE_DISPLAY_INCIDENT_DETAILS_MODAL_COMPLETED, + fetchingData: false, + error: null, + }) + .silentRun(); + }); + it('updateIncidentDetailsModal', () => { + const incident = {}; + return expectSaga(updateIncidentDetailsModal) + .withReducer(incidentDetails) + .dispatch({ + type: UPDATE_INCIDENT_DETAILS_MODAL_REQUESTED, + incident, + }) + .put({ + type: UPDATE_INCIDENT_DETAILS_MODAL_COMPLETED, + incident, + }) + .hasFinalState({ + displayIncidentDetailsModal: false, + incident, + status: UPDATE_INCIDENT_DETAILS_MODAL_COMPLETED, + fetchingData: false, + error: null, + }) + .silentRun(); + }); +}); diff --git a/src/redux/incident_details/selectors.js b/src/redux/incident_details/selectors.js new file mode 100644 index 00000000..994a4f64 --- /dev/null +++ b/src/redux/incident_details/selectors.js @@ -0,0 +1,2 @@ +const selectIncidentDetails = (state) => state.incidentDetails; +export default selectIncidentDetails; diff --git a/src/redux/persistence/config.js b/src/redux/persistence/config.js index 0410288d..10163fc6 100644 --- a/src/redux/persistence/config.js +++ b/src/redux/persistence/config.js @@ -9,6 +9,7 @@ export const persistConfig = { 'logEntries', 'querySettings', 'incidentActions', + 'incidentDetails', 'services', 'teams', 'users', diff --git a/src/redux/rootReducer.js b/src/redux/rootReducer.js index 38a390e0..28b56550 100644 --- a/src/redux/rootReducer.js +++ b/src/redux/rootReducer.js @@ -15,6 +15,7 @@ import logEntries from './log_entries/reducers'; import querySettings from './query_settings/reducers'; import incidentTable from './incident_table/reducers'; import incidentActions from './incident_actions/reducers'; +import incidentDetails from './incident_details/reducers'; import services from './services/reducers'; import teams from './teams/reducers'; import priorities from './priorities/reducers'; @@ -33,6 +34,7 @@ export default combineReducers({ querySettings: persistReducer(querySettingsPersistConfig, querySettings), incidentTable, incidentActions, + incidentDetails, services, teams, priorities, diff --git a/src/redux/rootSaga.js b/src/redux/rootSaga.js index 16400ed9..e9124aea 100644 --- a/src/redux/rootSaga.js +++ b/src/redux/rootSaga.js @@ -71,6 +71,11 @@ import { syncWithExternalSystemAsync, } from './incident_actions/sagas'; +import { + toggleDisplayIncidentDetailsModal, + updateIncidentDetailsModal, +} from './incident_details/sagas'; + import { toggleActionAlertsModal, updateActionAlertsModal, } from './action_alerts/sagas'; @@ -187,6 +192,10 @@ export default function* rootSaga() { runCustomIncidentActionAsync(), syncWithExternalSystemAsync(), + // Incident Details + toggleDisplayIncidentDetailsModal(), + updateIncidentDetailsModal(), + // Action Alerts Modal toggleActionAlertsModal(), updateActionAlertsModal(),