Skip to content

Commit

Permalink
Merge pull request #1231 from sadiqkhoja/features/get-deleted-submiss…
Browse files Browse the repository at this point in the history
…ions

Update Submissions OData to include `deletedAt`
  • Loading branch information
sadiqkhoja authored Oct 23, 2024
2 parents 389967b + 2a2fbae commit b44b698
Show file tree
Hide file tree
Showing 11 changed files with 224 additions and 554 deletions.
597 changes: 51 additions & 546 deletions docs/api.yaml

Large diffs are not rendered by default.

47 changes: 45 additions & 2 deletions lib/data/odata-filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,50 @@ const odataFilter = (expr, odataToColumnMap) => {
return op(ast);
};

// Returns sql expression to exclude deleted records if provided OData filter
// expression doesn't use `__system/deletedAt` field. Logic of this function
// can easily be merged with `OdataFilter()` but it is kept separate for the
// for the sake of simplicity and for separation of concerns.
const odataExcludeDeleted = (expr, odataToColumnMap) => {
const deleteAtColumn = odataToColumnMap.get('__system/deletedAt')
?? odataToColumnMap.get('$root/Submissions/__system/deletedAt');

const filterOutDeletedRecordsExp = sql`(${sql.identifier(deleteAtColumn.split('.'))} is null)`;

if (expr == null) return filterOutDeletedRecordsExp;

let ast; // still hate this.
try { ast = odataParser.filter(expr); } // eslint-disable-line brace-style
catch (ex) { throw Problem.user.unparseableODataExpression({ reason: ex.message }); }

const hasDeletedAtClause = (node) => {
if (node.type === 'FirstMemberExpression' || node.type === 'RootExpression') {
if (node.raw === '__system/deletedAt'
|| node.raw === '$root/Submissions/__system/deletedAt') return true;
} else if (node.type === 'MethodCallExpression') {
// n.b. we only support Unary (single-arity functions)
return hasDeletedAtClause(node.value.parameters[0]);
} else if (node.type === 'EqualsExpression'
|| node.type === 'NotEqualsExpression'
|| node.type === 'LesserThanExpression'
|| node.type === 'LesserOrEqualsExpression'
|| node.type === 'GreaterThanExpression'
|| node.type === 'GreaterOrEqualsExpression'
|| node.type === 'AndExpression'
|| node.type === 'OrExpression') {
return hasDeletedAtClause(node.value.left) || hasDeletedAtClause(node.value.right);
} else if (node.type === 'BoolParenExpression'
|| node.type === 'NotExpression') {
return hasDeletedAtClause(node.value);
}
return false;
};

if (!hasDeletedAtClause(ast)) return filterOutDeletedRecordsExp;

return sql`true`;
};

const odataOrderBy = (expr, odataToColumnMap, stableOrderColumn = null) => {
let initialOrder = null;
const clauses = expr.split(',').map((exp) => {
Expand Down Expand Up @@ -115,5 +159,4 @@ const odataOrderBy = (expr, odataToColumnMap, stableOrderColumn = null) => {
return sql`ORDER BY ${sql.join(clauses, sql`,`)}`;
};

module.exports = { odataFilter, odataOrderBy };

module.exports = { odataFilter, odataOrderBy, odataExcludeDeleted };
4 changes: 3 additions & 1 deletion lib/data/odata.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@ const submissionToOData = (fields, table, submission, options = {}) => new Promi
deviceId: submission.deviceId || null,
// because of how we compute this value we don't need to default it to 0:
edits: submission.aux.edit.count,
formVersion: submission.aux.exports.formVersion
formVersion: submission.aux.exports.formVersion,
deletedAt: submission.deletedAt
};

if (!options.metadata || options.metadata.__system) {
Expand Down Expand Up @@ -363,6 +364,7 @@ const systemFields = new Set([
'__system/deviceId',
'__system/edits',
'__system/formVersion',
'__system/deletedAt'
]);

module.exports = { submissionToOData, systemFields };
Expand Down
3 changes: 2 additions & 1 deletion lib/data/submission.js
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,8 @@ const filterableFields = [
['__system/submissionDate', 'submissions.createdAt'],
['__system/updatedAt', 'submissions.updatedAt'],
['__system/submitterId', 'submissions.submitterId'],
['__system/reviewState', 'submissions.reviewState']
['__system/reviewState', 'submissions.reviewState'],
['__system/deletedAt', 'submissions.deletedAt']
];

const odataToColumnMap = new Map(filterableFields.concat(filterableFields.map(f => ([`$root/Submissions/${f[0]}`, f[1]]))));
Expand Down
1 change: 1 addition & 0 deletions lib/formats/odata.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ const edmxTemplater = template(`<?xml version="1.0" encoding="UTF-8"?>
<ComplexType Name="metadata">
<Property Name="submissionDate" Type="Edm.DateTimeOffset"/>
<Property Name="updatedAt" Type="Edm.DateTimeOffset"/>
<Property Name="deletedAt" Type="Edm.DateTimeOffset"/>
<Property Name="submitterId" Type="Edm.String"/>
<Property Name="submitterName" Type="Edm.String"/>
<Property Name="attachmentsPresent" Type="Edm.Int64"/>
Expand Down
1 change: 1 addition & 0 deletions lib/model/frames/submission.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class Submission extends Frame.define(
'deviceId', readable, 'createdAt', readable,
'updatedAt', readable, 'reviewState', readable, writable,
'userAgent', readable, 'draft',
'deletedAt', readable,
embedded('submitter'),
embedded('currentVersion')
) {
Expand Down
6 changes: 3 additions & 3 deletions lib/model/query/submissions.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const { map } = require('ramda');
const { sql } = require('slonik');
const { Frame, table } = require('../frame');
const { Actor, Form, Submission } = require('../frames');
const { odataFilter, odataOrderBy } = require('../../data/odata-filter');
const { odataFilter, odataOrderBy, odataExcludeDeleted } = require('../../data/odata-filter');
const { odataToColumnMap, odataSubTableToColumnMap } = require('../../data/submission');
const { unjoiner, extender, equals, page, updater, QueryOptions, insertMany, markDeleted, markUndeleted } = require('../../util/db');
const { blankStringToNull, construct } = require('../../util/util');
Expand Down Expand Up @@ -231,7 +231,7 @@ const joinOnSkiptoken = (skiptoken, formId, draft) => {

const countByFormId = (formId, draft, options = QueryOptions.none) => ({ one }) => {
const filter = sql`${equals({ formId, draft })} AND
"deletedAt" IS NULL AND
${odataExcludeDeleted(options.filter, odataToColumnMap)} AND
${odataFilter(options.filter, odataToColumnMap)}`;
return one(sql`
SELECT * FROM
Expand Down Expand Up @@ -382,7 +382,7 @@ where
${encrypted ? sql`(form_defs."encKeyId" is null or form_defs."encKeyId" in (${sql.join(keyIds, sql`,`)})) and` : sql``}
${odataFilter(options.filter, options.isSubmissionsTable ? odataToColumnMap : odataSubTableToColumnMap)} and
${equals(options.condition)}
and submission_defs.current=true and submissions."formId"=${formId} and submissions."deletedAt" is null
and submission_defs.current=true and submissions."formId"=${formId} and ${odataExcludeDeleted(options.filter, options.isSubmissionsTable ? odataToColumnMap : odataSubTableToColumnMap)}
${options.orderby ? sql`
${odataOrderBy(options.orderby, odataToColumnMap, 'submissions.id')}`
: sql`order by submissions."createdAt" desc, submissions.id desc`}
Expand Down
Loading

0 comments on commit b44b698

Please sign in to comment.