Skip to content

Commit

Permalink
Merge pull request #2276 from HHS/main
Browse files Browse the repository at this point in the history
[Prod] Fix for the specialist role filter for topic frequency, SSDI additions, worker transactions
  • Loading branch information
Jones-QuarteyDana authored Jul 18, 2024
2 parents 8164d4d + fa573ab commit 7186ecd
Show file tree
Hide file tree
Showing 41 changed files with 1,829 additions and 299 deletions.
2 changes: 1 addition & 1 deletion deployment_config/prod_vars.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
env: prod
web_instances: 3
web_memory: 3GB
web_memory: 4GB
worker_instances: 2
worker_memory: 1GB
similarity_api_instances: 1
Expand Down
2 changes: 1 addition & 1 deletion email_templates/changes_requested_by_manager/html.pug
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ style
include ../email.css
p Hello,
p
p #{managerName} requested changed to report #{displayId}.
p #{managerName} requested changes to report #{displayId}.
if comments
p #{managerName} provided the following comments:
blockquote !{comments}
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/fetchers/activityReports.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ export const getRecipients = async (region) => {
return recipients.json();
};

export const getRecipientsForExistingAR = async (reportId) => {
const url = join(activityReportUrl, `${reportId}`, 'activity-recipients');
const recipients = await get(url);
return recipients.json();
};

export const getGoals = async (grantIds) => {
const params = grantIds.map((grantId) => `grantIds=${grantId}`);
const url = join(activityReportUrl, 'goals', `?${params.join('&')}`);
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/pages/ActivityReport/__tests__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ describe('ActivityReport', () => {

beforeEach(() => {
fetchMock.get('/api/activity-reports/activity-recipients?region=1', recipients);
fetchMock.get('/api/activity-reports/1/activity-recipients', recipients);
fetchMock.get('/api/activity-reports/groups?region=1', [{
id: 110,
name: 'Group 1',
Expand Down Expand Up @@ -155,6 +156,7 @@ describe('ActivityReport', () => {
};

fetchMock.get('/api/activity-reports/activity-recipients?region=1', groupRecipients, { overwriteRoutes: true });
fetchMock.get('/api/activity-reports/1/activity-recipients', groupRecipients, { overwriteRoutes: true });

const data = formData();
fetchMock.get('/api/activity-reports/1', { ...data, activityRecipients: [] });
Expand Down Expand Up @@ -226,6 +228,7 @@ describe('ActivityReport', () => {
};

fetchMock.get('/api/activity-reports/activity-recipients?region=1', groupRecipients, { overwriteRoutes: true });
fetchMock.get('/api/activity-reports/1/activity-recipients', groupRecipients, { overwriteRoutes: true });

const data = formData();
fetchMock.get('/api/activity-reports/1', { ...data, activityRecipients: [] });
Expand Down Expand Up @@ -363,6 +366,7 @@ describe('ActivityReport', () => {

describe('resetToDraft', () => {
it('navigates to the correct page', async () => {
fetchMock.get('/api/activity-reports/3/activity-recipients', recipients);
const data = formData();
// load the report
fetchMock.get('/api/activity-reports/3', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ describe('Local storage fallbacks', () => {

beforeEach(() => {
fetchMock.get('/api/activity-reports/activity-recipients?region=1', recipients);
fetchMock.get('/api/activity-reports/1/activity-recipients', recipients);
fetchMock.get('/api/activity-reports/groups?region=1', []);
fetchMock.get('/api/users/collaborators?region=1', []);
fetchMock.get('/api/activity-reports/approvers?region=1', []);
Expand Down
13 changes: 11 additions & 2 deletions frontend/src/pages/ActivityReport/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,14 @@ import {
submitReport,
saveReport,
getReport,
getRecipients,
getRecipientsForExistingAR,
createReport,
getCollaborators,
getApprovers,
reviewReport,
resetToDraft,
getGroupsForActivityReport,
getRecipients,
} from '../../fetchers/activityReports';
import useLocalStorage, { setConnectionActiveWithError } from '../../hooks/useLocalStorage';
import NetworkContext, { isOnlineMode } from '../../NetworkContext';
Expand Down Expand Up @@ -277,8 +278,16 @@ function ActivityReport({
};
}

const getRecips = async () => {
if (reportId.current && reportId.current !== 'new') {
return getRecipientsForExistingAR(reportId.current);
}

return getRecipients(report.regionId);
};

const apiCalls = [
getRecipients(report.regionId),
getRecips(),
getCollaborators(report.regionId),
getApprovers(report.regionId),
getGroupsForActivityReport(report.regionId),
Expand Down
179 changes: 135 additions & 44 deletions src/lib/apiErrorHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,34 +13,26 @@ import { sequelize } from '../models';
* @returns {Promise<number|null>} - The ID of the stored request error, or null if storing failed.
*/
async function logRequestError(req, operation, error, logContext) {
// Check if error logging should be suppressed
if (
operation !== 'SequelizeError'
&& process.env.SUPPRESS_ERROR_LOGGING
&& process.env.SUPPRESS_ERROR_LOGGING.toLowerCase() === 'true'
) {
return 0;
}
if (!error) {
return 0;
}

try {
// Prepare the response body for storage
const responseBody = typeof error === 'object'
&& error !== null ? { ...error, errorStack: error?.stack } : error;
? { ...error, errorStack: error?.stack }
: error;

// Prepare the request body for storage
const requestBody = {
...(req.body
&& typeof req.body === 'object'
&& Object.keys(req.body).length > 0
&& { body: req.body }),
...(req.params
&& typeof req.params === 'object'
&& Object.keys(req.params).length > 0
&& { params: req.params }),
...(req.query
&& typeof req.query === 'object'
&& Object.keys(req.query).length > 0
&& { query: req.query }),
...(req.body && typeof req.body === 'object' && Object.keys(req.body).length > 0 && { body: req.body }),
...(req.params && typeof req.params === 'object' && Object.keys(req.params).length > 0 && { params: req.params }),
...(req.query && typeof req.query === 'object' && Object.keys(req.query).length > 0 && { query: req.query }),
};

// Create a request error in the database and get its ID
Expand Down Expand Up @@ -69,15 +61,13 @@ async function logRequestError(req, operation, error, logContext) {
* @param {Object} logContext - The context for logging.
*/
export const handleError = async (req, res, error, logContext) => {
// Check if the environment is development
if (process.env.NODE_ENV === 'development') {
logger.error(error);
}

let operation;
let label;

// Check if the error is an instance of Sequelize.Error
if (error instanceof Sequelize.Error) {
operation = 'SequelizeError';
label = 'Sequelize error';
Expand All @@ -86,27 +76,17 @@ export const handleError = async (req, res, error, logContext) => {
label = 'UNEXPECTED ERROR';
}

// eslint-disable-next-line max-len
if (error instanceof Sequelize.ConnectionError || error instanceof Sequelize.ConnectionAcquireTimeoutError) {
if (error instanceof Sequelize.ConnectionError
|| error instanceof Sequelize.ConnectionAcquireTimeoutError) {
const pool = sequelize?.connectionManager?.pool;
const usedConnections = pool ? pool?.used?.length : null;
const waitingConnections = pool ? pool?.pending?.length : null;
const usedConnections = pool ? pool.used.length : null;
const waitingConnections = pool ? pool.pending.length : null;
logger.error(`${logContext.namespace} Connection Pool: Used Connections - ${usedConnections}, Waiting Connections - ${waitingConnections}`);
}

// Log the request error and get the error ID
const requestErrorId = await logRequestError(req, operation, error, logContext);

let errorMessage;
const errorMessage = error?.stack || error;

// Check if the error has a stack property
if (error?.stack) {
errorMessage = error.stack;
} else {
errorMessage = error;
}

// Log the error message with the error ID if available
if (requestErrorId) {
logger.error(`${logContext.namespace} - id: ${requestErrorId} ${label} - ${errorMessage}`);
} else {
Expand All @@ -117,12 +97,11 @@ export const handleError = async (req, res, error, logContext) => {
};

/**
* Handles any unexpected errors in an error handler catch block
*
* @param {*} req - request
* @param {*} res - response
* @param {*} error - error
* @param {*} logContext - useful data for logging
* Handles any unexpected errors in an error handler catch block.
* @param {Object} req - The request object.
* @param {Object} res - The response object.
* @param {Error} error - The error object.
* @param {Object} logContext - The context for logging.
*/
export function handleUnexpectedErrorInCatchBlock(req, res, error, logContext) {
logger.error(`${logContext.namespace} - Unexpected error in catch block - ${error}`);
Expand All @@ -131,11 +110,10 @@ export function handleUnexpectedErrorInCatchBlock(req, res, error, logContext) {

/**
* Handles API errors. Saves data in the RequestErrors table and sends 500 error.
*
* @param {*} req - request
* @param {*} res - response
* @param {*} error - error
* @param {*} logContext - useful data for logging
* @param {Object} req - The request object.
* @param {Object} res - The response object.
* @param {Error} error - The error object.
* @param {Object} logContext - The context for logging.
*/
export default async function handleErrors(req, res, error, logContext) {
try {
Expand All @@ -144,3 +122,116 @@ export default async function handleErrors(req, res, error, logContext) {
handleUnexpectedErrorInCatchBlock(req, res, e, logContext);
}
}

/**
* Logs a worker error and stores it in the database.
* @param {Object} job - The job object.
* @param {string} operation - The operation name.
* @param {Error} error - The error object.
* @param {Object} logContext - The logging context.
* @returns {Promise<number|null>} - The ID of the stored request error, or null if storing failed.
*/
const logWorkerError = async (job, operation, error, logContext) => {
if (
operation !== 'SequelizeError'
&& process.env.SUPPRESS_ERROR_LOGGING
&& process.env.SUPPRESS_ERROR_LOGGING.toLowerCase() === 'true'
) {
return 0;
}
if (!error) {
return 0;
}

try {
const responseBody = typeof error === 'object'
? { ...error, errorStack: error?.stack }
: error;

const requestBody = {
...(job.data && typeof job.data === 'object' && Object.keys(job.data).length > 0 && { data: job.data }),
};

const requestErrorId = await createRequestError({
operation,
uri: job.queue.name,
method: 'PROCESS_JOB',
requestBody,
responseBody,
responseCode: INTERNAL_SERVER_ERROR,
});

return requestErrorId;
} catch (e) {
logger.error(`${logContext.namespace} - Sequelize error - unable to store RequestError - ${e}`);
}

return null;
};

/**
* Handles errors in a worker job.
* @param {Object} job - The job object.
* @param {Error} error - The error object.
* @param {Object} logContext - The context for logging.
*/
export const handleWorkerError = async (job, error, logContext) => {
if (process.env.NODE_ENV === 'development') {
logger.error(error);
}

let operation;
let label;

if (error instanceof Sequelize.Error) {
operation = 'SequelizeError';
label = 'Sequelize error';
} else {
operation = 'UNEXPECTED_ERROR';
label = 'UNEXPECTED ERROR';
}

if (error instanceof Sequelize.ConnectionError
|| error instanceof Sequelize.ConnectionAcquireTimeoutError) {
const pool = sequelize?.connectionManager?.pool;
const usedConnections = pool ? pool.used.length : null;
const waitingConnections = pool ? pool.pending.length : null;
logger.error(`${logContext.namespace} Connection Pool: Used Connections - ${usedConnections}, Waiting Connections - ${waitingConnections}`);
}

const requestErrorId = await logWorkerError(job, operation, error, logContext);

const errorMessage = error?.stack || error;

if (requestErrorId) {
logger.error(`${logContext.namespace} - id: ${requestErrorId} ${label} - ${errorMessage}`);
} else {
logger.error(`${logContext.namespace} - ${label} - ${errorMessage}`);
}

// Handle job failure as needed
};

/**
* Handles any unexpected errors in a worker error handler catch block.
* @param {Object} job - The job object.
* @param {Error} error - The error object.
* @param {Object} logContext - The context for logging.
*/
export const handleUnexpectedWorkerError = (job, error, logContext) => {
logger.error(`${logContext.namespace} - Unexpected error in catch block - ${error}`);
};

/**
* Handles worker job errors. Logs the error and stores it in the database.
* @param {Object} job - The job object.
* @param {Error} error - The error object.
* @param {Object} logContext - The context for logging.
*/
export const handleWorkerErrors = async (job, error, logContext) => {
try {
await handleWorkerError(job, error, logContext);
} catch (e) {
handleUnexpectedWorkerError(job, e, logContext);
}
};
Loading

0 comments on commit 7186ecd

Please sign in to comment.