Console: refactor the table Modify tab

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/6419
GitOrigin-RevId: 620e37be5275e339bff2d3da6758d894f035987f
This commit is contained in:
Luca Restagno 2022-10-21 11:50:52 +02:00 committed by hasura-bot
parent e692c3b20d
commit b591d68d81
17 changed files with 767 additions and 528 deletions

View File

@ -4,13 +4,13 @@ import { useServerConfig } from '@/hooks';
import KnowMoreLink from '@/components/Common/KnowMoreLink/KnowMoreLink';
import ToolTip from '../../../../Common/Tooltip/Tooltip';
type ApolloFederationSupportProps = {
toggleApollofederation: () => void;
export type ApolloFederationSupportProps = {
toggleApolloFederation: () => void;
isApolloFederationSupported: boolean;
};
export const ApolloFederationSupport = ({
toggleApollofederation,
toggleApolloFederation,
isApolloFederationSupported,
}: ApolloFederationSupportProps) => {
const { data: configData, isLoading, isError } = useServerConfig();
@ -49,7 +49,7 @@ export const ApolloFederationSupport = ({
<div data-toggle="tooltip">
<Toggle
icons={false}
onChange={() => toggleApollofederation()}
onChange={() => toggleApolloFederation()}
checked={isApolloFederationSupported}
/>
</div>

View File

@ -33,8 +33,8 @@ export const getForeignKeyConfig = (foreignKey, orderedColumns) => {
return `${lCol}${refTableName} . ${rCol}`;
};
export const getExistingFKConstraints = (tableSchema, orderedColumns) => {
return tableSchema.foreign_key_constraints.map(fkc => {
export const getExistingFKConstraints = (tableSchema, orderedColumns) =>
(tableSchema?.foreign_key_constraints || []).map(fkc => {
const fk = {};
fk.refTableName = fkc.ref_table;
fk.refSchemaName = fkc.ref_table_table_schema;
@ -48,7 +48,6 @@ export const getExistingFKConstraints = (tableSchema, orderedColumns) => {
fk.colMappings.push({ column: '', refColumn: '' });
return fk;
});
};
export const generateFKConstraintName = (
tableName,

View File

@ -10,7 +10,6 @@ import {
rawSQLConnector,
addExistingTableViewConnector,
addTableConnector,
modifyTableConnector,
modifyViewConnector,
relationshipsConnector,
relationshipsViewConnector,
@ -34,6 +33,7 @@ import { getSourcesFromMetadata } from '../../../metadata/selector';
import { ManageContainer } from '@/features/Data';
import { Connect } from '@/features/ConnectDB';
import { TableInsertItemContainer } from './TableInsertItem/TableInsertItemContainer';
import { ModifyTableContainer } from './TableModify/ModifyTableContainer';
import { TableEditItemContainer } from './TableEditItem/TableEditItemContainer';
const makeDataRouter = (
@ -96,7 +96,7 @@ const makeDataRouter = (
<Route
path=":schema/tables/:table/modify"
onEnter={migrationRedirects}
component={modifyTableConnector(connect)}
component={ModifyTableContainer}
/>
<Route
path=":schema/tables/:table/relationships"

View File

@ -22,14 +22,14 @@ import { FaDatabase, FaFolder, FaTable } from 'react-icons/fa';
import styles from '../../../Common/TableCommon/Table.module.scss';
const TableHeader = ({
tabName,
count,
dispatch,
isCountEstimated,
table,
migrationMode,
readOnlyMode,
source,
dispatch,
table,
tabName,
}) => {
const tableName = table.table_name;
const tableSchema = table.table_schema;

View File

@ -132,7 +132,7 @@ const useColumnEditor = (dispatch: Dispatch, tableName: string) => {
interface ColumnCreatorProps {
dispatch: Dispatch;
tableName: string;
dataTypes: string[];
dataTypes: string[][];
validTypeCasts: Record<string, string[]>;
columnDefaultFunctions: Record<string, string[]>;
postgresVersion: string;

View File

@ -1,4 +1,3 @@
import PropTypes from 'prop-types';
import React from 'react';
import { connect, ConnectedProps } from 'react-redux';
@ -42,11 +41,6 @@ const ComputedFields = (props: ComputedFieldsProps) => {
);
};
ComputedFields.propTypes = {
currentSchema: PropTypes.string.isRequired,
dispatch: PropTypes.func.isRequired,
};
type OwnProps = {
tableSchema: Table;
};

View File

@ -1,20 +1,14 @@
import React, { useEffect } from 'react';
import { ordinalColSort } from '../utils';
import React from 'react';
import {
setForeignKeys,
saveForeignKeys,
removeForeignKey,
} from './ModifyActions';
import {
getForeignKeyConfig,
getExistingFKConstraints,
} from '../Common/Components/utils';
import { getForeignKeyConfig } from '../Common/Components/utils';
import ExpandableEditor from '../../../Common/Layout/ExpandableEditor/Editor';
import ForeignKeySelector from '../Common/Components/ForeignKeySelector';
import { updateSchemaInfo } from '../DataActions';
import { getConfirmation } from '../../../Common/utils/jsUtils';
import { dataSource } from '../../../../dataSources';
const ForeignKeyEditor = ({
tableSchema,
@ -23,36 +17,9 @@ const ForeignKeyEditor = ({
fkModify,
schemaList,
readOnlyMode,
orderedColumns,
existingForeignKeys,
}) => {
const columns = tableSchema.columns.sort(ordinalColSort);
// columns in the right order with their indices
const orderedColumns = columns.map((c, i) => ({
name: c.column_name,
index: i,
}));
// restructure the existing foreign keys and add it to fkModify (for easy processing)
const existingForeignKeys = getExistingFKConstraints(
tableSchema,
orderedColumns
);
const schemasToBeFetched = {};
existingForeignKeys.forEach(efk => {
schemasToBeFetched[efk.refSchemaName] = true;
});
existingForeignKeys.push({
refSchemaName: '',
refTableName: '',
onUpdate: dataSource?.violationActions?.[0],
onDelete: dataSource?.violationActions?.[0],
colMappings: [{ column: '', refColumn: '' }],
});
useEffect(() => {
dispatch(setForeignKeys(existingForeignKeys));
dispatch(updateSchemaInfo({ schemas: Object.keys(schemasToBeFetched) }));
}, []);
const numFks = fkModify.length;
// Map the foreign keys in the fkModify state and render

View File

@ -1810,7 +1810,7 @@ export const toggleTableAsEnum =
);
};
export const toggleAsApollofederation =
export const toggleAsApolloFederation =
(isApolloFederationSupported, successCallback, failureCallback) =>
(dispatch, getState) => {
const confirmMessage = `This will ${

View File

@ -1,466 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import { Button } from '@/new-components/Button';
import TableHeader from '../TableCommon/TableHeader';
import { getAllDataTypeMap } from '../Common/utils';
import {
deleteTableSql,
untrackTableSql,
RESET,
setUniqueKeys,
toggleTableAsEnum,
toggleAsApollofederation,
} from '../TableModify/ModifyActions';
import {
setTable,
fetchColumnTypeInfo,
RESET_COLUMN_TYPE_INFO,
fetchFunctionInit,
} from '../DataActions';
import ColumnEditorList from './ColumnEditorList';
import ColumnCreator from './ColumnCreator';
import PrimaryKeyEditor from './PrimaryKeyEditor';
import TableCommentEditor from './TableCommentEditor';
import EnumsSection, {
EnumTableModifyWarning,
} from '../Common/Components/EnumsSection';
import ForeignKeyEditor from './ForeignKeyEditor';
import UniqueKeyEditor from './UniqueKeyEditor';
import TriggerEditorList from './TriggerEditorList';
import CheckConstraints from './CheckConstraints';
import RootFields from './RootFields';
import { NotFoundError } from '../../../Error/PageNotFound';
import { getConfirmation } from '../../../Common/utils/jsUtils';
import {
currentDriver,
driverToLabel,
findTable,
generateTableDef,
getTableCustomColumnNames,
isFeatureSupported,
} from '../../../../dataSources';
import Tooltip from '../../../Common/Tooltip/Tooltip';
import {
foreignKeyDescription,
primaryKeyDescription,
uniqueKeyDescription,
checkConstraintsDescription,
indexFieldsDescription,
} from '../Common/TooltipMessages';
import { RightContainer } from '../../../Common/Layout/RightContainer';
import { NotSupportedNote } from '../../../Common/NotSupportedNote';
import ConnectedComputedFields from './ComputedFields';
import FeatureDisabled from '../FeatureDisabled';
import IndexFields from './IndexFields';
import PartitionInfo from './PartitionInfo';
import { FaFlask } from 'react-icons/fa';
import { ApolloFederationSupport } from '../Common/Components/ApolloFederationSupport';
class ModifyTable extends React.Component {
componentDidMount() {
if (!isFeatureSupported('tables.modify.enabled')) return;
const { dispatch } = this.props;
dispatch({ type: RESET });
dispatch(setTable(this.props.tableName));
if (!isFeatureSupported('tables.modify.readOnly'))
dispatch(fetchColumnTypeInfo());
dispatch(fetchFunctionInit());
}
componentWillUnmount() {
this.props.dispatch({
type: RESET_COLUMN_TYPE_INFO,
});
}
render() {
const {
tableName,
allTables,
dispatch,
migrationMode,
readOnlyMode,
currentSchema,
tableCommentEdit,
columnEdit,
pkModify,
fkModify,
checkConstraintsModify,
dataTypes,
validTypeCasts,
uniqueKeyModify,
columnDefaultFunctions,
schemaList,
tableEnum,
postgresVersion,
currentSource,
} = this.props;
if (!isFeatureSupported('tables.modify.enabled')) {
return (
<FeatureDisabled
tab="modify"
tableName={tableName}
schemaName={currentSchema}
/>
);
}
const dataTypeIndexMap = getAllDataTypeMap(dataTypes);
const table = findTable(
allTables,
generateTableDef(tableName, currentSchema)
);
if (!table && isFeatureSupported('tables.modify.enabled')) {
throw new NotFoundError();
}
const tableComment = table.comment;
const untrackBtn = (
<Button
type="submit"
className="mr-sm"
size="sm"
onClick={() => {
const confirmMessage = `This will remove the table "${tableName}" from the GraphQL schema`;
const isOk = getConfirmation(confirmMessage);
if (isOk) {
dispatch(untrackTableSql(tableName));
}
}}
data-test="untrack-table"
>
Untrack Table
</Button>
);
const deleteBtn = (
<Button
type="submit"
mode="destructive"
size="sm"
onClick={() => {
const confirmMessage = `This will permanently delete the table "${tableName}" from the database`;
const isOk = getConfirmation(confirmMessage, true, tableName);
if (isOk) {
dispatch(deleteTableSql(tableName, table));
}
}}
data-test="delete-table"
>
Delete table
</Button>
);
const getEnumsSection = () => {
const toggleEnum = () => dispatch(toggleTableAsEnum(table.is_enum));
return (
<React.Fragment>
<EnumsSection
isEnum={table.is_enum}
toggleEnum={toggleEnum}
loading={tableEnum.loading}
/>
</React.Fragment>
);
};
const toggleApollofederation = () =>
dispatch(toggleAsApollofederation(table.is_apollo_federation_supported));
return (
<RightContainer>
<div>
<TableHeader
dispatch={dispatch}
table={table}
source={currentSource}
tabName="modify"
migrationMode={migrationMode}
readOnlyMode={readOnlyMode}
/>
<br />
<div>
<div>
{isFeatureSupported('tables.modify.readOnly') && (
<div className={'py-sm px-sm bg-gray-200 rounded-md mb-md'}>
<p className="mb-xs font-bold">
<FaFlask aria-hidden="true" /> Coming soon for{' '}
{driverToLabel[currentDriver]}
</p>
<p className="m-0">
This page is currently read-only, but we're actively working
on making it available for the Console.
</p>
</div>
)}
{isFeatureSupported('tables.modify.comments.view') && (
<>
<div className="w-full sm:w-6/12 mb-lg">
<EnumTableModifyWarning isEnum={table.is_enum} />
<h4 className="flex items-center text-gray-600 font-semibold mb-formlabel">
Table Comments
</h4>
<TableCommentEditor
tableComment={tableComment}
tableCommentEdit={tableCommentEdit}
tableType="TABLE"
dispatch={dispatch}
readOnly={
!isFeatureSupported('tables.modify.comments.edit')
}
/>
</div>
</>
)}
<div className="w-full sm:w-full">
<h3 className="text-sm tracking-widest text-gray-400 uppercase font-semibold mb-sm">
Configure Fields
</h3>
{isFeatureSupported('tables.modify.columns.view') && (
<>
<h4 className="flex items-center text-gray-600 font-semibold mb-formlabel">
Columns
</h4>
<ColumnEditorList
validTypeCasts={validTypeCasts}
dataTypeIndexMap={dataTypeIndexMap}
tableSchema={table}
columnEdit={columnEdit}
dispatch={dispatch}
readOnlyMode={
!isFeatureSupported('tables.modify.columns.edit')
}
currentSchema={currentSchema}
columnDefaultFunctions={columnDefaultFunctions}
customColumnNames={getTableCustomColumnNames(table)}
/>
</>
)}
</div>
<div className="w-full mb-lg">
{isFeatureSupported('tables.modify.columns.edit') && (
<>
<ColumnCreator
dispatch={dispatch}
tableName={tableName}
dataTypes={dataTypes}
validTypeCasts={validTypeCasts}
columnDefaultFunctions={columnDefaultFunctions}
postgresVersion={postgresVersion}
/>
</>
)}
</div>
<h3 className="text-sm tracking-widest text-gray-400 uppercase font-semibold mb-sm">
Table Properties
</h3>
{isFeatureSupported('tables.modify.primaryKeys.view') && (
<>
<div className="w-full sm:w-6/12 mb-md">
<h4 className="flex items-center text-gray-600 font-semibold mb-formlabel">
Primary Key
<Tooltip message={primaryKeyDescription} />
</h4>
<PrimaryKeyEditor
tableSchema={table}
readOnlyMode={
!isFeatureSupported('tables.modify.primaryKeys.edit')
}
pkModify={pkModify}
dispatch={dispatch}
currentSchema={currentSchema}
/>
</div>
</>
)}
{isFeatureSupported('tables.modify.foreignKeys.view') && (
<>
<div className="w-full sm:w-8/12 mb-md">
<h4 className="flex items-center text-gray-600 font-semibold mb-formlabel">
Foreign Keys
<Tooltip message={foreignKeyDescription} />
</h4>
<ForeignKeyEditor
tableSchema={table}
currentSchema={currentSchema}
allSchemas={allTables}
schemaList={schemaList}
dispatch={dispatch}
fkModify={fkModify}
readOnlyMode={
!isFeatureSupported('tables.modify.foreignKeys.edit')
}
/>
</div>
</>
)}
{isFeatureSupported('tables.modify.uniqueKeys.view') && (
<>
<div className="w-full sm:w-6/12 mb-md">
<h4 className="flex items-center text-gray-600 font-semibold mb-formlabel">
Unique Keys
<Tooltip message={uniqueKeyDescription} />
</h4>
<UniqueKeyEditor
tableSchema={table}
currentSchema={currentSchema}
allSchemas={allTables}
dispatch={dispatch}
uniqueKeys={uniqueKeyModify}
setUniqueKeys={setUniqueKeys}
readOnlyMode={
!isFeatureSupported('tables.modify.uniqueKeys.edit')
}
/>
</div>
</>
)}
{isFeatureSupported('tables.modify.checkConstraints.view') && (
<>
<div className="w-full sm:w-6/12 mb-md">
<h4 className="flex items-center text-gray-600 font-semibold mb-formlabel">
Check Constraints
<Tooltip message={checkConstraintsDescription} />
</h4>
<NotSupportedNote unsupported={['mysql']} />
<CheckConstraints
constraints={table.check_constraints}
checkConstraintsModify={checkConstraintsModify}
dispatch={dispatch}
readOnlyMode={
!isFeatureSupported(
'tables.modify.checkConstraints.edit'
)
}
/>
</div>
</>
)}
{isFeatureSupported('tables.modify.indexes.view') ? (
<>
<div className="w-full sm:w-6/12 mb-md">
<h4 className="flex items-center text-gray-600 font-semibold mb-formlabel">
Indexes
<Tooltip message={indexFieldsDescription} />
</h4>
<IndexFields tableSchema={table} />
</div>
</>
) : null}
{table.table_type === 'PARTITIONED TABLE' && (
<PartitionInfo table={table} dispatch={dispatch} />
)}
{isFeatureSupported('tables.modify.triggers') && (
<>
<div className="w-full sm:w-6/12 mb-lg">
<h4 className="flex items-center text-gray-600 font-semibold mb-formlabel">
Triggers
</h4>
<NotSupportedNote unsupported={['mysql']} />
<TriggerEditorList
tableSchema={table}
dispatch={dispatch}
/>
</div>
</>
)}
<h3 className="text-sm tracking-widest text-gray-400 uppercase font-semibold mb-sm">
GraphQL Features
</h3>
{isFeatureSupported('tables.modify.computedFields') && (
<>
<div className="w-full sm:w-6/12 mb-md">
<ConnectedComputedFields tableSchema={table} />
</div>
</>
)}
{isFeatureSupported('tables.modify.customGqlRoot') && (
<>
<div className="w-full sm:w-6/12 mb-md">
<RootFields tableSchema={table} />
</div>
</>
)}
{isFeatureSupported('tables.modify.setAsEnum') &&
getEnumsSection()}
<ApolloFederationSupport
toggleApollofederation={toggleApollofederation}
isApolloFederationSupported={
table.is_apollo_federation_supported
}
/>
<div className="mb-lg">
{isFeatureSupported('tables.modify.untrack') && untrackBtn}
{isFeatureSupported('tables.modify.delete') && deleteBtn}
</div>
</div>
</div>
</div>
</RightContainer>
);
}
}
ModifyTable.propTypes = {
tableName: PropTypes.string.isRequired,
currentSchema: PropTypes.string.isRequired,
allTables: PropTypes.array.isRequired,
migrationMode: PropTypes.bool.isRequired,
readOnlyMode: PropTypes.bool.isRequired,
activeEdit: PropTypes.object.isRequired,
fkAdd: PropTypes.object.isRequired,
relAdd: PropTypes.object.isRequired,
ongoingRequest: PropTypes.bool.isRequired,
lastError: PropTypes.object,
lastFormError: PropTypes.object,
columnEdit: PropTypes.object.isRequired,
lastSuccess: PropTypes.bool,
dispatch: PropTypes.func.isRequired,
pkModify: PropTypes.array.isRequired,
fkModify: PropTypes.array.isRequired,
serverVersion: PropTypes.string,
};
const mapStateToProps = (state, ownProps) => ({
tableName: ownProps.params.table,
allTables: state.tables.allSchemas,
migrationMode: state.main.migrationMode,
readOnlyMode: state.main.readOnlyMode,
serverVersion: state.main.serverVersion,
currentSchema: state.tables.currentSchema,
columnEdit: state.tables.modify.columnEdit,
pkModify: state.tables.modify.pkModify,
fkModify: state.tables.modify.fkModify,
dataTypes: state.tables.columnDataTypes,
columnDefaultFunctions: state.tables.columnDefaultFunctions,
validTypeCasts: state.tables.columnTypeCasts,
columnDataTypeFetchErr: state.tables.columnDataTypeFetchErr,
schemaList: state.tables.schemaList,
postgresVersion: state.main.postgresVersion,
currentSource: state.tables.currentDataSource,
...state.tables.modify,
});
const modifyTableConnector = connect => connect(mapStateToProps)(ModifyTable);
export default modifyTableConnector;

View File

@ -0,0 +1,391 @@
import React from 'react';
import { FaFlask } from 'react-icons/fa';
import {
isFeatureSupported,
driverToLabel,
currentDriver,
getTableCustomColumnNames,
Table,
} from '@/dataSources';
import { AppDispatch } from '@/store';
import { IconTooltip } from '@/new-components/Tooltip';
import { NotSupportedNote } from '@/components/Common/NotSupportedNote';
import { Button } from '@/new-components/Button';
import {
primaryKeyDescription,
foreignKeyDescription,
uniqueKeyDescription,
checkConstraintsDescription,
indexFieldsDescription,
} from '../Common/TooltipMessages';
import { RightContainer } from '../../../Common/Layout/RightContainer';
import TableHeader from '../TableCommon/TableHeader';
import EnumsSection, {
EnumTableModifyWarning,
} from '../Common/Components/EnumsSection';
import { setUniqueKeys } from './ModifyActions';
import TableCommentEditor from './TableCommentEditor';
import ColumnEditorList from './ColumnEditorList';
import ColumnCreator from './ColumnCreator';
import PrimaryKeyEditor from './PrimaryKeyEditor';
import ForeignKeyEditor from './ForeignKeyEditor';
import UniqueKeyEditor from './UniqueKeyEditor';
import CheckConstraints from './CheckConstraints';
import IndexFields from './IndexFields';
import PartitionInfo from './PartitionInfo';
import TriggerEditorList from './TriggerEditorList';
import ConnectedComputedFields from './ComputedFields';
import RootFields from './RootFields';
import {
ApolloFederationSupport,
ApolloFederationSupportProps,
} from '../Common/Components/ApolloFederationSupport';
import { useModifyTableSupportedFeatures } from './hooks/useModifyTableSupportedFeatures';
import { ColumnName } from '../TableCommon/DataTableRowItem.types';
import {
CheckConstraintsType,
ColumnEdit,
DbDataType,
DbFunctionName,
ForeignKey,
TypeAvailable,
TypeCategory,
TypeDescription,
TypeDisplayName,
} from './ModifyTable.types';
export type ModifyTableProps = {
allTables: Table[];
checkConstraintsModify: CheckConstraintsType[];
columnDefaultFunctions: Record<DbDataType, DbFunctionName[]>;
columnEdit: ColumnEdit;
currentSchema: string;
currentSource: string;
dataTypeIndexMap: Record<DbDataType, string[]>;
dataTypes: [TypeAvailable, TypeDisplayName, TypeDescription, TypeCategory][];
dispatch: AppDispatch;
existingForeignKeys: ForeignKey[];
fkModify: ForeignKey[];
migrationMode: boolean;
onDeleteTable: () => void;
onToggleApolloFederation: ApolloFederationSupportProps['toggleApolloFederation'];
onToggleEnum: () => void;
onUntrackTable: () => void;
orderedColumns: { name: ColumnName; index: number }[];
pkModify: string[];
postgresVersion: string;
readOnlyMode: boolean;
schemaList: string[];
table: Table;
tableCommentEdit: { enabled: boolean; editedValue: string | null };
tableEnum: { loading: boolean };
tableName: string;
uniqueKeyModify: number[][];
validTypeCasts: Record<DbDataType, string[]>;
};
export const ModifyTable: React.VFC<ModifyTableProps> = ({
allTables,
checkConstraintsModify,
columnDefaultFunctions,
columnEdit,
currentSchema,
currentSource,
dataTypeIndexMap,
dataTypes,
dispatch,
existingForeignKeys,
fkModify,
migrationMode,
onDeleteTable,
onToggleApolloFederation,
onToggleEnum,
onUntrackTable,
orderedColumns,
pkModify,
postgresVersion,
readOnlyMode,
schemaList,
table,
tableCommentEdit,
tableEnum,
tableName,
uniqueKeyModify,
validTypeCasts,
}) => {
const {
areComputedFieldSupported,
areTriggersSupported,
isCheckConstraintsEditSupported,
isCheckConstraintsViewSupported,
isColumnsEditSupported,
isColumnsViewSupported,
isCommentsEditSupported,
isCommentsViewSupported,
isCustomGqlRootSupported,
isForeignKeysEditSupported,
isIndexViewSupported,
isModifySupported,
isPrimaryKeysEditSupported,
isPrimaryKeysViewSupported,
isReadOnlySupported,
isSetAsEnumSupported,
isUniqueKeysEditSupported,
isUntrackSupported,
} = useModifyTableSupportedFeatures();
return (
<RightContainer>
<div>
<TableHeader
dispatch={dispatch}
table={table}
source={currentSource}
tabName="modify"
migrationMode={migrationMode}
readOnlyMode={readOnlyMode}
count={undefined}
isCountEstimated={undefined}
/>
<br />
<div>
<div>
{isReadOnlySupported && (
<div className="py-sm px-sm bg-gray-200 rounded-md mb-md">
<p className="mb-xs font-bold">
<FaFlask aria-hidden="true" /> Coming soon for{' '}
{driverToLabel[currentDriver]}
</p>
<p className="m-0">
This page is currently read-only, but we&apos;re actively
working on making it available for the Console.
</p>
</div>
)}
{isCommentsViewSupported && (
<>
<div className="w-full sm:w-6/12 mb-lg">
<EnumTableModifyWarning isEnum={table.is_enum} />
<h4 className="flex items-center text-gray-600 font-semibold mb-formlabel">
Table Comments
</h4>
<TableCommentEditor
tableComment={table.comment}
tableCommentEdit={tableCommentEdit}
tableType="TABLE"
dispatch={dispatch}
readOnly={!isCommentsEditSupported}
/>
</div>
</>
)}
<div className="w-full sm:w-full">
<h3 className="text-sm tracking-widest text-gray-400 uppercase font-semibold mb-sm">
Configure Fields
</h3>
{isColumnsViewSupported && (
<>
<h4 className="flex items-center text-gray-600 font-semibold mb-formlabel">
Columns
</h4>
<ColumnEditorList
validTypeCasts={validTypeCasts}
dataTypeIndexMap={dataTypeIndexMap}
tableSchema={table}
columnEdit={columnEdit}
dispatch={dispatch}
readOnlyMode={!isColumnsEditSupported}
currentSchema={currentSchema}
columnDefaultFunctions={columnDefaultFunctions}
customColumnNames={getTableCustomColumnNames(table)}
/>
</>
)}
</div>
<div className="w-full mb-lg">
{isColumnsEditSupported && (
<>
<ColumnCreator
dispatch={dispatch}
tableName={tableName || ''}
dataTypes={dataTypes}
validTypeCasts={validTypeCasts}
columnDefaultFunctions={columnDefaultFunctions}
postgresVersion={postgresVersion}
/>
</>
)}
</div>
<h3 className="text-sm tracking-widest text-gray-400 uppercase font-semibold mb-sm">
Table Properties
</h3>
{isPrimaryKeysViewSupported && (
<>
<div className="w-full sm:w-6/12 mb-md">
<h4 className="flex items-center text-gray-600 font-semibold mb-formlabel">
Primary Key
<IconTooltip message={primaryKeyDescription} />
</h4>
<PrimaryKeyEditor
tableSchema={table}
readOnlyMode={!isPrimaryKeysEditSupported}
pkModify={pkModify}
dispatch={dispatch}
currentSchema={currentSchema}
/>
</div>
</>
)}
{isFeatureSupported('tables.modify.foreignKeys.view') && (
<>
<div className="w-full sm:w-8/12 mb-md">
<h4 className="flex items-center text-gray-600 font-semibold mb-formlabel">
Foreign Keys
<IconTooltip message={foreignKeyDescription} />
</h4>
<ForeignKeyEditor
tableSchema={table}
allSchemas={allTables}
dispatch={dispatch}
schemaList={schemaList}
fkModify={fkModify}
orderedColumns={orderedColumns}
existingForeignKeys={existingForeignKeys}
readOnlyMode={!isForeignKeysEditSupported}
/>
</div>
</>
)}
{isFeatureSupported('tables.modify.uniqueKeys.view') && (
<>
<div className="w-full sm:w-6/12 mb-md">
<h4 className="flex items-center text-gray-600 font-semibold mb-formlabel">
Unique Keys
<IconTooltip message={uniqueKeyDescription} />
</h4>
<UniqueKeyEditor
tableSchema={table}
dispatch={dispatch}
uniqueKeys={uniqueKeyModify}
setUniqueKeys={setUniqueKeys}
readOnlyMode={!isUniqueKeysEditSupported}
/>
</div>
</>
)}
{isCheckConstraintsViewSupported && (
<>
<div className="w-full sm:w-6/12 mb-md">
<h4 className="flex items-center text-gray-600 font-semibold mb-formlabel">
Check Constraints
<IconTooltip message={checkConstraintsDescription} />
</h4>
<NotSupportedNote unsupported={['mysql']} />
<CheckConstraints
constraints={table.check_constraints}
checkConstraintsModify={checkConstraintsModify}
dispatch={dispatch}
readOnlyMode={!isCheckConstraintsEditSupported}
/>
</div>
</>
)}
{isIndexViewSupported ? (
<>
<div className="w-full sm:w-6/12 mb-md">
<h4 className="flex items-center text-gray-600 font-semibold mb-formlabel">
Indexes
<IconTooltip message={indexFieldsDescription} />
</h4>
<IndexFields tableSchema={table} />
</div>
</>
) : null}
{table.table_type === 'PARTITIONED TABLE' && (
<PartitionInfo table={table} dispatch={dispatch} />
)}
{areTriggersSupported && (
<>
<div className="w-full sm:w-6/12 mb-lg">
<h4 className="flex items-center text-gray-600 font-semibold mb-formlabel">
Triggers
</h4>
<NotSupportedNote unsupported={['mysql']} />
<TriggerEditorList tableSchema={table} dispatch={dispatch} />
</div>
</>
)}
<h3 className="text-sm tracking-widest text-gray-400 uppercase font-semibold mb-sm">
GraphQL Features
</h3>
{areComputedFieldSupported && (
<>
<div className="w-full sm:w-6/12 mb-md">
<ConnectedComputedFields tableSchema={table} />
</div>
</>
)}
{isCustomGqlRootSupported && (
<>
<div className="w-full sm:w-6/12 mb-md">
<RootFields tableSchema={table} />
</div>
</>
)}
{isSetAsEnumSupported && (
<EnumsSection
isEnum={table.is_enum}
toggleEnum={() => onToggleEnum()}
loading={tableEnum.loading}
/>
)}
<ApolloFederationSupport
toggleApolloFederation={onToggleApolloFederation}
isApolloFederationSupported={
table?.is_apollo_federation_supported || false
}
/>
<div className="mb-lg">
{isUntrackSupported && (
<Button
type="submit"
className="mr-sm"
size="sm"
onClick={() => onUntrackTable()}
data-test="untrack-table"
>
Untrack Table
</Button>
)}
{isModifySupported && (
<Button
type="submit"
mode="destructive"
size="sm"
onClick={() => onDeleteTable()}
data-test="delete-table"
>
Delete table
</Button>
)}
</div>
</div>
</div>
</div>
</RightContainer>
);
};

View File

@ -0,0 +1,47 @@
import { ColumnName } from '../TableCommon/DataTableRowItem.types';
export type CheckConstraintsType = {
check: string;
name: string;
};
export type DbFunctionName = string;
export type DbDataType = string;
export type ColumnEdit = Record<
ColumnName,
{
comment: string;
customFieldName: string;
default: string;
display_type_name: string;
isArrayDataType: boolean;
isIdentity: boolean;
isNullable: boolean;
isOnlyPrimaryKey: boolean | undefined;
isUnique: boolean;
name: string;
pkConstraint: string | undefined;
schemaName: string;
tableName: string;
type: string;
}
>;
export type ForeignKey = {
colMappings: { column: string; refColumn: string }[];
constraintName: string;
onDelete: string;
onUpdate: string;
refSchemaName?: string;
refTableName?: string;
};
// Types available in a particular group
export type TypeAvailable = string;
// Display Name of a type
export type TypeDisplayName = string;
// Description of a type
export type TypeDescription = string;
// Category the particular type belongs to
export type TypeCategory = string;

View File

@ -0,0 +1,199 @@
import { getConfirmation } from '@/components/Common/utils/jsUtils';
import {
findTable,
generateTableDef,
isFeatureSupported,
Table,
} from '@/dataSources';
import { useAppDispatch, useAppSelector } from '@/store';
import React, { useEffect } from 'react';
import { getAllDataTypeMap } from '../Common/utils';
import {
fetchColumnTypeInfo,
fetchFunctionInit,
RESET_COLUMN_TYPE_INFO,
setTable,
} from '../DataActions';
import FeatureDisabled from '../FeatureDisabled';
import { useForeignKeys } from './hooks/useForeignKeys';
import {
deleteTableSql,
RESET,
toggleAsApolloFederation,
toggleTableAsEnum,
untrackTableSql,
} from './ModifyActions';
import { ModifyTable, ModifyTableProps } from './ModifyTable';
type ColumnEdit = {
comment: string;
customFieldName: string;
default: string;
display_type_name: string;
isArrayDataType: boolean;
isIdentity: boolean;
isNullable: boolean;
isOnlyPrimaryKey: boolean;
isUnique: boolean;
name: string;
pkConstraint: string;
schemaName: string;
tableName: string;
type: string;
};
type FkModify = {
colMappings: any[];
constraintName: string;
onDelete: string;
onUpdate: string;
refSchemaName: string;
refTableName: string;
};
type TableModify = {
columnEdit: Record<string, ColumnEdit>;
pkModify: string[];
fkModify: FkModify[];
tableEnum: any;
tableCommentEdit: any;
checkConstraintsModify: any;
uniqueKeyModify: any;
};
type Props = {
params: {
table: string;
};
};
export const ModifyTableContainer: React.VFC<Props> = ({ params }) => {
const dispatch = useAppDispatch();
const tableName = params.table;
useEffect(() => {
dispatch({ type: RESET });
dispatch(setTable(tableName));
if (!isFeatureSupported('tables.modify.readOnly')) {
dispatch(fetchColumnTypeInfo());
}
dispatch(fetchFunctionInit());
return () => {
dispatch({
type: RESET_COLUMN_TYPE_INFO,
});
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [tableName]);
const allTables: Table[] = useAppSelector(store => store.tables.allSchemas);
const migrationMode: boolean = useAppSelector(
store => store.main.migrationMode
);
const readOnlyMode = useAppSelector(store => store.main.readOnlyMode);
const currentSchema = useAppSelector(store => store.tables.currentSchema);
const tableModify: TableModify = useAppSelector(store => store.tables.modify);
const {
columnEdit,
pkModify,
fkModify,
tableEnum,
tableCommentEdit,
checkConstraintsModify,
uniqueKeyModify,
} = tableModify;
const dataTypes = useAppSelector(store => store.tables.columnDataTypes);
const columnDefaultFunctions = useAppSelector(
store => store.tables.columnDefaultFunctions
);
const validTypeCasts = useAppSelector(store => store.tables.columnTypeCasts);
const schemaList = useAppSelector(store => store.tables.schemaList);
const postgresVersion = useAppSelector(store => store.main.postgresVersion);
const currentSource = useAppSelector(store => store.tables.currentDataSource);
const table = findTable(
allTables,
generateTableDef(tableName, currentSchema)
);
const { orderedColumns, existingForeignKeys } = useForeignKeys({ table });
if (!table) {
return null;
}
const isModifyEnabled = isFeatureSupported('tables.modify.enabled');
if (!isModifyEnabled) {
return (
<FeatureDisabled
tab="modify"
tableName={tableName}
schemaName={currentSchema}
/>
);
}
const dataTypeIndexMap = getAllDataTypeMap(
dataTypes
) as ModifyTableProps['dataTypeIndexMap'];
const onUntrackTable = () => {
const confirmMessage = `This will remove the table "${tableName}" from the GraphQL schema`;
const isOk = getConfirmation(confirmMessage);
if (isOk) {
dispatch(untrackTableSql(tableName));
}
};
const onDeleteTable = () => {
const confirmMessage = `This will permanently delete the table "${tableName}" from the database`;
const isOk = getConfirmation(confirmMessage, true, tableName);
if (isOk) {
dispatch(deleteTableSql(tableName));
}
};
const onToggleEnum = () => dispatch(toggleTableAsEnum(table?.is_enum));
const onToggleApolloFederation = () =>
dispatch(
toggleAsApolloFederation(table?.is_apollo_federation_supported || false)
);
return (
<ModifyTable
allTables={allTables}
checkConstraintsModify={checkConstraintsModify}
columnDefaultFunctions={columnDefaultFunctions}
columnEdit={columnEdit}
currentSchema={currentSchema}
currentSource={currentSource}
dataTypeIndexMap={dataTypeIndexMap}
dataTypes={dataTypes}
dispatch={dispatch}
existingForeignKeys={existingForeignKeys}
fkModify={fkModify}
migrationMode={migrationMode}
onDeleteTable={onDeleteTable}
onToggleApolloFederation={onToggleApolloFederation}
onToggleEnum={onToggleEnum}
onUntrackTable={onUntrackTable}
orderedColumns={orderedColumns}
pkModify={pkModify}
postgresVersion={postgresVersion}
readOnlyMode={readOnlyMode}
schemaList={schemaList}
table={table}
tableCommentEdit={tableCommentEdit}
tableEnum={tableEnum}
tableName={tableName}
uniqueKeyModify={uniqueKeyModify}
validTypeCasts={validTypeCasts}
/>
);
};

View File

@ -0,0 +1,43 @@
import { dataSource, Table } from '@/dataSources';
import { useAppDispatch } from '@/store';
import { useEffect } from 'react';
import { getExistingFKConstraints } from '../../Common/Components/utils';
import { updateSchemaInfo } from '../../DataActions';
import { ordinalColSort } from '../../utils';
import { setForeignKeys } from '../ModifyActions';
type UseForeignKeysProps = {
table: Table | undefined;
};
export const useForeignKeys = ({ table }: UseForeignKeysProps) => {
const dispatch = useAppDispatch();
const columns = table?.columns?.sort(ordinalColSort) || [];
const orderedColumns = columns.map((c, i) => ({
name: c.column_name,
index: i,
}));
const existingForeignKeys = getExistingFKConstraints(table, orderedColumns);
const schemasToBeFetched: Record<string, boolean> = {};
existingForeignKeys.forEach((efk: { refSchemaName: string }) => {
schemasToBeFetched[efk.refSchemaName] = true;
});
existingForeignKeys.push({
onUpdate: dataSource?.violationActions?.[0],
onDelete: dataSource?.violationActions?.[0],
colMappings: [{ column: '', refColumn: '' }],
});
useEffect(() => {
dispatch(setForeignKeys(existingForeignKeys));
dispatch(updateSchemaInfo({ schemas: Object.keys(schemasToBeFetched) }));
}, []);
return {
orderedColumns,
existingForeignKeys,
};
};

View File

@ -0,0 +1,67 @@
import { isFeatureSupported } from '@/dataSources';
export const useModifyTableSupportedFeatures = () => {
const isUntrackSupported = isFeatureSupported('tables.modify.untrack');
const isModifySupported = isFeatureSupported('tables.modify.delete');
const isSetAsEnumSupported = isFeatureSupported('tables.modify.setAsEnum');
const isReadOnlySupported = isFeatureSupported('tables.modify.readOnly');
const isCommentsViewSupported = isFeatureSupported(
'tables.modify.comments.view'
);
const isCommentsEditSupported = isFeatureSupported(
'tables.modify.comments.edit'
);
const isColumnsViewSupported = isFeatureSupported(
'tables.modify.columns.view'
);
const isColumnsEditSupported = isFeatureSupported(
'tables.modify.columns.edit'
);
const isPrimaryKeysViewSupported = isFeatureSupported(
'tables.modify.primaryKeys.view'
);
const isPrimaryKeysEditSupported = isFeatureSupported(
'tables.modify.primaryKeys.edit'
);
const isForeignKeysEditSupported = isFeatureSupported(
'tables.modify.foreignKeys.edit'
);
const isUniqueKeysEditSupported = isFeatureSupported(
'tables.modify.uniqueKeys.edit'
);
const isCheckConstraintsViewSupported = isFeatureSupported(
'tables.modify.checkConstraints.view'
);
const isCheckConstraintsEditSupported = isFeatureSupported(
'tables.modify.checkConstraints.edit'
);
const isIndexViewSupported = isFeatureSupported('tables.modify.indexes.view');
const areTriggersSupported = isFeatureSupported('tables.modify.triggers');
const areComputedFieldSupported = isFeatureSupported(
'tables.modify.computedFields'
);
const isCustomGqlRootSupported = isFeatureSupported(
'tables.modify.customGqlRoot'
);
return {
areComputedFieldSupported,
areTriggersSupported,
isCheckConstraintsEditSupported,
isCheckConstraintsViewSupported,
isColumnsEditSupported,
isColumnsViewSupported,
isCommentsEditSupported,
isCommentsViewSupported,
isCustomGqlRootSupported,
isForeignKeysEditSupported,
isIndexViewSupported,
isModifySupported,
isPrimaryKeysEditSupported,
isPrimaryKeysViewSupported,
isReadOnlySupported,
isSetAsEnumSupported,
isUniqueKeysEditSupported,
isUntrackSupported,
};
};

View File

@ -12,7 +12,6 @@ import addExistingTableViewConnector from './Add/AddExistingTableView';
import addTableConnector from './Add/AddTable';
import rawSQLConnector from './RawSQL/RawSQL';
import permissionsSummaryConnector from './PermissionsSummary/PermissionsSummary';
import modifyTableConnector from './TableModify/ModifyTable';
import modifyViewConnector from './TableModify/ModifyView';
import relationshipsConnector from './TableRelationships/Relationships';
import relationshipsViewConnector from './TableRelationships/RelationshipsView';
@ -36,7 +35,6 @@ export {
addTableConnector,
rawSQLConnector,
permissionsSummaryConnector,
modifyTableConnector,
modifyViewConnector,
relationshipsConnector,
relationshipsViewConnector,

View File

@ -73,9 +73,8 @@ export const getTableNameWithSchema = (
return fullTableName;
};
export const findTable = (allTables: Table[], tableDef: QualifiedTable) => {
return allTables.find(t => isEqual(getTableDef(t), tableDef));
};
export const findTable = (allTables: Table[], tableDef: QualifiedTable) =>
allTables.find(t => isEqual(getTableDef(t), tableDef));
export const getTrackedTables = (tables: Table[]) => {
return tables.filter(t => t.is_table_tracked);

View File

@ -240,6 +240,7 @@ export interface Table extends BaseTable {
}[];
citusTableType?: string;
unique_constraints: UniqueKey[] | null;
is_apollo_federation_supported?: boolean;
}
export type Partition = {