Skip to content

Commit 00ed53a

Browse files
gatsbybotpieh
andauthored
fix(gatsby): nodeModel.findAll supports v5 sort argument structure (#37477) (#37479)
Co-authored-by: Michal Piechowiak <[email protected]>
1 parent 9ea51e1 commit 00ed53a

File tree

4 files changed

+141
-73
lines changed

4 files changed

+141
-73
lines changed

packages/gatsby/src/schema/__tests__/node-model.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,7 @@ describe(`NodeModel`, () => {
676676
nested: {
677677
foo: `foo1`,
678678
bar: `bar1`,
679+
sort_order: 10,
679680
},
680681
internal: {
681682
type: `Test`,
@@ -689,6 +690,7 @@ describe(`NodeModel`, () => {
689690
nested: {
690691
foo: `foo2`,
691692
bar: `bar2`,
693+
sort_order: 9,
692694
},
693695
internal: {
694696
type: `Test`,
@@ -1122,6 +1124,49 @@ describe(`NodeModel`, () => {
11221124
expect(result2[0].id).toBe(`id2`)
11231125
})
11241126

1127+
it(`findAll sorts using v5 sort fields`, async () => {
1128+
nodeModel.replaceFiltersCache()
1129+
1130+
const { entries } = await nodeModel.findAll(
1131+
{
1132+
query: {
1133+
sort: [{ nested: { sort_order: `asc` } }],
1134+
},
1135+
type: `Test`,
1136+
},
1137+
{ path: `/` }
1138+
)
1139+
1140+
const result = Array.from(entries)
1141+
1142+
expect(result.length).toBe(2)
1143+
expect(result[0].id).toBe(`id2`)
1144+
expect(result[1].id).toBe(`id1`)
1145+
})
1146+
1147+
it(`findAll sorts using legacy (pre-v5) sort fields`, async () => {
1148+
nodeModel.replaceFiltersCache()
1149+
1150+
const { entries } = await nodeModel.findAll(
1151+
{
1152+
query: {
1153+
sort: {
1154+
fields: [`nested.sort_order`],
1155+
order: [`asc`],
1156+
},
1157+
},
1158+
type: `Test`,
1159+
},
1160+
{ path: `/` }
1161+
)
1162+
1163+
const result = Array.from(entries)
1164+
1165+
expect(result.length).toBe(2)
1166+
expect(result[0].id).toBe(`id2`)
1167+
expect(result[1].id).toBe(`id1`)
1168+
})
1169+
11251170
it(`always uses a custom resolvers for query fields`, async () => {
11261171
// See https://github.com/gatsbyjs/gatsby/issues/27368
11271172
nodeModel.replaceFiltersCache()

packages/gatsby/src/schema/node-model.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ import { store } from "../redux"
1616
import { getDataStore, getNode, getTypes } from "../datastore"
1717
import { GatsbyIterable, isIterable } from "../datastore/common/iterable"
1818
import { wrapNode, wrapNodes } from "../utils/detect-node-mutations"
19-
import { toNodeTypeNames, fieldNeedToResolve } from "./utils"
19+
import {
20+
toNodeTypeNames,
21+
fieldNeedToResolve,
22+
maybeConvertSortInputObjectToSortPath,
23+
} from "./utils"
2024
import { getMaybeResolvedValue } from "./resolvers"
2125

2226
type TypeOrTypeName = string | GraphQLOutputType
@@ -193,7 +197,7 @@ class LocalNodeModel {
193197
}
194198

195199
async _query(args) {
196-
const { query = {}, type, stats, tracer } = args || {}
200+
let { query = {}, type, stats, tracer } = args || {}
197201

198202
// We don't support querying union types (yet?), because the combined types
199203
// need not have any fields in common.
@@ -236,6 +240,8 @@ class LocalNodeModel {
236240
}
237241
}
238242

243+
query = maybeConvertSortInputObjectToSortPath(query)
244+
239245
let materializationActivity
240246
if (tracer) {
241247
materializationActivity = reporter.phantomActivity(`Materialization`, {

packages/gatsby/src/schema/resolvers.ts

Lines changed: 7 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import {
1616
SelectionNode,
1717
FieldNode,
1818
} from "graphql"
19-
import isPlainObject from "lodash/isPlainObject"
2019
import { Path } from "graphql/jsutils/Path"
2120
import reporter from "gatsby-cli/lib/reporter"
2221
import { pathToArray } from "../query/utils"
@@ -29,48 +28,18 @@ import {
2928
import { IGatsbyNode } from "../redux/types"
3029
import { IQueryResult } from "../datastore/types"
3130
import { GatsbyIterable } from "../datastore/common/iterable"
32-
import { getResolvedFields, fieldPathNeedToResolve } from "./utils"
31+
import {
32+
getResolvedFields,
33+
fieldPathNeedToResolve,
34+
INestedPathStructureNode,
35+
pathObjectToPathString,
36+
} from "./utils"
3337

3438
type ResolvedLink = IGatsbyNode | Array<IGatsbyNode> | null
3539

3640
type nestedListOfStrings = Array<string | nestedListOfStrings>
3741
type nestedListOfNodes = Array<IGatsbyNode | nestedListOfNodes>
3842

39-
type NestedPathStructure = INestedPathStructureNode | true | "ASC" | "DESC"
40-
41-
interface INestedPathStructureNode {
42-
[key: string]: NestedPathStructure
43-
}
44-
45-
function pathObjectToPathString(input: INestedPathStructureNode): {
46-
path: string
47-
leaf: any
48-
} {
49-
const path: Array<string> = []
50-
let currentValue: NestedPathStructure | undefined = input
51-
let leaf: any = undefined
52-
while (currentValue) {
53-
if (isPlainObject(currentValue)) {
54-
const entries = Object.entries(currentValue)
55-
if (entries.length !== 1) {
56-
throw new Error(`Invalid field arg`)
57-
}
58-
for (const [key, value] of entries) {
59-
path.push(key)
60-
currentValue = value
61-
}
62-
} else {
63-
leaf = currentValue
64-
currentValue = undefined
65-
}
66-
}
67-
68-
return {
69-
path: path.join(`.`),
70-
leaf,
71-
}
72-
}
73-
7443
export function getMaybeResolvedValue(
7544
node: IGatsbyNode,
7645
field: string | INestedPathStructureNode,
@@ -113,39 +82,6 @@ export function findOne<TSource, TArgs>(
11382

11483
type PaginatedArgs<TArgs> = TArgs & { skip?: number; limit?: number; sort: any }
11584

116-
function maybeConvertSortInputObjectToSortPath<TArgs>(
117-
args: PaginatedArgs<TArgs>
118-
): any {
119-
if (!args.sort) {
120-
return args
121-
}
122-
123-
if (_CFLAGS_.GATSBY_MAJOR === `5`) {
124-
let sorts = args.sort
125-
if (!Array.isArray(sorts)) {
126-
sorts = [sorts]
127-
}
128-
129-
const modifiedSort: any = {
130-
fields: [],
131-
order: [],
132-
}
133-
134-
for (const sort of sorts) {
135-
const { path, leaf } = pathObjectToPathString(sort)
136-
modifiedSort.fields.push(path)
137-
modifiedSort.order.push(leaf)
138-
}
139-
140-
return {
141-
...args,
142-
sort: modifiedSort,
143-
}
144-
}
145-
146-
return args
147-
}
148-
14985
export function findManyPaginated<TSource, TArgs>(
15086
typeName: string
15187
): GatsbyResolver<TSource, PaginatedArgs<TArgs>> {
@@ -169,7 +105,7 @@ export function findManyPaginated<TSource, TArgs>(
169105
const limit = typeof args.limit === `number` ? args.limit + 2 : undefined
170106

171107
const extendedArgs = {
172-
...maybeConvertSortInputObjectToSortPath(args),
108+
...args,
173109
group: group || [],
174110
distinct: distinct || [],
175111
max: max || [],

packages/gatsby/src/schema/utils.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
ObjectTypeComposer,
1515
SchemaComposer,
1616
} from "graphql-compose"
17+
import isPlainObject from "lodash/isPlainObject"
1718

1819
import type { IGatsbyNodePartial } from "../datastore/in-memory/indexing"
1920
import { IGatsbyNode } from "../internal"
@@ -145,3 +146,83 @@ export function getResolvedFields(
145146
const resolvedNodes = store.getState().resolvedNodesCache.get(typeName)
146147
return resolvedNodes?.get(node.id)
147148
}
149+
150+
type NestedPathStructure = INestedPathStructureNode | true | "ASC" | "DESC"
151+
152+
export interface INestedPathStructureNode {
153+
[key: string]: NestedPathStructure
154+
}
155+
156+
export function pathObjectToPathString(input: INestedPathStructureNode): {
157+
path: string
158+
leaf: any
159+
} {
160+
const path: Array<string> = []
161+
let currentValue: NestedPathStructure | undefined = input
162+
let leaf: any = undefined
163+
while (currentValue) {
164+
if (isPlainObject(currentValue)) {
165+
const entries = Object.entries(currentValue)
166+
if (entries.length !== 1) {
167+
throw new Error(`Invalid field arg`)
168+
}
169+
for (const [key, value] of entries) {
170+
path.push(key)
171+
currentValue = value
172+
}
173+
} else {
174+
leaf = currentValue
175+
currentValue = undefined
176+
}
177+
}
178+
179+
return {
180+
path: path.join(`.`),
181+
leaf,
182+
}
183+
}
184+
185+
export function maybeConvertSortInputObjectToSortPath(args: any): any {
186+
if (!args.sort) {
187+
return args
188+
}
189+
190+
if (_CFLAGS_.GATSBY_MAJOR === `5`) {
191+
// check if it's already in expected format
192+
if (
193+
Array.isArray(args.sort?.fields) &&
194+
Array.isArray(args.sort?.order) &&
195+
args.sort.order.every(
196+
item =>
197+
typeof item === `string` &&
198+
(item.toLowerCase() === `asc` || item.toLowerCase() === `desc`)
199+
)
200+
) {
201+
return args
202+
}
203+
204+
let sorts = args.sort
205+
206+
if (!Array.isArray(sorts)) {
207+
sorts = [sorts]
208+
}
209+
210+
const modifiedSort: any = {
211+
fields: [],
212+
order: [],
213+
}
214+
215+
for (const sort of sorts) {
216+
const { path, leaf } = pathObjectToPathString(sort)
217+
modifiedSort.fields.push(path)
218+
modifiedSort.order.push(leaf)
219+
}
220+
221+
return {
222+
...args,
223+
sort: modifiedSort,
224+
}
225+
}
226+
227+
return args
228+
}

0 commit comments

Comments
 (0)