mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-13 19:33:55 +03:00
console: allow input object presets in remote schema permissions
GitOrigin-RevId: 6a535a5b9adb0a80d8f57682618aa7dd406236b0
This commit is contained in:
parent
4b76289d03
commit
75f37f69d3
@ -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
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -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
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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>;
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user