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 UUID's breaking relationship filters with POSTGRES #9155

Merged
merged 4 commits into from
Jun 24, 2024
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
5 changes: 5 additions & 0 deletions .changeset/fix-uuid-rel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@keystone-6/core': patch
---

Fix malformed uuid's from breaking relationship filters when using POSTGRESQL
2 changes: 1 addition & 1 deletion .changeset/light-lamps-eat.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
"@keystone-6/core": minor
'@keystone-6/core': minor
---

Add exports for internal AdminUI pagination components `Pagination`, `PaginationLabel` and `usePaginationParams` for use in custom pages
68 changes: 68 additions & 0 deletions examples/custom-id/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ type Order {
assignedTo: Person
options(where: OptionWhereInput! = {}, orderBy: [OptionOrderByInput!]! = [], take: Int, skip: Int! = 0, cursor: OptionWhereUniqueInput): [Option!]
optionsCount(where: OptionWhereInput! = {}): Int
products(where: ProductWhereInput! = {}, orderBy: [ProductOrderByInput!]! = [], take: Int, skip: Int! = 0, cursor: ProductWhereUniqueInput): [Product!]
productsCount(where: ProductWhereInput! = {}): Int
orderedAt: DateTime
}

Expand All @@ -193,6 +195,7 @@ input OrderWhereInput {
description: StringFilter
assignedTo: PersonWhereInput
options: OptionManyRelationFilter
products: ProductManyRelationFilter
orderedAt: DateTimeNullableFilter
}

Expand All @@ -202,6 +205,12 @@ input OptionManyRelationFilter {
none: OptionWhereInput
}

input ProductManyRelationFilter {
every: ProductWhereInput
some: ProductWhereInput
none: ProductWhereInput
}

input OrderOrderByInput {
id: OrderDirection
description: OrderDirection
Expand All @@ -212,6 +221,7 @@ input OrderUpdateInput {
description: String
assignedTo: PersonRelateToOneForUpdateInput
options: OptionRelateToManyForUpdateInput
products: ProductRelateToManyForUpdateInput
orderedAt: DateTime
}

Expand All @@ -222,6 +232,13 @@ input OptionRelateToManyForUpdateInput {
connect: [OptionWhereUniqueInput!]
}

input ProductRelateToManyForUpdateInput {
disconnect: [ProductWhereUniqueInput!]
set: [ProductWhereUniqueInput!]
create: [ProductCreateInput!]
connect: [ProductWhereUniqueInput!]
}

input OrderUpdateArgs {
where: OrderWhereUniqueInput!
data: OrderUpdateInput!
Expand All @@ -231,6 +248,7 @@ input OrderCreateInput {
description: String
assignedTo: PersonRelateToOneForCreateInput
options: OptionRelateToManyForCreateInput
products: ProductRelateToManyForCreateInput
orderedAt: DateTime
}

Expand All @@ -239,6 +257,11 @@ input OptionRelateToManyForCreateInput {
connect: [OptionWhereUniqueInput!]
}

input ProductRelateToManyForCreateInput {
create: [ProductCreateInput!]
connect: [ProductWhereUniqueInput!]
}

type Option {
id: ID!
description: String
Expand Down Expand Up @@ -274,6 +297,42 @@ input OptionCreateInput {
description: String
}

type Product {
id: ID!
sku: String
}

input ProductWhereUniqueInput {
id: ID
sku: String
}

input ProductWhereInput {
AND: [ProductWhereInput!]
OR: [ProductWhereInput!]
NOT: [ProductWhereInput!]
id: IDFilter
sku: StringFilter
}

input ProductOrderByInput {
id: OrderDirection
sku: OrderDirection
}

input ProductUpdateInput {
sku: String
}

input ProductUpdateArgs {
where: ProductWhereUniqueInput!
data: ProductUpdateInput!
}

input ProductCreateInput {
sku: String
}

"""
The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).
"""
Expand Down Expand Up @@ -304,6 +363,12 @@ type Mutation {
updateOptions(data: [OptionUpdateArgs!]!): [Option]
deleteOption(where: OptionWhereUniqueInput!): Option
deleteOptions(where: [OptionWhereUniqueInput!]!): [Option]
createProduct(data: ProductCreateInput!): Product
createProducts(data: [ProductCreateInput!]!): [Product]
updateProduct(where: ProductWhereUniqueInput!, data: ProductUpdateInput!): Product
updateProducts(data: [ProductUpdateArgs!]!): [Product]
deleteProduct(where: ProductWhereUniqueInput!): Product
deleteProducts(where: [ProductWhereUniqueInput!]!): [Product]
}

type Query {
Expand All @@ -319,6 +384,9 @@ type Query {
options(where: OptionWhereInput! = {}, orderBy: [OptionOrderByInput!]! = [], take: Int, skip: Int! = 0, cursor: OptionWhereUniqueInput): [Option!]
option(where: OptionWhereUniqueInput!): Option
optionsCount(where: OptionWhereInput! = {}): Int
products(where: ProductWhereInput! = {}, orderBy: [ProductOrderByInput!]! = [], take: Int, skip: Int! = 0, cursor: ProductWhereUniqueInput): [Product!]
product(where: ProductWhereUniqueInput!): Product
productsCount(where: ProductWhereInput! = {}): Int
keystone: KeystoneMeta!
}

Expand Down
7 changes: 7 additions & 0 deletions examples/custom-id/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ model Order {
assignedTo Person? @relation("Order_assignedTo", fields: [assignedToId], references: [id])
assignedToId String? @map("assignedTo")
options Option[] @relation("Order_options")
products Product[] @relation("Order_products")
orderedAt DateTime?

@@index([assignedToId])
Expand All @@ -45,3 +46,9 @@ model Option {
description String @default("")
from_Order_options Order[] @relation("Order_options")
}

model Product {
id String @id @default(uuid())
sku String @unique @default("")
from_Order_products Order[] @relation("Order_products")
}
10 changes: 10 additions & 0 deletions examples/custom-id/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export const lists = {
description: text({ validation: { isRequired: true } }),
assignedTo: relationship({ ref: 'Person', many: false }),
options: relationship({ ref: 'Option', many: true }),
products: relationship({ ref: 'Product', many: true }),
orderedAt: timestamp(),
},
hooks: {
Expand All @@ -65,4 +66,13 @@ export const lists = {
},
},
}),
Product: list({
access: allowAll,
db: {
idField: { kind: 'uuid' },
},
fields: {
sku: text({ validation: { isRequired: true }, isIndexed: 'unique' }),
},
}),
} satisfies Lists
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,6 @@ function ListPage ({ listKey }: ListPageProps) {
const searchParam = typeof query.search === 'string' ? query.search : ''
const [searchString, updateSearchString] = useState(searchParam)
const search = useFilter(searchParam, list, searchFields)

const updateSearch = (value: string) => {
const { search, ...queries } = query

Expand All @@ -176,36 +175,39 @@ function ListPage ({ listKey }: ListPageProps) {
}

const selectedFields = useSelectedFields(list, listViewFieldModesByField)

const {
data: newData,
error: newError,
refetch,
} = useQuery(
useMemo(() => {
const selectedGqlFields = [...selectedFields]
.map(fieldPath => {
return list.fields[fieldPath].controller.graphqlSelection
})
.map(fieldPath => list.fields[fieldPath].controller.graphqlSelection)
.join('\n')

// TODO: FIXME: this is bad
return gql`
query ($where: ${list.gqlNames.whereInputName}, $take: Int!, $skip: Int!, $orderBy: [${
list.gqlNames.listOrderName
}!]) {
items: ${
list.gqlNames.listQueryName
}(where: $where, take: $take, skip: $skip, orderBy: $orderBy) {
${
// TODO: maybe namespace all the fields instead of doing this
selectedFields.has('id') ? '' : 'id'
query (
$where: ${list.gqlNames.whereInputName},
$take: Int!,
$skip: Int!,
$orderBy: [${list.gqlNames.listOrderName}!]
) {
items: ${list.gqlNames.listQueryName}(
where: $where,
take: $take,
skip: $skip,
orderBy: $orderBy
) {
${
// TODO: maybe namespace all the fields instead of doing this
selectedFields.has('id') ? '' : 'id'
}
${selectedGqlFields}
}
${selectedGqlFields}
count: ${list.gqlNames.listQueryCountName}(where: $where)
}
count: ${list.gqlNames.listQueryCountName}(where: $where)
}
`
`
}, [list, selectedFields]),
{
fetchPolicy: 'cache-and-network',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,24 @@
/** @jsx jsx */

import 'intersection-observer'
import { type RefObject, useEffect, useMemo, useState, createContext, useContext, useRef } from 'react'
import {
type RefObject,
useEffect,
useMemo,
useState,
createContext,
useContext,
useRef
} from 'react'

import { jsx } from '@keystone-ui/core'
import { MultiSelect, Select, selectComponents } from '@keystone-ui/fields'
import type { ListMeta } from '../../../../types'
import { type ListMeta } from '../../../../types'
import {
type TypedDocumentNode,
ApolloClient,
gql,
InMemoryCache,
type TypedDocumentNode,
useApolloClient,
useQuery,
} from '../../../../admin-ui/apollo'
Expand Down Expand Up @@ -57,24 +65,35 @@ function isBigInt (x: string) {
}
}

export function useFilter (search: string, list: ListMeta, searchFields: string[]) {
// TODO: this is unfortunate, remove in breaking change?
function isUuid (x: unknown) {
if (typeof x !== 'string') return
if (x.length !== 36) return
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(x)
}

export function useFilter (value: string, list: ListMeta, searchFields: string[]) {
return useMemo(() => {
const trimmedSearch = search.trim()
const trimmedSearch = value.trim()
if (!trimmedSearch.length) return { OR: [] }

const conditions: Record<string, any>[] = []
const { type: idFieldType } = (list.fields.id.fieldMeta as any) ?? {}
if (idFieldType === 'String') {
conditions.push({ id: { equals: trimmedSearch } })
} else if (idFieldType === 'Int' && isInt(trimmedSearch)) {
const idField = list.fields.id.fieldMeta as { type: string, kind: string }
console.error({ idField, value, meta: list.fields.id.fieldMeta })

if (idField.type === 'String') {
// TODO: remove in breaking change?
if (idField.kind === 'uuid') {
if (isUuid(value)) {
conditions.push({ id: { equals: trimmedSearch } })
}
} else {
conditions.push({ id: { equals: trimmedSearch } })
}
} else if (idField.type === 'Int' && isInt(trimmedSearch)) {
conditions.push({ id: { equals: Number(trimmedSearch) } })
} else if (idFieldType === 'BigInt' && isBigInt(trimmedSearch)) {
conditions.push({ id: { equals: trimmedSearch } })
} else if (idFieldType === 'UUID') {
conditions.push({ id: { equals: trimmedSearch } }) // TODO: remove in breaking change?
}

if ((list.fields.id.fieldMeta as any)?.type === 'String') {
} else if (idField.type === 'BigInt' && isBigInt(trimmedSearch)) {
conditions.push({ id: { equals: trimmedSearch } })
}

Expand All @@ -89,7 +108,7 @@ export function useFilter (search: string, list: ListMeta, searchFields: string[
}

return { OR: conditions }
}, [search, list, searchFields])
}, [value, list, searchFields])
}

const idFieldAlias = '____id____'
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/lib/id-field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ function isString (x: IDType) {
return x
}

// TODO: this should be on the user, remove in breaking change
// TODO: this should be on the user, remove in breaking change?
function isUuid (x: IDType) {
if (typeof x !== 'string') return
if (x === '') return
Expand Down
39 changes: 20 additions & 19 deletions packages/fields-document/src/DocumentEditor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,26 +41,27 @@ import {
createDocumentEditor
} from './editor-shared'

const orderedListStyles = ['lower-roman', 'decimal', 'lower-alpha']
const unorderedListStyles = ['square', 'disc', 'circle']

let styles: any = {
const styles = {
flex: 1,
}

let listDepth = 10

while (listDepth--) {
let arr = Array.from({ length: listDepth })
if (arr.length) {
styles[arr.map(() => `ol`).join(' ')] = {
listStyle: orderedListStyles[listDepth % 3],
}
styles[arr.map(() => `ul`).join(' ')] = {
listStyle: unorderedListStyles[listDepth % 3],
}
}
}
'ol ol ol ol ol ol ol ol ol': { listStyle: 'lower-roman' },
'ol ol ol ol ol ol ol ol': { listStyle: 'lower-alpha' },
'ol ol ol ol ol ol ol': { listStyle: 'decimal' },
'ol ol ol ol ol ol': { listStyle: 'lower-roman' },
'ol ol ol ol ol': { listStyle: 'lower-alpha' },
'ol ol ol ol': { listStyle: 'decimal' },
'ol ol ol': { listStyle: 'lower-roman' },
'ol ol': { listStyle: 'lower-alpha' },
'ol': { listStyle: 'decimal' },
'ul ul ul ul ul ul ul ul ul': { listStyle: 'square' },
'ul ul ul ul ul ul ul ul': { listStyle: 'circle' },
'ul ul ul ul ul ul ul': { listStyle: 'disc' },
'ul ul ul ul ul ul': { listStyle: 'square' },
'ul ul ul ul ul': { listStyle: 'circle' },
'ul ul ul ul': { listStyle: 'disc' },
'ul ul ul': { listStyle: 'square' },
'ul ul': { listStyle: 'circle' },
'ul': { listStyle: 'disc' }
} as const

const HOTKEYS: Record<string, Mark> = {
'mod+b': 'bold',
Expand Down