diff --git a/.changeset/add-error-message.md b/.changeset/add-error-message.md new file mode 100644 index 00000000000..ddab1bd84b0 --- /dev/null +++ b/.changeset/add-error-message.md @@ -0,0 +1,5 @@ +--- +"@keystone-6/core": minor +--- + +Adds error messages for GraphQL errors on the List view in the AdminUI diff --git a/.changeset/fix-id-filters.md b/.changeset/fix-id-filters.md new file mode 100644 index 00000000000..31ec8609cfa --- /dev/null +++ b/.changeset/fix-id-filters.md @@ -0,0 +1,5 @@ +--- +"@keystone-6/core": patch +--- + +Fixes `Input error: only a int can be passed to id filters` for AdminUI diff --git a/packages/core/src/___internal-do-not-use-will-break-in-patch/admin-ui/id-field-view.tsx b/packages/core/src/___internal-do-not-use-will-break-in-patch/admin-ui/id-field-view.tsx index 66bf43c6f45..6bdf33da6dd 100644 --- a/packages/core/src/___internal-do-not-use-will-break-in-patch/admin-ui/id-field-view.tsx +++ b/packages/core/src/___internal-do-not-use-will-break-in-patch/admin-ui/id-field-view.tsx @@ -3,7 +3,7 @@ import { jsx } from '@keystone-ui/core'; import { FieldContainer, FieldLabel, TextInput } from '@keystone-ui/fields'; -import { +import type { CardValueComponent, CellComponent, FieldController, @@ -31,13 +31,12 @@ export const CardValue: CardValueComponent = ({ item, field }) => { export const controller = ( config: FieldControllerConfig -): FieldController & { idFieldKind: IdFieldConfig['kind'] } => { +): FieldController => { return { path: config.path, label: config.label, description: config.description, graphqlSelection: config.path, - idFieldKind: config.fieldMeta.kind, defaultValue: undefined, deserialize: () => {}, serialize: () => ({}), diff --git a/packages/core/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ListPage/index.tsx b/packages/core/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ListPage/index.tsx index c36ad622984..6b6aa8f5960 100644 --- a/packages/core/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ListPage/index.tsx +++ b/packages/core/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ListPage/index.tsx @@ -24,6 +24,7 @@ import { CellLink } from '../../../../admin-ui/components'; import { PageContainer, HEADER_HEIGHT } from '../../../../admin-ui/components/PageContainer'; import { Pagination, PaginationLabel } from '../../../../admin-ui/components/Pagination'; import { useList } from '../../../../admin-ui/context'; +import { GraphQLErrorNotice } from '../../../../admin-ui/components/GraphQLErrorNotice'; import { Link, useRouter } from '../../../../admin-ui/router'; import { useFilter } from '../../../../fields/types/relationship/views/RelationshipSelect'; import { CreateButtonLink } from '../../../../admin-ui/components/CreateButtonLink'; @@ -146,7 +147,7 @@ const ListPage = ({ listKey }: ListPageProps) => { const metaQuery = useQuery(listMetaGraphqlQuery, { variables: { listKey } }); - let { listViewFieldModesByField, filterableFields, orderableFields } = useMemo(() => { + const { listViewFieldModesByField, filterableFields, orderableFields } = useMemo(() => { const listViewFieldModesByField: Record = {}; const orderableFields = new Set(); const filterableFields = new Set(); @@ -183,26 +184,28 @@ const ListPage = ({ listKey }: ListPageProps) => { } }; - let selectedFields = useSelectedFields(list, listViewFieldModesByField); + const selectedFields = useSelectedFields(list, listViewFieldModesByField); - let { + const { data: newData, error: newError, refetch, } = useQuery( useMemo(() => { - let selectedGqlFields = [...selectedFields] + const selectedGqlFields = [...selectedFields] .map(fieldPath => { return 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) { + }(where: $where, take: $take, skip: $skip, orderBy: $orderBy) { ${ // TODO: maybe namespace all the fields instead of doing this selectedFields.has('id') ? '' : 'id' @@ -226,14 +229,12 @@ const ListPage = ({ listKey }: ListPageProps) => { } ); - let [dataState, setDataState] = useState({ data: newData, error: newError }); - + const [dataState, setDataState] = useState({ data: newData, error: newError }); if (newData && dataState.data !== newData) { setDataState({ data: newData, error: newError }); } const { data, error } = dataState; - const dataGetter = makeDataGetter< DeepNullable<{ count: number; items: { id: string; [key: string]: any }[] }> >(data, error?.graphQLErrors); @@ -260,10 +261,11 @@ const ListPage = ({ listKey }: ListPageProps) => { return ( } title={list.label}> - {metaQuery.error ? ( - // TODO: Show errors nicely and with information - 'Error...' - ) : data && metaQuery.data ? ( + {error?.graphQLErrors.length || error?.networkError ? ( + + ) : null} + {metaQuery.error ? 'Error...' : null} + {data && metaQuery.data ? ( {list.description !== null && (

{list.description}

diff --git a/packages/core/src/fields/types/relationship/views/RelationshipSelect.tsx b/packages/core/src/fields/types/relationship/views/RelationshipSelect.tsx index fd618d31b0b..09bc851c21f 100644 --- a/packages/core/src/fields/types/relationship/views/RelationshipSelect.tsx +++ b/packages/core/src/fields/types/relationship/views/RelationshipSelect.tsx @@ -44,13 +44,37 @@ function useDebouncedValue(value: T, limitMs: number) { return debouncedValue; } +function isInt(x: string) { + return Number.isInteger(Number(x)); +} + +function isBigInt(x: string) { + try { + BigInt(x); + return true; + } catch { + return true; + } +} + export function useFilter(search: string, list: ListMeta, searchFields: string[]) { return useMemo(() => { - if (!search.length) return { OR: [] }; - const trimmedSearch = search.trim(); + if (!trimmedSearch.length) return { OR: [] }; + const conditions: Record[] = []; - if (trimmedSearch.length > 0) { + const { type: idFieldType } = (list.fields.id.fieldMeta as any) ?? {}; + if (idFieldType === 'String') { + conditions.push({ id: { equals: trimmedSearch } }); + } else if (idFieldType === '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') { conditions.push({ id: { equals: trimmedSearch } }); } diff --git a/packages/core/src/lib/id-field.ts b/packages/core/src/lib/id-field.ts index e164fde68a8..0a17feec621 100644 --- a/packages/core/src/lib/id-field.ts +++ b/packages/core/src/lib/id-field.ts @@ -144,31 +144,31 @@ export function idFieldType( config: IdFieldConfig, isSingleton: boolean ): FieldTypeFunc { - const { kind, type, default_ } = unpack(config); + const { kind, type: type_, default_ } = unpack(config); const parseTypeFn = { Int: isInt, BigInt: isBigInt, String: isString, UUID: isUuid, // TODO: remove in breaking change - }[kind === 'uuid' ? 'UUID' : type]; + }[kind === 'uuid' ? 'UUID' : type_]; function parse(value: IDType) { const result = parseTypeFn(value); if (result === undefined) { - throw userInputError(`Only a ${type.toLowerCase()} can be passed to id filters`); + throw userInputError(`Only a ${type_.toLowerCase()} can be passed to id filters`); } return result; } return meta => { - if (kind === 'autoincrement' && type === 'BigInt' && meta.provider === 'sqlite') { - throw new Error(`{ kind: ${kind}, type: ${type} } is not supported by SQLite`); + if (meta.provider === 'sqlite' && kind === 'autoincrement' && type_ === 'BigInt') { + throw new Error(`{ kind: ${kind}, type: ${type_} } is not supported by SQLite`); } return fieldType({ kind: 'scalar', mode: 'required', - scalar: type, + scalar: type_, nativeType: NATIVE_TYPES[meta.provider]?.[kind], default: isSingleton ? undefined : default_, })({ @@ -195,7 +195,7 @@ export function idFieldType( }, }), views: '@keystone-6/core/___internal-do-not-use-will-break-in-patch/admin-ui/id-field-view', - getAdminMeta: () => ({ kind }), + getAdminMeta: () => ({ kind, type: type_ }), ui: { createView: { fieldMode: 'hidden', diff --git a/tests/api-tests/admin-meta.test.ts b/tests/api-tests/admin-meta.test.ts index 9cfb59df492..b1bca815cd2 100644 --- a/tests/api-tests/admin-meta.test.ts +++ b/tests/api-tests/admin-meta.test.ts @@ -70,6 +70,7 @@ test( description: null, fieldMeta: { kind: 'cuid', + type: 'String', }, isNonNull: [], itemView: {