From 75f37f69d3fdc85e7fc2b479bbc29a3890abcb73 Mon Sep 17 00:00:00 2001 From: Abhijeet Khangarot Date: Wed, 9 Jun 2021 14:21:23 +0530 Subject: [PATCH] console: allow input object presets in remote schema permissions GitOrigin-RevId: 6a535a5b9adb0a80d8f57682618aa7dd406236b0 --- CHANGELOG.md | 1 + .../RemoteSchema/Permissions/ArgSelect.tsx | 122 +- .../RemoteSchema/Permissions/Field.tsx | 107 +- .../RemoteSchema/Permissions/RSPInput.tsx | 4 +- .../__snapshots__/utils.test.ts.snap | 3456 +++++++++++++++++ .../RemoteSchema/Permissions/types.ts | 4 +- .../RemoteSchema/Permissions/utils.ts | 66 +- 7 files changed, 3688 insertions(+), 72 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f269fa5f8d..a5c9496aa9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ (Add entries below in the order of server, console, cli, docs, others) - console: add foreign key CRUD functionality to ms sql server tables - console: allow tracking of custom SQL functions having composite type (rowtype) input arguments +- console: allow input object presets in remote schema permissions - cli: add interactive prompt to get input when `--database-name` flag is missing diff --git a/console/src/components/Services/RemoteSchema/Permissions/ArgSelect.tsx b/console/src/components/Services/RemoteSchema/Permissions/ArgSelect.tsx index b35250ba48e..39ca2341e42 100644 --- a/console/src/components/Services/RemoteSchema/Permissions/ArgSelect.tsx +++ b/console/src/components/Services/RemoteSchema/Permissions/ArgSelect.tsx @@ -12,6 +12,8 @@ interface ArgSelectProps { value?: ArgTreeType | ReactText; level: number; setArg: (e: Record) => void; + isInputObjectType?: boolean; + isFirstLevelInputObjPreset?: boolean; } export const ArgSelect: React.FC = ({ @@ -20,6 +22,8 @@ export const ArgSelect: React.FC = ({ value, level, setArg = e => console.log(e), + isInputObjectType, + isFirstLevelInputObjPreset, }) => { const [expanded, setExpanded] = useState(false); const autoExpanded = useRef(false); @@ -47,7 +51,12 @@ export const ArgSelect: React.FC = ({ } }, [value, k, expanded]); + // for input object types having children, hide first level args + const hideInputArgName = isInputObjectType && level === 0; const { children } = getChildArguments(v as GraphQLInputField); + if (hideInputArgName && children) { + if (!expanded) setExpanded(true); + } const setArgVal = (val: Record) => { const prevVal = prevState.current; @@ -66,47 +75,86 @@ export const ArgSelect: React.FC = ({ if (children) { return ( <> - - {!expanded && ( - + {!hideInputArgName ? ( + <> + + {!expanded && ( + + )} + {expanded && ( + + )} + + ) : null} + {hideInputArgName ? ( + expanded && + Object.values(children).map(i => { + if (typeof value === 'string') return undefined; + const childVal = + value && typeof value === 'object' ? value[i?.name] : undefined; + return ( +
  • + setArgVal({ [k]: val })} + valueField={i} + value={childVal} + level={level + 1} + /> +
  • + ); + }) + ) : ( +
      + {expanded && + Object.values(children).map(i => { + if (typeof value === 'string') return undefined; + const childVal = + value && typeof value === 'object' + ? value[i?.name] + : undefined; + return ( +
    • + setArgVal({ [k]: val })} + valueField={i} + value={childVal} + level={level + 1} + /> +
    • + ); + })} +
    )} - {expanded && ( - - )} -
      - {expanded && - Object.values(children).map(i => { - if (typeof value === 'string') return undefined; - const childVal = - value && typeof value === 'object' ? value[i?.name] : undefined; - return ( -
    • - setArgVal({ [k]: val })} - valueField={i} - value={childVal} - level={level + 1} - /> -
    • - ); - })} -
    ); } + if (isFirstLevelInputObjPreset) { + return ( + {}} + isFirstLevelInputObjPreset + /> + ); + } return (
  • = ({ onExpand = console.log, expanded, }) => { + const [inputPresetMode, setInputPresetMode] = useState(false); + const [autoExpandInputPresets, setAutoExpandInputPresets] = useState( + false + ); const context: any = useContext(PermissionEditorContext); - const initState = - context.argTree && context.argTree[i.name] - ? { ...context.argTree[i.name] } + let initState; + if (i.parentName) { + initState = context.argTree?.[i.parentName]?.[i.name] + ? { ...context.argTree[i.parentName][i.name] } : {}; + } else { + initState = context.argTree?.[i.name] ? { ...context.argTree[i.name] } : {}; + } const [fieldVal, setfieldVal] = useState>(initState); const setArg = useCallback( (vStr: Record) => { @@ -44,6 +53,21 @@ export const Field: React.FC = ({ }, [setItem, i] ); + useEffect(() => { + // auto expand args for InputObjectTypes when there is prefilled values + // happens only first time when the node is created + if ( + fieldVal && + fieldVal !== {} && + Object.keys(fieldVal).length > 0 && + !isEmpty(fieldVal) && + !autoExpandInputPresets + ) { + setAutoExpandInputPresets(true); + setInputPresetMode(true); + } + }, [autoExpandInputPresets]); + useEffect(() => { if ( fieldVal && @@ -52,7 +76,13 @@ export const Field: React.FC = ({ !isEmpty(fieldVal) ) { context.setArgTree((argTree: Record) => { - return { ...argTree, [i.name]: fieldVal }; + const tree = i.parentName + ? { + ...argTree, + [i.parentName]: { ...argTree[i.parentName], [i.name]: fieldVal }, + } + : { ...argTree, [i.name]: fieldVal }; + return tree; }); } }, [fieldVal]); @@ -75,6 +105,13 @@ export const Field: React.FC = ({ expanded={expanded} /> ); + + const isFirstLevelInputObjPreset = + i.isInputObjectType && + inputPresetMode && + getChildArguments(i.args[i.name]) && + Object.keys(getChildArguments(i.args[i.name])).length === 0; + return ( <> = ({ > {i.name} - {i.args && Object.keys(i.args).length !== 0 && ' ('} - {i.args && ( -
      - {i.args && - Object.entries(i.args).map(([k, v]) => ( - - ))} -
    - )} - {i.args && Object.keys(i.args).length !== 0 && ' )'} + {(i.isInputObjectType && + inputPresetMode && + !isFirstLevelInputObjPreset) || + !i.isInputObjectType ? ( + <> + {i.args && Object.keys(i.args).length !== 0 && ' ('} + {i.args && ( +
      + {i.args && + Object.entries(i.args).map(([k, v]) => ( + + ))} +
    + )} + {i.args && Object.keys(i.args).length !== 0 && ' )'} + + ) : null} {i.return && ( : @@ -112,6 +157,24 @@ export const Field: React.FC = ({ )} + {/* show pen icon for input object types presets */} + {i.isInputObjectType && !inputPresetMode && !autoExpandInputPresets ? ( + + ) : null} + {i.isInputObjectType && inputPresetMode && isFirstLevelInputObjPreset ? ( + + ) : null} ); }; diff --git a/console/src/components/Services/RemoteSchema/Permissions/RSPInput.tsx b/console/src/components/Services/RemoteSchema/Permissions/RSPInput.tsx index f357b61f63d..d331aef1c80 100644 --- a/console/src/components/Services/RemoteSchema/Permissions/RSPInput.tsx +++ b/console/src/components/Services/RemoteSchema/Permissions/RSPInput.tsx @@ -13,6 +13,7 @@ interface RSPInputProps { v: GraphQLInputField; setArgVal: (v: Record) => void; setEditMode: (b: boolean) => void; + isFirstLevelInputObjPreset?: boolean; } const RSPInputComponent: React.FC = ({ k, @@ -21,6 +22,7 @@ const RSPInputComponent: React.FC = ({ setArgVal, v, setEditMode, + isFirstLevelInputObjPreset, }) => { const isSessionvar = () => { if ( @@ -66,7 +68,7 @@ const RSPInputComponent: React.FC = ({ return ( <> - + {!isFirstLevelInputObjPreset ? : null} {editMode ? ( <> ; return?: string; typeName?: string; children?: FieldType[]; defaultValue?: any; + isInputObjectType?: boolean; + parentName?: string; }; export type FieldType = CustomFieldType & GraphQLField; diff --git a/console/src/components/Services/RemoteSchema/Permissions/utils.ts b/console/src/components/Services/RemoteSchema/Permissions/utils.ts index 9d5c567d22c..c9fc57bf6bd 100644 --- a/console/src/components/Services/RemoteSchema/Permissions/utils.ts +++ b/console/src/components/Services/RemoteSchema/Permissions/utils.ts @@ -20,6 +20,7 @@ import { GraphQLInputType, buildSchema, GraphQLUnionType, + InputObjectTypeDefinitionNode, } from 'graphql'; import { isJsonString, @@ -169,6 +170,10 @@ export const getTree = ( return Object.values(introspectionSchemaFields).map( ({ name, args: argArray, type, ...rest }: any) => { let checked = false; + const parentName = + typeS === 'QUERY' + ? `type ${introspectionSchema?.getQueryType()?.name}` + : `type ${introspectionSchema?.getMutationType()?.name}`; const args = argArray.reduce((p: ArgTreeType, c: FieldType) => { return { ...p, [c.name]: { ...c } }; }, {}); @@ -179,7 +184,14 @@ export const getTree = ( ) { checked = true; } - return { name, checked, args, return: type.toString(), ...rest }; + return { + name, + checked, + args, + return: type.toString(), + parentName, + ...rest, + }; } ); } @@ -319,6 +331,11 @@ export const getType = ( if (v.defaultValue !== undefined) { field.defaultValue = v.defaultValue; } + if (value instanceof GraphQLInputObjectType) { + field.args = { [k]: v }; + field.isInputObjectType = true; + field.parentName = type.name; + } childArray.push(field); }); @@ -549,16 +566,17 @@ const getSDLField = ( if (!f.checked) return null; let fieldStr = f.name; + let valueStr = ''; // enum types don't have args if (!typeName.startsWith('enum')) { if (f.args && !isEmpty(f.args)) { fieldStr = `${fieldStr}(`; Object.values(f.args).forEach((arg: GraphQLInputField) => { - let valueStr = `${arg.name} : ${arg.type.inspect()}`; + valueStr = `${arg.name} : ${arg.type.inspect()}`; - if (argTree && argTree[f.name] && argTree[f.name][arg.name]) { - const argName = argTree[f.name][arg.name]; + if (argTree?.[type?.name]?.[f?.name]?.[arg?.name]) { + const argName = argTree[type.name][f.name][arg.name]; let unquoted; const isEnum = typeof argName === 'string' && @@ -592,9 +610,14 @@ const getSDLField = ( : `${fieldStr} : ${f.return} = ${f.defaultValue}`; } } - - result = `${result} + // only need the arg string for input object types + if (typeName.startsWith('input')) { + result = `${result} + ${valueStr}`; + } else { + result = `${result} ${fieldStr}`; + } }); return `${result}\n}`; }; @@ -732,14 +755,19 @@ const getPresets = (field: FieldDefinitionNode) => { return res; }; -const getFieldsMap = (fields: FieldDefinitionNode[]) => { - const res: Record = {}; +const getFieldsMap = (fields: FieldDefinitionNode[], parentName: string) => { + const type = `type ${parentName}`; + const res: Record = { [type]: {} }; fields.forEach(field => { - res[field?.name?.value] = getPresets(field); + res[type][field?.name?.value] = getPresets(field); }); return res; }; +type ArgTypesDefinition = + | ObjectTypeDefinitionNode + | InputObjectTypeDefinitionNode; + export const getArgTreeFromPermissionSDL = ( definition: string, introspectionSchema: GraphQLSchema @@ -747,12 +775,28 @@ export const getArgTreeFromPermissionSDL = ( const roots = getSchemaRoots(introspectionSchema); try { const schema: DocumentNode = parse(definition); - const defs = schema.definitions as ObjectTypeDefinitionNode[]; + const defs = schema.definitions as ArgTypesDefinition[]; const argTree = defs && defs.reduce((acc = [], i) => { if (i.name && i.fields && roots.includes(i?.name?.value)) { - const res = getFieldsMap(i.fields as FieldDefinitionNode[]); + const res = getFieldsMap( + i.fields as FieldDefinitionNode[], + i.name.value + ); + return { ...acc, ...res }; + } + if (i.name && i.fields && i.kind === 'InputObjectTypeDefinition') { + const type = `input ${i.name.value}`; + const res: Record = { [type]: {} }; + i.fields.forEach(field => { + if (field.directives && field.directives.length > 0) { + res[type][field.name?.value] = {}; + res[type][field.name?.value][field.name?.value] = getDirectives( + field + ); + } + }); return { ...acc, ...res }; } return acc;