feat: add populate property to Local / REST API (#8969)

### What?
Adds `populate` property to Local API and REST API operations that can
be used to specify `select` for a specific collection when it's
populated
```ts
const result = await payload.findByID({
  populate: {
   // type safe if you have generated types
    posts: {
      text: true,
    },
  },
  collection: 'pages',
  depth: 1,
  id: aboutPage.id,
})

result.relatedPost // only has text and id properties
``` 

```ts
fetch('https://localhost:3000/api/pages?populate[posts][text]=true') // highlight-line
  .then((res) => res.json())
  .then((data) => console.log(data))
```

It also overrides
[`defaultPopulate`](https://github.com/payloadcms/payload/pull/8934)

Ensures `defaultPopulate` doesn't affect GraphQL.

### How?
Implements the property for all operations that have the `depth`
argument.
This commit is contained in:
Sasha
2024-11-06 20:50:19 +02:00
committed by GitHub
parent 147d28e62c
commit a22c0e62fa
67 changed files with 487 additions and 47 deletions

View File

@@ -11,13 +11,14 @@ export const Pages: CollectionConfig<'pages'> = {
defaultPopulate: {
slug: true,
},
access: { read: () => true },
fields: [
{
name: 'content',
type: 'blocks',
blocks: [
{
slug: 'cta',
slug: 'introduction',
fields: [
{
name: 'title',
@@ -80,5 +81,9 @@ export const Pages: CollectionConfig<'pages'> = {
type: 'text',
required: true,
},
{
name: 'additional',
type: 'text',
},
],
}

View File

@@ -1612,24 +1612,26 @@ describe('Select', () => {
})
})
describe('defaultPopulate', () => {
describe('populate / defaultPopulate', () => {
let homePage: Page
let aboutPage: Page
let expectedHomePage: { id: number | string; slug: string }
let expectedHomePageOverride: { additional: string; id: number | string }
beforeAll(async () => {
homePage = await payload.create({
depth: 0,
collection: 'pages',
data: { content: [], slug: 'home' },
data: { content: [], slug: 'home', additional: 'additional-data' },
})
expectedHomePage = { id: homePage.id, slug: homePage.slug }
expectedHomePageOverride = { id: homePage.id, additional: homePage.additional }
aboutPage = await payload.create({
depth: 0,
collection: 'pages',
data: {
content: [
{
blockType: 'cta',
blockType: 'introduction',
richTextSlate: [
{
type: 'relationship',
@@ -1745,6 +1747,151 @@ describe('Select', () => {
expect(richTextLexicalRel.value).toMatchObject(expectedHomePage)
expect(richTextSlateRel.value).toMatchObject(expectedHomePage)
})
it('graphQL - should retrieve fields against defaultPopulate', async () => {
const query = `query {
Pages {
docs {
id,
content {
... on Introduction {
link {
doc {
id,
additional,
slug,
}
},
richTextLexical(depth: 1)
richTextSlate(depth: 1)
}
}
}
}
}`
const {
data: {
Pages: {
docs: [
{
content: [
{
link,
richTextSlate: [richTextSlateRel],
richTextLexical: {
root: {
children: [richTextLexicalRel],
},
},
},
],
},
],
},
},
} = await restClient
.GRAPHQL_POST({
body: JSON.stringify({ query }),
})
.then((res) => res.json())
expect(link.doc).toMatchObject({
id: homePage.id,
additional: homePage.additional,
slug: homePage.slug,
})
expect(richTextLexicalRel.value).toMatchObject(homePage)
expect(richTextSlateRel.value).toMatchObject(homePage)
})
it('local API - should populate and override defaultSelect select shape from the populate arg', async () => {
const result = await payload.findByID({
populate: {
pages: {
additional: true,
},
},
collection: 'pages',
depth: 1,
id: aboutPage.id,
})
const {
content: [
{
link: { doc, docHasManyPoly, docMany, docPoly },
richTextSlate: [richTextSlateRel],
richTextLexical: {
root: {
children: [richTextLexicalRel],
},
},
},
],
} = result
expect(doc).toStrictEqual(expectedHomePageOverride)
expect(docMany).toStrictEqual([expectedHomePageOverride])
expect(docPoly).toStrictEqual({
relationTo: 'pages',
value: expectedHomePageOverride,
})
expect(docHasManyPoly).toStrictEqual([
{
relationTo: 'pages',
value: expectedHomePageOverride,
},
])
expect(richTextLexicalRel.value).toStrictEqual(expectedHomePageOverride)
expect(richTextSlateRel.value).toStrictEqual(expectedHomePageOverride)
})
it('rEST API - should populate and override defaultSelect select shape from the populate arg', async () => {
const result = await restClient
.GET(`/pages/${aboutPage.id}`, {
query: {
populate: {
pages: {
additional: true,
},
},
depth: 1,
},
})
.then((res) => res.json())
const {
content: [
{
link: { doc, docHasManyPoly, docMany, docPoly },
richTextSlate: [richTextSlateRel],
richTextLexical: {
root: {
children: [richTextLexicalRel],
},
},
},
],
} = result
expect(doc).toMatchObject(expectedHomePageOverride)
expect(docMany).toMatchObject([expectedHomePageOverride])
expect(docPoly).toMatchObject({
relationTo: 'pages',
value: expectedHomePageOverride,
})
expect(docHasManyPoly).toMatchObject([
{
relationTo: 'pages',
value: expectedHomePageOverride,
},
])
expect(richTextLexicalRel.value).toMatchObject(expectedHomePageOverride)
expect(richTextSlateRel.value).toMatchObject(expectedHomePageOverride)
})
})
})

View File

@@ -297,10 +297,11 @@ export interface Page {
| null;
id?: string | null;
blockName?: string | null;
blockType: 'cta';
blockType: 'introduction';
}[]
| null;
slug: string;
additional?: string | null;
updatedAt: string;
createdAt: string;
}
@@ -605,7 +606,7 @@ export interface PagesSelect<T extends boolean = true> {
content?:
| T
| {
cta?:
introduction?:
| T
| {
title?: T;
@@ -625,6 +626,7 @@ export interface PagesSelect<T extends boolean = true> {
};
};
slug?: T;
additional?: T;
updatedAt?: T;
createdAt?: T;
}