Compare commits

...

1 Commits

Author SHA1 Message Date
Jan Prochazka
61222bef9b WIP 2021-01-24 18:54:59 +01:00
7 changed files with 439 additions and 127 deletions

View File

@@ -0,0 +1,17 @@
import { TableInfo } from 'dbgate-types';
import uuidv1 from 'uuid/v1';
export function generateTableGroupId(table: TableInfo): TableInfo {
if (!table) return table;
if (!table.groupId) {
return {
...table,
columns: table.columns.map(col => ({
...col,
groupid: uuidv1(),
})),
groupId: uuidv1(),
};
}
return table;
}

View File

@@ -8,3 +8,4 @@ export * from './driverBase';
export * from './SqlDumper';
export * from './testPermission';
export * from './splitPostgresQuery';
export * from './diffTools';

View File

@@ -9,6 +9,7 @@ export interface ColumnReference {
}
export interface ConstraintInfo extends NamedObjectInfo {
groupId?: string;
constraintName: string;
constraintType: string;
}
@@ -27,6 +28,7 @@ export interface ForeignKeyInfo extends ColumnsConstraintInfo {
}
export interface ColumnInfo {
groupId?: string;
columnName: string;
notNull: boolean;
autoIncrement: boolean;
@@ -42,6 +44,7 @@ export interface ColumnInfo {
}
export interface DatabaseObjectInfo extends NamedObjectInfo {
groupId?: string;
objectId?: string;
createDate?: string;
modifyDate?: string;

View File

@@ -0,0 +1,161 @@
import React from 'react';
import styled from 'styled-components';
import _ from 'lodash';
import ObjectListControl from '../utility/ObjectListControl';
import { TableColumn } from '../utility/TableControl';
import { useTableInfo, useDbCore } from '../utility/metadataLoaders';
import useTheme from '../theme/useTheme';
import ColumnLabel from '../datagrid/ColumnLabel';
import { FontIcon } from '../icons';
import useEditorData from '../utility/useEditorData';
import { generateTableGroupId } from 'dbgate-tools';
import useShowModal from '../modals/showModal';
import ReactDOM from 'react-dom';
import TableEditorToolbar from './TableEditorToolbar';
import { ColumnEditorModal } from './TableEditorModals';
const WhitePage = styled.div`
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
background-color: ${props => props.theme.main_background};
overflow: auto;
`;
const IconTextSpan = styled.span`
white-space: nowrap;
`;
function getConstraintIcon(data) {
if (data.constraintType == 'primaryKey') return 'img primary-key';
if (data.constraintType == 'foreignKey') return 'img foreign-key';
return null;
}
function ConstraintLabel({ data }) {
const icon = getConstraintIcon(data);
return (
<IconTextSpan>
<FontIcon icon={icon} /> {data.constraintName}
</IconTextSpan>
);
}
export default function TableEditor({ tableInfo, setTableInfo, toolbarPortalRef, tabVisible }) {
const { columns, primaryKey, foreignKeys, dependencies } = tableInfo;
const theme = useTheme();
const showModal = useShowModal();
return (
<WhitePage theme={theme}>
<ObjectListControl
collection={columns.map((x, index) => ({ ...x, ordinal: index + 1 }))}
NameComponent={({ data }) => <ColumnLabel {...data} forceIcon />}
// makeAppObj={columnAppObject}
title="Columns"
>
<TableColumn
fieldName="notNull"
header="Not NULL"
sortable={true}
formatter={row => (row.notNull ? 'YES' : 'NO')}
/>
<TableColumn fieldName="dataType" header="Data Type" sortable={true} />
<TableColumn fieldName="defaultValue" header="Default value" sortable={true} />
<TableColumn
fieldName="isSparse"
header="Is Sparse"
sortable={true}
formatter={row => (row.isSparse ? 'YES' : 'NO')}
/>
<TableColumn fieldName="computedExpression" header="Computed Expression" sortable={true} />
<TableColumn
fieldName="isPersisted"
header="Is Persisted"
sortable={true}
formatter={row => (row.isPersisted ? 'YES' : 'NO')}
/>
{/* {_.includes(dbCaps.columnListOptionalColumns, 'referencedTableNamesFormatted') && (
<TableColumn fieldName="referencedTableNamesFormatted" header="References" sortable={true} />
)}
<TableColumn
fieldName="actions"
header=""
formatter={row => (
<span>
<Link
linkElementId={encodeHtmlId(`button_delete_column_${row.column.name}`)}
onClick={() => this.deleteColumn(row)}
>
Delete
</Link>{' '}
|{' '}
<Link
linkElementId={encodeHtmlId(`button_edit_column__${row.column.name}`)}
onClick={() => this.editColumn(row)}
>
Edit
</Link>
</span>
)}
/> */}
</ObjectListControl>
<ObjectListControl collection={_.compact([primaryKey])} NameComponent={ConstraintLabel} title="Primary key">
<TableColumn
fieldName="columns"
header="Columns"
formatter={row => row.columns.map(x => x.columnName).join(', ')}
/>
</ObjectListControl>
<ObjectListControl collection={foreignKeys} NameComponent={ConstraintLabel} title="Foreign keys">
<TableColumn
fieldName="baseColumns"
header="Base columns"
formatter={row => row.columns.map(x => x.columnName).join(', ')}
/>
<TableColumn fieldName="refTable" header="Referenced table" formatter={row => row.refTableName} />
<TableColumn
fieldName="refColumns"
header="Referenced columns"
formatter={row => row.columns.map(x => x.refColumnName).join(', ')}
/>
<TableColumn fieldName="updateAction" header="ON UPDATE" />
<TableColumn fieldName="deleteAction" header="ON DELETE" />
</ObjectListControl>
<ObjectListControl collection={dependencies} NameComponent={ConstraintLabel} title="Dependencies">
<TableColumn
fieldName="baseColumns"
header="Base columns"
formatter={row => row.columns.map(x => x.columnName).join(', ')}
/>
<TableColumn fieldName="baseTable" header="Base table" formatter={row => row.pureName} />
<TableColumn
fieldName="refColumns"
header="Referenced columns"
formatter={row => row.columns.map(x => x.refColumnName).join(', ')}
/>
<TableColumn fieldName="updateAction" header="ON UPDATE" />
<TableColumn fieldName="deleteAction" header="ON DELETE" />
</ObjectListControl>
{toolbarPortalRef &&
toolbarPortalRef.current &&
tabVisible &&
ReactDOM.createPortal(
<TableEditorToolbar
save={() => {}}
addColumn={() =>
showModal(modalState => (
<ColumnEditorModal modalState={modalState} setTableInfo={setTableInfo} tableInfo={tableInfo} />
))
}
/>,
toolbarPortalRef.current
)}
</WhitePage>
);
}

View File

@@ -0,0 +1,210 @@
import _ from 'lodash';
import React from 'react';
import ModalBase from '../modals/ModalBase';
import ModalContent from '../modals/ModalContent';
import ModalFooter from '../modals/ModalFooter';
import ModalHeader from '../modals/ModalHeader';
import { FormProvider } from '../utility/FormProvider';
import { FormCheckboxField, FormTextField } from '../utility/forms';
import FormStyledButton from '../widgets/FormStyledButton';
export function ColumnEditorModal({ modalState, columnInfo = undefined, tableInfo, setTableInfo }) {
const initialValues = columnInfo
? {
..._.pick(columnInfo, ['columnName', 'dataType', 'autoIncrement', 'defaultValue', 'computedExpression']),
}
: {};
return (
<FormProvider initialValues={initialValues}>
<ModalBase modalState={modalState}>
<ModalHeader modalState={modalState}>{columnInfo ? 'Edit column' : 'Add new column'}</ModalHeader>
<ModalContent>
<FormTextField name="columnName" label="Column name" />
<FormTextField name="dataType" label="Data type" />
<FormCheckboxField name="notNull" label="NOT NULL" />
{/* <FormCheckboxField name="isPrimaryKey" label="Is Primary Key" /> */}
<FormCheckboxField name="autoIncrement" label="Is Autoincrement" />
<FormTextField name="defaultValue" label="Default value" />
<FormTextField name="computedExpression" label="Computed expression" />
</ModalContent>
<ModalFooter>
<FormStyledButton value="Close" onClick={() => modalState.close()} />
</ModalFooter>
</ModalBase>
</FormProvider>
);
// <>
// <ModalFormItemText
// inputElementId="column_editor_column_name"
// field="name"
// label="Column name"
// ref={x => (this.domName = x)}
// />
// <ModalFormItemTextWithDropDown inputElementId="column_editor_data_type" field="dataType" label="Data type">
// <DropDownMenuItem onClick={this.setDataType.bind(this, 'int')}>int</DropDownMenuItem>
// <DropDownMenuItem onClick={this.setDataType.bind(this, 'nvarchar(250)')}>nvarchar(250)</DropDownMenuItem>
// <DropDownMenuItem onClick={this.setDataType.bind(this, 'datetime')}>datetime</DropDownMenuItem>
// <DropDownMenuItem onClick={this.setDataType.bind(this, 'numeric(10,2)')}>numeric(10,2)</DropDownMenuItem>
// <DropDownMenuItem onClick={this.setDataType.bind(this, 'float')}>float</DropDownMenuItem>
// </ModalFormItemTextWithDropDown>
// <ModalFormItemCheckBox inputElementId="column_editor_not_null" field="notNull" label="NOT NULL" />
// <ModalFormItemCheckBox inputElementId="column_editor_primary_key" field="primaryKey" label="Is Primary Key" />
// <ModalFormItemCheckBox
// inputElementId="column_editor_auto_increment"
// field="autoIncrement"
// label="Is Autoincrement"
// />
// <ModalFormItemText inputElementId="column_editor_default_value" field="defaultValue" label="Default value" />
// <div className="form-group row">
// <label className="col-sm-4 form-control-label">Referenced table</label>
// <div className="col-sm-8">
// <Select
// options={selectTableOptions}
// value={this.refTableName}
// onChange={value => this.changeRefTable(value.value)}
// clearable={false}
// />
// </div>
// </div>
// {_.includes(dbCaps.columnListOptionalColumns, 'isSparse') && (
// <ModalFormItemCheckBox inputElementId="column_editor_is_sparse" field="isSparse" label="Is Sparse" />
// )}
// {_.includes(dbCaps.columnListOptionalColumns, 'computedExpression') && (
// <ModalFormItemText
// inputElementId="column_editor_computed_expression"
// field="computedExpression"
// label="Computed Expression"
// />
// )}
// {_.includes(dbCaps.columnListOptionalColumns, 'isPersisted') && !!this.props.model.computedExpression && (
// <ModalFormItemCheckBox inputElementId="column_editor_is_persisted" field="isPersisted" label="Is Persisted" />
// )}
// </>
// );
}
// export class ColumnEditorDialog extends RefTableObjectEditorBase<ColumnInfo> {
// wasPrimaryKey: boolean;
// renderBody() {
// var dbCaps = getDatabaseEngineCaps(this.props.database);
// let selectTableOptions = [];
// if (this.tables) selectTableOptions = this.tables.map(function (x) { return { value: x.fullName, label: x.fullName }; });
// return <div>
// <ModalFormItemText inputElementId='column_editor_column_name' field='name' label="Column name" ref={x => this.domName = x} />
// <ModalFormItemTextWithDropDown inputElementId='column_editor_data_type' field='dataType' label="Data type" >
// <DropDownMenuItem onClick={this.setDataType.bind(this, 'int')}>int</DropDownMenuItem>
// <DropDownMenuItem onClick={this.setDataType.bind(this, 'nvarchar(250)')}>nvarchar(250)</DropDownMenuItem>
// <DropDownMenuItem onClick={this.setDataType.bind(this, 'datetime')}>datetime</DropDownMenuItem>
// <DropDownMenuItem onClick={this.setDataType.bind(this, 'numeric(10,2)')}>numeric(10,2)</DropDownMenuItem>
// <DropDownMenuItem onClick={this.setDataType.bind(this, 'float')}>float</DropDownMenuItem>
// </ModalFormItemTextWithDropDown>
// <ModalFormItemCheckBox inputElementId='column_editor_not_null' field='notNull' label="NOT NULL" />
// <ModalFormItemCheckBox inputElementId='column_editor_primary_key' field='primaryKey' label="Is Primary Key" />
// <ModalFormItemCheckBox inputElementId='column_editor_auto_increment' field='autoIncrement' label="Is Autoincrement" />
// <ModalFormItemText inputElementId='column_editor_default_value' field='defaultValue' label="Default value" />
// <div className='form-group row'>
// <label className='col-sm-4 form-control-label'>Referenced table</label>
// <div className='col-sm-8'>
// <Select options={selectTableOptions} value={this.refTableName} onChange={value => this.changeRefTable(value.value)} clearable={false} />
// </div>
// </div>
// {_.includes(dbCaps.columnListOptionalColumns, 'isSparse') &&
// <ModalFormItemCheckBox inputElementId='column_editor_is_sparse' field='isSparse' label='Is Sparse' />
// }
// {_.includes(dbCaps.columnListOptionalColumns, 'computedExpression') &&
// <ModalFormItemText inputElementId='column_editor_computed_expression' field='computedExpression' label='Computed Expression' />
// }
// {_.includes(dbCaps.columnListOptionalColumns, 'isPersisted') && !!this.props.model.computedExpression &&
// <ModalFormItemCheckBox inputElementId='column_editor_is_persisted' field='isPersisted' label='Is Persisted' />
// }
// </div>;
// }
// async okButtonClick(result = 'addnew') {
// await this.validate();
// this.okButtonClickBase(result);
// }
// async onCreated() {
// if (this.props.addNewObject) {
// this.okButtonTitle = 'Save and Add Next';
// }
// this.wasPrimaryKey = this.props.model.primaryKey;
// await super.onCreated();
// }
// renderAdditionalButtons() {
// if (this.props.addNewObject) {
// return <button type="button" className="btn btn-primary" onClick={() => this.okButtonClick('close')} ref={x => this.domOkButton = x}>Save and Close</button>
// }
// }
// setDataType(type: string) {
// this.props.model.dataType = type;
// this.forceUpdate();
// }
// get refTableName() {
// let col = new TableColumnClientObject(this.props.table, this.props.model, this.props.tableInfo);
// let fk = col.getForeignKeys().filter(x => x.columns.length == 1)[0];
// if (!fk) return null;
// return createFullName(fk.refSchemaName, fk.refTableName);
// }
// async changeRefTable(table) {
// let newTable = this.tables.filter(x => x.fullName == table)[0];
// if (!newTable) return;
// let col = new TableColumnClientObject(this.props.table, this.props.model, this.props.tableInfo);
// let fks = col.getForeignKeys().filter(x => x.columns.length == 1);
// _.remove(this.props.tableInfo.foreignKeys, x => _.includes(fks, x));
// this.refTableInfo = await newTable.getTableInfoAsync();
// let refInfo = this.refTableInfo;
// if (refInfo.primaryKey && refInfo.primaryKey.columns.length == 1) {
// let column = refInfo.columns.filter(x => x.name == refInfo.primaryKey.columns[0].refColumnName)[0];
// if (column) {
// this.props.model.dataType = column.dataType;
// if (!this.props.model.name) this.props.model.name = column.name;
// let fk = {
// refTableName: refInfo.name,
// refSchemaName: refInfo.schema,
// columns: [{ refColumnName: this.props.model.name }],
// refColumns: [{ refColumnName: column.name }],
// };
// this.props.tableInfo.foreignKeys.push(fk as any);
// }
// }
// this.forceUpdate();
// }
// async validate() {
// if (this.props.model.primaryKey && !this.wasPrimaryKey) {
// if (!this.props.tableInfo.primaryKey) {
// this.props.tableInfo.primaryKey = { columns: [] } as any;
// }
// this.props.tableInfo.primaryKey.columns.push({ refColumnName: this.props.model.name });
// }
// if (!this.props.model.primaryKey && this.wasPrimaryKey) {
// if (this.props.tableInfo.primaryKey) {
// _.remove(this.props.tableInfo.primaryKey.columns, x => x.refColumnName == this.props.model.name);
// if (this.props.tableInfo.primaryKey.columns.length == 0) {
// this.props.tableInfo.primaryKey = null;
// }
// }
// }
// }
// }

View File

@@ -0,0 +1,15 @@
import React from 'react';
import ToolbarButton from '../widgets/ToolbarButton';
export default function TableEditorToolbar({ addColumn, save }) {
return (
<>
<ToolbarButton onClick={addColumn} icon="icon add">
Add column
</ToolbarButton>
<ToolbarButton disabled={!save} onClick={save} icon="icon save">
Save
</ToolbarButton>
</>
);
}

View File

@@ -7,135 +7,40 @@ import { useTableInfo, useDbCore } from '../utility/metadataLoaders';
import useTheme from '../theme/useTheme';
import ColumnLabel from '../datagrid/ColumnLabel';
import { FontIcon } from '../icons';
import useEditorData from '../utility/useEditorData';
import { generateTableGroupId } from 'dbgate-tools';
import TableEditor from '../tableeditor/TableEditor';
const WhitePage = styled.div`
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
background-color: ${props => props.theme.main_background};
overflow: auto;
`;
const IconTextSpan = styled.span`
white-space: nowrap;
`;
function getConstraintIcon(data) {
if (data.constraintType == 'primaryKey') return 'img primary-key';
if (data.constraintType == 'foreignKey') return 'img foreign-key';
return null;
}
function ConstraintLabel({ data }) {
const icon = getConstraintIcon(data);
return (
<IconTextSpan>
<FontIcon icon={icon} /> {data.constraintName}
</IconTextSpan>
);
}
export default function TableStructureTab({ conid, database, schemaName, pureName, objectTypeField = 'tables' }) {
const theme = useTheme();
export default function TableStructureTab({
conid,
database,
schemaName,
pureName,
tabid,
objectTypeField = 'tables',
toolbarPortalRef,
tabVisible,
}) {
const tableInfo = useDbCore({ conid, database, schemaName, pureName, objectTypeField });
if (!tableInfo) return null;
const { columns, primaryKey, foreignKeys, dependencies } = tableInfo;
const { editorData, setEditorData } = useEditorData({ tabid });
const tableInfoWithGroupId = React.useMemo(() => generateTableGroupId(tableInfo), [tableInfo]);
const showTable = editorData && editorData.current ? editorData.current : tableInfoWithGroupId;
const handleSetTableInfo = current => {
setEditorData({
base: editorData ? editorData.base : tableInfoWithGroupId,
current,
});
};
if (!showTable) return null;
return (
<WhitePage theme={theme}>
<ObjectListControl
collection={columns.map((x, index) => ({ ...x, ordinal: index + 1 }))}
NameComponent={({ data }) => <ColumnLabel {...data} forceIcon />}
// makeAppObj={columnAppObject}
title="Columns"
>
<TableColumn
fieldName="notNull"
header="Not NULL"
sortable={true}
formatter={row => (row.notNull ? 'YES' : 'NO')}
/>
<TableColumn fieldName="dataType" header="Data Type" sortable={true} />
<TableColumn fieldName="defaultValue" header="Default value" sortable={true} />
<TableColumn
fieldName="isSparse"
header="Is Sparse"
sortable={true}
formatter={row => (row.isSparse ? 'YES' : 'NO')}
/>
<TableColumn fieldName="computedExpression" header="Computed Expression" sortable={true} />
<TableColumn
fieldName="isPersisted"
header="Is Persisted"
sortable={true}
formatter={row => (row.isPersisted ? 'YES' : 'NO')}
/>
{/* {_.includes(dbCaps.columnListOptionalColumns, 'referencedTableNamesFormatted') && (
<TableColumn fieldName="referencedTableNamesFormatted" header="References" sortable={true} />
)}
<TableColumn
fieldName="actions"
header=""
formatter={row => (
<span>
<Link
linkElementId={encodeHtmlId(`button_delete_column_${row.column.name}`)}
onClick={() => this.deleteColumn(row)}
>
Delete
</Link>{' '}
|{' '}
<Link
linkElementId={encodeHtmlId(`button_edit_column__${row.column.name}`)}
onClick={() => this.editColumn(row)}
>
Edit
</Link>
</span>
)}
/> */}
</ObjectListControl>
<ObjectListControl collection={_.compact([primaryKey])} NameComponent={ConstraintLabel} title="Primary key">
<TableColumn
fieldName="columns"
header="Columns"
formatter={row => row.columns.map(x => x.columnName).join(', ')}
/>
</ObjectListControl>
<ObjectListControl collection={foreignKeys} NameComponent={ConstraintLabel} title="Foreign keys">
<TableColumn
fieldName="baseColumns"
header="Base columns"
formatter={row => row.columns.map(x => x.columnName).join(', ')}
/>
<TableColumn fieldName="refTable" header="Referenced table" formatter={row => row.refTableName} />
<TableColumn
fieldName="refColumns"
header="Referenced columns"
formatter={row => row.columns.map(x => x.refColumnName).join(', ')}
/>
<TableColumn fieldName="updateAction" header="ON UPDATE" />
<TableColumn fieldName="deleteAction" header="ON DELETE" />
</ObjectListControl>
<ObjectListControl collection={dependencies} NameComponent={ConstraintLabel} title="Dependencies">
<TableColumn
fieldName="baseColumns"
header="Base columns"
formatter={row => row.columns.map(x => x.columnName).join(', ')}
/>
<TableColumn fieldName="baseTable" header="Base table" formatter={row => row.pureName} />
<TableColumn
fieldName="refColumns"
header="Referenced columns"
formatter={row => row.columns.map(x => x.refColumnName).join(', ')}
/>
<TableColumn fieldName="updateAction" header="ON UPDATE" />
<TableColumn fieldName="deleteAction" header="ON DELETE" />
</ObjectListControl>
</WhitePage>
<TableEditor
tableInfo={showTable}
setTableInfo={handleSetTableInfo}
toolbarPortalRef={toolbarPortalRef}
tabVisible={tabVisible}
/>
);
}