console: allow input object presets in remote schema permissions

GitOrigin-RevId: 6a535a5b9adb0a80d8f57682618aa7dd406236b0
This commit is contained in:
Abhijeet Khangarot 2021-06-09 14:21:23 +05:30 committed by hasura-bot
parent 4b76289d03
commit 75f37f69d3
7 changed files with 3688 additions and 72 deletions

View File

@ -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

View File

@ -12,6 +12,8 @@ interface ArgSelectProps {
value?: ArgTreeType | ReactText;
level: number;
setArg: (e: Record<string, unknown>) => void;
isInputObjectType?: boolean;
isFirstLevelInputObjPreset?: boolean;
}
export const ArgSelect: React.FC<ArgSelectProps> = ({
@ -20,6 +22,8 @@ export const ArgSelect: React.FC<ArgSelectProps> = ({
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<ArgSelectProps> = ({
}
}, [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<string, any>) => {
const prevVal = prevState.current;
@ -66,47 +75,86 @@ export const ArgSelect: React.FC<ArgSelectProps> = ({
if (children) {
return (
<>
<button onClick={toggleExpandMode} style={{ marginLeft: '-1em' }}>
{expanded ? '-' : '+'}
</button>
{!expanded && (
<label
className={`${styles.argSelect} ${styles.fw_medium}`}
htmlFor={k}
>
{k}:
</label>
{!hideInputArgName ? (
<>
<button onClick={toggleExpandMode} style={{ marginLeft: '-1em' }}>
{expanded ? '-' : '+'}
</button>
{!expanded && (
<label
className={`${styles.argSelect} ${styles.fw_medium}`}
htmlFor={k}
>
{k}:
</label>
)}
{expanded && (
<label
className={`${styles.argSelect} ${styles.fw_large}`}
htmlFor={k}
>
{k}:
</label>
)}
</>
) : 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 (
<li key={i.name}>
<ArgSelect
keyName={i.name}
setArg={val => setArgVal({ [k]: val })}
valueField={i}
value={childVal}
level={level + 1}
/>
</li>
);
})
) : (
<ul>
{expanded &&
Object.values(children).map(i => {
if (typeof value === 'string') return undefined;
const childVal =
value && typeof value === 'object'
? value[i?.name]
: undefined;
return (
<li key={i.name}>
<ArgSelect
keyName={i.name}
setArg={val => setArgVal({ [k]: val })}
valueField={i}
value={childVal}
level={level + 1}
/>
</li>
);
})}
</ul>
)}
{expanded && (
<label
className={`${styles.argSelect} ${styles.fw_large}`}
htmlFor={k}
>
{k}:
</label>
)}
<ul>
{expanded &&
Object.values(children).map(i => {
if (typeof value === 'string') return undefined;
const childVal =
value && typeof value === 'object' ? value[i?.name] : undefined;
return (
<li key={i.name}>
<ArgSelect
keyName={i.name}
setArg={val => setArgVal({ [k]: val })}
valueField={i}
value={childVal}
level={level + 1}
/>
</li>
);
})}
</ul>
</>
);
}
if (isFirstLevelInputObjPreset) {
return (
<RSPInput
v={v as GraphQLInputField}
k={k}
editMode
setArgVal={setArgVal}
value={value}
setEditMode={() => {}}
isFirstLevelInputObjPreset
/>
);
}
return (
<li>
<RSPInput

View File

@ -11,7 +11,8 @@ import { CollapsedField } from './CollapsedField';
import { ArgSelect } from './ArgSelect';
import { isEmpty } from '../../../Common/utils/jsUtils';
import styles from '../../../Common/Permissions/PermissionStyles.scss';
import { generateTypeString } from './utils';
import { generateTypeString, getChildArguments } from './utils';
import Pen from './Pen';
export interface FieldProps {
i: FieldType;
@ -26,11 +27,19 @@ export const Field: React.FC<FieldProps> = ({
onExpand = console.log,
expanded,
}) => {
const [inputPresetMode, setInputPresetMode] = useState<boolean>(false);
const [autoExpandInputPresets, setAutoExpandInputPresets] = useState<boolean>(
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<Record<string, any>>(initState);
const setArg = useCallback(
(vStr: Record<string, unknown>) => {
@ -44,6 +53,21 @@ export const Field: React.FC<FieldProps> = ({
},
[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<FieldProps> = ({
!isEmpty(fieldVal)
) {
context.setArgTree((argTree: Record<string, any>) => {
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<FieldProps> = ({
expanded={expanded}
/>
);
const isFirstLevelInputObjPreset =
i.isInputObjectType &&
inputPresetMode &&
getChildArguments(i.args[i.name]) &&
Object.keys(getChildArguments(i.args[i.name])).length === 0;
return (
<>
<span
@ -83,23 +120,31 @@ export const Field: React.FC<FieldProps> = ({
>
{i.name}
</span>
{i.args && Object.keys(i.args).length !== 0 && ' ('}
{i.args && (
<ul data-test={i.name}>
{i.args &&
Object.entries(i.args).map(([k, v]) => (
<ArgSelect
key={k}
keyName={k}
valueField={v}
value={fieldVal[k]}
setArg={setArg}
level={0}
/>
))}
</ul>
)}
{i.args && Object.keys(i.args).length !== 0 && ' )'}
{(i.isInputObjectType &&
inputPresetMode &&
!isFirstLevelInputObjPreset) ||
!i.isInputObjectType ? (
<>
{i.args && Object.keys(i.args).length !== 0 && ' ('}
{i.args && (
<ul data-test={i.name}>
{i.args &&
Object.entries(i.args).map(([k, v]) => (
<ArgSelect
key={k}
keyName={k}
valueField={v}
value={fieldVal[k]}
setArg={setArg}
level={0}
isInputObjectType={i.isInputObjectType}
/>
))}
</ul>
)}
{i.args && Object.keys(i.args).length !== 0 && ' )'}
</>
) : null}
{i.return && (
<span className={styles.fw_large}>
:
@ -112,6 +157,24 @@ export const Field: React.FC<FieldProps> = ({
</a>
</span>
)}
{/* show pen icon for input object types presets */}
{i.isInputObjectType && !inputPresetMode && !autoExpandInputPresets ? (
<button onClick={() => setInputPresetMode(true)}>
<Pen />
</button>
) : null}
{i.isInputObjectType && inputPresetMode && isFirstLevelInputObjPreset ? (
<ArgSelect
key={i.name}
keyName={i.name}
valueField={i.args[i.name]}
value={fieldVal[i.name]}
setArg={setArg}
level={0}
isInputObjectType={i.isInputObjectType}
isFirstLevelInputObjPreset
/>
) : null}
</>
);
};

View File

@ -13,6 +13,7 @@ interface RSPInputProps {
v: GraphQLInputField;
setArgVal: (v: Record<string, unknown>) => void;
setEditMode: (b: boolean) => void;
isFirstLevelInputObjPreset?: boolean;
}
const RSPInputComponent: React.FC<RSPInputProps> = ({
k,
@ -21,6 +22,7 @@ const RSPInputComponent: React.FC<RSPInputProps> = ({
setArgVal,
v,
setEditMode,
isFirstLevelInputObjPreset,
}) => {
const isSessionvar = () => {
if (
@ -66,7 +68,7 @@ const RSPInputComponent: React.FC<RSPInputProps> = ({
return (
<>
<label htmlFor={k}> {k}:</label>
{!isFirstLevelInputObjPreset ? <label htmlFor={k}> {k}:</label> : null}
{editMode ? (
<>
<input

View File

@ -79,11 +79,13 @@ export type ChildArgumentType = {
export type CustomFieldType = {
name: string;
checked: boolean;
args?: GraphQLArgument[];
args?: Record<string, GraphQLArgument>;
return?: string;
typeName?: string;
children?: FieldType[];
defaultValue?: any;
isInputObjectType?: boolean;
parentName?: string;
};
export type FieldType = CustomFieldType & GraphQLField<any, any>;

View File

@ -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<string, any> = {};
const getFieldsMap = (fields: FieldDefinitionNode[], parentName: string) => {
const type = `type ${parentName}`;
const res: Record<string, any> = { [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<string, any> = { [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;