Skip to content

Commit

Permalink
fix(gatsby): nodeModel.findAll supports v5 sort argument structure (#…
Browse files Browse the repository at this point in the history
…37477)

* add failing test

* fix(gatsby): nodeModel.findAll supports v5 sort argument structure
  • Loading branch information
pieh committed Jan 17, 2023
1 parent 87487ba commit 949132b
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 73 deletions.
45 changes: 45 additions & 0 deletions packages/gatsby/src/schema/__tests__/node-model.js
Expand Up @@ -676,6 +676,7 @@ describe(`NodeModel`, () => {
nested: {
foo: `foo1`,
bar: `bar1`,
sort_order: 10,
},
internal: {
type: `Test`,
Expand All @@ -689,6 +690,7 @@ describe(`NodeModel`, () => {
nested: {
foo: `foo2`,
bar: `bar2`,
sort_order: 9,
},
internal: {
type: `Test`,
Expand Down Expand Up @@ -1122,6 +1124,49 @@ describe(`NodeModel`, () => {
expect(result2[0].id).toBe(`id2`)
})

it(`findAll sorts using v5 sort fields`, async () => {
nodeModel.replaceFiltersCache()

const { entries } = await nodeModel.findAll(
{
query: {
sort: [{ nested: { sort_order: `asc` } }],
},
type: `Test`,
},
{ path: `/` }
)

const result = Array.from(entries)

expect(result.length).toBe(2)
expect(result[0].id).toBe(`id2`)
expect(result[1].id).toBe(`id1`)
})

it(`findAll sorts using legacy (pre-v5) sort fields`, async () => {
nodeModel.replaceFiltersCache()

const { entries } = await nodeModel.findAll(
{
query: {
sort: {
fields: [`nested.sort_order`],
order: [`asc`],
},
},
type: `Test`,
},
{ path: `/` }
)

const result = Array.from(entries)

expect(result.length).toBe(2)
expect(result[0].id).toBe(`id2`)
expect(result[1].id).toBe(`id1`)
})

it(`always uses a custom resolvers for query fields`, async () => {
// See https://github.com/gatsbyjs/gatsby/issues/27368
nodeModel.replaceFiltersCache()
Expand Down
10 changes: 8 additions & 2 deletions packages/gatsby/src/schema/node-model.js
Expand Up @@ -16,7 +16,11 @@ import { store } from "../redux"
import { getDataStore, getNode, getTypes } from "../datastore"
import { GatsbyIterable, isIterable } from "../datastore/common/iterable"
import { wrapNode, wrapNodes } from "../utils/detect-node-mutations"
import { toNodeTypeNames, fieldNeedToResolve } from "./utils"
import {
toNodeTypeNames,
fieldNeedToResolve,
maybeConvertSortInputObjectToSortPath,
} from "./utils"
import { getMaybeResolvedValue } from "./resolvers"

type TypeOrTypeName = string | GraphQLOutputType
Expand Down Expand Up @@ -193,7 +197,7 @@ class LocalNodeModel {
}

async _query(args) {
const { query = {}, type, stats, tracer } = args || {}
let { query = {}, type, stats, tracer } = args || {}

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

query = maybeConvertSortInputObjectToSortPath(query)

let materializationActivity
if (tracer) {
materializationActivity = reporter.phantomActivity(`Materialization`, {
Expand Down
78 changes: 7 additions & 71 deletions packages/gatsby/src/schema/resolvers.ts
Expand Up @@ -16,7 +16,6 @@ import {
SelectionNode,
FieldNode,
} from "graphql"
import isPlainObject from "lodash/isPlainObject"
import { Path } from "graphql/jsutils/Path"
import reporter from "gatsby-cli/lib/reporter"
import { pathToArray } from "../query/utils"
Expand All @@ -29,48 +28,18 @@ import {
import { IGatsbyNode } from "../redux/types"
import { IQueryResult } from "../datastore/types"
import { GatsbyIterable } from "../datastore/common/iterable"
import { getResolvedFields, fieldPathNeedToResolve } from "./utils"
import {
getResolvedFields,
fieldPathNeedToResolve,
INestedPathStructureNode,
pathObjectToPathString,
} from "./utils"

type ResolvedLink = IGatsbyNode | Array<IGatsbyNode> | null

type nestedListOfStrings = Array<string | nestedListOfStrings>
type nestedListOfNodes = Array<IGatsbyNode | nestedListOfNodes>

type NestedPathStructure = INestedPathStructureNode | true | "ASC" | "DESC"

interface INestedPathStructureNode {
[key: string]: NestedPathStructure
}

function pathObjectToPathString(input: INestedPathStructureNode): {
path: string
leaf: any
} {
const path: Array<string> = []
let currentValue: NestedPathStructure | undefined = input
let leaf: any = undefined
while (currentValue) {
if (isPlainObject(currentValue)) {
const entries = Object.entries(currentValue)
if (entries.length !== 1) {
throw new Error(`Invalid field arg`)
}
for (const [key, value] of entries) {
path.push(key)
currentValue = value
}
} else {
leaf = currentValue
currentValue = undefined
}
}

return {
path: path.join(`.`),
leaf,
}
}

export function getMaybeResolvedValue(
node: IGatsbyNode,
field: string | INestedPathStructureNode,
Expand Down Expand Up @@ -113,39 +82,6 @@ export function findOne<TSource, TArgs>(

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

function maybeConvertSortInputObjectToSortPath<TArgs>(
args: PaginatedArgs<TArgs>
): any {
if (!args.sort) {
return args
}

if (_CFLAGS_.GATSBY_MAJOR === `5`) {
let sorts = args.sort
if (!Array.isArray(sorts)) {
sorts = [sorts]
}

const modifiedSort: any = {
fields: [],
order: [],
}

for (const sort of sorts) {
const { path, leaf } = pathObjectToPathString(sort)
modifiedSort.fields.push(path)
modifiedSort.order.push(leaf)
}

return {
...args,
sort: modifiedSort,
}
}

return args
}

export function findManyPaginated<TSource, TArgs>(
typeName: string
): GatsbyResolver<TSource, PaginatedArgs<TArgs>> {
Expand All @@ -169,7 +105,7 @@ export function findManyPaginated<TSource, TArgs>(
const limit = typeof args.limit === `number` ? args.limit + 2 : undefined

const extendedArgs = {
...maybeConvertSortInputObjectToSortPath(args),
...args,
group: group || [],
distinct: distinct || [],
max: max || [],
Expand Down
81 changes: 81 additions & 0 deletions packages/gatsby/src/schema/utils.ts
Expand Up @@ -14,6 +14,7 @@ import {
ObjectTypeComposer,
SchemaComposer,
} from "graphql-compose"
import isPlainObject from "lodash/isPlainObject"

import type { IGatsbyNodePartial } from "../datastore/in-memory/indexing"
import { IGatsbyNode } from "../internal"
Expand Down Expand Up @@ -145,3 +146,83 @@ export function getResolvedFields(
const resolvedNodes = store.getState().resolvedNodesCache.get(typeName)
return resolvedNodes?.get(node.id)
}

type NestedPathStructure = INestedPathStructureNode | true | "ASC" | "DESC"

export interface INestedPathStructureNode {
[key: string]: NestedPathStructure
}

export function pathObjectToPathString(input: INestedPathStructureNode): {
path: string
leaf: any
} {
const path: Array<string> = []
let currentValue: NestedPathStructure | undefined = input
let leaf: any = undefined
while (currentValue) {
if (isPlainObject(currentValue)) {
const entries = Object.entries(currentValue)
if (entries.length !== 1) {
throw new Error(`Invalid field arg`)
}
for (const [key, value] of entries) {
path.push(key)
currentValue = value
}
} else {
leaf = currentValue
currentValue = undefined
}
}

return {
path: path.join(`.`),
leaf,
}
}

export function maybeConvertSortInputObjectToSortPath(args: any): any {
if (!args.sort) {
return args
}

if (_CFLAGS_.GATSBY_MAJOR === `5`) {
// check if it's already in expected format
if (
Array.isArray(args.sort?.fields) &&
Array.isArray(args.sort?.order) &&
args.sort.order.every(
item =>
typeof item === `string` &&
(item.toLowerCase() === `asc` || item.toLowerCase() === `desc`)
)
) {
return args
}

let sorts = args.sort

if (!Array.isArray(sorts)) {
sorts = [sorts]
}

const modifiedSort: any = {
fields: [],
order: [],
}

for (const sort of sorts) {
const { path, leaf } = pathObjectToPathString(sort)
modifiedSort.fields.push(path)
modifiedSort.order.push(leaf)
}

return {
...args,
sort: modifiedSort,
}
}

return args
}

0 comments on commit 949132b

Please sign in to comment.