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

feat: use database transactions for the bulk generation #157

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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/spicy-hotels-pull.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pluginpal/webtools-core": minor
---

Make the 'languages' field for an URL pattern optional. Patterns without any languages set will be used as a fallback for localized content types.
5 changes: 5 additions & 0 deletions .changeset/strong-sheep-train.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pluginpal/webtools-core": minor
---

Use database transactions in the bulk generation service.
6 changes: 3 additions & 3 deletions packages/core/admin/components/LanguageCheckboxes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,19 @@ import {
Field,
FieldLabel,
FieldError,
FieldHint,
} from '@strapi/design-system';
import { request } from '@strapi/helper-plugin';
import { EnabledContentTypes } from '../../types/enabled-contenttypes';

type Props = {
selectedLanguages: string[];
onChange: (selectedLanguages: string[]) => any;
error?: any;
};

const LanguageCheckboxes = ({
selectedLanguages,
onChange,
error,
}: Props) => {
const [languages, setLanguages] = React.useState<EnabledContentTypes>([]);
const [loading, setLoading] = React.useState<boolean>(false);
Expand All @@ -41,7 +40,7 @@ const LanguageCheckboxes = ({
}

return (
<Field name="password" error={error as string}>
<Field name="password" hint="Leave empty to select all languages.">
<FieldLabel>Select the language</FieldLabel>
<Flex direction="column" alignItems="start" gap="1" marginTop="2">
{languages.map((contentType) => (
Expand All @@ -64,6 +63,7 @@ const LanguageCheckboxes = ({
</Checkbox>
))}
<FieldError />
<FieldHint />
</Flex>
</Field>
);
Expand Down
5 changes: 0 additions & 5 deletions packages/core/admin/screens/Patterns/CreatePage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -229,11 +229,6 @@ const CreatePatternPage = () => {
<LanguageCheckboxes
onChange={(newLanguages) => setFieldValue('languages', newLanguages)}
selectedLanguages={values.languages}
error={
errors.languages && touched.languages
? errors.languages
: null
}
/>
</GridItem>
</GridItem>
Expand Down
5 changes: 0 additions & 5 deletions packages/core/admin/screens/Patterns/EditPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -273,11 +273,6 @@ const EditPatternPage = () => {
<LanguageCheckboxes
onChange={(newLanguages) => setFieldValue('languages', newLanguages)}
selectedLanguages={values.languages}
error={
errors.languages && touched.languages
? errors.languages
: null
}
/>
</GridItem>
</GridItem>
Expand Down
5 changes: 0 additions & 5 deletions packages/core/admin/screens/Patterns/EditPage/utils/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,6 @@ const schema = yup.object().shape({
label: yup.string().required(translatedErrors.required),
pattern: yup.string().required(translatedErrors.required),
contenttype: yup.string().required(translatedErrors.required),
languages: yup.array().when('localized', {
is: true,
then: yup.array().min(1, 'Select at least one language'),
otherwise: yup.array().notRequired(),
}),
});

export default schema;
72 changes: 34 additions & 38 deletions packages/core/server/admin-api/services/bulk-generate.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { Common } from '@strapi/types';
import { Common, Attribute } from '@strapi/types';
// import { Attribute } from '@strapi/strapi';
import snakeCase from 'lodash/snakeCase';
import { getPluginService } from '../../util/getPluginService';
import { GenerationType } from '../../types';
import { duplicateCheck } from './url-alias';

export interface GenerateParams { types: Common.UID.ContentType[], generationType: GenerationType }

Expand Down Expand Up @@ -77,27 +80,23 @@ const generateUrlAliases = async (parms: GenerateParams) => {

// Map over all the types sent in the request.
await Promise.all(types.map(async (type) => {
const { collectionName, info } = strapi.contentTypes[type];
const { singularName } = info;
const newTransaction = await strapi.db.connection.transaction();

if (generationType === 'all') {
// Delete all the URL aliases for the given type.
await getPluginService('url-alias').deleteMany({
// @ts-ignore
locale: 'all',
filters: {
contenttype: type,
},
});
await newTransaction('wt_url_alias')
.where('contenttype', type)
.delete();
}

if (generationType === 'only_generated') {
// Delete all the auto generated URL aliases of the given type.
await getPluginService('url-alias').deleteMany({
// @ts-ignore
locale: 'all',
filters: {
contenttype: type,
generated: true,
},
});
// Delete all the URL aliases for the given type.
await newTransaction('wt_url_alias')
.where('contenttype', type)
.andWhere('generated', true)
.delete();
}

let relations: string[] = [];
Expand All @@ -115,16 +114,10 @@ const generateUrlAliases = async (parms: GenerateParams) => {
}));

// Query all the entities of the type that do not have a corresponding URL alias.
const entities = await strapi.entityService.findMany(type, {
filters: {
url_alias: null,
},
locale: 'all',
// @ts-ignore
populate: {
...relations.reduce((obj, key) => ({ ...obj, [key]: {} }), {}),
},
});
const entities = await newTransaction(collectionName)
.leftJoin(`${collectionName}_url_alias_links`, `${collectionName}.id`, `${collectionName}_url_alias_links.${snakeCase(singularName)}_id`)
.where(`${collectionName}_url_alias_links`, null)
.select('*') as Attribute.GetValues<Common.UID.ContentType, Attribute.GetNonPopulatableKeys<Common.UID.ContentType>>[];

/**
* @todo
Expand All @@ -135,32 +128,35 @@ const generateUrlAliases = async (parms: GenerateParams) => {
*/
// For all those entities we will create a URL alias and connect it to the entity.
// eslint-disable-next-line no-restricted-syntax
for (const entity of entities) {
await Promise.all(entities.map(async (entity) => {
// @ts-ignore
// eslint-disable-next-line no-await-in-loop, @typescript-eslint/no-unsafe-argument
const urlPattern = await getPluginService('urlPatternService').findByUid(type, entity.locale);
const generatedPath = getPluginService('urlPatternService').resolvePattern(type, entity, urlPattern);

// eslint-disable-next-line no-await-in-loop
const newUrlAlias = await getPluginService('urlAliasService').create({
url_path: generatedPath,
const urlPath = await duplicateCheck(generatedPath);

const newUrlAlias = await newTransaction('wt_url_alias').insert({
generated: true,
contenttype: type,
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
locale: entity.locale,
});
url_path: urlPath,
}, '*') as unknown as Attribute.GetValues<Common.UID.ContentType, Attribute.GetNonPopulatableKeys<Common.UID.ContentType>>[];

// eslint-disable-next-line no-await-in-loop
await strapi.entityService.update(type, entity.id, {
data: {
// @ts-ignore
url_alias: newUrlAlias.id,
},
});
await newTransaction(`${collectionName}_url_alias_links`)
.insert({
[`${snakeCase(singularName)}_id`]: entity.id,
url_alias_id: newUrlAlias[0].id,
});

generatedCount += 1;
}
}));

await newTransaction.commit();
}));

return generatedCount;
Expand Down
2 changes: 1 addition & 1 deletion packages/core/server/admin-api/services/url-alias.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { getPluginService } from '../../util/getPluginService';
/**
* Finds a path from the original path that is unique
*/
const duplicateCheck = async (
export const duplicateCheck = async (
originalPath: string,
ignoreId?: Entity.ID,
ext: number = -1,
Expand Down
14 changes: 12 additions & 2 deletions packages/core/server/admin-api/services/url-pattern.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,21 @@ export default () => ({
filters: {
contenttype: uid,
},
fields: ['pattern', 'languages'],
});

if (langcode) {
patterns = patterns
.filter((pattern) => (pattern.languages as string).includes(langcode));
const allPatterns = patterns;

patterns = allPatterns
.filter((pattern) => (pattern.languages as any[]).includes(langcode));

// If no pattern is found for the given language, check if there is
// any pattern that is not language specific. We should use that as a fallback.
if (patterns.length === 0) {
patterns = allPatterns
.filter((pattern) => (pattern.languages as any[]).length === 0);
}
}

if (!patterns[0]) {
Expand Down
Loading
Loading