From 1a33e01b604f997f24eeb11fc1984ed13da05c4e Mon Sep 17 00:00:00 2001 From: Snorre Eskeland Brekke Date: Fri, 7 Jul 2023 12:07:55 +0200 Subject: [PATCH] fix: truthy hidden and readOnly fields should not be assistable --- plugin/README.md | 3 ++ plugin/src/assistInspector/helpers.ts | 2 +- .../src/fieldActions/assistFieldActions.tsx | 6 ++-- plugin/src/helpers/assistSupported.ts | 20 ++++++++--- .../schemas/serialize/serializeSchema.test.ts | 26 ++++++++++++++ .../src/schemas/serialize/serializeSchema.ts | 2 ++ studio/schemas/mockArticle.tsx | 36 +++++++++++++++++++ 7 files changed, 87 insertions(+), 8 deletions(-) diff --git a/plugin/README.md b/plugin/README.md index a4465e3..cc48291 100644 --- a/plugin/README.md +++ b/plugin/README.md @@ -160,6 +160,9 @@ The following types are not supported, and behave as excluded types: * [Image](https://www.sanity.io/docs/image-type) (supported when image has custom fields) * [File](https://www.sanity.io/docs/file-type) (never supported, even when file has custom fields) +Types and fields with `hidden` or `readonly` with a truthy value (`true` or `function`) are not supported. +(Hidden and readOnly fields can be referenced in instructions still) + Fields with these types will not be changed by the assistant, do not have AI Assist actions, and cannot be referenced in instructions. Objects where all fields are excluded or unsupported and arrays where all member types are excluded or unsupported diff --git a/plugin/src/assistInspector/helpers.ts b/plugin/src/assistInspector/helpers.ts index 62569ee..2e333f8 100644 --- a/plugin/src/assistInspector/helpers.ts +++ b/plugin/src/assistInspector/helpers.ts @@ -82,7 +82,7 @@ export function getFieldRefs( const fields = field.type.jsonType === 'object' ? getFieldRefs(field.type, fieldRef, depth + 1) : [] - if (!isAssistSupported(field.type)) { + if (!isAssistSupported(field.type, true)) { return fields } return [fieldRef, ...fields] diff --git a/plugin/src/fieldActions/assistFieldActions.tsx b/plugin/src/fieldActions/assistFieldActions.tsx index 7affbbb..342bcf8 100644 --- a/plugin/src/fieldActions/assistFieldActions.tsx +++ b/plugin/src/fieldActions/assistFieldActions.tsx @@ -28,7 +28,9 @@ function node(node: DocumentFieldActionItem | DocumentFieldActionGroup) { export const assistFieldActions: DocumentFieldAction = { name: 'sanity-assist-actions', useAction(props) { - const assistSupported = useAssistSupported(props.path, props.schemaType) + const {schemaType} = props + const assistSupported = useAssistSupported(props.path, schemaType) + const isDocumentLevel = props.path.length === 0 const { @@ -47,7 +49,7 @@ export const assistFieldActions: DocumentFieldAction = { // conditional hook _should_ be safe here since the logical path will be stable isDocumentLevel ? // eslint-disable-next-line react-hooks/rules-of-hooks - useAssistDocumentContextValue(props.documentId, props.schemaType as ObjectSchemaType) + useAssistDocumentContextValue(props.documentId, schemaType as ObjectSchemaType) : // eslint-disable-next-line react-hooks/rules-of-hooks useAssistDocumentContext() diff --git a/plugin/src/helpers/assistSupported.ts b/plugin/src/helpers/assistSupported.ts index 178ac58..2f5ff75 100644 --- a/plugin/src/helpers/assistSupported.ts +++ b/plugin/src/helpers/assistSupported.ts @@ -6,30 +6,40 @@ export function isSchemaAssistEnabled(type: SchemaType) { return !(type.options as AssistOptions | undefined)?.aiWritingAssistance?.exclude } -export function isAssistSupported(type: SchemaType) { +export function isAssistSupported(type: SchemaType, allowReadonlyHidden = false) { if (!isSchemaAssistEnabled(type)) { return false } - if (isUnsupportedType(type)) { + if (isDisabled(type, allowReadonlyHidden)) { return false } if (type.jsonType === 'array') { - const unsupportedArray = type.of.every((t) => isUnsupportedType(t)) + const unsupportedArray = type.of.every((t) => isDisabled(t, allowReadonlyHidden)) return !unsupportedArray } if (type.jsonType === 'object') { - const unsupportedObject = type.fields.every((field) => isUnsupportedType(field.type)) + const unsupportedObject = type.fields.every((field) => + isDisabled(field.type, allowReadonlyHidden) + ) return !unsupportedObject } return true } -function isUnsupportedType(type: SchemaType) { +function isDisabled(type: SchemaType, allowReadonlyHidden: boolean) { + const readonlyHidden = !!type.readOnly || !!type.hidden return ( !isSchemaAssistEnabled(type) || + isUnsupportedType(type) || + (!allowReadonlyHidden && readonlyHidden) + ) +} + +function isUnsupportedType(type: SchemaType) { + return ( type.jsonType === 'number' || type.name === 'sanity.imageCrop' || type.name === 'sanity.imageHotspot' || diff --git a/plugin/src/schemas/serialize/serializeSchema.test.ts b/plugin/src/schemas/serialize/serializeSchema.test.ts index 60e5cc3..4326722 100644 --- a/plugin/src/schemas/serialize/serializeSchema.test.ts +++ b/plugin/src/schemas/serialize/serializeSchema.test.ts @@ -391,4 +391,30 @@ describe('serializeSchema', () => { {name: 'list', title: 'String', type: 'string', values: ['a', 'b']}, ]) }) + + test('should exclude truthy hidden and readonly', () => { + const schema = Schema.compile({ + name: 'test', + types: [ + { + type: 'document', + name: 'article', + fields: [ + {type: 'string', name: 'title', hidden: () => true}, + {type: 'some', name: 'some'}, + ], + }, + defineType({ + type: 'object', + name: 'some', + readOnly: true, + fields: [{type: 'string', name: 'title'}], + }), + ], + }) + + const serializedTypes = serializeSchema(schema, {leanFormat: true}) + + expect(serializedTypes).toEqual([]) + }) }) diff --git a/plugin/src/schemas/serialize/serializeSchema.ts b/plugin/src/schemas/serialize/serializeSchema.ts index e69121f..c6450d7 100644 --- a/plugin/src/schemas/serialize/serializeSchema.ts +++ b/plugin/src/schemas/serialize/serializeSchema.ts @@ -29,6 +29,7 @@ export function serializeSchema(schema: Schema, options?: Options): SerializedSc .filter((t) => !(hiddenTypes.includes(t) || t.startsWith('sanity.'))) .map((t) => schema.get(t)) .filter((t): t is SchemaType => !!t) + .filter((t) => !t.hidden && !t.readOnly) .map((t) => getSchemaStub(t, schema, options)) .filter((t) => { if ('to' in t && t.to && !t.to.length) { @@ -106,6 +107,7 @@ function serializeFields( return schemaType.fields .filter((f) => !['sanity.imageHotspot', 'sanity.imageCrop'].includes(f.type?.name ?? '')) .filter((f) => isAssistSupported(f.type)) + .filter((f) => !f.type.hidden && !f.type.readOnly) .map((field) => serializeMember(schema, field.type, field.name, options)) } diff --git a/studio/schemas/mockArticle.tsx b/studio/schemas/mockArticle.tsx index b9d912d..54f7a54 100644 --- a/studio/schemas/mockArticle.tsx +++ b/studio/schemas/mockArticle.tsx @@ -374,5 +374,41 @@ export const mockArticle = defineType({ }), ], }), + defineField({ + type: 'image', + name: 'inlineImage', + title: 'Image (inline type)', + fields: [ + defineField({ + type: 'string', + name: 'altText', + title: 'Alt text', + }), + ], + options: { + imagePromptField: 'altText', + }, + }), + defineField({ + type: 'object', + name: 'inlineObject', + title: 'Inline object', + fields: [ + defineField({ + type: 'string', + name: 'readOnly', + readOnly: true, + }), + defineField({ + type: 'string', + name: 'hidden', + hidden: true, + }), + defineField({ + type: 'string', + name: 'Write', + }), + ], + }), ], })