query design ops, add reference

This commit is contained in:
Jan Prochazka
2020-12-30 08:33:39 +01:00
parent 4962d81661
commit 1de9b9f1fb
7 changed files with 186 additions and 19 deletions

View File

@@ -1,4 +1,4 @@
import { DatabaseInfo, DatabaseInfoObjects } from 'dbgate-types'; import { ColumnInfo, DatabaseInfo, DatabaseInfoObjects, TableInfo } from 'dbgate-types';
export function fullNameFromString(name) { export function fullNameFromString(name) {
const m = name.match(/\[([^\]]+)\]\.\[([^\]]+)\]/); const m = name.match(/\[([^\]]+)\]\.\[([^\]]+)\]/);
@@ -45,3 +45,7 @@ export function findObjectLike(
// @ts-ignore // @ts-ignore
return dbinfo[objectTypeField].find((x) => equalStringLike(x.pureName, pureName)); return dbinfo[objectTypeField].find((x) => equalStringLike(x.pureName, pureName));
} }
export function findForeignKeyForColumn(table: TableInfo, column: ColumnInfo) {
return (table.foreignKeys || []).find((fk) => fk.columns.find((col) => col.columnName == column.columnName));
}

View File

@@ -7,6 +7,7 @@ import useTheme from '../theme/useTheme';
import DesignerReference from './DesignerReference'; import DesignerReference from './DesignerReference';
import cleanupDesignColumns from './cleanupDesignColumns'; import cleanupDesignColumns from './cleanupDesignColumns';
import { isConnectedByReference } from './designerTools'; import { isConnectedByReference } from './designerTools';
import { getTableInfo } from '../utility/metadataLoaders';
const Wrapper = styled.div` const Wrapper = styled.div`
flex: 1; flex: 1;
@@ -20,7 +21,7 @@ const Canvas = styled.div`
position: relative; position: relative;
`; `;
export default function Designer({ value, onChange }) { export default function Designer({ value, onChange, conid, database }) {
const { tables, references } = value || {}; const { tables, references } = value || {};
const theme = useTheme(); const theme = useTheme();
@@ -39,10 +40,11 @@ export default function Designer({ value, onChange }) {
json.designerId = uuidv1(); json.designerId = uuidv1();
json.left = e.clientX - rect.left; json.left = e.clientX - rect.left;
json.top = e.clientY - rect.top; json.top = e.clientY - rect.top;
onChange({
...value, onChange((current) => ({
tables: [...(tables || []), json], ...current,
}); tables: [...(current.tables || []), json],
}));
}; };
const changeTable = React.useCallback( const changeTable = React.useCallback(
@@ -70,6 +72,9 @@ export default function Designer({ value, onChange }) {
onChange((current) => ({ onChange((current) => ({
...current, ...current,
tables: (current.tables || []).filter((x) => x.designerId != table.designerId), tables: (current.tables || []).filter((x) => x.designerId != table.designerId),
references: (current.references || []).filter(
(x) => x.sourceId != table.designerId && x.targetId != table.designerId
),
})); }));
}, },
[onChange] [onChange]
@@ -144,6 +149,45 @@ export default function Designer({ value, onChange }) {
}); });
}; };
const handleAddReferenceByColumn = async (designerId, foreignKey) => {
const toTable = await getTableInfo({
conid,
database,
pureName: foreignKey.refTableName,
schemaName: foreignKey.refSchemaName,
});
const newTableDesignerId = uuidv1();
onChange((current) => {
const fromTable = (current.tables || []).find((x) => x.designerId == designerId);
if (!fromTable) return;
return {
...current,
tables: [
...(current.tables || []),
{
...toTable,
left: fromTable.left + 300,
top: fromTable.top + 50,
designerId: newTableDesignerId,
},
],
references: [
...(current.references || []),
{
designerId: uuidv1(),
sourceId: fromTable.designerId,
targetId: newTableDesignerId,
joinType: 'INNER JOIN',
columns: foreignKey.columns.map((col) => ({
source: col.columnName,
target: col.refColumnName,
})),
},
],
};
});
};
const handleSelectColumn = React.useCallback( const handleSelectColumn = React.useCallback(
(column) => { (column) => {
onChange((current) => ({ onChange((current) => ({
@@ -211,6 +255,7 @@ export default function Designer({ value, onChange }) {
onCreateReference={handleCreateReference} onCreateReference={handleCreateReference}
onSelectColumn={handleSelectColumn} onSelectColumn={handleSelectColumn}
onChangeColumn={handleChangeColumn} onChangeColumn={handleChangeColumn}
onAddReferenceByColumn={handleAddReferenceByColumn}
table={table} table={table}
onChangeTable={changeTable} onChangeTable={changeTable}
onBringToFront={bringToFront} onBringToFront={bringToFront}

View File

@@ -1,11 +1,16 @@
import React from 'react'; import React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { findForeignKeyForColumn } from 'dbgate-tools';
import ColumnLabel from '../datagrid/ColumnLabel'; import ColumnLabel from '../datagrid/ColumnLabel';
import { FontIcon } from '../icons'; import { FontIcon } from '../icons';
import useTheme from '../theme/useTheme'; import useTheme from '../theme/useTheme';
import DomTableRef from './DomTableRef'; import DomTableRef from './DomTableRef';
import _ from 'lodash'; import _ from 'lodash';
import { CheckboxField } from '../utility/inputs'; import { CheckboxField } from '../utility/inputs';
import { useShowMenu } from '../modals/showMenu';
import { DropDownMenuDivider, DropDownMenuItem } from '../modals/DropDownMenu';
import useShowModal from '../modals/showModal';
import InputTextModal from '../modals/InputTextModal';
const Wrapper = styled.div` const Wrapper = styled.div`
position: absolute; position: absolute;
@@ -80,12 +85,50 @@ const ColumnLine = styled.div`
`} `}
`; `;
function TableContextMenu({ remove, setTableAlias, removeTableAlias }) {
return (
<>
<DropDownMenuItem onClick={remove}>Remove</DropDownMenuItem>
<DropDownMenuDivider />
<DropDownMenuItem onClick={setTableAlias}>Set table alias</DropDownMenuItem>
{!!removeTableAlias && <DropDownMenuItem onClick={removeTableAlias}>Remove table alias</DropDownMenuItem>}
</>
);
}
function ColumnContextMenu({ setSortOrder, addReference }) {
return (
<>
<DropDownMenuItem onClick={() => setSortOrder(1)}>Sort ascending</DropDownMenuItem>
<DropDownMenuItem onClick={() => setSortOrder(-1)}>Sort descending</DropDownMenuItem>
<DropDownMenuItem onClick={() => setSortOrder(0)}>Unsort</DropDownMenuItem>
{!!addReference && <DropDownMenuItem onClick={addReference}>Add reference</DropDownMenuItem>}
</>
);
}
function ColumnDesignerIcons({ column, designerId, designer }) {
const designerColumn = (designer.columns || []).find(
(x) => x.designerId == designerId && x.columnName == column.columnName
);
if (!designerColumn) return null;
return (
<>
{!!designerColumn.filter && <FontIcon icon="img filter" />}
{designerColumn.sortOrder > 0 && <FontIcon icon="img sort-asc" />}
{designerColumn.sortOrder < 0 && <FontIcon icon="img sort-desc" />}
{!!designerColumn.isGrouped && <FontIcon icon="img group" />}
</>
);
}
export default function DesignerTable({ export default function DesignerTable({
table, table,
onChangeTable, onChangeTable,
onBringToFront, onBringToFront,
onRemoveTable, onRemoveTable,
onCreateReference, onCreateReference,
onAddReferenceByColumn,
onSelectColumn, onSelectColumn,
onChangeColumn, onChangeColumn,
sourceDragColumn, sourceDragColumn,
@@ -97,11 +140,13 @@ export default function DesignerTable({
setChangeToken, setChangeToken,
designer, designer,
}) { }) {
const { pureName, columns, left, top, designerId } = table; const { pureName, columns, left, top, designerId, alias } = table;
const [movingPosition, setMovingPosition] = React.useState(null); const [movingPosition, setMovingPosition] = React.useState(null);
const movingPositionRef = React.useRef(null); const movingPositionRef = React.useRef(null);
const theme = useTheme(); const theme = useTheme();
const domObjectsRef = React.useRef({}); const domObjectsRef = React.useRef({});
const showMenu = useShowMenu();
const showModal = useShowModal();
const moveStartXRef = React.useRef(null); const moveStartXRef = React.useRef(null);
const moveStartYRef = React.useRef(null); const moveStartYRef = React.useRef(null);
@@ -185,6 +230,71 @@ export default function DesignerTable({
changeTokenDebounced.current(); changeTokenDebounced.current();
}; };
const handleSetTableAlias = () => {
showModal((modalState) => (
<InputTextModal
modalState={modalState}
value={alias || ''}
label="New alias"
header="Set table alias"
onConfirm={(newAlias) => {
onChangeTable({
...table,
alias: newAlias,
});
}}
/>
));
};
const handleHeaderContextMenu = (event) => {
event.preventDefault();
showMenu(
event.pageX,
event.pageY,
<TableContextMenu
remove={() => onRemoveTable({ designerId })}
setTableAlias={handleSetTableAlias}
removeTableAlias={
alias
? () =>
onChangeTable({
...table,
alias: null,
})
: null
}
/>
);
};
const handleColumnContextMenu = (column) => (event) => {
event.preventDefault();
const foreignKey = findForeignKeyForColumn(table, column);
showMenu(
event.pageX,
event.pageY,
<ColumnContextMenu
setSortOrder={(sortOrder) => {
onChangeColumn(
{
...column,
designerId,
},
(col) => ({ ...col, sortOrder })
);
}}
addReference={
foreignKey
? () => {
onAddReferenceByColumn(designerId, foreignKey);
}
: null
}
/>
);
};
return ( return (
<Wrapper <Wrapper
theme={theme} theme={theme}
@@ -195,8 +305,8 @@ export default function DesignerTable({
onMouseDown={() => onBringToFront(table)} onMouseDown={() => onBringToFront(table)}
ref={(dom) => dispatchDomColumn('', dom)} ref={(dom) => dispatchDomColumn('', dom)}
> >
<Header onMouseDown={headerMouseDown} theme={theme}> <Header onMouseDown={headerMouseDown} theme={theme} onContextMenu={handleHeaderContextMenu}>
<HeaderLabel>{pureName}</HeaderLabel> <HeaderLabel>{alias || pureName}</HeaderLabel>
<CloseWrapper onClick={() => onRemoveTable(table)} theme={theme}> <CloseWrapper onClick={() => onRemoveTable(table)} theme={theme}>
<FontIcon icon="icon close" /> <FontIcon icon="icon close" />
</CloseWrapper> </CloseWrapper>
@@ -204,6 +314,7 @@ export default function DesignerTable({
<ColumnsWrapper> <ColumnsWrapper>
{(columns || []).map((column) => ( {(columns || []).map((column) => (
<ColumnLine <ColumnLine
onContextMenu={handleColumnContextMenu(column)}
key={column.columnName} key={column.columnName}
theme={theme} theme={theme}
draggable draggable
@@ -278,7 +389,8 @@ export default function DesignerTable({
} }
}} }}
/> />
<ColumnLabel {...column} forceIcon /> <ColumnLabel {...column} foreignKey={findForeignKeyForColumn(table, column)} forceIcon />
<ColumnDesignerIcons column={column} designerId={designerId} designer={designer} />
</ColumnLine> </ColumnLine>
))} ))}
</ColumnsWrapper> </ColumnsWrapper>

View File

@@ -3,5 +3,5 @@ import styled from 'styled-components';
import Designer from './Designer'; import Designer from './Designer';
export default function QueryDesigner({ value, conid, database, engine, onChange }) { export default function QueryDesigner({ value, conid, database, engine, onChange }) {
return <Designer value={value} onChange={onChange}></Designer>; return <Designer value={value} onChange={onChange} conid={conid} database={database}></Designer>;
} }

View File

@@ -34,7 +34,7 @@ export function findConnectingReference(
tables2: DesignerTableInfo[], tables2: DesignerTableInfo[],
additionalCondition: (ref: DesignerReferenceInfo) => boolean additionalCondition: (ref: DesignerReferenceInfo) => boolean
) { ) {
for (const ref of designer.references) { for (const ref of designer.references || []) {
if (additionalCondition(ref) && referenceIsConnecting(ref, tables1, tables2)) { if (additionalCondition(ref) && referenceIsConnecting(ref, tables1, tables2)) {
return ref; return ref;
} }
@@ -119,6 +119,7 @@ export function isConnectedByReference(
table2: { designerId: string }, table2: { designerId: string },
withoutRef: { designerId: string } withoutRef: { designerId: string }
) { ) {
if (!designer.references) return false;
const creator = new DesignerComponentCreator({ const creator = new DesignerComponentCreator({
...designer, ...designer,
references: withoutRef references: withoutRef

View File

@@ -85,6 +85,8 @@ const iconNames = {
'img reference': 'mdi mdi-link-box', 'img reference': 'mdi mdi-link-box',
'img link': 'mdi mdi-link', 'img link': 'mdi mdi-link',
'img filter': 'mdi mdi-filter',
'img group': 'mdi mdi-group',
}; };
export function FontIcon({ icon, className = '', ...other }) { export function FontIcon({ icon, className = '', ...other }) {

View File

@@ -102,7 +102,7 @@ export default function QueryDesignTab({
sesid, sesid,
sql: sqlPreview, sql: sqlPreview,
}); });
}, [busy]); }, [busy, conid, sessionId, database, sqlPreview]);
const handleCancel = () => { const handleCancel = () => {
axios.post('sessions/cancel', { axios.post('sessions/cancel', {
@@ -118,12 +118,15 @@ export default function QueryDesignTab({
setBusy(false); setBusy(false);
}; };
const handleKeyDown = React.useCallback((e) => { const handleKeyDown = React.useCallback(
if (e.keyCode == keycodes.f5) { (e) => {
e.preventDefault(); if (e.keyCode == keycodes.f5) {
handleExecute(); e.preventDefault();
} handleExecute();
}, []); }
},
[handleExecute]
);
React.useEffect(() => { React.useEffect(() => {
if (tabVisible) { if (tabVisible) {