Skip to content

Commit 5e1b21f

Browse files
Log error based on expected number of entities if one fails (#1701)
Error message includes total number of entities as well as how many of them had problems, which specific entities in the submission had problems, and what those problems were. Message tries to capture the idea that even the successful entities got skipped when some failed. Co-authored-by: Matthew White <[email protected]>
1 parent dc28c67 commit 5e1b21f

File tree

3 files changed

+95
-4
lines changed

3 files changed

+95
-4
lines changed

lib/model/query/entities.js

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -533,9 +533,27 @@ const _processSubmissionEvent = (event, parentEvent) => async ({ Datasets, Entit
533533
};
534534
const sourceId = await Entities.createOrUpdateSource(sourceDetails, submissionDefId, event.id, forceOutOfOrderProcessing);
535535

536-
for (const entityData of entities) {
537-
// eslint-disable-next-line no-await-in-loop
538-
await Entities._processEntityData(sourceId, entityData, event, form, submissionDef, forceOutOfOrderProcessing);
536+
const errors = [];
537+
const totalEntities = entities.length;
538+
539+
for (const [i, entityData] of entities.entries()) {
540+
try {
541+
// eslint-disable-next-line no-await-in-loop
542+
await Entities._processEntityData(sourceId, entityData, event, form, submissionDef, forceOutOfOrderProcessing);
543+
} catch (err) {
544+
if (totalEntities === 1)
545+
throw (err);
546+
else
547+
errors.push({
548+
errorMessage: err.message,
549+
problem: err.isProblem === true ? err : null,
550+
index: i
551+
});
552+
}
553+
}
554+
555+
if (errors.length > 0) {
556+
throw Problem.user.multiEntityError({ count: errors.length, total: totalEntities, errors });
539557
}
540558

541559
return null;

lib/util/problem.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,13 @@ const problems = {
148148

149149
invalidRange: problem(400.42, () => `There is a problem with a range specification. Perhaps start and end have been swapped?`),
150150

151+
multiEntityError: problem(400.43, ({ count, total, errors }) => {
152+
const joined = errors
153+
.map(error => `(Entity ${error.index + 1}) ${error.errorMessage}`)
154+
.join(' ');
155+
return `Failed to process ${total} entities in submission due to ${count} errors: ${joined}`;
156+
}),
157+
151158
// no detail information for security reasons.
152159
authenticationFailed: problem(401.2, () => 'Could not authenticate with the provided credentials.'),
153160

test/integration/api/entities-from-repeats.js

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,7 @@ describe('Entities from Repeats', () => {
431431
}));
432432
});
433433

434-
describe('entity sources and backlog', () => {
434+
describe('errors, entity sources and backlog', () => {
435435
it('should assign the same entity source to multiple entities created by the same submission', testService(async (service, container) => {
436436
const asAlice = await service.login('alice');
437437

@@ -486,6 +486,72 @@ describe('Entities from Repeats', () => {
486486
.then(({ body }) => { body.length.should.equal(0); });
487487
}));
488488

489+
it('should log an error when one of multiple entities fails', testService(async (service, container) => {
490+
const asAlice = await service.login('alice');
491+
492+
const subXml = `<data xmlns:jr="http://openrosa.org/javarosa" xmlns:orx="http://openrosa.org/xforms" id="repeatEntityTrees" version="1">
493+
<plot_id>1</plot_id>
494+
<tree>
495+
<species>pine</species>
496+
<circumference>12</circumference>
497+
<meta>
498+
<entity dataset="trees" create="1" id="f73ea0a0-f51f-4d13-a7cb-c2123ba06f34">
499+
<label>Pine</label>
500+
</entity>
501+
</meta>
502+
</tree>
503+
<tree>
504+
<species>oak</species>
505+
<circumference>13</circumference>
506+
<meta>
507+
<entity dataset="trees" create="1" id="090c56ff-25f4-4503-b760-f6bef8528152">
508+
</entity>
509+
</meta>
510+
</tree>
511+
<tree>
512+
<species>maple</species>
513+
<circumference>14</circumference>
514+
<meta>
515+
<entity dataset="trees" create="1" id="invalid-uuid">
516+
<label>Maple</label>
517+
</entity>
518+
</meta>
519+
</tree>
520+
<meta>
521+
<instanceID>one</instanceID>
522+
</meta>
523+
</data>`;
524+
525+
await asAlice.post('/v1/projects/1/forms?publish=true')
526+
.send(testData.forms.repeatEntityTrees)
527+
.set('Content-Type', 'application/xml')
528+
.expect(200);
529+
530+
await asAlice.post('/v1/projects/1/forms/repeatEntityTrees/submissions')
531+
.send(subXml)
532+
.set('Content-Type', 'application/xml')
533+
.expect(200);
534+
535+
await exhaust(container);
536+
537+
await asAlice.get('/v1/projects/1/forms/repeatEntityTrees/submissions/one/audits')
538+
.then(({ body }) => {
539+
body[0].action.should.equal('entity.error');
540+
body[0].details.problem.problemCode.should.equal(400.43);
541+
542+
body[0].details.errorMessage.includes('Failed to process 3 entities in submission due to 2 errors').should.be.true();
543+
body[0].details.errorMessage.includes('(Entity 2) Required parameter label missing').should.be.true();
544+
body[0].details.errorMessage.includes('(Entity 3) Invalid input data type: expected (uuid)').should.be.true();
545+
546+
const { problemDetails } = body[0].details.problem;
547+
problemDetails.should.containEql({ count: 2, total: 3 });
548+
const detailErrors = problemDetails.errors;
549+
detailErrors.length.should.equal(2);
550+
detailErrors[0].problem.problemCode.should.equal(400.2);
551+
detailErrors[1].problem.problemCode.should.equal(400.11);
552+
});
553+
}));
554+
489555
it('should note what happens when two entities hold submission into backlog', testService(async (service, container) => {
490556
const asAlice = await service.login('alice');
491557

0 commit comments

Comments
 (0)