mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 01:12:56 +03:00
support customising GraphQL fields from console (#3175)
This commit is contained in:
parent
637c2cb555
commit
a1a851b3d1
@ -238,3 +238,19 @@ export const getSchemaTables = (allTables, tableSchema) => {
|
||||
export const getSchemaTableNames = (allTables, tableSchema) => {
|
||||
return getSchemaTables(allTables, tableSchema).map(t => getTableName(t));
|
||||
};
|
||||
|
||||
/*** Custom table fields utils ***/
|
||||
|
||||
export const getTableCustomRootFields = table => {
|
||||
if (table.configuration) {
|
||||
return table.configuration.custom_root_fields;
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
export const getTableCustomColumnNames = table => {
|
||||
if (table.configuration) {
|
||||
return table.configuration.custom_column_names;
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
@ -23,3 +23,19 @@ export const getDropPermissionQuery = (action, tableDef, role) => {
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getSetCustomRootFieldsQuery = (
|
||||
tableDef,
|
||||
rootFields,
|
||||
customColumnNames
|
||||
) => {
|
||||
return {
|
||||
type: 'set_table_custom_fields',
|
||||
version: 2,
|
||||
args: {
|
||||
table: tableDef,
|
||||
custom_root_fields: rootFields,
|
||||
custom_column_names: customColumnNames,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import Toggle from 'react-toggle';
|
||||
import styles from '../../../../Common/Common.scss';
|
||||
import ToolTip from '../../../../Common/Tooltip/Tooltip';
|
||||
|
||||
const enumCompatibilityDocsUrl =
|
||||
'https://docs.hasura.io/1.0/graphql/manual/schema/enums.html#create-enum-table';
|
||||
@ -53,15 +54,19 @@ const EnumsSection = ({ isEnum, toggleEnum, loading }) => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h4 className={`${styles.subheading_text}`}>Set table as enum</h4>
|
||||
<h4 className={`${styles.subheading_text}`}>
|
||||
Set table as enum
|
||||
<ToolTip
|
||||
message={
|
||||
'Expose the table values as GraphQL enums in the GraphQL API'
|
||||
}
|
||||
/>
|
||||
</h4>
|
||||
<div
|
||||
className={`${styles.display_flex} ${styles.add_mar_bottom}`}
|
||||
title={title}
|
||||
data-toggle="tooltip"
|
||||
>
|
||||
<span className={styles.add_mar_right_mid}>
|
||||
Expose the table values as GraphQL enums
|
||||
</span>
|
||||
<Toggle checked={isEnum} icons={false} onChange={toggleEnum} />
|
||||
</div>
|
||||
{getCompatibilityNote()}
|
||||
|
@ -0,0 +1,90 @@
|
||||
import React from 'react';
|
||||
import styles from '../../../../Common/Common.scss';
|
||||
import { getRootFieldLabel } from './utils';
|
||||
import CollapsibleToggle from '../../../../Common/CollapsibleToggle/CollapsibleToggle';
|
||||
|
||||
const RootFieldEditor = ({
|
||||
rootFields,
|
||||
disabled,
|
||||
selectOnChange,
|
||||
selectByPkOnChange,
|
||||
selectAggOnChange,
|
||||
insertOnChange,
|
||||
updateOnChange,
|
||||
deleteOnChange,
|
||||
tableName,
|
||||
}) => {
|
||||
const {
|
||||
select,
|
||||
select_by_pk: selectByPk,
|
||||
select_aggregate: selectAgg,
|
||||
insert,
|
||||
update,
|
||||
delete: _delete,
|
||||
} = rootFields;
|
||||
|
||||
const getDefaultRootField = rfType => {
|
||||
if (rfType.includes('select')) {
|
||||
return rfType.replace('select', tableName);
|
||||
}
|
||||
return `${rfType}_${tableName}`;
|
||||
};
|
||||
|
||||
const getRow = (rfType, value, onChange) => (
|
||||
<div
|
||||
className={`${styles.display_flex} row ${styles.add_mar_bottom_small}`}
|
||||
>
|
||||
<div className={`${styles.add_mar_right} col-md-3`}>
|
||||
{getRootFieldLabel(rfType)}
|
||||
</div>
|
||||
<div className={'col-md-5'}>
|
||||
<input
|
||||
type="text"
|
||||
value={value || ''}
|
||||
placeholder={`${getDefaultRootField(rfType)} (default)`}
|
||||
className="form-control"
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const getSection = rfType => {
|
||||
return (
|
||||
<div className={`${styles.add_mar_bottom_mid}`}>
|
||||
<CollapsibleToggle
|
||||
title={rfType === 'query' ? 'Query and Subscription' : 'Mutation'}
|
||||
useDefaultTitleStyle
|
||||
isOpen
|
||||
>
|
||||
{rfType === 'query' && (
|
||||
<div className={`${styles.add_pad_left} ${styles.add_pad_right}`}>
|
||||
{getRow('select', select, selectOnChange)}
|
||||
{getRow('select_by_pk', selectByPk, selectByPkOnChange)}
|
||||
{getRow('select_aggregate', selectAgg, selectAggOnChange)}
|
||||
</div>
|
||||
)}
|
||||
{rfType === 'mutation' && (
|
||||
<div className={`${styles.add_pad_left} ${styles.add_pad_right}`}>
|
||||
{getRow('insert', insert, insertOnChange)}
|
||||
{getRow('update', update, updateOnChange)}
|
||||
{getRow('delete', _delete, deleteOnChange)}
|
||||
</div>
|
||||
)}
|
||||
</CollapsibleToggle>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.add_mar_bottom_mid}>
|
||||
{getSection('query')}
|
||||
{getSection('mutation')}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RootFieldEditor;
|
@ -117,3 +117,15 @@ export const getKeyDef = (config, constraintName) => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const getRootFieldLabel = rfType => {
|
||||
const labels = {
|
||||
select: 'Select',
|
||||
select_by_pk: 'Select by PK',
|
||||
select_aggregate: 'Select Aggregate',
|
||||
insert: 'Insert',
|
||||
update: 'Update',
|
||||
delete: 'Delete',
|
||||
};
|
||||
return labels[rfType];
|
||||
};
|
||||
|
@ -126,6 +126,14 @@ const defaultModifyState = {
|
||||
colMappings: [{ column: '', refColumn: '' }],
|
||||
isToggled: false,
|
||||
},
|
||||
rootFieldsEdit: {
|
||||
select: '',
|
||||
select_by_pk: '',
|
||||
select_aggregate: '',
|
||||
insert: '',
|
||||
update: '',
|
||||
delete: '',
|
||||
},
|
||||
permissionsState: { ...defaultPermissionsState },
|
||||
prevPermissionState: { ...defaultPermissionsState },
|
||||
ongoingRequest: false,
|
||||
|
@ -25,6 +25,7 @@ import {
|
||||
TOGGLE_ENUM,
|
||||
TOGGLE_ENUM_SUCCESS,
|
||||
TOGGLE_ENUM_FAILURE,
|
||||
MODIFY_ROOT_FIELD,
|
||||
} from '../TableModify/ModifyActions';
|
||||
|
||||
// TABLE RELATIONSHIPS
|
||||
@ -606,6 +607,11 @@ const modifyReducer = (tableName, schemas, modifyStateOrig, action) => {
|
||||
loading: false,
|
||||
},
|
||||
};
|
||||
case MODIFY_ROOT_FIELD:
|
||||
return {
|
||||
...modifyState,
|
||||
rootFieldsEdit: action.data,
|
||||
};
|
||||
default:
|
||||
return modifyState;
|
||||
}
|
||||
|
@ -4,6 +4,11 @@ import SearchableSelectBox from '../../../Common/SearchableSelect/SearchableSele
|
||||
import CustomInputAutoSuggest from '../../../Common/CustomInputAutoSuggest/CustomInputAutoSuggest';
|
||||
|
||||
import { getValidAlterOptions } from './utils';
|
||||
import Tooltip from '../../../Common/Tooltip/Tooltip';
|
||||
import {
|
||||
checkFeatureSupport,
|
||||
CUSTOM_GRAPHQL_FIELDS_SUPPORT,
|
||||
} from '../../../../helpers/versionUtils';
|
||||
|
||||
const ColumnEditor = ({
|
||||
onSubmit,
|
||||
@ -57,18 +62,47 @@ const ColumnEditor = ({
|
||||
const updateColumnType = selected => {
|
||||
dispatch(editColumn(colName, 'type', selected.value));
|
||||
};
|
||||
const updateColumnDef = (e, data) => {
|
||||
const toggleColumnNullable = e => {
|
||||
dispatch(editColumn(colName, 'isNullable', e.target.value === 'true'));
|
||||
};
|
||||
const toggleColumnUnique = e => {
|
||||
dispatch(editColumn(colName, 'isUnique', e.target.value === 'true'));
|
||||
};
|
||||
const updateColumnDefault = (e, data) => {
|
||||
const { newValue } = data;
|
||||
dispatch(editColumn(colName, 'default', newValue));
|
||||
};
|
||||
const updateColumnComment = e => {
|
||||
dispatch(editColumn(colName, 'comment', e.target.value));
|
||||
};
|
||||
const toggleColumnNullable = e => {
|
||||
dispatch(editColumn(colName, 'isNullable', e.target.value === 'true'));
|
||||
const updateColumnCustomField = e => {
|
||||
dispatch(editColumn(colName, 'customFieldName', e.target.value));
|
||||
};
|
||||
const toggleColumnUnique = e => {
|
||||
dispatch(editColumn(colName, 'isUnique', e.target.value === 'true'));
|
||||
|
||||
const getColumnCustomFieldInput = () => {
|
||||
if (!checkFeatureSupport(CUSTOM_GRAPHQL_FIELDS_SUPPORT)) return;
|
||||
|
||||
return (
|
||||
<div className={`${styles.display_flex} form-group`}>
|
||||
<label className={'col-xs-4'}>
|
||||
Custom GraphQL field
|
||||
<Tooltip
|
||||
message={
|
||||
'Expose the column with a different name in the GraphQL API'
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<div className="col-xs-6">
|
||||
<input
|
||||
className="input-sm form-control"
|
||||
value={selectedProperties[colName].customFieldName}
|
||||
onChange={updateColumnCustomField}
|
||||
type="text"
|
||||
data-test="edit-col-custom-field"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getColumnDefaultInput = () => {
|
||||
@ -79,7 +113,7 @@ const ColumnEditor = ({
|
||||
options={defaultOptions}
|
||||
className="input-sm form-control"
|
||||
value={selectedProperties[colName].default || ''}
|
||||
onChange={updateColumnDef}
|
||||
onChange={updateColumnDefault}
|
||||
type="text"
|
||||
disabled={columnProperties.pkConstraint}
|
||||
data-test="edit-col-default"
|
||||
@ -92,7 +126,7 @@ const ColumnEditor = ({
|
||||
<div className={`${styles.colEditor} container-fluid`}>
|
||||
<form className="form-horizontal" onSubmit={onSubmit}>
|
||||
<div className={`${styles.display_flex} form-group`}>
|
||||
<label className="col-xs-2">Name</label>
|
||||
<label className={'col-xs-4'}>Name</label>
|
||||
<div className="col-xs-6">
|
||||
<input
|
||||
className="input-sm form-control"
|
||||
@ -104,7 +138,7 @@ const ColumnEditor = ({
|
||||
</div>
|
||||
</div>
|
||||
<div className={`${styles.display_flex} form-group`}>
|
||||
<label className="col-xs-2">Type</label>
|
||||
<label className={'col-xs-4'}>Type</label>
|
||||
<div className="col-xs-6">
|
||||
<SearchableSelectBox
|
||||
options={alterOptions}
|
||||
@ -118,7 +152,7 @@ const ColumnEditor = ({
|
||||
</div>
|
||||
</div>
|
||||
<div className={`${styles.display_flex} form-group`}>
|
||||
<label className="col-xs-2">Nullable</label>
|
||||
<label className={'col-xs-4'}>Nullable</label>
|
||||
<div className="col-xs-6">
|
||||
<select
|
||||
className="input-sm form-control"
|
||||
@ -133,7 +167,7 @@ const ColumnEditor = ({
|
||||
</div>
|
||||
</div>
|
||||
<div className={`${styles.display_flex} form-group`}>
|
||||
<label className="col-xs-2">Unique</label>
|
||||
<label className={'col-xs-4'}>Unique</label>
|
||||
<div className="col-xs-6">
|
||||
<select
|
||||
className="input-sm form-control"
|
||||
@ -148,11 +182,11 @@ const ColumnEditor = ({
|
||||
</div>
|
||||
</div>
|
||||
<div className={`${styles.display_flex} form-group`}>
|
||||
<label className="col-xs-2">Default</label>
|
||||
<label className={'col-xs-4'}>Default</label>
|
||||
<div className="col-xs-6">{getColumnDefaultInput()}</div>
|
||||
</div>
|
||||
<div className={`${styles.display_flex} form-group`}>
|
||||
<label className="col-xs-2">Comment</label>
|
||||
<label className={'col-xs-4'}>Comment</label>
|
||||
<div className="col-xs-6">
|
||||
<input
|
||||
className="input-sm form-control"
|
||||
@ -163,6 +197,7 @@ const ColumnEditor = ({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{getColumnCustomFieldInput()}
|
||||
</form>
|
||||
<div className="row">
|
||||
<br />
|
||||
|
@ -22,6 +22,10 @@ import GqlCompatibilityWarning from '../../../Common/GqlCompatibilityWarning/Gql
|
||||
|
||||
import styles from './ModifyTable.scss';
|
||||
import { getConfirmation } from '../../../Common/utils/jsUtils';
|
||||
import {
|
||||
checkFeatureSupport,
|
||||
CUSTOM_GRAPHQL_FIELDS_SUPPORT,
|
||||
} from '../../../../helpers/versionUtils';
|
||||
|
||||
const ColumnEditorList = ({
|
||||
tableSchema,
|
||||
@ -31,6 +35,7 @@ const ColumnEditorList = ({
|
||||
validTypeCasts,
|
||||
dataTypeIndexMap,
|
||||
columnDefaultFunctions,
|
||||
customColumnNames,
|
||||
}) => {
|
||||
const tableName = tableSchema.table_name;
|
||||
|
||||
@ -79,6 +84,10 @@ const ColumnEditorList = ({
|
||||
comment: col.comment || '',
|
||||
};
|
||||
|
||||
if (checkFeatureSupport(CUSTOM_GRAPHQL_FIELDS_SUPPORT)) {
|
||||
columnProperties.customFieldName = customColumnNames[colName] || '';
|
||||
}
|
||||
|
||||
const onSubmit = toggleEditor => {
|
||||
dispatch(saveColumnChangesSql(colName, col, toggleEditor));
|
||||
};
|
||||
@ -148,7 +157,14 @@ const ColumnEditorList = ({
|
||||
const collapsedLabel = () => {
|
||||
return (
|
||||
<div key={colName}>
|
||||
<b>{colName}</b> {gqlCompatibilityWarning()} - {keyProperties()}
|
||||
<b>
|
||||
{colName}
|
||||
<i>
|
||||
{columnProperties.customFieldName &&
|
||||
` → ${columnProperties.customFieldName}`}
|
||||
</i>
|
||||
</b>{' '}
|
||||
{gqlCompatibilityWarning()} - {keyProperties()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -199,9 +215,9 @@ const ColumnEditorList = ({
|
||||
* "Data type",
|
||||
* "User friendly name of the data type",
|
||||
* "Description of the data type",
|
||||
* "Comma seperated castable data types",
|
||||
* "Comma seperated user friendly names of the castable data types",
|
||||
* "Colon seperated user friendly description of the castable data types"
|
||||
* "Comma separated castable data types",
|
||||
* "Comma separated user friendly names of the castable data types",
|
||||
* "Colon separated user friendly description of the castable data types"
|
||||
* ]
|
||||
* */
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import requestAction from '../../../../utils/requestAction';
|
||||
import Endpoints, { globalCookiePolicy } from '../../../../Endpoints';
|
||||
import { CLI_CONSOLE_MODE } from '../../../../constants';
|
||||
import {
|
||||
updateSchemaInfo,
|
||||
handleMigrationErrors,
|
||||
@ -34,15 +35,22 @@ import {
|
||||
generateTableDef,
|
||||
getTableCheckConstraints,
|
||||
findTableCheckConstraint,
|
||||
getTableCustomRootFields,
|
||||
getTableCustomColumnNames,
|
||||
} from '../../../Common/utils/pgUtils';
|
||||
import { getSetCustomRootFieldsQuery } from '../../../Common/utils/v1QueryUtils';
|
||||
|
||||
import {
|
||||
fetchColumnCastsQuery,
|
||||
convertArrayToJson,
|
||||
getCreatePkSql,
|
||||
getDropPkSql,
|
||||
sanitiseRootFields,
|
||||
} from './utils';
|
||||
import { CLI_CONSOLE_MODE } from '../../../../constants';
|
||||
import {
|
||||
checkFeatureSupport,
|
||||
CUSTOM_GRAPHQL_FIELDS_SUPPORT,
|
||||
} from '../../../../helpers/versionUtils';
|
||||
|
||||
const DELETE_PK_WARNING =
|
||||
'Without a primary key there is no way to uniquely identify a row of a table';
|
||||
@ -77,6 +85,9 @@ const TOGGLE_ENUM = 'ModifyTable/TOGGLE_ENUM';
|
||||
const TOGGLE_ENUM_SUCCESS = 'ModifyTable/TOGGLE_ENUM_SUCCESS';
|
||||
const TOGGLE_ENUM_FAILURE = 'ModifyTable/TOGGLE_ENUM_FAILURE';
|
||||
|
||||
export const MODIFY_ROOT_FIELD = 'ModifyTable/MODIFY_ROOT_FIELD';
|
||||
const SET_CUSTOM_ROOT_FIELDS = 'ModifyTable/SET_CUSTOM_ROOT_FIELDS';
|
||||
|
||||
const RESET = 'ModifyTable/RESET';
|
||||
|
||||
const toggleEnumSuccess = () => ({
|
||||
@ -92,6 +103,11 @@ const setForeignKeys = fks => ({
|
||||
fks,
|
||||
});
|
||||
|
||||
const modifyRootFields = rootFields => ({
|
||||
type: MODIFY_ROOT_FIELD,
|
||||
data: rootFields,
|
||||
});
|
||||
|
||||
const editColumn = (column, key, value) => ({
|
||||
type: EDIT_COLUMN,
|
||||
column,
|
||||
@ -134,6 +150,61 @@ const resetPrimaryKeys = () => ({
|
||||
type: RESET_PRIMARY_KEY,
|
||||
});
|
||||
|
||||
export const setCustomRootFields = successCb => (dispatch, getState) => {
|
||||
const {
|
||||
allSchemas: allTables,
|
||||
currentTable: tableName,
|
||||
currentSchema: schemaName,
|
||||
modify: { rootFieldsEdit: newRootFields },
|
||||
} = getState().tables;
|
||||
|
||||
dispatch({ type: SET_CUSTOM_ROOT_FIELDS });
|
||||
|
||||
const tableDef = generateTableDef(tableName, schemaName);
|
||||
|
||||
const table = findTable(allTables, tableDef);
|
||||
|
||||
const existingRootFields = getTableCustomRootFields(table);
|
||||
const existingCustomColumnNames = getTableCustomColumnNames(table);
|
||||
|
||||
const upQuery = getSetCustomRootFieldsQuery(
|
||||
tableDef,
|
||||
sanitiseRootFields(newRootFields),
|
||||
existingCustomColumnNames
|
||||
);
|
||||
const downQuery = getSetCustomRootFieldsQuery(
|
||||
tableDef,
|
||||
existingRootFields,
|
||||
existingCustomColumnNames
|
||||
);
|
||||
|
||||
const migrationName = `set_custom_root_fields_${schemaName}_${tableName}`;
|
||||
const requestMsg = 'Setting custom root fields...';
|
||||
const successMsg = 'Setting custom root fields successful';
|
||||
const errorMsg = 'Setting custom root fields failed';
|
||||
const customOnSuccess = () => {
|
||||
if (successCb) {
|
||||
successCb();
|
||||
}
|
||||
};
|
||||
const customOnError = err => {
|
||||
dispatch({ type: UPDATE_MIGRATION_STATUS_ERROR, data: err });
|
||||
};
|
||||
|
||||
makeMigrationCall(
|
||||
dispatch,
|
||||
getState,
|
||||
[upQuery],
|
||||
[downQuery],
|
||||
migrationName,
|
||||
customOnSuccess,
|
||||
customOnError,
|
||||
requestMsg,
|
||||
successMsg,
|
||||
errorMsg
|
||||
);
|
||||
};
|
||||
|
||||
export const removeCheckConstraint = constraintName => (dispatch, getState) => {
|
||||
const confirmMessage = `This will permanently delete the check constraint "${constraintName}" from this table`;
|
||||
const isOk = getConfirmation(confirmMessage, true, constraintName);
|
||||
@ -1463,6 +1534,7 @@ const saveColumnChangesSql = (colName, column, onSuccess) => {
|
||||
const comment = columnEdit.comment || '';
|
||||
const newName = columnEdit.name;
|
||||
const currentSchema = columnEdit.schemaName;
|
||||
const customFieldName = columnEdit.customFieldName;
|
||||
const checkIfFunctionFormat = isPostgresFunction(def);
|
||||
// ALTER TABLE <table> ALTER COLUMN <column> TYPE <column_type>;
|
||||
let defWithQuotes;
|
||||
@ -1471,18 +1543,16 @@ const saveColumnChangesSql = (colName, column, onSuccess) => {
|
||||
} else {
|
||||
defWithQuotes = def;
|
||||
}
|
||||
|
||||
const tableDef = generateTableDef(tableName, currentSchema);
|
||||
const table = findTable(getState().tables.allSchemas, tableDef);
|
||||
|
||||
// check if column type has changed before making it part of the migration
|
||||
const originalColType = column.data_type; // "value"
|
||||
const originalColDefault = column.column_default; // null or "value"
|
||||
const originalColComment = column.comment; // null or "value"
|
||||
const originalColNullable = column.is_nullable; // "YES" or "NO"
|
||||
const originalColUnique = isColumnUnique(
|
||||
getState().tables.allSchemas.find(
|
||||
table =>
|
||||
table.table_name === tableName && table.table_schema === currentSchema
|
||||
),
|
||||
colName
|
||||
);
|
||||
const originalColUnique = isColumnUnique(table, colName);
|
||||
|
||||
/* column type up/down migration */
|
||||
const columnChangesUpQuery =
|
||||
@ -1540,6 +1610,41 @@ const saveColumnChangesSql = (colName, column, onSuccess) => {
|
||||
]
|
||||
: [];
|
||||
|
||||
/* column custom field up/down migration*/
|
||||
if (checkFeatureSupport(CUSTOM_GRAPHQL_FIELDS_SUPPORT)) {
|
||||
const existingCustomColumnNames = getTableCustomColumnNames(table);
|
||||
const existingRootFields = getTableCustomRootFields(table);
|
||||
const newCustomColumnNames = { ...existingCustomColumnNames };
|
||||
let isCustomFieldNameChanged = false;
|
||||
if (customFieldName) {
|
||||
if (customFieldName !== existingCustomColumnNames[colName]) {
|
||||
isCustomFieldNameChanged = true;
|
||||
newCustomColumnNames[colName] = customFieldName.trim();
|
||||
}
|
||||
} else {
|
||||
if (existingCustomColumnNames[colName]) {
|
||||
isCustomFieldNameChanged = true;
|
||||
delete newCustomColumnNames[colName];
|
||||
}
|
||||
}
|
||||
if (isCustomFieldNameChanged) {
|
||||
schemaChangesUp.push(
|
||||
getSetCustomRootFieldsQuery(
|
||||
tableDef,
|
||||
existingRootFields,
|
||||
newCustomColumnNames
|
||||
)
|
||||
);
|
||||
schemaChangesDown.push(
|
||||
getSetCustomRootFieldsQuery(
|
||||
tableDef,
|
||||
existingRootFields,
|
||||
existingCustomColumnNames
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* column default up/down migration */
|
||||
if (def.trim() !== '') {
|
||||
// ALTER TABLE ONLY <table> ALTER COLUMN <column> SET DEFAULT <default>;
|
||||
@ -2381,4 +2486,5 @@ export {
|
||||
deleteTrigger,
|
||||
toggleEnumSuccess,
|
||||
toggleEnumFailure,
|
||||
modifyRootFields,
|
||||
};
|
||||
|
@ -4,7 +4,11 @@ import TableHeader from '../TableCommon/TableHeader';
|
||||
|
||||
import { getAllDataTypeMap } from '../Common/utils';
|
||||
|
||||
import { TABLE_ENUMS_SUPPORT } from '../../../../helpers/versionUtils';
|
||||
import {
|
||||
checkFeatureSupport,
|
||||
CUSTOM_GRAPHQL_FIELDS_SUPPORT,
|
||||
TABLE_ENUMS_SUPPORT,
|
||||
} from '../../../../helpers/versionUtils';
|
||||
import globals from '../../../../Globals';
|
||||
|
||||
import {
|
||||
@ -31,6 +35,7 @@ import ForeignKeyEditor from './ForeignKeyEditor';
|
||||
import UniqueKeyEditor from './UniqueKeyEditor';
|
||||
import TriggerEditorList from './TriggerEditorList';
|
||||
import CheckConstraints from './CheckConstraints';
|
||||
import RootFields from './RootFields';
|
||||
import styles from './ModifyTable.scss';
|
||||
import { NotFoundError } from '../../../Error/PageNotFound';
|
||||
|
||||
@ -39,7 +44,10 @@ import {
|
||||
getTableCheckConstraints,
|
||||
findTable,
|
||||
generateTableDef,
|
||||
getTableCustomRootFields,
|
||||
getTableCustomColumnNames,
|
||||
} from '../../../Common/utils/pgUtils';
|
||||
import Tooltip from '../../../Common/Tooltip/Tooltip';
|
||||
|
||||
class ModifyTable extends React.Component {
|
||||
componentDidMount() {
|
||||
@ -48,11 +56,13 @@ class ModifyTable extends React.Component {
|
||||
dispatch(setTable(this.props.tableName));
|
||||
dispatch(fetchColumnTypeInfo());
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.dispatch({
|
||||
type: RESET_COLUMN_TYPE_INFO,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
tableName,
|
||||
@ -70,6 +80,7 @@ class ModifyTable extends React.Component {
|
||||
columnDefaultFunctions,
|
||||
schemaList,
|
||||
tableEnum,
|
||||
rootFieldsEdit,
|
||||
} = this.props;
|
||||
|
||||
const dataTypeIndexMap = getAllDataTypeMap(dataTypes);
|
||||
@ -144,6 +155,33 @@ class ModifyTable extends React.Component {
|
||||
};
|
||||
|
||||
// if (table.primary_key.columns > 0) {}
|
||||
const getTableRootFieldsSection = () => {
|
||||
if (!checkFeatureSupport(CUSTOM_GRAPHQL_FIELDS_SUPPORT)) return null;
|
||||
|
||||
const existingRootFields = getTableCustomRootFields(table);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<h4 className={styles.subheading_text}>
|
||||
Custom GraphQL Root Fields
|
||||
<Tooltip
|
||||
message={
|
||||
'Change the root fields for the table in the GraphQL API'
|
||||
}
|
||||
/>
|
||||
</h4>
|
||||
<RootFields
|
||||
existingRootFields={existingRootFields}
|
||||
rootFieldsEdit={rootFieldsEdit}
|
||||
dispatch={dispatch}
|
||||
tableName={tableName}
|
||||
/>
|
||||
<hr />
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
// if (tableSchema.primary_key.columns > 0) {}
|
||||
return (
|
||||
<div className={`${styles.container} container-fluid`}>
|
||||
<TableHeader
|
||||
@ -177,6 +215,7 @@ class ModifyTable extends React.Component {
|
||||
dispatch={dispatch}
|
||||
currentSchema={currentSchema}
|
||||
columnDefaultFunctions={columnDefaultFunctions}
|
||||
customColumnNames={getTableCustomColumnNames(table)}
|
||||
/>
|
||||
<hr />
|
||||
<h4 className={styles.subheading_text}>Add a new column</h4>
|
||||
@ -225,6 +264,7 @@ class ModifyTable extends React.Component {
|
||||
dispatch={dispatch}
|
||||
/>
|
||||
<hr />
|
||||
{getTableRootFieldsSection()}
|
||||
{getEnumsSection()}
|
||||
{untrackBtn}
|
||||
{deleteBtn}
|
||||
|
@ -0,0 +1,97 @@
|
||||
import React from 'react';
|
||||
import ExpandableEditor from '../../../Common/Layout/ExpandableEditor/Editor';
|
||||
import RootFieldEditor from '../Common/ReusableComponents/RootFieldEditor';
|
||||
import { modifyRootFields, setCustomRootFields } from './ModifyActions';
|
||||
import { isEmpty } from '../../../Common/utils/jsUtils';
|
||||
|
||||
import styles from './ModifyTable.scss';
|
||||
|
||||
const RootFields = ({
|
||||
existingRootFields,
|
||||
rootFieldsEdit,
|
||||
dispatch,
|
||||
tableName,
|
||||
}) => {
|
||||
const setRootFieldsBulk = rf => {
|
||||
dispatch(modifyRootFields(rf));
|
||||
};
|
||||
|
||||
const onChange = (field, customField) => {
|
||||
const newRootFields = {
|
||||
...rootFieldsEdit,
|
||||
[field]: customField,
|
||||
};
|
||||
dispatch(modifyRootFields(newRootFields));
|
||||
};
|
||||
|
||||
const collapsedLabel = () => {
|
||||
const customRootFieldLabels = [];
|
||||
|
||||
Object.keys(existingRootFields).forEach(rootField => {
|
||||
const customRootField = existingRootFields[rootField];
|
||||
if (customRootField) {
|
||||
customRootFieldLabels.push(
|
||||
<React.Fragment>
|
||||
{!isEmpty(customRootFieldLabels) && ', '}
|
||||
<span className={styles.display_inline} key={rootField}>
|
||||
<i>{rootField}</i> → {customRootField}
|
||||
</span>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
if (isEmpty(customRootFieldLabels)) {
|
||||
customRootFieldLabels.push('No root fields customised');
|
||||
}
|
||||
|
||||
return <span>{customRootFieldLabels}</span>;
|
||||
};
|
||||
|
||||
const editorExpanded = () => (
|
||||
<RootFieldEditor
|
||||
tableName={tableName}
|
||||
rootFields={rootFieldsEdit}
|
||||
selectOnChange={e => {
|
||||
onChange('select', e.target.value);
|
||||
}}
|
||||
selectByPkOnChange={e => {
|
||||
onChange('select_by_pk', e.target.value);
|
||||
}}
|
||||
selectAggOnChange={e => {
|
||||
onChange('select_aggregate', e.target.value);
|
||||
}}
|
||||
insertOnChange={e => {
|
||||
onChange('insert', e.target.value);
|
||||
}}
|
||||
updateOnChange={e => {
|
||||
onChange('update', e.target.value);
|
||||
}}
|
||||
deleteOnChange={e => {
|
||||
onChange('delete', e.target.value);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const expandCallback = () => {
|
||||
setRootFieldsBulk(existingRootFields);
|
||||
};
|
||||
|
||||
const saveFunc = toggleEditor => {
|
||||
dispatch(setCustomRootFields(toggleEditor));
|
||||
};
|
||||
|
||||
return (
|
||||
<ExpandableEditor
|
||||
editorExpanded={editorExpanded}
|
||||
expandCallback={expandCallback}
|
||||
collapsedLabel={collapsedLabel}
|
||||
saveFunc={saveFunc}
|
||||
property="custom-root-fields"
|
||||
service="modify-table"
|
||||
isCollapsable
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default RootFields;
|
@ -85,6 +85,18 @@ const getDropPkSql = ({ schemaName, tableName, constraintName }) => {
|
||||
return `alter table "${schemaName}"."${tableName}" drop constraint "${constraintName}";`;
|
||||
};
|
||||
|
||||
export const sanitiseRootFields = rootFields => {
|
||||
const santisedRootFields = {};
|
||||
Object.keys(rootFields).forEach(rootFieldType => {
|
||||
let rootField = rootFields[rootFieldType];
|
||||
if (rootField !== null) {
|
||||
rootField = rootField.trim() || null;
|
||||
}
|
||||
santisedRootFields[rootFieldType] = rootField;
|
||||
});
|
||||
return santisedRootFields;
|
||||
};
|
||||
|
||||
export {
|
||||
convertArrayToJson,
|
||||
getValidAlterOptions,
|
||||
|
@ -1,5 +1,8 @@
|
||||
import globals from '../../../Globals';
|
||||
import { TABLE_ENUMS_SUPPORT } from '../../../helpers/versionUtils';
|
||||
import {
|
||||
TABLE_ENUMS_SUPPORT,
|
||||
CUSTOM_GRAPHQL_FIELDS_SUPPORT,
|
||||
checkFeatureSupport,
|
||||
} from '../../../helpers/versionUtils';
|
||||
|
||||
export const INTEGER = 'integer';
|
||||
export const SERIAL = 'serial';
|
||||
@ -233,21 +236,22 @@ export const fetchTrackedTableListQuery = options => {
|
||||
columns: ['*'],
|
||||
order_by: {
|
||||
column: 'constraint_name',
|
||||
type: 'asc'
|
||||
}
|
||||
}
|
||||
type: 'asc',
|
||||
},
|
||||
},
|
||||
],
|
||||
order_by: [{ column: 'table_name', type: 'asc' }],
|
||||
},
|
||||
};
|
||||
|
||||
const supportEnums =
|
||||
globals.featuresCompatibility &&
|
||||
globals.featuresCompatibility[TABLE_ENUMS_SUPPORT];
|
||||
if (supportEnums) {
|
||||
if (checkFeatureSupport(TABLE_ENUMS_SUPPORT)) {
|
||||
query.args.columns.push('is_enum');
|
||||
}
|
||||
|
||||
if (checkFeatureSupport(CUSTOM_GRAPHQL_FIELDS_SUPPORT)) {
|
||||
query.args.columns.push('configuration');
|
||||
}
|
||||
|
||||
if (
|
||||
(options.schemas && options.schemas.length !== 0) ||
|
||||
(options.tables && options.tables.length !== 0)
|
||||
@ -490,6 +494,7 @@ export const mergeLoadSchemaData = (
|
||||
let _refFkConstraints = [];
|
||||
let _isEnum = false;
|
||||
let _checkConstraints = [];
|
||||
let _configuration = {};
|
||||
|
||||
if (_isTableTracked) {
|
||||
_primaryKey = trackedTableInfo.primary_key;
|
||||
@ -498,6 +503,7 @@ export const mergeLoadSchemaData = (
|
||||
_uniqueConstraints = trackedTableInfo.unique_constraints;
|
||||
_isEnum = trackedTableInfo.is_enum;
|
||||
_checkConstraints = trackedTableInfo.check_constraints;
|
||||
_configuration = trackedTableInfo.configuration;
|
||||
|
||||
_fkConstraints = fkData.filter(
|
||||
fk => fk.table_schema === _tableSchema && fk.table_name === _tableName
|
||||
@ -527,6 +533,7 @@ export const mergeLoadSchemaData = (
|
||||
opp_foreign_key_constraints: _refFkConstraints,
|
||||
view_info: _viewInfo,
|
||||
is_enum: _isEnum,
|
||||
configuration: _configuration,
|
||||
};
|
||||
|
||||
_mergedTableData.push(_mergedInfo);
|
||||
|
@ -1,3 +1,5 @@
|
||||
import globals from '../Globals';
|
||||
|
||||
const semver = require('semver');
|
||||
|
||||
export const FT_JWT_ANALYZER = 'JWTAnalyzer';
|
||||
@ -6,6 +8,7 @@ export const REMOTE_SCHEMA_TIMEOUT_CONF_SUPPORT =
|
||||
'remoteSchemaTimeoutConfSupport';
|
||||
export const TABLE_ENUMS_SUPPORT = 'tableEnumsSupport';
|
||||
export const EXISTS_PERMISSION_SUPPORT = 'existsPermissionSupport';
|
||||
export const CUSTOM_GRAPHQL_FIELDS_SUPPORT = 'customGraphQLFieldsSupport';
|
||||
export const COMPUTED_FIELDS_SUPPORT = 'computedFieldsSupport';
|
||||
|
||||
// list of feature launch versions
|
||||
@ -16,6 +19,7 @@ const featureLaunchVersions = {
|
||||
[REMOTE_SCHEMA_TIMEOUT_CONF_SUPPORT]: 'v1.0.0-beta.5',
|
||||
[TABLE_ENUMS_SUPPORT]: 'v1.0.0-beta.6',
|
||||
[EXISTS_PERMISSION_SUPPORT]: 'v1.0.0-beta.7',
|
||||
[CUSTOM_GRAPHQL_FIELDS_SUPPORT]: 'v1.0.0-beta.8',
|
||||
[COMPUTED_FIELDS_SUPPORT]: 'v1.0.0-beta.8',
|
||||
};
|
||||
|
||||
@ -45,3 +49,9 @@ export const versionGT = (version1, version2) => {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const checkFeatureSupport = feature => {
|
||||
return (
|
||||
globals.featuresCompatibility && globals.featuresCompatibility[feature]
|
||||
);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user