Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/brave-eyes-mate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"strapi-plugin-webtools": patch
---

fix: wrap the bulk generate service in a db transaction to prevent #280
5 changes: 5 additions & 0 deletions .changeset/silver-grapes-see.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"strapi-plugin-webtools": patch
---

fix: make sure error notifications are shown with type 'danger' instead of 'warning'
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const TableRow: FC<Props> = ({
})
.catch(() => {
if (onDelete) onDelete();
toggleNotification({ type: 'warning', message: formatMessage({ id: 'notification.error' }) });
toggleNotification({ type: 'danger', message: formatMessage({ id: 'notification.error' }) });
});
};

Expand Down
2 changes: 1 addition & 1 deletion packages/core/admin/screens/List/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const List = () => {
toggleNotification({ type: 'success', message: formatMessage({ id: 'webtools.success.url-alias.generate', defaultMessage: response.data.message }) });
})
.catch(() => {
toggleNotification({ type: 'warning', message: formatMessage({ id: 'notification.error' }) });
toggleNotification({ type: 'danger', message: formatMessage({ id: 'notification.error' }) });
});

await queryClient.invalidateQueries('url-alias');
Expand Down
2 changes: 1 addition & 1 deletion packages/core/admin/screens/Patterns/CreatePage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const CreatePatternPage = () => {
setSubmitting(false);
} catch (err) {
toggleNotification({
type: 'warning',
type: 'danger',
message: formatMessage({ id: 'notification.error' }),
});
setSubmitting(false);
Expand Down
2 changes: 1 addition & 1 deletion packages/core/admin/screens/Patterns/EditPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ const EditPatternPage = () => {
setSubmitting(false);
} catch (err) {
toggleNotification({
type: 'warning',
type: 'danger',
message: formatMessage({ id: 'notification.error' }),
});
setSubmitting(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const TableBody: React.FC<Props> = ({ patterns, contentTypes }) => {
toggleNotification({ type: 'success', message: formatMessage({ id: 'webtools.settings.success.delete' }) });
})
.catch(() => {
toggleNotification({ type: 'warning', message: formatMessage({ id: 'notification.error' }) });
toggleNotification({ type: 'danger', message: formatMessage({ id: 'notification.error' }) });
});
};

Expand Down
153 changes: 78 additions & 75 deletions packages/core/server/services/bulk-generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,104 +20,107 @@ const generateUrlAliases = async (params: GenerateParams): Promise<number> => {

// Map over all the types sent in the request.
await Promise.all(types.map(async (type) => {
if (generationType === 'all') {
// Delete all the URL aliases for the given type.
await strapi.db.query('plugin::webtools.url-alias').deleteMany({
where: { contenttype: type },
});
}
await strapi.db.transaction(async () => {
if (generationType === 'all') {
// Delete all the URL aliases for the given type.
await strapi.db.query('plugin::webtools.url-alias').deleteMany({
where: { contenttype: type },
});
}

if (generationType === 'only_generated') {
// Delete all the auto generated URL aliases of the given type.
await strapi.db.query('plugin::webtools.url-alias').deleteMany({
where: { contenttype: type, generated: true },
});
}
if (generationType === 'only_generated') {
// Delete all the auto generated URL aliases of the given type.
await strapi.db.query('plugin::webtools.url-alias').deleteMany({
where: { contenttype: type, generated: true },
});
}

let relations: string[] = [];
let relations: string[] = [];

// Get all relations for the type from all patterns for all languages.
const urlPatterns = await getPluginService('url-pattern').findByUid(type);
urlPatterns.forEach((urlPattern) => {
const languageRelations = getPluginService('url-pattern').getRelationsFromPattern(urlPattern);
relations = [...relations, ...languageRelations];
});

// Query all the entities of the type that do not have a corresponding URL alias.
const entities = await strapi.documents(type as 'api::test.test').findMany({
filters: { url_alias: null },
populate: {
...relations.reduce((obj, key) => ({ ...obj, [key]: {} }), {}),
localizations: {
populate: {
...relations.reduce((obj, key) => ({ ...obj, [key]: {} }), {}),
},
},
},
});

/**
* @todo
* We should do a Promise.all(entities.map()) here to speed up the process.
* Using that method we can create all the URL aliases in parallel.
* Currently this is not possible due to the duplicateCheck function.
* Race conditions can occur when two entities have the same URL path.
*/
// eslint-disable-next-line no-restricted-syntax
for (const entity of entities) {
// FIXME: just filter the `urlPatterns` we already have.
// eslint-disable-next-line no-await-in-loop
const entityUrlPatterns = await getPluginService('url-pattern').findByUid(type, entity.locale);
const resolvedPath = getPluginService('url-pattern').resolvePattern(type, entity, entityUrlPatterns[0]);

// eslint-disable-next-line no-await-in-loop
const newUrlAlias = await strapi.documents('plugin::webtools.url-alias').create({
data: {
url_path: resolvedPath,
generated: true,
contenttype: type,
locale: entity.locale,
},
// Get all relations for the type from all patterns for all languages.
const urlPatterns = await getPluginService('url-pattern').findByUid(type);
urlPatterns.forEach((urlPattern) => {
const languageRelations = getPluginService('url-pattern').getRelationsFromPattern(urlPattern);
relations = [...relations, ...languageRelations];
});

// eslint-disable-next-line no-await-in-loop
await strapi.documents(type as 'api::test.test').update({
locale: entity.locale,
documentId: entity.documentId,
data: {
url_alias: [newUrlAlias.documentId],
// Query all the entities of the type that do not have a corresponding URL alias.
const entities = await strapi.documents(type as 'api::test.test').findMany({
filters: { url_alias: null },
populate: {
...relations.reduce((obj, key) => ({ ...obj, [key]: {} }), {}),
localizations: {
populate: {
...relations.reduce((obj, key) => ({ ...obj, [key]: {} }), {}),
},
},
},
});

// eslint-disable-next-line no-await-in-loop
await Promise.all(entity.localizations.map(async (loc) => {
const patterns = await getPluginService('url-pattern').findByUid(type, loc.locale);
const path = getPluginService('url-pattern').resolvePattern(type, loc, patterns[0]);

const alias = await strapi.documents('plugin::webtools.url-alias').update({
documentId: newUrlAlias.documentId,
locale: loc.locale,
/**
* @todo
* We should do a Promise.all(entities.map()) here to speed up the process.
* Using that method we can create all the URL aliases in parallel.
* Currently this is not possible due to the duplicateCheck function.
* Race conditions can occur when two entities have the same URL path.
*/
// eslint-disable-next-line no-restricted-syntax
for (const entity of entities) {
// FIXME: just filter the `urlPatterns` we already have.
// eslint-disable-next-line no-await-in-loop
const entityUrlPatterns = await getPluginService('url-pattern').findByUid(type, entity.locale);
const resolvedPath = getPluginService('url-pattern').resolvePattern(type, entity, entityUrlPatterns[0]);

// eslint-disable-next-line no-await-in-loop
const newUrlAlias = await strapi.documents('plugin::webtools.url-alias').create({
data: {
url_path: path,
url_path: resolvedPath,
generated: true,
contenttype: type,
locale: entity.locale,
},
});

// eslint-disable-next-line no-await-in-loop
await strapi.documents(type as 'api::test.test').update({
locale: entity.locale,
documentId: entity.documentId,
locale: loc.locale,
data: {
url_alias: [alias.documentId],
url_alias: [newUrlAlias.documentId],
},
});
}));

generatedCount += 1;
}
// eslint-disable-next-line no-await-in-loop
await Promise.all(entity.localizations.map(async (loc) => {
const patterns = await getPluginService('url-pattern').findByUid(type, loc.locale);
const path = getPluginService('url-pattern').resolvePattern(type, loc, patterns[0]);

const alias = await strapi.documents('plugin::webtools.url-alias').update({
documentId: newUrlAlias.documentId,
locale: loc.locale,
data: {
url_path: path,
generated: true,
contenttype: type,
locale: entity.locale,
},
});

await strapi.documents(type as 'api::test.test').update({
documentId: entity.documentId,
locale: loc.locale,
data: {
url_alias: [alias.documentId],
},
});
}));

generatedCount += 1;
}
});
}));


return generatedCount;
};

Expand Down