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

fix(gatsby): nodeModel.findAll supports v5 sort argument structure #37477

Merged
merged 2 commits into from
Jan 17, 2023
Merged
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
45 changes: 45 additions & 0 deletions packages/gatsby/src/schema/__tests__/node-model.js
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
}