fix: handle multiple locales in relationship population (#1452)

This commit is contained in:
Dan Ribbens
2022-11-28 14:24:01 -05:00
committed by GitHub
parent e9d2163601
commit 04c689c5b0
4 changed files with 446 additions and 284 deletions

View File

@@ -10,6 +10,7 @@ type PopulateArgs = {
data: Record<string, unknown>
field: RelationshipField | UploadField
index?: number
key?: string
showHiddenFields: boolean
}
@@ -22,6 +23,7 @@ const populate = async ({
data,
field,
index,
key,
showHiddenFields,
}: PopulateArgs) => {
const dataToUpdate = dataReference;
@@ -33,7 +35,7 @@ const populate = async ({
let relationshipValue;
const shouldPopulate = depth && currentDepth <= depth;
if (typeof id !== 'string' && typeof id !== 'number' && typeof id?.toString === 'function') {
if (typeof id !== 'string' && typeof id !== 'number' && typeof id?.toString === 'function' && typeof id !== 'object') {
id = id.toString();
}
@@ -55,11 +57,17 @@ const populate = async ({
relationshipValue = id;
}
if (typeof index === 'number') {
if (typeof index === 'number' && typeof key === 'string') {
if (Array.isArray(field.relationTo)) {
dataToUpdate[field.name][index].value = relationshipValue;
dataToUpdate[field.name][key][index].value = relationshipValue;
} else {
dataToUpdate[field.name][index] = relationshipValue;
dataToUpdate[field.name][key][index] = relationshipValue;
}
} else if (typeof index === 'number' || typeof key === 'string') {
if (Array.isArray(field.relationTo)) {
dataToUpdate[field.name][index ?? key].value = relationshipValue;
} else {
dataToUpdate[field.name][index ?? key] = relationshipValue;
}
} else if (Array.isArray(field.relationTo)) {
dataToUpdate[field.name].value = relationshipValue;
@@ -90,27 +98,67 @@ const relationshipPopulationPromise = async ({
}: PromiseArgs): Promise<void> => {
const resultingDoc = siblingDoc;
const populateDepth = fieldHasMaxDepth(field) && field.maxDepth < depth ? field.maxDepth : depth;
const rowPromises = [];
if (fieldSupportsMany(field) && field.hasMany && Array.isArray(siblingDoc[field.name])) {
const rowPromises = [];
siblingDoc[field.name].forEach((relatedDoc, index) => {
const rowPromise = async () => {
if (relatedDoc) {
await populate({
depth: populateDepth,
currentDepth,
req,
overrideAccess,
data: relatedDoc,
dataReference: resultingDoc,
field,
index,
showHiddenFields,
if (fieldSupportsMany(field) && field.hasMany) {
if (req.locale === 'all' && typeof siblingDoc[field.name] === 'object') {
Object.keys(siblingDoc[field.name]).forEach((key) => {
if (Array.isArray(siblingDoc[field.name][key])) {
siblingDoc[field.name][key].forEach((relatedDoc, index) => {
const rowPromise = async () => {
await populate({
depth: populateDepth,
currentDepth,
req,
overrideAccess,
data: siblingDoc[field.name][key][index],
dataReference: resultingDoc,
field,
index,
key,
showHiddenFields,
});
};
rowPromises.push(rowPromise());
});
}
};
});
} else if (Array.isArray(siblingDoc[field.name])) {
siblingDoc[field.name].forEach((relatedDoc, index) => {
const rowPromise = async () => {
if (relatedDoc) {
await populate({
depth: populateDepth,
currentDepth,
req,
overrideAccess,
data: relatedDoc,
dataReference: resultingDoc,
field,
index,
showHiddenFields,
});
}
};
rowPromises.push(rowPromise());
});
}
} else if (typeof siblingDoc[field.name] === 'object' && req.locale === 'all') {
Object.keys(siblingDoc[field.name]).forEach((key) => {
const rowPromise = async () => {
await populate({
depth: populateDepth,
currentDepth,
req,
overrideAccess,
data: siblingDoc[field.name][key],
dataReference: resultingDoc,
field,
key,
showHiddenFields,
});
};
rowPromises.push(rowPromise());
});
@@ -127,6 +175,7 @@ const relationshipPopulationPromise = async ({
showHiddenFields,
});
}
await Promise.all(rowPromises);
};
export default relationshipPopulationPromise;

View File

@@ -1,6 +1,6 @@
import { buildConfig } from '../buildConfig';
import { devUser } from '../credentials';
import { LocalizedPost } from './payload-types';
import { LocalizedPost, RelationshipLocalized } from './payload-types';
import {
defaultLocale,
englishTitle,
@@ -21,7 +21,7 @@ export type LocalizedPostAllLocale = LocalizedPost & {
export const slug = 'localized-posts';
export const withLocalizedRelSlug = 'with-localized-relationship';
export const relationshipLocalizedSlug = 'relationship-localized';
export const withRequiredLocalizedFields = 'localized-required';
const openAccess = {
@@ -133,6 +133,37 @@ export default buildConfig({
},
],
},
{
slug: relationshipLocalizedSlug,
fields: [
{
name: 'relationship',
type: 'relationship',
relationTo: slug,
localized: true,
},
{
name: 'relationshipHasMany',
type: 'relationship',
relationTo: slug,
hasMany: true,
localized: true,
},
{
name: 'relationMultiRelationTo',
type: 'relationship',
relationTo: [slug, 'dummy'],
localized: true,
},
{
name: 'relationMultiRelationToHasMany',
type: 'relationship',
relationTo: [slug, 'dummy'],
hasMany: true,
localized: true,
},
],
},
{
slug: 'dummy',
access: openAccess,
@@ -210,10 +241,10 @@ export default buildConfig({
},
});
await payload.create({
await payload.create<RelationshipLocalized>({
collection: withLocalizedRelSlug,
data: {
localizedRelationship: localizedRelation.id,
relationship: localizedRelation.id,
localizedRelationHasManyField: [localizedRelation.id, localizedRelation2.id],
localizedRelationMultiRelationTo: { relationTo: collection, value: localizedRelation.id },
localizedRelationMultiRelationToHasMany: [
@@ -222,5 +253,18 @@ export default buildConfig({
],
},
});
await payload.create({
collection: relationshipLocalizedSlug,
locale: 'en',
data: {
relationship: localizedRelation.id,
relationshipHasMany: [localizedRelation.id, localizedRelation2.id],
relationMultiRelationTo: { relationTo: collection, value: localizedRelation.id },
relationMultiRelationToHasMany: [
{ relationTo: slug, value: localizedRelation.id },
{ relationTo: slug, value: localizedRelation2.id },
],
},
});
},
});

View File

@@ -2,9 +2,14 @@ import mongoose from 'mongoose';
import { GraphQLClient } from 'graphql-request';
import { initPayloadTest } from '../helpers/configHelpers';
import payload from '../../src';
import type { LocalizedPost, WithLocalizedRelationship, LocalizedRequired } from './payload-types';
import type {
LocalizedPost,
WithLocalizedRelationship,
LocalizedRequired,
RelationshipLocalized,
} from './payload-types';
import type { LocalizedPostAllLocale } from './config';
import config, { slug, withLocalizedRelSlug, withRequiredLocalizedFields } from './config';
import config, { relationshipLocalizedSlug, slug, withLocalizedRelSlug, withRequiredLocalizedFields } from './config';
import {
defaultLocale,
englishTitle,
@@ -191,271 +196,283 @@ describe('Localization', () => {
expect(result.docs[0].id).toEqual(localizedPost.id);
});
});
});
describe('Localized Relationship', () => {
let localizedRelation: LocalizedPost;
let localizedRelation2: LocalizedPost;
let withRelationship: WithLocalizedRelationship;
describe('Localized Relationship', () => {
let localizedRelation: LocalizedPost;
let localizedRelation2: LocalizedPost;
let withRelationship: WithLocalizedRelationship;
beforeAll(async () => {
localizedRelation = await createLocalizedPost({
title: {
[defaultLocale]: relationEnglishTitle,
[spanishLocale]: relationSpanishTitle,
},
});
localizedRelation2 = await createLocalizedPost({
title: {
[defaultLocale]: relationEnglishTitle2,
[spanishLocale]: relationSpanishTitle2,
},
});
beforeAll(async () => {
localizedRelation = await createLocalizedPost({
title: {
[defaultLocale]: relationEnglishTitle,
[spanishLocale]: relationSpanishTitle,
withRelationship = await payload.create({
collection: withLocalizedRelSlug,
data: {
localizedRelationship: localizedRelation.id,
localizedRelationHasManyField: [localizedRelation.id, localizedRelation2.id],
localizedRelationMultiRelationTo: { relationTo: slug, value: localizedRelation.id },
localizedRelationMultiRelationToHasMany: [
{ relationTo: slug, value: localizedRelation.id },
{ relationTo: slug, value: localizedRelation2.id },
],
},
});
});
describe('regular relationship', () => {
it('can query localized relationship', async () => {
const result = await payload.find<WithLocalizedRelationship>({
collection: withLocalizedRelSlug,
where: {
'localizedRelationship.title': {
equals: localizedRelation.title,
},
});
localizedRelation2 = await createLocalizedPost({
title: {
[defaultLocale]: relationEnglishTitle2,
[spanishLocale]: relationSpanishTitle2,
},
});
},
});
withRelationship = await payload.create({
expect(result.docs[0].id).toEqual(withRelationship.id);
});
it('specific locale', async () => {
const result = await payload.find<WithLocalizedRelationship>({
collection: withLocalizedRelSlug,
locale: spanishLocale,
where: {
'localizedRelationship.title': {
equals: relationSpanishTitle,
},
},
});
expect(result.docs[0].id).toEqual(withRelationship.id);
});
it('all locales', async () => {
const result = await payload.find<WithLocalizedRelationship>({
collection: withLocalizedRelSlug,
locale: 'all',
where: {
'localizedRelationship.title.es': {
equals: relationSpanishTitle,
},
},
});
expect(result.docs[0].id).toEqual(withRelationship.id);
});
it('populates relationships with all locales', async () => {
// the relationship fields themselves are localized on this collection
const result = await payload.find<RelationshipLocalized>({
collection: relationshipLocalizedSlug,
locale: 'all',
depth: 1,
});
expect((result.docs[0].relationship as any).en.id).toBeDefined();
expect((result.docs[0].relationshipHasMany as any).en[0].id).toBeDefined();
expect((result.docs[0].relationMultiRelationTo as any).en.value.id).toBeDefined();
expect((result.docs[0].relationMultiRelationToHasMany as any).en[0].value.id).toBeDefined();
});
});
describe('relationship - hasMany', () => {
it('default locale', async () => {
const result = await payload.find<WithLocalizedRelationship>({
collection: withLocalizedRelSlug,
where: {
'localizedRelationHasManyField.title': {
equals: localizedRelation.title,
},
},
});
expect(result.docs[0].id).toEqual(withRelationship.id);
// Second relationship
const result2 = await payload.find<WithLocalizedRelationship>({
collection: withLocalizedRelSlug,
where: {
'localizedRelationHasManyField.title': {
equals: localizedRelation2.title,
},
},
});
expect(result2.docs[0].id).toEqual(withRelationship.id);
});
it('specific locale', async () => {
const result = await payload.find<WithLocalizedRelationship>({
collection: withLocalizedRelSlug,
locale: spanishLocale,
where: {
'localizedRelationHasManyField.title': {
equals: relationSpanishTitle,
},
},
});
expect(result.docs[0].id).toEqual(withRelationship.id);
// Second relationship
const result2 = await payload.find<WithLocalizedRelationship>({
collection: withLocalizedRelSlug,
locale: spanishLocale,
where: {
'localizedRelationHasManyField.title': {
equals: relationSpanishTitle2,
},
},
});
expect(result2.docs[0].id).toEqual(withRelationship.id);
});
it('relationship population uses locale', async () => {
const result = await payload.findByID<WithLocalizedRelationship>({
collection: withLocalizedRelSlug,
depth: 1,
id: withRelationship.id,
locale: spanishLocale,
});
expect((result.localizedRelationship as LocalizedPost).title).toEqual(relationSpanishTitle);
});
it('all locales', async () => {
const queryRelation = (where: Where) => {
return payload.find<WithLocalizedRelationship>({
collection: withLocalizedRelSlug,
data: {
localizedRelationship: localizedRelation.id,
localizedRelationHasManyField: [localizedRelation.id, localizedRelation2.id],
localizedRelationMultiRelationTo: { relationTo: slug, value: localizedRelation.id },
localizedRelationMultiRelationToHasMany: [
{ relationTo: slug, value: localizedRelation.id },
{ relationTo: slug, value: localizedRelation2.id },
],
locale: 'all',
where,
});
};
const result = await queryRelation({
'localizedRelationHasManyField.title.en': {
equals: relationEnglishTitle,
},
});
expect(result.docs[0].id).toEqual(withRelationship.id);
// First relationship - spanish
const result2 = await queryRelation({
'localizedRelationHasManyField.title.es': {
equals: relationSpanishTitle,
},
});
expect(result2.docs[0].id).toEqual(withRelationship.id);
// Second relationship - english
const result3 = await queryRelation({
'localizedRelationHasManyField.title.en': {
equals: relationEnglishTitle2,
},
});
expect(result3.docs[0].id).toEqual(withRelationship.id);
// Second relationship - spanish
const result4 = await queryRelation({
'localizedRelationHasManyField.title.es': {
equals: relationSpanishTitle2,
},
});
expect(result4.docs[0].id).toEqual(withRelationship.id);
});
});
describe('relationTo multi', () => {
it('by id', async () => {
const result = await payload.find<WithLocalizedRelationship>({
collection: withLocalizedRelSlug,
where: {
'localizedRelationMultiRelationTo.value': {
equals: localizedRelation.id,
},
});
},
});
describe('regular relationship', () => {
it('can query localized relationship', async () => {
const result = await payload.find<WithLocalizedRelationship>({
collection: withLocalizedRelSlug,
where: {
'localizedRelationship.title': {
equals: localizedRelation.title,
},
},
});
expect(result.docs[0].id).toEqual(withRelationship.id);
expect(result.docs[0].id).toEqual(withRelationship.id);
});
it('specific locale', async () => {
const result = await payload.find<WithLocalizedRelationship>({
collection: withLocalizedRelSlug,
locale: spanishLocale,
where: {
'localizedRelationship.title': {
equals: relationSpanishTitle,
},
},
});
expect(result.docs[0].id).toEqual(withRelationship.id);
});
it('all locales', async () => {
const result = await payload.find<WithLocalizedRelationship>({
collection: withLocalizedRelSlug,
locale: 'all',
where: {
'localizedRelationship.title.es': {
equals: relationSpanishTitle,
},
},
});
expect(result.docs[0].id).toEqual(withRelationship.id);
});
// Second relationship
const result2 = await payload.find<WithLocalizedRelationship>({
collection: withLocalizedRelSlug,
locale: spanishLocale,
where: {
'localizedRelationMultiRelationTo.value': {
equals: localizedRelation.id,
},
},
});
describe('relationship - hasMany', () => {
it('default locale', async () => {
const result = await payload.find<WithLocalizedRelationship>({
collection: withLocalizedRelSlug,
where: {
'localizedRelationHasManyField.title': {
equals: localizedRelation.title,
},
},
});
expect(result2.docs[0].id).toEqual(withRelationship.id);
});
});
expect(result.docs[0].id).toEqual(withRelationship.id);
// Second relationship
const result2 = await payload.find<WithLocalizedRelationship>({
collection: withLocalizedRelSlug,
where: {
'localizedRelationHasManyField.title': {
equals: localizedRelation2.title,
},
},
});
expect(result2.docs[0].id).toEqual(withRelationship.id);
});
it('specific locale', async () => {
const result = await payload.find<WithLocalizedRelationship>({
collection: withLocalizedRelSlug,
locale: spanishLocale,
where: {
'localizedRelationHasManyField.title': {
equals: relationSpanishTitle,
},
},
});
expect(result.docs[0].id).toEqual(withRelationship.id);
// Second relationship
const result2 = await payload.find<WithLocalizedRelationship>({
collection: withLocalizedRelSlug,
locale: spanishLocale,
where: {
'localizedRelationHasManyField.title': {
equals: relationSpanishTitle2,
},
},
});
expect(result2.docs[0].id).toEqual(withRelationship.id);
});
it('relationship population uses locale', async () => {
const result = await payload.findByID<WithLocalizedRelationship>({
collection: withLocalizedRelSlug,
depth: 1,
id: withRelationship.id,
locale: spanishLocale,
});
expect((result.localizedRelationship as LocalizedPost).title).toEqual(relationSpanishTitle);
});
it('all locales', async () => {
const queryRelation = (where: Where) => {
return payload.find<WithLocalizedRelationship>({
collection: withLocalizedRelSlug,
locale: 'all',
where,
});
};
const result = await queryRelation({
'localizedRelationHasManyField.title.en': {
equals: relationEnglishTitle,
},
});
expect(result.docs[0].id).toEqual(withRelationship.id);
// First relationship - spanish
const result2 = await queryRelation({
'localizedRelationHasManyField.title.es': {
equals: relationSpanishTitle,
},
});
expect(result2.docs[0].id).toEqual(withRelationship.id);
// Second relationship - english
const result3 = await queryRelation({
'localizedRelationHasManyField.title.en': {
equals: relationEnglishTitle2,
},
});
expect(result3.docs[0].id).toEqual(withRelationship.id);
// Second relationship - spanish
const result4 = await queryRelation({
'localizedRelationHasManyField.title.es': {
equals: relationSpanishTitle2,
},
});
expect(result4.docs[0].id).toEqual(withRelationship.id);
});
describe('relationTo multi hasMany', () => {
it('by id', async () => {
const result = await payload.find<WithLocalizedRelationship>({
collection: withLocalizedRelSlug,
where: {
'localizedRelationMultiRelationToHasMany.value': {
equals: localizedRelation.id,
},
},
});
describe('relationTo multi', () => {
it('by id', async () => {
const result = await payload.find<WithLocalizedRelationship>({
collection: withLocalizedRelSlug,
where: {
'localizedRelationMultiRelationTo.value': {
equals: localizedRelation.id,
},
},
});
expect(result.docs[0].id).toEqual(withRelationship.id);
expect(result.docs[0].id).toEqual(withRelationship.id);
// Second relationship
const result2 = await payload.find<WithLocalizedRelationship>({
collection: withLocalizedRelSlug,
locale: spanishLocale,
where: {
'localizedRelationMultiRelationTo.value': {
equals: localizedRelation.id,
},
},
});
expect(result2.docs[0].id).toEqual(withRelationship.id);
});
// First relationship - spanish locale
const result2 = await payload.find<WithLocalizedRelationship>({
collection: withLocalizedRelSlug,
locale: spanishLocale,
where: {
'localizedRelationMultiRelationToHasMany.value': {
equals: localizedRelation.id,
},
},
});
describe('relationTo multi hasMany', () => {
it('by id', async () => {
const result = await payload.find<WithLocalizedRelationship>({
collection: withLocalizedRelSlug,
where: {
'localizedRelationMultiRelationToHasMany.value': {
equals: localizedRelation.id,
},
},
});
expect(result2.docs[0].id).toEqual(withRelationship.id);
expect(result.docs[0].id).toEqual(withRelationship.id);
// First relationship - spanish locale
const result2 = await payload.find<WithLocalizedRelationship>({
collection: withLocalizedRelSlug,
locale: spanishLocale,
where: {
'localizedRelationMultiRelationToHasMany.value': {
equals: localizedRelation.id,
},
},
});
expect(result2.docs[0].id).toEqual(withRelationship.id);
// Second relationship
const result3 = await payload.find<WithLocalizedRelationship>({
collection: withLocalizedRelSlug,
where: {
'localizedRelationMultiRelationToHasMany.value': {
equals: localizedRelation2.id,
},
},
});
expect(result3.docs[0].id).toEqual(withRelationship.id);
// Second relationship - spanish locale
const result4 = await payload.find<WithLocalizedRelationship>({
collection: withLocalizedRelSlug,
where: {
'localizedRelationMultiRelationToHasMany.value': {
equals: localizedRelation2.id,
},
},
});
expect(result4.docs[0].id).toEqual(withRelationship.id);
});
// Second relationship
const result3 = await payload.find<WithLocalizedRelationship>({
collection: withLocalizedRelSlug,
where: {
'localizedRelationMultiRelationToHasMany.value': {
equals: localizedRelation2.id,
},
},
});
expect(result3.docs[0].id).toEqual(withRelationship.id);
// Second relationship - spanish locale
const result4 = await payload.find<WithLocalizedRelationship>({
collection: withLocalizedRelSlug,
where: {
'localizedRelationMultiRelationToHasMany.value': {
equals: localizedRelation2.id,
},
},
});
expect(result4.docs[0].id).toEqual(withRelationship.id);
});
});
});

View File

@@ -63,7 +63,7 @@ export interface LocalizedRequired {
export interface WithLocalizedRelationship {
id: string;
localizedRelationship?: string | LocalizedPost;
localizedRelationHasManyField?: (string | LocalizedPost)[];
localizedRelationHasManyField?: string[] | LocalizedPost[];
localizedRelationMultiRelationTo?:
| {
value: string | LocalizedPost;
@@ -73,16 +73,27 @@ export interface WithLocalizedRelationship {
value: string | Dummy;
relationTo: 'dummy';
};
localizedRelationMultiRelationToHasMany?: (
| {
value: string | LocalizedPost;
relationTo: 'localized-posts';
}
| {
value: string | Dummy;
relationTo: 'dummy';
}
)[];
localizedRelationMultiRelationToHasMany?:
| (
| {
value: string;
relationTo: 'localized-posts';
}
| {
value: string;
relationTo: 'dummy';
}
)[]
| (
| {
value: LocalizedPost;
relationTo: 'localized-posts';
}
| {
value: Dummy;
relationTo: 'dummy';
}
)[];
createdAt: string;
updatedAt: string;
}
@@ -96,3 +107,44 @@ export interface Dummy {
createdAt: string;
updatedAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "relationship-localized".
*/
export interface RelationshipLocalized {
id: string;
relationship?: string | LocalizedPost;
relationshipHasMany?: string[] | LocalizedPost[];
relationMultiRelationTo?:
| {
value: string | LocalizedPost;
relationTo: 'localized-posts';
}
| {
value: string | Dummy;
relationTo: 'dummy';
};
relationMultiRelationToHasMany?:
| (
| {
value: string;
relationTo: 'localized-posts';
}
| {
value: string;
relationTo: 'dummy';
}
)[]
| (
| {
value: LocalizedPost;
relationTo: 'localized-posts';
}
| {
value: Dummy;
relationTo: 'dummy';
}
)[];
createdAt: string;
updatedAt: string;
}