mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 09:22:43 +03:00
This commit is contained in:
parent
029bda1bd3
commit
315c399ba3
@ -14,6 +14,12 @@
|
||||
|
||||
Added a component that allows adding check constraints while creating a new table in the same way as it can be done on the `Modify` view.
|
||||
|
||||
### Select dropdown for Enum columns (console)
|
||||
|
||||
If a table has a field referencing an Enum table via a foreign key, then there will be a select dropdown with all possible enum values on `Insert Row` and `Edit Row` views on the Console.
|
||||
|
||||
(close #3748) (#3810)
|
||||
|
||||
### Other changes
|
||||
|
||||
- console: disable editing action relationships
|
||||
|
@ -193,6 +193,53 @@ export function getRelationshipRefTable(table, relationship) {
|
||||
return _refTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} currentSchema
|
||||
* @param {string} currentTable
|
||||
* @param {Array<{[key: string]: any}>} allSchemas
|
||||
*
|
||||
* @returns {Array<{
|
||||
* columnName: string,
|
||||
* enumTableName: string,
|
||||
* enumColumnName: string,
|
||||
* }>}
|
||||
*/
|
||||
export const getEnumColumnMappings = (allSchemas, tableName, tableSchema) => {
|
||||
const currentTable = findTable(
|
||||
allSchemas,
|
||||
generateTableDef(tableName, tableSchema)
|
||||
);
|
||||
|
||||
const relationsMap = [];
|
||||
if (!currentTable.foreign_key_constraints.length) return;
|
||||
|
||||
currentTable.foreign_key_constraints.map(
|
||||
({ ref_table, ref_table_table_schema, column_mapping }) => {
|
||||
const refTableSchema = findTable(
|
||||
allSchemas,
|
||||
generateTableDef(ref_table, ref_table_table_schema)
|
||||
);
|
||||
if (!refTableSchema.is_enum) return;
|
||||
|
||||
const keys = Object.keys(column_mapping);
|
||||
if (!keys.length) return;
|
||||
|
||||
const _columnName = keys[0];
|
||||
const _enumColumnName = column_mapping[_columnName];
|
||||
|
||||
if (_columnName && _enumColumnName) {
|
||||
relationsMap.push({
|
||||
columnName: _columnName,
|
||||
enumTableName: ref_table,
|
||||
enumColumnName: _enumColumnName,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return relationsMap;
|
||||
};
|
||||
|
||||
/*** Table/View permissions utils ***/
|
||||
|
||||
export const getTablePermissions = (table, role = null, action = null) => {
|
||||
|
@ -225,3 +225,16 @@ export const getDeleteQuery = (pkClause, tableName, schemaName) => {
|
||||
|
||||
export const getBulkDeleteQuery = (pkClauses, tableName, schemaName) =>
|
||||
pkClauses.map(pkClause => getDeleteQuery(pkClause, tableName, schemaName));
|
||||
|
||||
export const getEnumOptionsQuery = (request, currentSchema) => {
|
||||
return {
|
||||
type: 'select',
|
||||
args: {
|
||||
table: {
|
||||
name: request.enumTableName,
|
||||
schema: currentSchema,
|
||||
},
|
||||
columns: [request.enumColumnName],
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@ -3,10 +3,10 @@ import React, { Component } from 'react';
|
||||
import Helmet from 'react-helmet';
|
||||
|
||||
import Button from '../../../Common/Button/Button';
|
||||
import PrimaryKeySelector from '../Common/ReusableComponents/PrimaryKeySelector';
|
||||
import PrimaryKeySelector from '../Common/Components/PrimaryKeySelector';
|
||||
import ForeignKeyWrapper from './ForeignKeyWrapper';
|
||||
import UniqueKeyWrapper from './UniqueKeyWrapper';
|
||||
import FrequentlyUsedColumnSelector from '../Common/ReusableComponents/FrequentlyUsedColumnSelector';
|
||||
import FrequentlyUsedColumnSelector from '../Common/Components/FrequentlyUsedColumnSelector';
|
||||
|
||||
import { showErrorNotification } from '../../Common/Notification';
|
||||
|
||||
|
@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
|
||||
|
||||
import ExpandableEditor from '../../../Common/Layout/ExpandableEditor/Editor';
|
||||
import { removeCheckConstraint, setCheckConstraints } from './AddActions';
|
||||
import { ConstraintExpandedContent } from '../Common/ReusableComponents/ConstraintExpandedContent';
|
||||
import { ConstraintExpandedContent } from '../Common/Components/ConstraintExpandedContent';
|
||||
|
||||
const CheckConstraints = ({ dispatch, constraints }) => {
|
||||
const [addConstraintsState, setAddConstraintsState] = useState([]);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import ExpandableEditor from '../../../Common/Layout/ExpandableEditor/Editor';
|
||||
import ForeignKeySelector from '../Common/ReusableComponents/ForeignKeySelector';
|
||||
import { getForeignKeyConfig } from '../Common/ReusableComponents/utils';
|
||||
import ForeignKeySelector from '../Common/Components/ForeignKeySelector';
|
||||
import { getForeignKeyConfig } from '../Common/Components/utils';
|
||||
import { setForeignKeys, toggleFk, clearFkToggle } from './AddActions';
|
||||
|
||||
const ForeignKeyWrapper = ({
|
||||
|
@ -1,10 +1,7 @@
|
||||
import React from 'react';
|
||||
import ExpandableEditor from '../../../Common/Layout/ExpandableEditor/Editor';
|
||||
import UniqueKeySelector from '../Common/ReusableComponents/UniqueKeySelector';
|
||||
import {
|
||||
getUkeyPkeyConfig,
|
||||
getKeyDef,
|
||||
} from '../Common/ReusableComponents/utils';
|
||||
import UniqueKeySelector from '../Common/Components/UniqueKeySelector';
|
||||
import { getUkeyPkeyConfig, getKeyDef } from '../Common/Components/utils';
|
||||
|
||||
const UniqueKeyWrapper = ({
|
||||
// allSchemas,
|
||||
|
@ -61,9 +61,7 @@ const PrimaryKeySelector = ({ primaryKeys, columns, setPk, dispatch }) => {
|
||||
<div key={i} className={`form-group ${styles.pkEditorWrapper}`}>
|
||||
<select
|
||||
value={pk || ''}
|
||||
className={`${styles.select} ${styles.sample} form-control ${
|
||||
styles.add_pad_left
|
||||
}`}
|
||||
className={`${styles.select} ${styles.sample} form-control ${styles.add_pad_left}`}
|
||||
onChange={dispatchSet}
|
||||
data-test={`primary-key-select-${i}`}
|
||||
>
|
@ -0,0 +1,122 @@
|
||||
import React from 'react';
|
||||
|
||||
import { JSONB, JSONDTYPE, TEXT, BOOLEAN, getPlaceholder } from '../../utils';
|
||||
import JsonInput from '../../../../Common/CustomInputTypes/JsonInput';
|
||||
import TextInput from '../../../../Common/CustomInputTypes/TextInput';
|
||||
import styles from '../../../../Common/TableCommon/Table.scss';
|
||||
import { isColumnAutoIncrement } from '../../../../Common/utils/pgUtils';
|
||||
|
||||
export const TypedInput = ({
|
||||
enumOptions,
|
||||
col,
|
||||
index,
|
||||
clone,
|
||||
inputRef,
|
||||
onChange,
|
||||
onFocus,
|
||||
prevValue,
|
||||
hasDefault,
|
||||
}) => {
|
||||
const {
|
||||
column_name: colName,
|
||||
data_type: colType,
|
||||
column_default: colDefault,
|
||||
} = col;
|
||||
|
||||
const isAutoIncrement = isColumnAutoIncrement(col);
|
||||
const placeHolder = hasDefault ? colDefault : getPlaceholder(colType);
|
||||
const getDefaultValue = () => {
|
||||
if (prevValue) return prevValue;
|
||||
if (clone && colName in clone) return clone[colName];
|
||||
return '';
|
||||
};
|
||||
|
||||
const onClick = e => {
|
||||
e.target
|
||||
.closest('.radio-inline')
|
||||
.querySelector('input[type="radio"]').checked = true;
|
||||
e.target.focus();
|
||||
};
|
||||
|
||||
const standardInputProps = {
|
||||
onChange,
|
||||
onFocus,
|
||||
onClick,
|
||||
ref: inputRef,
|
||||
'data-test': `typed-input-${index}`,
|
||||
className: `form-control ${styles.insertBox}`,
|
||||
defaultValue: getDefaultValue(),
|
||||
type: 'text',
|
||||
placeholder: 'text',
|
||||
};
|
||||
if (enumOptions && enumOptions[colName]) {
|
||||
return (
|
||||
<select
|
||||
{...standardInputProps}
|
||||
className={`form-control ${styles.insertBox}`}
|
||||
defaultValue={prevValue || ''}
|
||||
>
|
||||
<option disabled value="">
|
||||
-- enum value --
|
||||
</option>
|
||||
{enumOptions[colName].map(option => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
);
|
||||
}
|
||||
|
||||
if (isAutoIncrement) {
|
||||
return <input {...standardInputProps} readOnly placeholder={placeHolder} />;
|
||||
}
|
||||
|
||||
if (prevValue && typeof prevValue === 'object') {
|
||||
return (
|
||||
<JsonInput
|
||||
standardProps={{
|
||||
...standardInputProps,
|
||||
defaultValue: JSON.stringify(prevValue),
|
||||
}}
|
||||
placeholderProp={getPlaceholder(colType)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
switch (colType) {
|
||||
case JSONB:
|
||||
case JSONDTYPE:
|
||||
return (
|
||||
<JsonInput
|
||||
{...standardInputProps}
|
||||
defaultValue={
|
||||
prevValue ? JSON.stringify(prevValue) : getDefaultValue()
|
||||
}
|
||||
placeholderProp={placeHolder}
|
||||
/>
|
||||
);
|
||||
|
||||
case TEXT:
|
||||
return (
|
||||
<TextInput
|
||||
standardProps={standardInputProps}
|
||||
placeholderProp={placeHolder}
|
||||
/>
|
||||
);
|
||||
|
||||
case BOOLEAN:
|
||||
return (
|
||||
<select {...standardInputProps} defaultValue={placeHolder}>
|
||||
<option value="" disabled>
|
||||
-- bool --
|
||||
</option>
|
||||
<option value="true">True</option>
|
||||
<option value="false">False</option>
|
||||
</select>
|
||||
);
|
||||
|
||||
default:
|
||||
return <input {...standardInputProps} placeholder={placeHolder} />;
|
||||
}
|
||||
};
|
@ -64,9 +64,7 @@ const UniqueKeySelector = ({
|
||||
return (
|
||||
<div key={i} className={`form-group ${styles.pkEditorWrapper}`}>
|
||||
<select
|
||||
className={`${styles.select} ${styles.sample} form-control ${
|
||||
styles.add_pad_left
|
||||
}`}
|
||||
className={`${styles.select} ${styles.sample} form-control ${styles.add_pad_left}`}
|
||||
data-test={`unique-key-${index}-column-${i}`}
|
||||
value={uk}
|
||||
onChange={setUniqueCol}
|
||||
@ -87,9 +85,7 @@ const UniqueKeySelector = ({
|
||||
return (
|
||||
<div key={numCols} className={`form-group ${styles.pkEditorWrapper}`}>
|
||||
<select
|
||||
className={`${styles.select} ${styles.sample} form-control ${
|
||||
styles.add_pad_left
|
||||
}`}
|
||||
className={`${styles.select} ${styles.sample} form-control ${styles.add_pad_left}`}
|
||||
data-test={`unique-key-${index}-column-${numCols}`}
|
||||
onChange={selectUniqueCol}
|
||||
value={''}
|
@ -61,7 +61,7 @@ export const generateFKConstraintName = (
|
||||
tableName,
|
||||
lCols,
|
||||
existingConstraints,
|
||||
ignoreConstraints = []
|
||||
ignoreConstraints = [],
|
||||
) => {
|
||||
const expectedName = `${tableName}_${lCols
|
||||
.map(lc => lc.replace(/"/g, ''))
|
@ -38,7 +38,7 @@ import { isEmpty } from '../../../Common/utils/jsUtils';
|
||||
import { getConfirmation } from '../../../Common/utils/jsUtils';
|
||||
import ToolTip from '../../../Common/Tooltip/Tooltip';
|
||||
import KnowMoreLink from '../../../Common/KnowMoreLink/KnowMoreLink';
|
||||
import RawSqlButton from '../Common/ReusableComponents/RawSqlButton';
|
||||
import RawSqlButton from '../Common/Components/RawSqlButton';
|
||||
|
||||
class Schema extends Component {
|
||||
constructor(props) {
|
||||
|
@ -11,12 +11,16 @@ import {
|
||||
generateTableDef,
|
||||
getColumnType,
|
||||
getTableColumn,
|
||||
getEnumColumnMappings,
|
||||
} from '../../../Common/utils/pgUtils';
|
||||
import { getEnumOptionsQuery } from '../../../Common/utils/v1QueryUtils';
|
||||
|
||||
const E_SET_EDITITEM = 'EditItem/E_SET_EDITITEM';
|
||||
const E_ONGOING_REQ = 'EditItem/E_ONGOING_REQ';
|
||||
const E_REQUEST_SUCCESS = 'EditItem/E_REQUEST_SUCCESS';
|
||||
const E_REQUEST_ERROR = 'EditItem/E_REQUEST_ERROR';
|
||||
const E_FETCH_ENUM_OPTIONS_SUCCESS = 'EditItem/E_FETCH_ENUM_SUCCESS';
|
||||
const E_FETCH_ENUM_OPTIONS_ERROR = 'EditItem/E_FETCH_ENUM_ERROR';
|
||||
const MODAL_CLOSE = 'EditItem/MODAL_CLOSE';
|
||||
const MODAL_OPEN = 'EditItem/MODAL_OPEN';
|
||||
|
||||
@ -124,6 +128,53 @@ const editItem = (tableName, colValues) => {
|
||||
};
|
||||
};
|
||||
|
||||
const fetchEnumOptions = () => {
|
||||
return (dispatch, getState) => {
|
||||
const {
|
||||
tables: { allSchemas, currentTable, currentSchema },
|
||||
} = getState();
|
||||
|
||||
const requests = getEnumColumnMappings(
|
||||
allSchemas,
|
||||
currentTable,
|
||||
currentSchema
|
||||
);
|
||||
|
||||
if (!requests) return;
|
||||
|
||||
const options = {
|
||||
method: 'POST',
|
||||
credentials: globalCookiePolicy,
|
||||
headers: dataHeaders(getState),
|
||||
};
|
||||
const url = Endpoints.query;
|
||||
|
||||
requests.forEach(request => {
|
||||
const req = getEnumOptionsQuery(request, currentSchema);
|
||||
|
||||
return dispatch(
|
||||
requestAction(url, {
|
||||
...options,
|
||||
body: JSON.stringify(req),
|
||||
})
|
||||
).then(
|
||||
data =>
|
||||
dispatch({
|
||||
type: E_FETCH_ENUM_OPTIONS_SUCCESS,
|
||||
data: {
|
||||
columnName: request.columnName,
|
||||
options: data.reduce(
|
||||
(acc, d) => [...acc, ...Object.values(d)],
|
||||
[]
|
||||
),
|
||||
},
|
||||
}),
|
||||
() => dispatch({ type: E_FETCH_ENUM_OPTIONS_ERROR })
|
||||
);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
/* ************ reducers *********************** */
|
||||
const editReducer = (tableName, state, action) => {
|
||||
switch (action.type) {
|
||||
@ -164,6 +215,16 @@ const editReducer = (tableName, state, action) => {
|
||||
lastError: 'server-failure',
|
||||
lastSuccess: null,
|
||||
};
|
||||
case E_FETCH_ENUM_OPTIONS_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
enumOptions: {
|
||||
...state.enumOptions,
|
||||
[action.data.columnName]: action.data.options,
|
||||
},
|
||||
};
|
||||
case E_FETCH_ENUM_OPTIONS_ERROR:
|
||||
return { ...state, enumOptions: null };
|
||||
case MODAL_OPEN:
|
||||
return { ...state, isModalOpen: true };
|
||||
case MODAL_CLOSE:
|
||||
@ -174,4 +235,11 @@ const editReducer = (tableName, state, action) => {
|
||||
};
|
||||
|
||||
export default editReducer;
|
||||
export { editItem, modalOpen, modalClose, E_SET_EDITITEM, E_ONGOING_REQ };
|
||||
export {
|
||||
editItem,
|
||||
fetchEnumOptions,
|
||||
modalOpen,
|
||||
modalClose,
|
||||
E_SET_EDITITEM,
|
||||
E_ONGOING_REQ,
|
||||
};
|
||||
|
@ -1,11 +1,8 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import TableHeader from '../TableCommon/TableHeader';
|
||||
import JsonInput from '../../../Common/CustomInputTypes/JsonInput';
|
||||
import TextInput from '../../../Common/CustomInputTypes/TextInput';
|
||||
import Button from '../../../Common/Button/Button';
|
||||
import ReloadEnumValuesButton from '../Common/ReusableComponents/ReloadEnumValuesButton';
|
||||
import { getPlaceholder, BOOLEAN, JSONB, JSONDTYPE, TEXT } from '../utils';
|
||||
import ReloadEnumValuesButton from '../Common/Components/ReloadEnumValuesButton';
|
||||
import { ordinalColSort } from '../utils';
|
||||
|
||||
// import RichTextEditor from 'react-rte';
|
||||
@ -14,6 +11,8 @@ import globals from '../../../../Globals';
|
||||
import { E_ONGOING_REQ, editItem } from './EditActions';
|
||||
import { findTable, generateTableDef } from '../../../Common/utils/pgUtils';
|
||||
import { getTableBrowseRoute } from '../../../Common/utils/routesUtils';
|
||||
import { TypedInput } from '../Common/Components/TypedInput';
|
||||
import { fetchEnumOptions } from './EditActions';
|
||||
|
||||
class EditItem extends Component {
|
||||
constructor() {
|
||||
@ -21,6 +20,10 @@ class EditItem extends Component {
|
||||
this.state = { insertedRows: 0, editorColumnMap: {}, currentColumn: null };
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.dispatch(fetchEnumOptions());
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
tableName,
|
||||
@ -34,6 +37,7 @@ class EditItem extends Component {
|
||||
lastSuccess,
|
||||
count,
|
||||
dispatch,
|
||||
enumOptions,
|
||||
} = this.props;
|
||||
|
||||
// check if item exists
|
||||
@ -63,7 +67,6 @@ class EditItem extends Component {
|
||||
|
||||
const elements = columns.map((col, i) => {
|
||||
const colName = col.column_name;
|
||||
const colType = col.data_type;
|
||||
const hasDefault = col.column_default && col.column_default.trim() !== '';
|
||||
const isNullable = col.is_nullable && col.is_nullable !== 'NO';
|
||||
const isIdentity = col.is_identity && col.is_identity !== 'NO';
|
||||
@ -76,79 +79,6 @@ class EditItem extends Component {
|
||||
nullNode: null,
|
||||
defaultNode: null,
|
||||
};
|
||||
const inputRef = node => (refs[colName].valueInput = node);
|
||||
|
||||
const clicker = e => {
|
||||
e.target
|
||||
.closest('.radio-inline')
|
||||
.querySelector('input[type="radio"]').checked = true;
|
||||
e.target.focus();
|
||||
};
|
||||
|
||||
const standardEditProps = {
|
||||
className: `form-control ${styles.insertBox}`,
|
||||
'data-test': `typed-input-${i}`,
|
||||
defaultValue: prevValue,
|
||||
ref: inputRef,
|
||||
type: 'text',
|
||||
onClick: clicker,
|
||||
};
|
||||
|
||||
const placeHolder = hasDefault
|
||||
? col.column_default
|
||||
: getPlaceholder(colType);
|
||||
|
||||
let typedInput = (
|
||||
<input {...standardEditProps} placeholder={placeHolder} />
|
||||
);
|
||||
|
||||
if (typeof prevValue === 'object') {
|
||||
typedInput = (
|
||||
<JsonInput
|
||||
standardProps={{
|
||||
...standardEditProps,
|
||||
defaultValue: JSON.stringify(prevValue),
|
||||
}}
|
||||
placeholderProp={getPlaceholder(colType)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
switch (colType) {
|
||||
case JSONB:
|
||||
case JSONDTYPE:
|
||||
typedInput = (
|
||||
<JsonInput
|
||||
standardProps={{
|
||||
...standardEditProps,
|
||||
defaultValue: JSON.stringify(prevValue),
|
||||
}}
|
||||
placeholderProp={getPlaceholder(colType)}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case TEXT:
|
||||
typedInput = (
|
||||
<TextInput
|
||||
standardProps={{ ...standardEditProps }}
|
||||
placeholderProp={getPlaceholder(colType)}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case BOOLEAN:
|
||||
typedInput = (
|
||||
<select {...standardEditProps}>
|
||||
<option value="" disabled>
|
||||
-- bool --
|
||||
</option>
|
||||
<option value="true">True</option>
|
||||
<option value="false">False</option>
|
||||
</select>
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={i} className="form-group">
|
||||
@ -167,7 +97,16 @@ class EditItem extends Component {
|
||||
name={colName + '-value'}
|
||||
value="option1"
|
||||
/>
|
||||
{typedInput}
|
||||
<TypedInput
|
||||
inputRef={node => {
|
||||
refs[colName].valueInput = node;
|
||||
}}
|
||||
prevValue={prevValue}
|
||||
enumOptions={enumOptions}
|
||||
col={col}
|
||||
index={i}
|
||||
hasDefault={hasDefault}
|
||||
/>
|
||||
</label>
|
||||
<label className={styles.radioLabel + ' radio-inline'}>
|
||||
<input
|
||||
@ -297,6 +236,7 @@ EditItem.propTypes = {
|
||||
readOnlyMode: PropTypes.bool.isRequired,
|
||||
count: PropTypes.number,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
enumOptions: PropTypes.object,
|
||||
};
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
|
@ -24,7 +24,7 @@ import {
|
||||
} from './FilterActions.js';
|
||||
import { setDefaultQuery, runQuery, setOffset } from './FilterActions';
|
||||
import Button from '../../../Common/Button/Button';
|
||||
import ReloadEnumValuesButton from '../Common/ReusableComponents/ReloadEnumValuesButton';
|
||||
import ReloadEnumValuesButton from '../Common/Components/ReloadEnumValuesButton';
|
||||
|
||||
const history = createHistory();
|
||||
|
||||
|
@ -7,6 +7,8 @@ import {
|
||||
showSuccessNotification,
|
||||
} from '../../Common/Notification';
|
||||
import dataHeaders from '../Common/Headers';
|
||||
import { getEnumColumnMappings } from '../../../Common/utils/pgUtils';
|
||||
import { getEnumOptionsQuery } from '../../../Common/utils/v1QueryUtils';
|
||||
|
||||
const I_SET_CLONE = 'InsertItem/I_SET_CLONE';
|
||||
const I_RESET = 'InsertItem/I_RESET';
|
||||
@ -15,6 +17,8 @@ const I_REQUEST_SUCCESS = 'InsertItem/I_REQUEST_SUCCESS';
|
||||
const I_REQUEST_ERROR = 'InsertItem/I_REQUEST_ERROR';
|
||||
const _CLOSE = 'InsertItem/_CLOSE';
|
||||
const _OPEN = 'InsertItem/_OPEN';
|
||||
const I_FETCH_ENUM_OPTIONS_SUCCESS = 'InsertItem/I_FETCH_ENUM_SUCCESS';
|
||||
const I_FETCH_ENUM_OPTIONS_ERROR = 'InsertItem/I_FETCH_ENUM_ERROR';
|
||||
|
||||
const Open = () => ({ type: _OPEN });
|
||||
const Close = () => ({ type: _CLOSE });
|
||||
@ -106,6 +110,53 @@ const insertItem = (tableName, colValues) => {
|
||||
};
|
||||
};
|
||||
|
||||
const fetchEnumOptions = () => {
|
||||
return (dispatch, getState) => {
|
||||
const {
|
||||
tables: { allSchemas, currentTable, currentSchema },
|
||||
} = getState();
|
||||
|
||||
const requests = getEnumColumnMappings(
|
||||
allSchemas,
|
||||
currentTable,
|
||||
currentSchema
|
||||
);
|
||||
|
||||
if (!requests) return;
|
||||
|
||||
const options = {
|
||||
method: 'POST',
|
||||
credentials: globalCookiePolicy,
|
||||
headers: dataHeaders(getState),
|
||||
};
|
||||
const url = Endpoints.query;
|
||||
|
||||
requests.forEach(request => {
|
||||
const req = getEnumOptionsQuery(request, currentSchema);
|
||||
|
||||
return dispatch(
|
||||
requestAction(url, {
|
||||
...options,
|
||||
body: JSON.stringify(req),
|
||||
})
|
||||
).then(
|
||||
data =>
|
||||
dispatch({
|
||||
type: I_FETCH_ENUM_OPTIONS_SUCCESS,
|
||||
data: {
|
||||
columnName: request.columnName,
|
||||
options: data.reduce(
|
||||
(acc, d) => [...acc, ...Object.values(d)],
|
||||
[]
|
||||
),
|
||||
},
|
||||
}),
|
||||
() => dispatch({ type: I_FETCH_ENUM_OPTIONS_ERROR })
|
||||
);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
/* ************ reducers *********************** */
|
||||
const insertReducer = (tableName, state, action) => {
|
||||
switch (action.type) {
|
||||
@ -115,6 +166,7 @@ const insertReducer = (tableName, state, action) => {
|
||||
ongoingRequest: false,
|
||||
lastError: null,
|
||||
lastSuccess: null,
|
||||
enumOptions: null,
|
||||
};
|
||||
case I_SET_CLONE:
|
||||
return {
|
||||
@ -122,6 +174,7 @@ const insertReducer = (tableName, state, action) => {
|
||||
ongoingRequest: false,
|
||||
lastError: null,
|
||||
lastSuccess: null,
|
||||
enumOptions: null,
|
||||
};
|
||||
case I_ONGOING_REQ:
|
||||
return {
|
||||
@ -157,10 +210,20 @@ const insertReducer = (tableName, state, action) => {
|
||||
return { ...state, isOpen: true };
|
||||
case _CLOSE:
|
||||
return { ...state, isOpen: false };
|
||||
case I_FETCH_ENUM_OPTIONS_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
enumOptions: {
|
||||
...state.enumOptions,
|
||||
[action.data.columnName]: action.data.options,
|
||||
},
|
||||
};
|
||||
case I_FETCH_ENUM_OPTIONS_ERROR:
|
||||
return { ...state, enumOptions: null };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default insertReducer;
|
||||
export { insertItem, I_SET_CLONE, I_RESET, Open, Close };
|
||||
export { fetchEnumOptions, insertItem, I_SET_CLONE, I_RESET, Open, Close };
|
||||
|
@ -1,14 +1,11 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import TableHeader from '../TableCommon/TableHeader';
|
||||
import JsonInput from '../../../Common/CustomInputTypes/JsonInput';
|
||||
import TextInput from '../../../Common/CustomInputTypes/TextInput';
|
||||
import Button from '../../../Common/Button/Button';
|
||||
import ReloadEnumValuesButton from '../Common/ReusableComponents/ReloadEnumValuesButton';
|
||||
import { getPlaceholder, BOOLEAN, JSONB, JSONDTYPE, TEXT } from '../utils';
|
||||
import ReloadEnumValuesButton from '../Common/Components/ReloadEnumValuesButton';
|
||||
import { ordinalColSort } from '../utils';
|
||||
|
||||
import { insertItem, I_RESET } from './InsertActions';
|
||||
import { insertItem, I_RESET, fetchEnumOptions } from './InsertActions';
|
||||
import { setTable } from '../DataActions';
|
||||
import { NotFoundError } from '../../../Error/PageNotFound';
|
||||
import {
|
||||
@ -16,6 +13,8 @@ import {
|
||||
generateTableDef,
|
||||
isColumnAutoIncrement,
|
||||
} from '../../../Common/utils/pgUtils';
|
||||
import { TypedInput } from '../Common/Components/TypedInput';
|
||||
import styles from '../../../Common/TableCommon/Table.scss';
|
||||
|
||||
class InsertItem extends Component {
|
||||
constructor() {
|
||||
@ -25,6 +24,7 @@ class InsertItem extends Component {
|
||||
|
||||
componentDidMount() {
|
||||
this.props.dispatch(setTable(this.props.tableName));
|
||||
this.props.dispatch(fetchEnumOptions());
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@ -35,9 +35,9 @@ class InsertItem extends Component {
|
||||
// when use state object remember to do it inside a class method.
|
||||
// Since the state variable lifecycle is tied to the instance of the class
|
||||
// and making this change using an anonymous function will cause errors.
|
||||
this.setState({
|
||||
insertedRows: this.state.insertedRows + 1,
|
||||
});
|
||||
this.setState(prev => ({
|
||||
insertedRows: prev.insertedRows + 1,
|
||||
}));
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -53,10 +53,9 @@ class InsertItem extends Component {
|
||||
lastSuccess,
|
||||
count,
|
||||
dispatch,
|
||||
enumOptions,
|
||||
} = this.props;
|
||||
|
||||
const styles = require('../../../Common/TableCommon/Table.scss');
|
||||
|
||||
const currentTable = findTable(
|
||||
schemas,
|
||||
generateTableDef(tableName, currentSchema)
|
||||
@ -74,109 +73,45 @@ class InsertItem extends Component {
|
||||
|
||||
const elements = columns.map((col, i) => {
|
||||
const colName = col.column_name;
|
||||
const colType = col.data_type;
|
||||
const hasDefault = col.column_default && col.column_default.trim() !== '';
|
||||
const isNullable = col.is_nullable && col.is_nullable !== 'NO';
|
||||
const isIdentity = col.is_identity && col.is_identity !== 'NO';
|
||||
const isAutoIncrement = isColumnAutoIncrement(col);
|
||||
|
||||
refs[colName] = { valueNode: null, nullNode: null, defaultNode: null };
|
||||
const inputRef = node => (refs[colName].valueNode = node);
|
||||
|
||||
const clicker = e => {
|
||||
e.target
|
||||
.closest('.radio-inline')
|
||||
.querySelector('input[type="radio"]').checked = true;
|
||||
e.target.focus();
|
||||
const onChange = (e, val) => {
|
||||
if (isAutoIncrement) return;
|
||||
if (!isNullable && !hasDefault) return;
|
||||
|
||||
const textValue = typeof val === 'string' ? val : e.target.value;
|
||||
|
||||
const radioToSelectWhenEmpty =
|
||||
hasDefault || isIdentity
|
||||
? refs[colName].defaultNode
|
||||
: refs[colName].nullNode;
|
||||
|
||||
refs[colName].insertRadioNode.checked = !!textValue.length;
|
||||
radioToSelectWhenEmpty.checked = !textValue.length;
|
||||
};
|
||||
const onFocus = e => {
|
||||
if (isAutoIncrement) return;
|
||||
if (!isNullable && !hasDefault) return;
|
||||
const textValue = e.target.value;
|
||||
if (
|
||||
textValue === undefined ||
|
||||
textValue === null ||
|
||||
textValue.length === 0
|
||||
) {
|
||||
const radioToSelectWhenEmpty = hasDefault
|
||||
? refs[colName].defaultNode
|
||||
: refs[colName].nullNode;
|
||||
|
||||
const standardInputProps = {
|
||||
className: `form-control ${styles.insertBox}`,
|
||||
'data-test': `typed-input-${i}`,
|
||||
defaultValue: clone && colName in clone ? clone[colName] : '',
|
||||
ref: inputRef,
|
||||
type: 'text',
|
||||
onClick: clicker,
|
||||
onChange: (e, val) => {
|
||||
if (isAutoIncrement) return;
|
||||
if (!isNullable && !hasDefault) return;
|
||||
|
||||
const textValue = typeof val === 'string' ? val : e.target.value;
|
||||
|
||||
const radioToSelectWhenEmpty =
|
||||
hasDefault || isIdentity
|
||||
? refs[colName].defaultNode
|
||||
: refs[colName].nullNode;
|
||||
refs[colName].insertRadioNode.checked = !!textValue.length;
|
||||
radioToSelectWhenEmpty.checked = !textValue.length;
|
||||
},
|
||||
onFocus: e => {
|
||||
if (isAutoIncrement) return;
|
||||
if (!isNullable && !hasDefault) return;
|
||||
const textValue = e.target.value;
|
||||
if (
|
||||
textValue === undefined ||
|
||||
textValue === null ||
|
||||
textValue.length === 0
|
||||
) {
|
||||
const radioToSelectWhenEmpty = hasDefault
|
||||
? refs[colName].defaultNode
|
||||
: refs[colName].nullNode;
|
||||
|
||||
refs[colName].insertRadioNode.checked = false;
|
||||
radioToSelectWhenEmpty.checked = true;
|
||||
}
|
||||
},
|
||||
placeholder: 'text',
|
||||
refs[colName].insertRadioNode.checked = false;
|
||||
radioToSelectWhenEmpty.checked = true;
|
||||
}
|
||||
};
|
||||
|
||||
const placeHolder = hasDefault
|
||||
? col.column_default
|
||||
: getPlaceholder(colType);
|
||||
|
||||
let typedInput = (
|
||||
<input {...standardInputProps} placeholder={placeHolder} />
|
||||
);
|
||||
|
||||
if (isAutoIncrement) {
|
||||
typedInput = (
|
||||
<input {...standardInputProps} readOnly placeholder={placeHolder} />
|
||||
);
|
||||
}
|
||||
|
||||
switch (colType) {
|
||||
case JSONB:
|
||||
case JSONDTYPE:
|
||||
typedInput = (
|
||||
<JsonInput
|
||||
standardProps={standardInputProps}
|
||||
placeholderProp={getPlaceholder(colType)}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case TEXT:
|
||||
typedInput = (
|
||||
<TextInput
|
||||
standardProps={{ ...standardInputProps }}
|
||||
placeholderProp={getPlaceholder(colType)}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case BOOLEAN:
|
||||
typedInput = (
|
||||
<select {...standardInputProps} defaultValue={placeHolder}>
|
||||
<option value="" disabled>
|
||||
-- bool --
|
||||
</option>
|
||||
<option value="true">True</option>
|
||||
<option value="false">False</option>
|
||||
</select>
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={i} className="form-group">
|
||||
<label
|
||||
@ -196,7 +131,17 @@ class InsertItem extends Component {
|
||||
value="option1"
|
||||
defaultChecked={!hasDefault & !isNullable}
|
||||
/>
|
||||
{typedInput}
|
||||
<TypedInput
|
||||
inputRef={node => {
|
||||
refs[colName].valueNode = node;
|
||||
}}
|
||||
enumOptions={enumOptions}
|
||||
col={col}
|
||||
clone={clone}
|
||||
onChange={onChange}
|
||||
onFocus={onFocus}
|
||||
index={i}
|
||||
/>
|
||||
</label>
|
||||
<label className={styles.radioLabel + ' radio-inline'}>
|
||||
<input
|
||||
@ -350,6 +295,7 @@ InsertItem.propTypes = {
|
||||
readOnlyMode: PropTypes.bool.isRequired,
|
||||
count: PropTypes.number,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
enumOptions: PropTypes.object,
|
||||
};
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
removeCheckConstraint,
|
||||
} from './ModifyActions';
|
||||
import { getCheckConstraintBoolExp } from '../../../Common/utils/sqlUtils';
|
||||
import { ConstraintExpandedContent } from '../Common/ReusableComponents/ConstraintExpandedContent';
|
||||
import { ConstraintExpandedContent } from '../Common/Components/ConstraintExpandedContent';
|
||||
|
||||
const CheckConstraints = ({
|
||||
constraints,
|
||||
|
@ -16,7 +16,7 @@ import Button from '../../../Common/Button/Button';
|
||||
import { addColSql } from '../TableModify/ModifyActions';
|
||||
|
||||
import styles from './ModifyTable.scss';
|
||||
import FrequentlyUsedColumnSelector from '../Common/ReusableComponents/FrequentlyUsedColumnSelector';
|
||||
import FrequentlyUsedColumnSelector from '../Common/Components/FrequentlyUsedColumnSelector';
|
||||
|
||||
const useColumnEditor = (dispatch, tableName) => {
|
||||
const initialState = {
|
||||
|
@ -4,7 +4,7 @@ import AceEditor from 'react-ace';
|
||||
import styles from './ModifyTable.scss';
|
||||
import { getConfirmation } from '../../../Common/utils/jsUtils';
|
||||
import ExpandableEditor from '../../../Common/Layout/ExpandableEditor/Editor';
|
||||
import RawSqlButton from '../Common/ReusableComponents/RawSqlButton';
|
||||
import RawSqlButton from '../Common/Components/RawSqlButton';
|
||||
import Tooltip from '../../../Common/Tooltip/Tooltip';
|
||||
import {
|
||||
findFunction,
|
||||
|
@ -8,9 +8,9 @@ import {
|
||||
import {
|
||||
getForeignKeyConfig,
|
||||
getExistingFKConstraints,
|
||||
} from '../Common/ReusableComponents/utils';
|
||||
} from '../Common/Components/utils';
|
||||
import ExpandableEditor from '../../../Common/Layout/ExpandableEditor/Editor';
|
||||
import ForeignKeySelector from '../Common/ReusableComponents/ForeignKeySelector';
|
||||
import ForeignKeySelector from '../Common/Components/ForeignKeySelector';
|
||||
import { updateSchemaInfo } from '../DataActions';
|
||||
|
||||
import { getConfirmation } from '../../../Common/utils/jsUtils';
|
||||
|
@ -25,7 +25,7 @@ import {
|
||||
pgConfTypes,
|
||||
generateFKConstraintName,
|
||||
getUniqueConstraintName,
|
||||
} from '../Common/ReusableComponents/utils';
|
||||
} from '../Common/Components/utils';
|
||||
|
||||
import { isPostgresFunction } from '../utils';
|
||||
import {
|
||||
|
@ -24,7 +24,7 @@ import PrimaryKeyEditor from './PrimaryKeyEditor';
|
||||
import TableCommentEditor from './TableCommentEditor';
|
||||
import EnumsSection, {
|
||||
EnumTableModifyWarning,
|
||||
} from '../Common/ReusableComponents/EnumsSection';
|
||||
} from '../Common/Components/EnumsSection';
|
||||
import ForeignKeyEditor from './ForeignKeyEditor';
|
||||
import UniqueKeyEditor from './UniqueKeyEditor';
|
||||
import TriggerEditorList from './TriggerEditorList';
|
||||
|
@ -4,13 +4,10 @@ import {
|
||||
setPrimaryKeys,
|
||||
savePrimaryKeys,
|
||||
} from './ModifyActions';
|
||||
import PrimaryKeySelector from '../Common/ReusableComponents/PrimaryKeySelector';
|
||||
import PrimaryKeySelector from '../Common/Components/PrimaryKeySelector';
|
||||
import ExpandableEditor from '../../../Common/Layout/ExpandableEditor/Editor';
|
||||
import { showSuccessNotification } from '../../Common/Notification';
|
||||
import {
|
||||
getUkeyPkeyConfig,
|
||||
getKeyDef,
|
||||
} from '../Common/ReusableComponents/utils';
|
||||
import { getUkeyPkeyConfig, getKeyDef } from '../Common/Components/utils';
|
||||
|
||||
import styles from './ModifyTable.scss';
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import ExpandableEditor from '../../../Common/Layout/ExpandableEditor/Editor';
|
||||
import RootFieldEditor from '../Common/ReusableComponents/RootFieldEditor';
|
||||
import RootFieldEditor from '../Common/Components/RootFieldEditor';
|
||||
import { modifyRootFields, setCustomRootFields } from './ModifyActions';
|
||||
import { isEmpty } from '../../../Common/utils/jsUtils';
|
||||
|
||||
|
@ -1,11 +1,8 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { ordinalColSort } from '../utils';
|
||||
import ExpandableEditor from '../../../Common/Layout/ExpandableEditor/Editor';
|
||||
import UniqueKeySelector from '../Common/ReusableComponents/UniqueKeySelector';
|
||||
import {
|
||||
getUkeyPkeyConfig,
|
||||
getKeyDef,
|
||||
} from '../Common/ReusableComponents/utils';
|
||||
import UniqueKeySelector from '../Common/Components/UniqueKeySelector';
|
||||
import { getUkeyPkeyConfig, getKeyDef } from '../Common/Components/utils';
|
||||
import { saveUniqueKey, removeUniqueKey } from './ModifyActions';
|
||||
|
||||
import { getConfirmation } from '../../../Common/utils/jsUtils';
|
||||
|
Loading…
Reference in New Issue
Block a user