diff --git a/client/src/scenes/UploadDLockData/components/FileUpload.js b/client/src/scenes/UploadDLockData/components/FileUpload.js
index e0f9b23..30bd4f8 100644
--- a/client/src/scenes/UploadDLockData/components/FileUpload.js
+++ b/client/src/scenes/UploadDLockData/components/FileUpload.js
@@ -4,7 +4,7 @@ import { Form } from 'semantic-ui-react';
import UnknownMapping from './UnknownMapping';
-import { uploadDoorLockData, fetchMappings } from '../../../store/actions';
+import { uploadDoorLockData, fetchMappings, checkProcessing } from '../../../store/actions';
class FileUpload extends Component {
constructor(props) {
@@ -20,8 +20,9 @@ class FileUpload extends Component {
}
componentDidMount() {
- const { fetchMappings } = this.props;
+ const { fetchMappings, checkProcessing } = this.props;
fetchMappings();
+ checkProcessing();
}
componentWillReceiveProps(nextProps, nextContext) {
@@ -36,6 +37,7 @@ class FileUpload extends Component {
this.setState({unknownMappings: filteredUnknownMappings});
}
+
extractMappingFromFileName(fileName) {
const contentBetweenBracketsRegex = /\[(.*?)\]/;
const rawContent = fileName.match(contentBetweenBracketsRegex)[1];
@@ -52,7 +54,11 @@ class FileUpload extends Component {
const { existingMappings } = mappings;
- return existingMappings.find(mapping => (mapping.officeSlug === officeSlug) && (mapping.resourceSlug === resourceSlug));
+ if (existingMappings && Array.isArray(existingMappings)){
+ return existingMappings.find(mapping => (mapping.officeSlug === officeSlug) && (mapping.resourceSlug === resourceSlug));
+ }else{
+ return false;
+ }
}
onFileChange(event) {
@@ -85,14 +91,24 @@ class FileUpload extends Component {
}
};
+ refreshProcessingStatus = (event) => {
+ event.preventDefault();
+ const { checkProcessing } = this.props;
+ checkProcessing();
+ };
+
render() {
- const { pendingUpload } = this.props;
+ const { pendingUpload, pendingStatus, processingStatus } = this.props;
const { unknownMappings, files } = this.state;
- const uploadDisabled = pendingUpload || unknownMappings.length > 0 || !files;
+ const processing = processingStatus && processingStatus.processing ? processingStatus.processing : false;
+
+ const uploadDisabled = pendingStatus || processing || pendingUpload || unknownMappings.length > 0 || !files;
return (
+ {processing &&
Processing in progress. Please try again in a few minutes
}
+ {processing &&
Refresh}
({
pendingMappings: state.mappingsData.pending,
mappings: state.mappingsData.result,
addedMapping: state.addMapping.result,
+ pendingStatus: state.checkProcessing.pending,
+ processingStatus: state.checkProcessing.result,
});
const mapDispatchToProps = (dispatch) => ({
uploadDoorLockData: (doorLockDataFiles) => uploadDoorLockData(dispatch, doorLockDataFiles),
fetchMappings: () => fetchMappings(dispatch),
+ checkProcessing: () => checkProcessing(dispatch),
});
export default connect(mapStateToProps, mapDispatchToProps)(FileUpload);
diff --git a/client/src/scenes/UploadDLockData/components/UploadResults.js b/client/src/scenes/UploadDLockData/components/UploadResults.js
index 6a6bbfa..c414a80 100644
--- a/client/src/scenes/UploadDLockData/components/UploadResults.js
+++ b/client/src/scenes/UploadDLockData/components/UploadResults.js
@@ -2,7 +2,20 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Loader, Message, Tab, Label, Menu } from 'semantic-ui-react';
+import { checkProcessing } from '../../../store/actions';
+
class UploadResults extends Component {
+
+ componentDidUpdate(prevProps, prevState, snapshot) {
+ const previousPending = prevProps.pending ? prevProps.pending : false;
+ const newPending = this.props.pending ? this.props.pending : false;
+
+ if (previousPending && !newPending){
+ const { checkProcessing } = this.props;
+ checkProcessing();
+ }
+ }
+
render(){
const {pending, result, error} = this.props;
@@ -80,7 +93,10 @@ const mapStateToProps = (state) => ({
pending: state.doorLockData.pending,
result: state.doorLockData.result,
error: state.doorLockData.error,
-
});
-export default connect(mapStateToProps)(UploadResults);
+const mapDispatchToProps = (dispatch) => ({
+ checkProcessing: () => checkProcessing(dispatch),
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(UploadResults);
diff --git a/client/src/store/actions/integrationActions.js b/client/src/store/actions/integrationActions.js
index 79a9068..3cb1ef1 100644
--- a/client/src/store/actions/integrationActions.js
+++ b/client/src/store/actions/integrationActions.js
@@ -17,6 +17,9 @@ import {
ADD_FEES_TO_ORD_PENDING,
ADD_FEES_TO_ORD_SUCCESS,
ADD_FEES_TO_ORD_FAILED,
+ CHECK_PROCESSING_PENDING,
+ CHECK_PROCESSING_SUCCESS,
+ CHECK_PROCESSING_FAILED,
} from '../constants';
import API from '../../utilities/api';
@@ -95,3 +98,14 @@ export const addFeesToOrd = (dispatch, dateRange, memberIds) => {
dispatch({type: ADD_FEES_TO_ORD_FAILED, payload: error.response});
});
};
+
+export const checkProcessing = (dispatch) => {
+ dispatch({type: CHECK_PROCESSING_PENDING});
+ API.get('integration/processing')
+ .then(response => {
+ dispatch({type: CHECK_PROCESSING_SUCCESS, payload: response.data});
+ })
+ .catch(error => {
+ dispatch({type: CHECK_PROCESSING_FAILED, payload: error.response});
+ });
+};
diff --git a/client/src/store/constants.js b/client/src/store/constants.js
index 6d67aef..f2c950a 100644
--- a/client/src/store/constants.js
+++ b/client/src/store/constants.js
@@ -25,3 +25,7 @@ export const FETCH_MEMBER_INCIDENTS_FAILED = 'FETCH_MEMBER_INCIDENTS_FAILED';
export const ADD_FEES_TO_ORD_PENDING = 'ADD_FEES_TO_ORD_PENDING';
export const ADD_FEES_TO_ORD_SUCCESS = 'ADD_FEES_TO_ORD_SUCCESS';
export const ADD_FEES_TO_ORD_FAILED = 'ADD_FEES_TO_ORD_FAILED';
+
+export const CHECK_PROCESSING_PENDING = 'CHECK_PROCESSING_PENDING';
+export const CHECK_PROCESSING_SUCCESS = 'CHECK_PROCESSING_SUCCESS';
+export const CHECK_PROCESSING_FAILED = 'CHECK_PROCESSING_FAILED';
diff --git a/client/src/store/reducers/checkProcessingReducer.js b/client/src/store/reducers/checkProcessingReducer.js
new file mode 100644
index 0000000..ad649de
--- /dev/null
+++ b/client/src/store/reducers/checkProcessingReducer.js
@@ -0,0 +1,38 @@
+import {
+ CHECK_PROCESSING_PENDING,
+ CHECK_PROCESSING_SUCCESS,
+ CHECK_PROCESSING_FAILED,
+} from '../constants';
+
+const initialState = {
+ pending: false,
+ result: null,
+ error: null,
+};
+
+export const checkProcessing = (state, action) => {
+ state = state || initialState;
+ action = action || {};
+
+ switch(action.type){
+ case CHECK_PROCESSING_PENDING:
+ return Object.assign({}, state, {
+ pending: true,
+ error: null,
+ });
+ case CHECK_PROCESSING_SUCCESS:
+ return Object.assign({}, state, {
+ pending: false,
+ result: action.payload,
+ error: null,
+ });
+ case CHECK_PROCESSING_FAILED:
+ return Object.assign({}, state, {
+ pending: false,
+ result: {},
+ error: action.payload,
+ });
+ default:
+ return state;
+ }
+};
diff --git a/client/src/store/reducers/index.js b/client/src/store/reducers/index.js
index 3eb9e9f..27640e6 100644
--- a/client/src/store/reducers/index.js
+++ b/client/src/store/reducers/index.js
@@ -7,6 +7,7 @@ import { incidentsReport } from './incidentsReportReducer';
import { membersList } from './membersListReducer';
import { memberIncidents} from './memberIncidentsReducer';
import { addFeesStatus } from './addFeesToOrdReducer';
+import { checkProcessing } from './checkProcessingReducer';
export const rootReducer = combineReducers({
doorLockData,
@@ -16,5 +17,6 @@ export const rootReducer = combineReducers({
membersList,
memberIncidents,
addFeesStatus,
+ checkProcessing,
});
diff --git a/constants/constants.js b/constants/constants.js
index 1d06ee2..95fdd47 100644
--- a/constants/constants.js
+++ b/constants/constants.js
@@ -62,6 +62,7 @@ const officeRnDAPIErrors = {
FAILED_TO_ADD_FEES: 'Failed to add fees in ORD',
};
const integrationServiceErrors = {
+ PROCESSING_TRY_AGAIN: 'Incident calculations are in progress. Please try again in a few minutes',
IMPORT_SUCCESSFUL_CALCULATION_FAILED: 'Import succeeded but fetching reservations and calculating charges failed',
FAILED_TO_SAVE_BOOKINGS: 'Failed to save booking reservations',
FAILED_TO_SAVE_DOOR_LOCK_ENTRIES: 'Failed to save door lock entries',
diff --git a/controllers/doorLock.js b/controllers/doorLock.js
index a75cf2c..6861308 100644
--- a/controllers/doorLock.js
+++ b/controllers/doorLock.js
@@ -5,96 +5,126 @@ const { fetchAllBookings, writeBookingReservation } = require('../services/offic
const { calculateDoorLockCharges } = require('../services/integration/doorLockCharges');
const { integrationServiceErrors } = require('../constants/constants');
const { checkBookingChanges } = require('../services/integration/checkBookingChange');
+const { checkIfProcessing, setStartProcessing, setDoneProcessing } = require('../services/integration/processingStatus');
const IncomingForm = require('formidable').IncomingForm;
const uploadDoorLockData = (req, res) => {
- const form = new IncomingForm();
- const fileParsers = [];
+ checkIfProcessing()
+ .then((processing) => {
+ if (processing){
+ res.status(500).send(integrationServiceErrors.PROCESSING_TRY_AGAIN);
+ }else{
+ const form = new IncomingForm();
+ const fileParsers = [];
- form.on('file', (field, file) => {
- if (file && file.type === 'text/csv') {
- fileParsers.push(parseDoorLockDataFile(file));
- }
- });
-
- form.on('end', () => {
- Promise.all(fileParsers)
- .then((parserResults) => {
- const parsedData = [];
- const parserErrors = [];
- const unknownMembers = [];
-
- parserResults.forEach((parserResult) => {
- parsedData.push(...parserResult.parsedData);
- parserErrors.push(...parserResult.errors);
-
- parserResult.unknownMembers.forEach((newUnknownMember) => {
- // Check if member is already labeled as unknown in different file
- if (!unknownMembers.find((unknownMember) => unknownMember.details === newUnknownMember.details)){
- unknownMembers.push(newUnknownMember);
- }
- });
+ form.on('file', (field, file) => {
+ if (file && file.type === 'text/csv') {
+ fileParsers.push(parseDoorLockDataFile(file));
+ }
});
- const asyncWriteJobs = [];
+ form.on('end', () => {
+ Promise.all(fileParsers)
+ .then((parserResults) => {
+ const parsedData = [];
+ const parserErrors = [];
+ const unknownMembers = [];
- parsedData.forEach((entry) => asyncWriteJobs.push(writeDoorLockEvent(entry)));
+ parserResults.forEach((parserResult) => {
+ parsedData.push(...parserResult.parsedData);
+ parserErrors.push(...parserResult.errors);
- Promise.all(asyncWriteJobs)
- .then(() => {
- checkBookingChanges()
- .then(() => {
- calculateDoorLockCharges()
- .then(() => {
- res.json({
- parsedData,
- parserErrors,
- unknownMembers
- });
- })
- .catch((error) => {
- console.log('Error : ', error);
- res.status(500).send(integrationServiceErrors.IMPORT_SUCCESSFUL_CALCULATION_FAILED);
- });
- })
- .catch((error) => {
- console.log('Error : ', error);
- res.status(500).send(integrationServiceErrors.IMPORT_SUCCESSFUL_CALCULATION_FAILED);
+ parserResult.unknownMembers.forEach((newUnknownMember) => {
+ // Check if member is already labeled as unknown in different file
+ if (!unknownMembers.find((unknownMember) => unknownMember.details === newUnknownMember.details)){
+ unknownMembers.push(newUnknownMember);
+ }
+ });
});
- })
- .catch((error) => {
- console.log(integrationServiceErrors.FAILED_TO_SAVE_DOOR_LOCK_ENTRIES);
- console.log(error);
- res.status(500).send(integrationServiceErrors.FAILED_TO_SAVE_DATA_GENERIC);
- });
- /*fetchAllBookings()
- .then((bookingEntries) => {
- const asyncJobs = [];
- bookingEntries.forEach((bookingEntry) => asyncJobs.push(writeBookingReservation(bookingEntry)));
+ const asyncWriteJobs = [];
- Promise.all(asyncJobs)
- .then(() => {
- calculateDoorLockCharges();
- })
- .catch((error) => {
- console.log('Error updating booking reservations : ');
- console.log(error);
- })
- })
- .catch((error) => {
- console.log(integrationServiceErrors.FAILED_TO_SAVE_BOOKINGS);
- console.log(error);
- return;
- });
- */
- })
- .catch((error) => {
- res.status(500).send(error);
- });
- });
- form.parse(req);
+ parsedData.forEach((entry) => asyncWriteJobs.push(writeDoorLockEvent(entry)));
+
+ Promise.all(asyncWriteJobs)
+ .then(() => {
+ res.json({
+ parsedData,
+ parserErrors,
+ unknownMembers
+ });
+
+ setStartProcessing()
+ .then(() => {
+ checkBookingChanges()
+ .then(() => {
+ calculateDoorLockCharges()
+ .then(() => {
+ setDoneProcessing()
+ .catch((error) => {
+ console.log('Error in process done indication');
+ console.log(error);
+ })
+ })
+ .catch((error) => {
+ console.log('Error : ', error);
+ setDoneProcessing();
+ // res.status(500).send(integrationServiceErrors.IMPORT_SUCCESSFUL_CALCULATION_FAILED);
+ });
+
+ })
+ .catch((error) => {
+ console.log('Error : ', error);
+ setDoneProcessing();
+ // res.status(500).send(integrationServiceErrors.IMPORT_SUCCESSFUL_CALCULATION_FAILED);
+ });
+ })
+ .catch((error) => {
+ console.log('Error in processing indicator');
+ console.log(error);
+ setDoneProcessing();
+ });
+ })
+ .catch((error) => {
+ console.log(integrationServiceErrors.FAILED_TO_SAVE_DOOR_LOCK_ENTRIES);
+ console.log(error);
+ res.status(500).send(integrationServiceErrors.FAILED_TO_SAVE_DATA_GENERIC);
+ });
+
+ /*fetchAllBookings()
+ .then((bookingEntries) => {
+ const asyncJobs = [];
+ bookingEntries.forEach((bookingEntry) => asyncJobs.push(writeBookingReservation(bookingEntry)));
+
+ Promise.all(asyncJobs)
+ .then(() => {
+ calculateDoorLockCharges();
+ })
+ .catch((error) => {
+ console.log('Error updating booking reservations : ');
+ console.log(error);
+ })
+ })
+ .catch((error) => {
+ console.log(integrationServiceErrors.FAILED_TO_SAVE_BOOKINGS);
+ console.log(error);
+ return;
+ });
+ */
+ })
+ .catch((error) => {
+ res.status(500).send(error);
+ });
+ });
+ form.parse(req);
+ }
+ })
+ .catch((error) => {
+ console.log('Error while checking processing : ');
+ console.log(error);
+ res.status(500).send('')
+ });
};
module.exports = {
diff --git a/controllers/integration.js b/controllers/integration.js
index ffe2ef0..3c83bed 100644
--- a/controllers/integration.js
+++ b/controllers/integration.js
@@ -5,6 +5,7 @@ const { getAllIncidents } = require('../services/integration/reports');
const { getMembersFeesForDateRange } = require('../services/integration/invoiceIntegration');
const { deleteFeesFromORD, addFeesToORD } = require('../services/officeRnD/fees');
const { checkBookingChanges } = require('../services/integration/checkBookingChange');
+const { checkIfProcessing } = require('../services/integration/processingStatus');
const getKnownOfficeResourceMappings = (req, res) => {
const dataToFetch = [getMappingsFromDatabase(), fetchOffices(), fetchResources() ];
@@ -111,10 +112,25 @@ const addFees = (req, res) => {
}
};
+const checkProcessingStatus = (req, res) => {
+ checkIfProcessing()
+ .then((processing) => {
+ res.send({
+ processing
+ })
+ })
+ .catch((error) => {
+ console.log('Error checking if processing ');
+ console.log(error);
+ res.status(500).send(error);
+ });
+};
+
module.exports = {
getKnownOfficeResourceMappings,
addNewMapping,
getAllIncidentsController,
getMemberIncidents,
addFees,
+ checkProcessingStatus,
};
diff --git a/migrations/20190725110004-add-processings-table.js b/migrations/20190725110004-add-processings-table.js
new file mode 100644
index 0000000..06e3cd0
--- /dev/null
+++ b/migrations/20190725110004-add-processings-table.js
@@ -0,0 +1,26 @@
+'use strict';
+
+module.exports = {
+ up: (queryInterface, Sequelize) => {
+ return queryInterface.createTable('processings', {
+ id: {
+ allowNull: false,
+ autoIncrement: true,
+ primaryKey: true,
+ type: Sequelize.INTEGER
+ },
+ processing: Sequelize.BOOLEAN,
+ createdAt: {
+ allowNull: false,
+ type: Sequelize.DATE
+ },
+ updatedAt: {
+ allowNull: false,
+ type: Sequelize.DATE
+ }
+ });
+ },
+ down: (queryInterface, Sequelize) => {
+ return queryInterface.dropTable('processings');
+ }
+};
diff --git a/models/processing.js b/models/processing.js
new file mode 100644
index 0000000..0163891
--- /dev/null
+++ b/models/processing.js
@@ -0,0 +1,11 @@
+'use strict';
+
+module.exports = (sequelize, DataTypes) => {
+ const processing = sequelize.define('processing', {
+ processing: DataTypes.BOOLEAN,
+ }, {});
+ processing.associate = function(models) {
+ // associations can be defined here
+ };
+ return processing;
+};
diff --git a/routes/index.js b/routes/index.js
index 11efed8..46284bd 100644
--- a/routes/index.js
+++ b/routes/index.js
@@ -8,7 +8,8 @@ const {
addNewMapping,
getAllIncidentsController,
getMemberIncidents,
- addFees
+ addFees,
+ checkProcessingStatus,
} = require('../controllers/integration');
const { calculateDoorLockCharges } = require('../services/integration/doorLockCharges');
@@ -29,6 +30,8 @@ router.get('/officeRnD/membersList', fetchMembersList);
router.post('/integration/addFees', addFees);
+router.get('/integration/processing', checkProcessingStatus);
+
// temporary route, manually trigger door lock charge calculations
router.get('/calculate', (req, res) => { calculateDoorLockCharges(); res.send();});
diff --git a/services/integration/processingStatus.js b/services/integration/processingStatus.js
new file mode 100644
index 0000000..6c7b5de
--- /dev/null
+++ b/services/integration/processingStatus.js
@@ -0,0 +1,46 @@
+'use strict';
+
+const db = require('../../models/index');
+
+const setProcessingValue = (value) => {
+ return new Promise((resolve, reject) => {
+ const values = {processing: value};
+ db.processing.update(values, {where:{}})
+ .then(() => {
+ resolve(true);
+ })
+ .catch((error) => reject(error));
+ });
+};
+
+const setStartProcessing = () => {
+ return setProcessingValue(true);
+};
+
+const setDoneProcessing = () => {
+ return setProcessingValue(false);
+};
+
+const checkIfProcessing = () => {
+ return new Promise((resolve, reject) => {
+ db.processing.findAll()
+ .then((results) => {
+ if (results && results.length > 0){
+ resolve(results[0].getDataValue('processing'));
+ }else{
+ db.processing.bulkCreate([{processing: false}])
+ .then(() => {
+ resolve(false);
+ })
+ .catch((error) => reject(error));
+ }
+ })
+ .catch((error) => reject(error));
+ });
+};
+
+module.exports = {
+ setStartProcessing,
+ setDoneProcessing,
+ checkIfProcessing
+};