Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pro 6416 missing fields #76

Merged
merged 22 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
991a7a0
updates getRelatedDocsFromSchema to fetch relationships with findForE…
ValJed Aug 15, 2024
76522cc
reworks related routes to get related types of related types
ValJed Aug 16, 2024
bcbbdf7
if no relatedIds just continue looping
ValJed Aug 19, 2024
850e656
fixes recursion not working properly for relationships
ValJed Aug 19, 2024
0a5b23a
Do not add docIds twice
ValJed Aug 20, 2024
b8d9253
extract related field logic in function
ValJed Aug 21, 2024
13c4474
moves route related logic in method to be testable
ValJed Aug 21, 2024
2e79251
test related route logic
ValJed Aug 21, 2024
4fcfd87
uses a cloned req with right apos mode when fetching related docs
ValJed Aug 21, 2024
ba7d5a3
fixes tests
ValJed Aug 21, 2024
a96724c
if doc has no aposMode use normal req to get related docs
ValJed Aug 22, 2024
e8f4891
uses document mode even inside sub fields
ValJed Aug 22, 2024
6ba5f1f
fixes use of getRelatedDocsFromSchema for attachments in overrideDupl…
ValJed Aug 22, 2024
b72f77c
fixes tests
ValJed Aug 22, 2024
19bc3ef
removes logs
ValJed Aug 22, 2024
f13c0fa
removes old getRelatedDocsFromSchema method
ValJed Aug 22, 2024
aa1fd75
removes false comment
ValJed Aug 26, 2024
40a1600
adds changelog
ValJed Aug 26, 2024
0364a39
getRelatedTypes takes a related schema to work in a reduce
ValJed Aug 26, 2024
5f7e41b
related route takes an array of types
ValJed Aug 26, 2024
88a11dc
tests that export contain non projected fields
ValJed Aug 27, 2024
e8020a8
fixes changelog wording
ValJed Aug 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## UNRELEASED

### Fixes

* Exported related documents now contain the entier document and not only the projected fields.
ValJed marked this conversation as resolved.
Show resolved Hide resolved
* The `related` route also returns the related types of the exported documents related documents.

## 2.3.0 (2024-08-08)

### Adds
Expand Down
41 changes: 9 additions & 32 deletions lib/apiRoutes.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,41 +10,18 @@ module.exports = self => {
throw self.apos.error('forbidden');
}

const type = self.apos.launder.string(req.query.type);
if (!type) {
throw self.apos.error('invalid');
}

const { schema = [] } = self.apos.modules[type];

// Limit recursions in order to avoid the "Maximum call stack size exceeded" error
// if widgets or pieces are related to themselves.
const maxRecursions = 10;
let recursions = 0;

const relatedTypes = schema
.flatMap(searchRelationships)
.filter(Boolean);
const types = self.apos.launder.strings(req.query.types);

return [ ...new Set(relatedTypes) ];
const related = types.reduce((acc, type) => {
const manager = self.apos.doc.getManager(type);
if (!manager) {
throw self.apos.error('invalid');
}

function searchRelationships(obj) {
const shouldRecurse = recursions <= maxRecursions;
return self.getRelatedTypes(req, manager.schema, acc);
}, []);

if (obj.type === 'relationship') {
return self.canExport(req, obj.withType) && obj.withType;
} else if (obj.type === 'array' || obj.type === 'object') {
recursions++;
return shouldRecurse && obj.schema.flatMap(searchRelationships);
} else if (obj.type === 'area') {
recursions++;
const widgets = self.apos.area.getWidgets(obj.options);
return Object.keys(widgets).flatMap(widget => {
const { schema = [] } = self.apos.modules[`${widget}-widget`];
return shouldRecurse && schema.flatMap(searchRelationships);
});
}
}
return related;
}
},
post: {
Expand Down
223 changes: 169 additions & 54 deletions lib/methods/export.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,47 +32,49 @@ module.exports = self => {
}

const hasRelatedTypes = !!relatedTypes.length;
const docs = await self.getDocs(req, ids, hasRelatedTypes, manager, reporting);
const cleanDocs = self.clean(docs);

const docs = (await self.getDocs(req, ids, hasRelatedTypes, manager, reporting))
.map((doc) => self.apos.util.clonePermanent(doc));

if (!hasRelatedTypes) {
return self.exportFile(
req,
reporting,
format,
{ docs: cleanDocs },
{ docs },
expiration
);
}

const relatedDocs = docs.flatMap(doc =>
self.getRelatedDocsFromSchema(req, {
const allDocs = [ ...docs ];
for (const doc of docs) {
await self.getRelatedDocsFromSchema(req, {
doc,
schema: self.apos.modules[doc.type].schema,
relatedTypes
})
);
const allCleanDocs = [ ...self.clean(relatedDocs), ...cleanDocs ];
relatedTypes,
storedData: allDocs
});
}

if (!format.includeAttachments) {
return self.exportFile(
req,
reporting,
format,
{ docs: allCleanDocs },
{ docs: allDocs },
expiration
);
}

const attachmentsIds = uniqBy([ ...docs, ...relatedDocs ], doc => doc._id)
.flatMap(doc =>
self.getRelatedDocsFromSchema(req, {
doc,
schema: self.apos.modules[doc.type].schema,
type: 'attachment'
})
)
.map(attachment => attachment._id);
const attachmentsIds = [];
for (const doc of allDocs) {
await self.getRelatedDocsFromSchema(req, {
doc,
schema: self.apos.modules[doc.type].schema,
type: 'attachment',
storedData: attachmentsIds
});
}

const attachments = await self.getAttachments(attachmentsIds);
const cleanAttachments = self.clean(attachments);
Expand All @@ -92,7 +94,7 @@ module.exports = self => {
reporting,
format,
{
docs: allCleanDocs,
docs: allDocs,
attachments: cleanAttachments,
attachmentUrls
},
Expand Down Expand Up @@ -198,66 +200,147 @@ module.exports = self => {
return self.canImportOrExport(req, docType, 'export');
},

getRelatedDocsFromSchema(
req,
{
doc,
schema,
type = 'relationship',
relatedTypes,
recursion = 0
}
) {
return schema.flatMap(field => {
async getRelatedDocsFromSchema(req, {
doc,
schema,
relatedTypes,
storedData,
type = 'relationship',
recursion = 0,
mode = doc.aposMode || req.mode
}) {
recursion++;
for (const field of schema) {
const fieldValue = doc[field.name];
const shouldRecurse = recursion <= MAX_RECURSION;

if (!fieldValue) {
return [];
if (!field.withType && !fieldValue) {
continue;
}
if (field.withType && relatedTypes && !relatedTypes.includes(field.withType)) {
return [];
continue;
}
if (field.withType && !self.canExport(req, field.withType)) {
return [];
continue;
}

if (shouldRecurse && field.type === 'array') {
return fieldValue.flatMap((subField) => self.getRelatedDocsFromSchema(req, {
doc: subField,
schema: field.schema,
type,
relatedTypes,
recursion: recursion + 1
}));
for (const subField of fieldValue) {
await self.getRelatedDocsFromSchema(req, {
doc: subField,
schema: field.schema,
type,
relatedTypes,
recursion,
storedData,
mode
});
}
continue;
}

if (shouldRecurse && field.type === 'object') {
return self.getRelatedDocsFromSchema(req, {
await self.getRelatedDocsFromSchema(req, {
doc: fieldValue,
schema: field.schema,
type,
relatedTypes,
recursion: recursion + 1
recursion,
storedData,
mode
});
continue;
}

if (shouldRecurse && field.type === 'area') {
return (fieldValue.items || []).flatMap((widget) => self.getRelatedDocsFromSchema(req, {
doc: widget,
schema: self.apos.modules[`${widget?.type}-widget`]?.schema || [],
type,
relatedTypes,
recursion: recursion + 1
}));
for (const widget of (fieldValue.items || [])) {
const schema = self.apos.modules[`${widget?.type}-widget`]?.schema || [];
await self.getRelatedDocsFromSchema(req, {
doc: widget,
schema,
type,
relatedTypes,
recursion,
storedData,
mode
});
}
continue;
}

if (field.type === type) {
return Array.isArray(fieldValue) ? fieldValue : [ fieldValue ];
await self.handleRelatedField(req, {
doc,
field,
fieldValue,
relatedTypes,
type,
storedData,
shouldRecurse,
recursion,
mode
});
}
}
},

return [];
});
async handleRelatedField(req, {
doc,
field,
fieldValue,
relatedTypes,
type,
storedData,
shouldRecurse,
recursion,
mode
}) {
if (type === 'attachment') {
if (fieldValue && !storedData.includes(fieldValue._id)) {
storedData.push(fieldValue._id);
}
return;
}

const manager = self.apos.doc.getManager(field.withType);
const relatedIds = doc[field.idsStorage];
if (!relatedIds?.length) {
return;
}
const criteria = {
aposDocId: { $in: relatedIds },
aposLocale: doc.aposLocale || `${req.locale}:${mode}`
};

const clonedReq = mode ? req.clone({ mode }) : req;
const foundRelated = await manager
.findForEditing(clonedReq, criteria)
.permission('view')
.relationships(false)
.areas(false)
.toArray();

for (const related of foundRelated) {
const alreadyAdded = storedData.some(({ _id }) => _id === related._id);
if (alreadyAdded) {
continue;
}

storedData.push(self.apos.util.clonePermanent(related));
if (!shouldRecurse) {
continue;
}

const relatedManager = self.apos.doc.getManager(related.type);
await self.getRelatedDocsFromSchema(req, {
doc: related,
schema: relatedManager.schema,
type,
relatedTypes,
recursion,
storedData
});
}
},

async exportFile(req, reporting, format, data, expiration) {
Expand Down Expand Up @@ -379,6 +462,38 @@ module.exports = self => {
}
});
}, ms);
},

getRelatedTypes(req, schema = [], related = []) {
return findSchemaRelatedTypes(schema, related);

function findSchemaRelatedTypes(schema, related, recursions = 0) {
recursions++;
if (recursions >= MAX_RECURSION) {
return related;
}
Copy link
Contributor Author

@ValJed ValJed Aug 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure we need this recursion limit since we parse each type schema only once.

for (const field of schema) {
if (
field.type === 'relationship' &&
self.canExport(req, field.withType) &&
!related.includes(field.withType)
) {
related.push(field.withType);
const relatedManager = self.apos.doc.getManager(field.withType);
findSchemaRelatedTypes(relatedManager.schema, related, recursions);
} else if ([ 'array', 'object' ].includes(field.type)) {
findSchemaRelatedTypes(field.schema, related, recursions);
} else if (field.type === 'area') {
const widgets = self.apos.area.getWidgets(field.options);
for (const widget of Object.keys(widgets)) {
const { schema = [] } = self.apos.modules[`${widget}-widget`];
findSchemaRelatedTypes(schema, related, recursions);
}
}
}

return related;
}
}
};
};
Loading
Loading