console: add GraphQL customisation under Remote schema edit tab

https://github.com/hasura/graphql-engine-mono/pull/2302

GitOrigin-RevId: 60e420298f568779b2732a3fd90388f8adefa599
This commit is contained in:
Vijay Prasanna 2021-09-21 13:23:01 +05:30 committed by hasura-bot
parent 21254256a1
commit 11d66753be
13 changed files with 989 additions and 26 deletions

View File

@ -20,6 +20,7 @@
- server: `introspect_remote_schema` API now returns original remote schema instead of customized schema
- server: prevent empty subscription roots in the schema (#6898)
- console: support tracking of functions with return a single row
- console: add GraphQL customisation under Remote schema edit tab
## v2.0.9

View File

@ -0,0 +1,105 @@
import { getElementFromAlias } from '../../../helpers/eventHelpers';
type CustomizationSettingsType = {
root_fields_namespace: string;
type_names: {
prefix: string;
suffix: string;
mapping: Record<string, string>;
};
field_names: {
parent_type: string;
prefix: string;
suffix: string;
mapping: Record<string, string>;
}[];
};
export const modifyCustomization = (
customizationSettings: CustomizationSettingsType | undefined
) => {
cy.get(getElementFromAlias('remote-schema-edit-modify-btn'))
.should('exist')
.click();
cy.get(getElementFromAlias('remote-schema-customization-editor-expand-btn'))
.should('exist')
.click();
// add root field name
cy.get(getElementFromAlias('remote-schema-customization-root-field-input'))
.clear()
.type(customizationSettings?.root_fields_namespace || '');
cy.get(
getElementFromAlias('remote-schema-customization-type-name-prefix-input')
)
.clear()
.type(customizationSettings?.type_names.prefix || '');
cy.get(
getElementFromAlias('remote-schema-customization-type-name-suffix-input')
)
.clear()
.type(customizationSettings?.type_names.suffix || '');
// add type name
let key = Object.keys(customizationSettings?.type_names?.mapping || {})[0];
cy.get(
getElementFromAlias('remote-schema-customization-type-name-lhs-input')
).select(key);
cy.get(
getElementFromAlias('remote-schema-customization-type-name-0-rhs-input')
)
.clear()
.type(customizationSettings?.type_names?.mapping[key] || '');
cy.get(getElementFromAlias('remote-schema-editor')).should('exist').click();
cy.get(getElementFromAlias('remote-schema-customization-open-field-mapping'))
.should('exist')
.click();
// click the field mapping button
cy.get(
getElementFromAlias(
'remote-schema-customization-field-type-parent-type-input'
)
).select(customizationSettings?.field_names[0].parent_type || '');
cy.get(
getElementFromAlias(
'remote-schema-customization-field-type-field-prefix-input'
)
)
.clear()
.type(customizationSettings?.field_names[0].prefix || '');
cy.get(
getElementFromAlias(
'remote-schema-customization-field-type-field-suffix-input'
)
)
.clear()
.type(customizationSettings?.field_names[0].suffix || '');
// remote-schema-customization-field-type-lhs-input
key = Object.keys(customizationSettings?.field_names[0].mapping || {})[0];
cy.get(
getElementFromAlias('remote-schema-customization-field-type-lhs-input')
).select(key);
// remote-schema-customization-field-type-rhs-input
cy.get(
getElementFromAlias('remote-schema-customization-field-type-0-rhs-input')
)
.clear()
.type(customizationSettings?.field_names[0].mapping[key] || '');
cy.get(getElementFromAlias('remote-schema-editor')).should('exist').click();
cy.get(getElementFromAlias('add-field-customization'))
.should('exist')
.click();
cy.get(getElementFromAlias('remote-schema-edit-save-btn'))
.should('exist')
.click();
};

View File

@ -0,0 +1,107 @@
/* eslint no-unused-vars: 0 */
/* eslint import/prefer-default-export: 0 */
import { testMode } from '../../../helpers/common';
import { expectNotif } from '../../data/manage-database/common.spec';
import { setMetaData } from '../../validators/validators';
import { modifyCustomization } from './spec';
// const visitRoute = () => {
// describe('Setup route', () => {
// it('Visit the index route', () => {
// // Visit the index route
// cy.visit('/remote-schemas/manage/schemas');
// // Get and set validation metadata
// setMetaData();
// });
// });
// };
const createRemoteSchema = (remoteSchemaName: string) => {
const postBody = {
type: 'add_remote_schema',
args: {
name: remoteSchemaName,
definition: {
url: 'https://graphql-pokemon2.vercel.app',
forward_client_headers: true,
timeout_seconds: 60,
},
},
};
cy.request('POST', 'http://localhost:8080/v1/metadata', postBody).then(
response => {
expect(response.body).to.have.property('message', 'success'); // true
}
);
};
const removeRemoteSchema = (remoteSchemaName: string) => {
const postBody = {
type: 'remove_remote_schema',
args: {
name: remoteSchemaName,
},
};
cy.request('POST', 'http://localhost:8080/v1/metadata', postBody).then(
response => {
expect(response.body).to.have.property('message', 'success'); // true
}
);
};
const editSchemaTests = () => {
describe('Modify an existing remote schema', () => {
describe('Create a remote schema for testing', () => {
it('add a remote schema via the API', () => {
createRemoteSchema('test_remote_schema');
});
});
describe('Edit the remote schema settings', () => {
it('Visit the modify page', () => {
cy.visit('/remote-schemas/manage/test_remote_schema/modify');
setMetaData();
});
it('Modify the remote schema settings', () => {
modifyCustomization({
root_fields_namespace: 'test_root_namespace',
type_names: {
prefix: 'test_prefix',
suffix: 'test_suffix',
mapping: {
Pokemon: 'renamed_type_name_mapping',
},
},
field_names: [
{
parent_type: 'PokemonDimension',
prefix: 'test_parent_type_prefix',
suffix: 'test_parent_type_suffix',
mapping: {
minimum: 'test_field_name',
},
},
],
});
});
it('expect success notification', () => {
expectNotif('success', {
title: 'Remote schema modified',
});
});
});
describe('Remove remote schema', () => {
it('Remove the remote schema via the API', () => {
removeRemoteSchema('test_remote_schema');
});
});
});
};
if (testMode !== 'cli') {
// setup();
editSchemaTests();
}

View File

@ -21,6 +21,7 @@ const ENV_URL_CHANGED = '@addRemoteSchema/ENV_URL_CHANGED';
const NAME_CHANGED = '@addRemoteSchema/NAME_CHANGED';
const TIMEOUT_CONF_CHANGED = '@addRemoteSchema/TIMEOUT_CONF_CHANGED';
const COMMENT_CHANGED = '@addRemoteSchema/COMMENT_CHANGED';
const CUSTOMIZATION_CHANGED = '@addRemoteSchema/CUSTOMIZATION_CHANGED';
// const HEADER_CHANGED = '@addRemoteSchema/HEADER_CHANGED';
const ADDING_REMOTE_SCHEMA = '@addRemoteSchema/ADDING_REMOTE_SCHEMA';
const ADD_REMOTE_SCHEMA_FAIL = '@addRemoteSchema/ADD_REMOTE_SCHEMA_FAIL';
@ -48,6 +49,7 @@ const inputEventMap = {
manualUrl: MANUAL_URL_CHANGED,
timeoutConf: TIMEOUT_CONF_CHANGED,
comment: COMMENT_CHANGED,
customization: CUSTOMIZATION_CHANGED,
};
/* Action creators */
@ -251,6 +253,7 @@ const modifyRemoteSchema = () => (dispatch, getState) => {
timeout_seconds: timeoutSeconds,
forward_client_headers: currState.forwardClientHeaders,
headers: getReqHeader(getState().remoteSchemas.headerData.headers),
customization: currState.customization,
};
const remoteSchemaComment = currState?.comment;
@ -281,6 +284,7 @@ const modifyRemoteSchema = () => (dispatch, getState) => {
timeout_seconds: oldTimeout,
headers: currState.editState.originalHeaders,
forward_client_headers: currState.editState.originalForwardClientHeaders,
currState: currState.editState.oldCustomization,
};
if (!currState.editState.originalUrl) {
@ -360,6 +364,11 @@ const addRemoteSchemaReducer = (state = addState, action) => {
...state,
comment: action.data,
};
case CUSTOMIZATION_CHANGED:
return {
...state,
customization: action.data,
};
case ADDING_REMOTE_SCHEMA:
return {
...state,
@ -404,6 +413,7 @@ const addRemoteSchemaReducer = (state = addState, action) => {
: '60',
forwardClientHeaders: action.data.definition.forward_client_headers,
comment: action.data?.comment || '',
customization: action.data.definition?.customization,
editState: {
...state,
isModify: false,
@ -414,6 +424,7 @@ const addRemoteSchemaReducer = (state = addState, action) => {
originalForwardClientHeaders:
action.data.definition.forward_client_headers || false,
originalComment: action.data?.comment || '',
originalCustomization: action.data.definition?.customization,
},
isFetching: false,
isFetchError: null,

View File

@ -10,6 +10,7 @@ import {
} from '../Add/addRemoteSchemaReducer';
import CommonHeader from '../../../Common/Layout/ReusableHeader/Header';
import GraphQLCustomizationEdit from './GraphQLCustomization/GraphQLCustomizationEdit';
class Common extends React.Component {
getPlaceHolderText(valType) {
@ -24,6 +25,10 @@ class Common extends React.Component {
this.props.dispatch(inputChange(fieldName, e.target.value));
}
handleCustomizationInputChange(updateValue) {
this.props.dispatch(inputChange('customization', updateValue));
}
toggleUrlParam(e) {
const field = e.target.getAttribute('value');
this.props.dispatch(inputChange(field, ''));
@ -44,7 +49,9 @@ class Common extends React.Component {
forwardClientHeaders,
comment,
isNew = false,
customization,
} = this.props;
const { isModify } = this.props.editState;
const isDisabled = !isNew && !isModify;
@ -252,6 +259,32 @@ class Common extends React.Component {
data-test="remote-schema-comment"
/>
</label>
<hr className="my-lg" />
{/* <GraphQLCustomization mode="edit" customization={customization} dispatch={this.props.dispatch} /> */}
{isNew ? null : (
<>
<div className="text-lg font-bold">
GraphQL Customizations{' '}
<OverlayTrigger
placement="right"
overlay={
<Tooltip id="tooltip-cascade">
Individual Types and Fields will be editable after saving.
</Tooltip>
}
>
<i className="fa fa-question-circle" aria-hidden="true" />
</OverlayTrigger>
</div>
<GraphQLCustomizationEdit
remoteSchemaName={name}
graphQLCustomization={customization}
dispatch={this.props.dispatch}
onChange={this.handleCustomizationInputChange.bind(this)}
isDisabled={isDisabled}
/>
</>
)}
</div>
);
}

View File

@ -0,0 +1,181 @@
import React, { useState, useEffect } from 'react';
import { OverlayTrigger } from 'react-bootstrap';
import Tooltip from 'react-bootstrap/lib/Tooltip';
import styles from '../../RemoteSchema.scss';
import { Button } from '../../../../Common';
import TypeMapping from './TypeMapping';
type FieldNameType = {
parentType?: string;
prefix?: string;
suffix?: string;
mapping?: { type: string; custom_name: string }[];
};
type Props = {
types: { typeName: string; fields: string[] }[];
fieldName: FieldNameType;
mode: 'edit' | 'create';
onChange: (updateFieldName: FieldNameType) => void;
onDelete?: () => void;
onSave?: () => void;
onClose?: () => void;
label?: string;
};
const tooltip = (
<Tooltip id="tooltip-cascade">
Field remapping takes precedence to prefixes and suffixes.
</Tooltip>
);
const SelectOne = ({
options,
value,
onChange,
label,
}: {
options: string[];
value: string | undefined;
onChange: (e: any) => void;
label?: string;
}) => (
<select
value={value}
onChange={onChange}
className="form-control"
data-test={label}
>
<option value="">Select Type ...</option>
{options.map((op, i) => (
<option value={op} key={i}>
{op}
</option>
))}
</select>
);
const FieldNames = ({
types,
fieldName,
mode,
onChange,
onDelete,
onSave,
onClose,
label,
}: Props) => {
const [fieldNameInput, setFieldNameInput] = useState<
FieldNameType | undefined
>(undefined);
useEffect(() => {
setFieldNameInput(fieldName);
}, [fieldName]);
return (
<div className={styles.CustomEditor}>
{mode === 'edit' ? null : (
<div>
<Button size="xs" onClick={onClose}>
Close
</Button>
</div>
)}
<div className="flex items-center mt-md">
<label className="w-1/3">Parent Type</label>
<div className="w-2/3">
<SelectOne
options={types.map(v => v.typeName)}
value={fieldNameInput?.parentType}
onChange={e => {
onChange({
...fieldNameInput,
parentType: e.target.value,
});
}}
label={`remote-schema-customization-${label}-parent-type-input`}
/>
</div>
</div>
<div className="flex items-center mt-md">
<label className="w-1/3">Field Prefix</label>
<div className="w-2/3">
<input
type="text"
className="form-control"
placeholder="prefix_"
value={fieldNameInput?.prefix}
onChange={e =>
onChange({
...fieldNameInput,
prefix: e.target.value,
})
}
data-test={`remote-schema-customization-${label}-field-prefix-input`}
/>
</div>
</div>
<div className="flex items-center mt-md">
<label className="w-1/3">Field Suffix</label>
<div className="w-2/3">
<input
type="text"
className="form-control"
placeholder="_suffix"
value={fieldNameInput?.suffix}
onChange={e =>
onChange({
...fieldNameInput,
suffix: e.target.value,
})
}
data-test={`remote-schema-customization-${label}-field-suffix-input`}
/>
</div>
</div>
<div className="text-lg font-bold mt-md">
Remap Field Names{' '}
<OverlayTrigger placement="right" overlay={tooltip}>
<i className="fa fa-question-circle" aria-hidden="true" />
</OverlayTrigger>
<TypeMapping
types={
types.find(x => x.typeName === fieldNameInput?.parentType)
?.fields || []
}
typeMappings={fieldNameInput?.mapping || []}
onChange={updatedMaps =>
onChange({
...fieldNameInput,
mapping: updatedMaps,
})
}
label={label}
/>
</div>
{mode === 'edit' ? (
<div className="mt-md flex justify-end">
<Button color="red" size="sm" onClick={onDelete}>
Remove
</Button>
</div>
) : (
<div className="mt-md">
<Button
size="sm"
color="yellow"
onClick={onSave}
data-test="add-field-customization"
>
Add Field Customization
</Button>
</div>
)}
</div>
);
};
export default FieldNames;

View File

@ -0,0 +1,335 @@
import React, { useState, useEffect } from 'react';
import { OverlayTrigger } from 'react-bootstrap';
import Tooltip from 'react-bootstrap/lib/Tooltip';
import { GraphQLSchema } from 'graphql';
import styles from '../../RemoteSchema.scss';
import { Button } from '../../../../Common';
import { graphQLCustomization as GType } from '../../types';
import TypeMapping from './TypeMapping';
import FieldNames from './FieldNames';
import { useIntrospectionSchemaRemote } from '../../graphqlUtils';
import globals from '../../../../../Globals';
import { Dispatch } from '../../../../../types';
import { checkDefaultGQLScalarType } from '../../Permissions/utils';
type Props = {
graphQLCustomization: GType;
onChange: (updatedGraphQLCustomization: GType) => void;
remoteSchemaName: string;
dispatch: Dispatch;
isDisabled: boolean;
};
type TypeNamesType = {
prefix?: string;
suffix?: string;
mapping?: { type: string; custom_name: string }[];
};
type FieldNamesType = { parentType?: string } & TypeNamesType;
const convertToMapping = (
values: { type: string; custom_name: string }[]
): Record<string, string> => {
const obj: Record<string, string> = {};
values.forEach(v => {
obj[v.type] = v.custom_name;
});
return obj;
};
const GraphQLCustomizationEdit = ({
graphQLCustomization,
remoteSchemaName,
dispatch,
onChange,
isDisabled,
}: Props) => {
const res = useIntrospectionSchemaRemote(
remoteSchemaName,
{
'x-hasura-admin-secret': globals.adminSecret,
},
dispatch
);
const schema = res.schema as GraphQLSchema | null;
const [types, setTypes] = useState<
{
typeName: string;
fields: string[];
}[]
>([]);
const { error } = res;
useEffect(() => {
setTypes(
Object.entries(schema?.getTypeMap() || {})
.map(([typeName, x]) => ({
typeName,
// eslint-disable-next-line no-underscore-dangle
fields: Object.keys((x as any)._fields || {}),
}))
.filter(({ typeName }) => !checkDefaultGQLScalarType(typeName))
);
}, [schema]);
const [openEditor, setOpenEditor] = useState(false);
const [rootFieldNamespace, setRootFieldNamespace] = useState<
string | undefined
>(undefined);
const [typeNames, setTypesNames] = useState<undefined | TypeNamesType>(
undefined
);
const [fieldNames, setFieldNames] = useState<undefined | FieldNamesType[]>(
undefined
);
const [showFieldCustomizationBtn, updateShowFieldCustomizationBtn] = useState(
true
);
const [tempFieldName, setTempFieldName] = useState<
FieldNamesType | undefined
>(undefined);
useEffect(() => {
setRootFieldNamespace(graphQLCustomization?.root_fields_namespace);
}, [graphQLCustomization?.root_fields_namespace]);
useEffect(() => {
setTypesNames({
prefix: graphQLCustomization?.type_names?.prefix,
suffix: graphQLCustomization?.type_names?.suffix,
mapping: Object.entries(
graphQLCustomization?.type_names?.mapping || {}
).map(([type, custom_name]) => ({
type,
custom_name,
})),
});
}, [graphQLCustomization?.type_names]);
useEffect(() => {
if (graphQLCustomization?.field_names)
setFieldNames(
graphQLCustomization?.field_names.map(fieldName => {
return {
parentType: fieldName.parent_type,
prefix: fieldName.prefix,
suffix: fieldName.suffix,
mapping: Object.entries(fieldName.mapping || {}).map(
([type, custom_name]) => ({
type,
custom_name,
})
),
};
})
);
}, [graphQLCustomization?.field_names]);
if (error) return <div>Something went wrong with schema introspection</div>;
return (
<>
<br />
{!openEditor ? (
<Button
size="xs"
className="mt-md"
onClick={() => setOpenEditor(true)}
disabled={isDisabled}
data-test="remote-schema-customization-editor-expand-btn"
>
{!graphQLCustomization ? 'Add' : 'Edit'}
</Button>
) : (
<div className={styles.CustomEditor} data-test="remote-schema-editor">
<Button size="xs" onClick={() => setOpenEditor(false)}>
close
</Button>
<div className="flex items-center mt-md">
<label className="w-1/3">Root Field Namespace</label>
<div className="w-2/3">
<input
type="text"
className="form-control"
placeholder="namespace_"
value={rootFieldNamespace}
data-test="remote-schema-customization-root-field-input"
onChange={e =>
onChange({
...graphQLCustomization,
root_fields_namespace: e.target.value,
})
}
/>
</div>
</div>
<div className="text-lg font-bold mt-md">Type Names</div>
<div className="flex items-center mt-md">
<label className="w-1/3">Prefix</label>
<div className="w-2/3">
<input
type="text"
className="form-control"
placeholder="prefix_"
value={typeNames?.prefix}
data-test="remote-schema-customization-type-name-prefix-input"
onChange={e =>
onChange({
...graphQLCustomization,
type_names: {
...graphQLCustomization?.type_names,
prefix: e.target.value,
},
})
}
/>
</div>
</div>
<div className="flex items-center mt-md">
<label className="w-1/3">Suffix</label>
<div className="w-2/3">
<input
type="text"
className="form-control"
placeholder="_suffix"
value={typeNames?.suffix}
data-test="remote-schema-customization-type-name-suffix-input"
onChange={e =>
onChange({
...graphQLCustomization,
type_names: {
...graphQLCustomization?.type_names,
suffix: e.target.value,
},
})
}
/>
</div>
</div>
<div className="text-lg font-bold mt-md">
Rename Type Names{' '}
<OverlayTrigger
placement="right"
overlay={
<Tooltip id="tooltip-cascade">
Type remapping takes precedence to prefixes and suffixes.
</Tooltip>
}
>
<i className="fa fa-question-circle" aria-hidden="true" />
</OverlayTrigger>
</div>
<TypeMapping
onChange={updatedMaps =>
onChange({
...graphQLCustomization,
type_names: {
...graphQLCustomization?.type_names,
mapping: convertToMapping(updatedMaps),
},
})
}
types={types.map(v => v.typeName)}
typeMappings={typeNames?.mapping ?? []}
label="type-name"
/>
{/* Existing Field Names */}
<div className="text-lg font-bold mt-md">Field Names</div>
{(fieldNames ?? []).map((fm, i) => (
<FieldNames
mode="edit"
types={types.filter(
x =>
!fieldNames
?.filter(v => v.parentType !== fm.parentType)
.map(v => v.parentType)
.includes(x.typeName)
)}
fieldName={fm}
onChange={updatedFieldName =>
onChange({
...graphQLCustomization,
field_names: graphQLCustomization.field_names?.map((x, j) => {
if (i !== j) return x;
return {
parent_type: updatedFieldName.parentType,
prefix: updatedFieldName.prefix,
suffix: updatedFieldName.suffix,
mapping: convertToMapping(updatedFieldName.mapping || []),
};
}),
})
}
onDelete={() => {
onChange({
...graphQLCustomization,
field_names: graphQLCustomization.field_names?.filter(
(x, j) => i !== j
),
});
}}
label={`field-type-${i}`}
/>
))}
{/* Adding a new field name */}
{showFieldCustomizationBtn ? (
<Button
size="xs"
className="mt-md"
onClick={() => updateShowFieldCustomizationBtn(false)}
data-test="remote-schema-customization-open-field-mapping"
>
Add Field Mapping
</Button>
) : (
<FieldNames
mode="create"
types={types.filter(
x => !fieldNames?.map(v => v.parentType).includes(x.typeName)
)}
fieldName={tempFieldName || {}}
onChange={updatedFieldName => {
setTempFieldName(updatedFieldName);
}}
onClose={() => {
updateShowFieldCustomizationBtn(true);
setTempFieldName(undefined);
}}
onSave={() => {
const newObj = {
parent_type: tempFieldName?.parentType,
prefix: tempFieldName?.prefix,
suffix: tempFieldName?.suffix,
mapping: convertToMapping(tempFieldName?.mapping || []),
};
onChange({
...graphQLCustomization,
field_names: [
...(graphQLCustomization?.field_names || []),
newObj,
],
});
updateShowFieldCustomizationBtn(true);
setTempFieldName(undefined);
}}
label="field-type"
/>
)}
</div>
)}
</>
);
};
export default GraphQLCustomizationEdit;

View File

@ -0,0 +1,149 @@
import React, { useEffect, useState } from 'react';
import styles from '../../RemoteSchema.scss';
type TypeMap = { type: string; custom_name: string };
type Props = {
types: string[];
typeMappings: TypeMap[];
onChange: (updatedMaps: TypeMap[]) => void;
label?: string;
};
const SelectOne = ({
options,
value,
onChange,
label,
}: {
options: Props['types'];
value: string;
onChange: (e: any) => void;
label?: string;
}) => (
<select
value={value}
onChange={onChange}
className="form-control font-normal"
data-test={`remote-schema-customization-${label}-lhs-input`}
>
<option value="" className="text-base">
Select Type ...
</option>
{options.map((op, i) => (
<option value={op} key={i}>
{op}
</option>
))}
</select>
);
const TypeMapping = ({ types, typeMappings, onChange, label }: Props) => {
const [existingMaps, updateExistingMaps] = useState(typeMappings);
const [newMap, setNewMap] = useState<TypeMap>({ type: '', custom_name: '' });
const onModifyItem = (index: number, newVal: TypeMap) => {
const updatedMaps = existingMaps;
updatedMaps[index] = newVal;
onChange(updatedMaps);
};
const onAddItem = (inputMap: TypeMap) => {
onChange([...existingMaps, inputMap]);
setNewMap({ type: '', custom_name: '' });
};
const onDeleteItem = (index: number) => {
onChange(existingMaps.filter((t, i) => i !== index));
};
useEffect(() => {
updateExistingMaps(typeMappings);
}, [typeMappings]);
return (
<>
{existingMaps.map(({ type, custom_name }, i) => {
return (
<div className="flex items-center mt-md" key={i}>
<label className="w-2/3">
<SelectOne
options={[
...types.filter(
t => !existingMaps.map(x => x.type).includes(t)
),
type,
]}
value={type}
key={i}
onChange={e => {
onModifyItem(i, {
type: e.target.value,
custom_name,
});
}}
label={`${label ?? 'no-value'}-${i}`}
/>
</label>
<span className="pl-md pr-md"> : </span>
<div className="w-2/3">
<input
type="text"
value={custom_name}
className="form-control font-normal"
onChange={e => {
onModifyItem(i, { type, custom_name: e.target.value });
}}
data-test={`remote-schema-customization-${
label ?? 'no-value'
}-${i}-rhs-input`}
/>
</div>
<div>
<i
className={`${styles.fontAwosomeClose} fa-lg fa fa-times`}
data-test={`remove-type-map-${i}`}
onClick={() => onDeleteItem(i)}
/>
</div>
</div>
);
})}
<div className="flex items-center mt-md" style={{ maxWidth: '96%' }}>
<label className="w-2/3">
<SelectOne
options={types.filter(
t => !existingMaps.map(x => x.type).includes(t)
)}
label={`${label ?? 'no-value'}`}
value={newMap.type}
onChange={e => {
setNewMap({ ...newMap, type: e.target.value });
onAddItem({ ...newMap, type: e.target.value });
}}
/>
</label>
<span className="pl-md pr-md"> : </span>
<div className="w-2/3">
<input
type="text"
value={newMap.custom_name}
className="form-control font-normal"
data-test={`remote-schema-customization-${
label ?? 'no-value'
}-rhs-input`}
onChange={e =>
setNewMap({ ...newMap, custom_name: e.target.value })
}
// onBlur={() => {
// onAddItem(newMap);
// }}
/>
</div>
</div>
</>
);
};
export default TypeMapping;

View File

@ -512,7 +512,7 @@ const isEnumType = (type: GraphQLInputType): boolean => {
};
// Check if type belongs to default gql scalar types
const checkDefaultGQLScalarType = (typeName: string): boolean => {
export const checkDefaultGQLScalarType = (typeName: string): boolean => {
const gqlDefaultTypes = ['Boolean', 'Float', 'String', 'Int', 'ID'];
if (gqlDefaultTypes.indexOf(typeName) > -1) return true;
return false;

View File

@ -318,3 +318,18 @@
display: flex;
align-items: center;
}
.CustomEditor {
padding: 15px;
background-color: white;
border: 1px solid #ccc;
max-width: 700px;
margin-top: 5px;
}
.fontAwosomeClose {
margin-left: 10px;
width: 15px;
min-width: 15px;
cursor: pointer;
}

View File

@ -23,6 +23,7 @@ const addState: AddState = {
name: '',
forwardClientHeaders: false,
comment: '',
customization: undefined,
...asyncState,
editState: {
id: -1,
@ -34,6 +35,7 @@ const addState: AddState = {
originalTimeoutConf: '',
originalForwardClientHeaders: false,
originalComment: '',
originalCustomization: undefined,
},
};

View File

@ -18,6 +18,21 @@ export type AsyncState = {
isFetchError: any;
};
export type graphQLCustomization = {
root_fields_namespace?: string;
type_names?: {
prefix?: string;
suffix?: string;
mapping?: Record<string, string>;
};
field_names?: {
parent_type?: string;
prefix?: string;
suffix?: string;
mapping?: Record<string, string>;
}[];
};
export type EditState = {
id: number;
isModify: boolean;
@ -28,6 +43,7 @@ export type EditState = {
originalTimeoutConf: string;
originalForwardClientHeaders: boolean;
originalComment?: string;
originalCustomization?: graphQLCustomization;
};
export type AddState = AsyncState & {
@ -39,6 +55,7 @@ export type AddState = AsyncState & {
forwardClientHeaders: boolean;
editState: EditState;
comment?: string;
customization?: graphQLCustomization;
};
export type ListState = AsyncState & {

View File

@ -69,10 +69,9 @@ a[class=''] {
background-color: rgb(239, 239, 239);
}
/* To remove botstrap html font size from 10px */
html {
font-size: 14px;
font-size: 14px;
}
/* React Select - Fixes Inner Outline in Select Box */
@ -82,23 +81,28 @@ input[id*='react-select'][id$='-input']:focus {
/* ** GraphiQL CSS tweaks to fix compatibility */
/* Input fixes from Tailwind */
.graphiql-container input, .graphiql-container select{
.graphiql-container input,
.graphiql-container select {
min-width: min-content;
padding: 4px 8px;
height: 32px;
border-radius: 4px;
border: 1px solid rgb(203, 213, 225) !important;
box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0.05) 0px 1px 2px 0px;
box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(0, 0, 0, 0.05) 0px 1px 2px 0px;
}
.graphiql-container input:focus, .graphiql-container select:focus{
.graphiql-container input:focus,
.graphiql-container select:focus {
border: 1px solid rgb(251, 191, 36) !important;
box-shadow: rgb(255, 255, 255) 0px 0px 0px 0px, rgb(253, 230, 138) 0px 0px 0px 2px, rgba(0, 0, 0, 0.05) 0px 1px 2px 0px;
box-shadow: rgb(255, 255, 255) 0px 0px 0px 0px,
rgb(253, 230, 138) 0px 0px 0px 2px, rgba(0, 0, 0, 0.05) 0px 1px 2px 0px;
}
.graphiql-explorer-actions > select {
font-family: system, -apple-system, 'San Francisco', '.SFNSDisplay-Regular', 'Helvetica Neue', helvetica, 'Lucida Grande', arial, sans-serif;
font-family: system, -apple-system, 'San Francisco', '.SFNSDisplay-Regular',
'Helvetica Neue', helvetica, 'Lucida Grande', arial, sans-serif;
font-variant-caps: normal;
font-weight:400;
font-weight: 400;
border: 1px solid #cbd5e1;
font-size: 14px;
padding: 5px 8px;
@ -106,44 +110,47 @@ input[id*='react-select'][id$='-input']:focus {
margin-left: 8px;
margin-right: 8px;
height: 32px;
box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0.05) 0px 1px 2px 0px;
box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(0, 0, 0, 0.05) 0px 1px 2px 0px;
}
.graphiql-explorer-actions > select:focus {
border: 1px solid rgb(251, 191, 36) !important;
box-shadow: rgb(255, 255, 255) 0px 0px 0px 0px, rgb(253, 230, 138) 0px 0px 0px 2px, rgba(0, 0, 0, 0.05) 0px 1px 2px 0px;
box-shadow: rgb(255, 255, 255) 0px 0px 0px 0px,
rgb(253, 230, 138) 0px 0px 0px 2px, rgba(0, 0, 0, 0.05) 0px 1px 2px 0px;
}
/* While we're fixing the inputs maybe the Explorer header + footer */
/* Fixes header spacing */
.doc-explorer-title-bar{
height:46px !important;
.doc-explorer-title-bar {
height: 46px !important;
}
.docExplorerHide{
.docExplorerHide {
padding: 16px !important;
}
/* Fixes spacing in operation title namer */
.graphiql-explorer-root{
padding-top:0 !important;
.graphiql-explorer-root {
padding-top: 0 !important;
}
.graphiql-operation-title-bar{
margin-top:10px;
.graphiql-operation-title-bar {
margin-top: 10px;
}
.graphiql-operation-title-bar > span > input{
margin-right:4px;
.graphiql-operation-title-bar > span > input {
margin-right: 4px;
}
/* Fixes footer spacing */
.graphiql-explorer-actions > span {
font-family: system, -apple-system, 'San Francisco', '.SFNSDisplay-Regular', 'Helvetica Neue', helvetica, 'Lucida Grande', arial, sans-serif;
font-family: system, -apple-system, 'San Francisco', '.SFNSDisplay-Regular',
'Helvetica Neue', helvetica, 'Lucida Grande', arial, sans-serif;
font-variant-caps: normal;
font-weight:500;
font-weight: 500;
font-size: 12px;
letter-spacing:0px;
letter-spacing: 0px;
text-transform: capitalize;
}
.graphiql-explorer-actions{
.graphiql-explorer-actions {
margin: 4px 0px 0px -8px !important;
width: calc(100% + 16px) !important;
padding:10px !important;
}
padding: 10px !important;
}