Skip to content

Commit

Permalink
Merge pull request #2347 from graphcommerce-org/fix/dynamic-rows
Browse files Browse the repository at this point in the history
Resolve issue where the dynamic rows UI wouldn’t load any definitions
  • Loading branch information
paales authored Aug 5, 2024
2 parents 3d3c776 + 7fa50a2 commit 648da6f
Show file tree
Hide file tree
Showing 10 changed files with 127 additions and 216 deletions.
5 changes: 5 additions & 0 deletions .changeset/afraid-rockets-rule.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@graphcommerce/hygraph-dynamic-rows-ui": patch
---

Resolve issue where the dynamic rows UI wouldn’t load any definitions
126 changes: 51 additions & 75 deletions packages/hygraph-dynamic-rows-ui/components/PropertyPicker.tsx
Original file line number Diff line number Diff line change
@@ -1,91 +1,59 @@
import { ApolloClient, InMemoryCache } from '@apollo/client'
import { useFieldExtension } from '@hygraph/app-sdk-react'
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
import { ApolloClient, gql, InMemoryCache, useQuery } from '@apollo/client'
import { FieldExtensionProps, useFieldExtension } from '@hygraph/app-sdk-react'
import { TextField } from '@mui/material'
import { getIntrospectionQuery, IntrospectionQuery } from 'graphql'
import { useEffect, useMemo, useState } from 'react'
import { createOptionsFromInterfaceObject, objectifyGraphQLInterface } from '../lib'
import { fetchGraphQLInterface } from '../lib/fetchGraphQLInterface'
import { __Field } from '../types'
import { getFieldPaths } from '../lib/getFieldPaths'

function useClient(extension: FieldExtensionProps['extension']) {
return useMemo(
() =>
new ApolloClient({
uri:
typeof extension.config.backend === 'string'
? extension.config.backend
: 'https://graphcommerce.vercel.app/api/graphql', // fallback on the standard GraphCommerce Schema
cache: new InMemoryCache(),
}),
[extension.config.backend],
)
}

export function PropertyPicker() {
const { value, onChange, extension } = useFieldExtension()
const fieldExtension = useFieldExtension()

const { value, onChange, extension } = fieldExtension
const [localValue, setLocalValue] = useState<string | undefined | null>(
typeof value === 'string' ? value : undefined,
)
const [fields, setFields] = useState<__Field[] | null>(null)

const client = useClient(extension)
const { data, loading, error } = useQuery<IntrospectionQuery>(gql(getIntrospectionQuery()), {
client,
})
// eslint-disable-next-line no-underscore-dangle
const schema = data?.__schema

useEffect(() => {
onChange(localValue).catch((err) => err)
}, [localValue, onChange])

const graphQLInterfaceQuery = useMemo(() => {
const client = new ApolloClient({
uri:
typeof extension.config.backend === 'string'
? extension.config.backend
: 'https://graphcommerce.vercel.app/api/graphql', // fallback on the standard GraphCommerce Schema
cache: new InMemoryCache(),
})
return fetchGraphQLInterface(client)
}, [extension.config.backend])

// Prepare options
const numberOptions = useMemo(
() =>
createOptionsFromInterfaceObject(
objectifyGraphQLInterface(fields, 'number', ['ProductInterface']),
),
[fields],
)
const textOptions = useMemo(
() =>
createOptionsFromInterfaceObject(
objectifyGraphQLInterface(fields, 'text', ['ProductInterface']),
),
[fields],
)
const allOptions = useMemo(
() => ({
text: [...textOptions, { label: 'url', id: 'url' }].sort((a, b) => {
if (!a.label.includes('.') && !b.label.includes('.')) {
return a.label.localeCompare(b.label)
}
if (a.label.includes('.')) {
return 1
}
return -1
}),
number: [...numberOptions, { label: 'url', id: 'url' }],
}),
[numberOptions, textOptions],
)

// For now this we can not split number and text field options anymore as Hygraph made parent field apiId unreachable :/
// const fieldType = field.parent.apiId ?? 'ConditionText'
// const options = fieldType === 'ConditionNumber' ? allOptions.number : allOptions.text
const options = [...allOptions.text, ...allOptions.number]

if (!fields) {
Promise.resolve(graphQLInterfaceQuery)
.then((res) => {
const newFields: __Field[] = res?.data.__type?.fields

setFields(newFields)
})
.catch((err) => err)

return <div>Loading fields...</div>
}
if (options.length < 1) return <div>No properties available</div>
if (options.length > 10000) return <div>Too many properties to display</div>
const fieldPaths = useMemo(() => {
if (!schema) return []
return getFieldPaths(schema, ['ProductInterface'])
.sort((a, b) => a.depth() - b.depth())
.map((fp) => fp.stringify())
.filter<string>((v) => v !== undefined)
}, [schema])

return (
<TextField
id='property-selector'
select
select={!!fieldPaths.length}
variant='outlined'
size='small'
SelectProps={{
native: true,
variant: 'outlined',
}}
value={localValue}
onChange={(v) => {
Expand All @@ -95,6 +63,7 @@ export function PropertyPicker() {
fullWidth
sx={{
mt: '4px',
fontSize: '0.8em',
'& .MuiInputBase-root': {
borderRadius: { xs: '2px!important' },
},
Expand All @@ -119,11 +88,18 @@ export function PropertyPicker() {
},
}}
>
{options.map((o) => (
<option key={o.id} value={o.id}>
{o.label}
</option>
))}
{fieldPaths.length > 0 ? (
<>
<option value='url'>url</option>
{fieldPaths.map((fp) => (
<option key={fp} value={fp}>
{fp}
</option>
))}
</>
) : (
<>{loading ? 'Loading..' : error?.message}</>
)}
</TextField>
)
}

This file was deleted.

This file was deleted.

14 changes: 0 additions & 14 deletions packages/hygraph-dynamic-rows-ui/lib/fetchGraphQLInterface.ts

This file was deleted.

66 changes: 66 additions & 0 deletions packages/hygraph-dynamic-rows-ui/lib/getFieldPaths.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { IntrospectionField, IntrospectionOutputTypeRef, IntrospectionSchema } from 'graphql'

function getType(type: IntrospectionOutputTypeRef) {
switch (type.kind) {
case 'NON_NULL':
case 'LIST':
return getType(type.ofType)
default:
return type
}
}

class FieldPath {
constructor(
public field: IntrospectionField,
private prev: FieldPath | undefined,
) {}

stringify(filter?: string[]): string | undefined {
if (this.field.type.kind === 'SCALAR' && filter && !filter.includes(this.field.type.name)) {
return undefined
}

const prevStr = this.prev?.stringify(filter)
return prevStr ? `${prevStr}.${this.field.name}` : this.field.name
}

depth = () => (this?.prev?.depth() ?? 0) + 1
}

export function getFieldPaths(
schema: IntrospectionSchema,
types: string[],
prevPath: FieldPath | undefined = undefined,
): FieldPath[] {
const typeName = types[types.length - 1]

const paths: FieldPath[] = []
const type = schema.types.find((t) => t.name === typeName)

if (!type) return paths

if ((prevPath?.depth() ?? 0) > 3) return paths

if (type.kind === 'OBJECT' || type.kind === 'INTERFACE') {
type.fields.forEach((field) => {
const t = getType(field.type)

if (!types.includes(t.name) && !field.deprecationReason) {
const newTypes = [...types, t.name]

const newPath = new FieldPath(field, prevPath)

if (t.kind === 'OBJECT' || t.kind === 'INTERFACE') {
paths.push(...getFieldPaths(schema, newTypes, newPath))
} else if (t.kind === 'SCALAR' || t.kind === 'ENUM') {
paths.push(newPath)
} else if (t.kind === 'UNION') {
// not supported currently
}
}
})
}

return paths
}
4 changes: 0 additions & 4 deletions packages/hygraph-dynamic-rows-ui/lib/index.ts

This file was deleted.

62 changes: 0 additions & 62 deletions packages/hygraph-dynamic-rows-ui/lib/objectifyGraphQLInterface.ts

This file was deleted.

7 changes: 3 additions & 4 deletions packages/hygraph-dynamic-rows-ui/pages/property-picker.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { Wrapper } from '@hygraph/app-sdk-react'
import React from 'react'
import { PropertyPicker } from '..'
import React, { useEffect } from 'react'
import { PropertyPicker } from '../components/PropertyPicker'

export default function DRPropertyPicker() {
const fieldContainer = React.useRef<HTMLDivElement | null>(null)

React.useEffect(() => {
useEffect(() => {
/**
* Some styling is being undone here to resolve conflicts between Hygraph App SDK and CssAndFramerMotionProvider.
*/

const frameBox1 = fieldContainer?.current?.parentElement
if (frameBox1) {
frameBox1.style.position = 'static'
Expand Down
Loading

0 comments on commit 648da6f

Please sign in to comment.