mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-19 00:16:00 +00:00
group by in datagrid
This commit is contained in:
@@ -1,16 +1,6 @@
|
|||||||
<script lang="ts" context="module">
|
<script lang="ts" context="module">
|
||||||
export const extractKey = ({ schemaName, pureName }) => (schemaName ? `${schemaName}.${pureName}` : pureName);
|
export const extractKey = ({ schemaName, pureName }) => (schemaName ? `${schemaName}.${pureName}` : pureName);
|
||||||
export const createMatcher = ({ pureName }) => filter => filterName(filter, pureName);
|
export const createMatcher = ({ pureName }) => filter => filterName(filter, pureName);
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import _ from 'lodash';
|
|
||||||
import AppObjectCore from './AppObjectCore.svelte';
|
|
||||||
import { currentDatabase, openedConnections } from '../stores';
|
|
||||||
import openNewTab from '../utility/openNewTab';
|
|
||||||
import { filterName } from 'dbgate-datalib';
|
|
||||||
|
|
||||||
export let data;
|
|
||||||
|
|
||||||
const icons = {
|
const icons = {
|
||||||
tables: 'img table',
|
tables: 'img table',
|
||||||
@@ -19,20 +9,85 @@
|
|||||||
functions: 'img function',
|
functions: 'img function',
|
||||||
};
|
};
|
||||||
|
|
||||||
function handleClick() {
|
const defaultTabs = {
|
||||||
const { schemaName, pureName, conid, database, objectTypeField } = data;
|
tables: 'TableDataTab',
|
||||||
openNewTab({
|
views: 'ViewDataTab',
|
||||||
title: data.pureName,
|
};
|
||||||
icon: 'img table',
|
|
||||||
tabComponent: 'TableDataTab',
|
export async function openDatabaseObjectDetail(
|
||||||
|
tabComponent,
|
||||||
|
sqlTemplate,
|
||||||
|
{ schemaName, pureName, conid, database, objectTypeField },
|
||||||
|
forceNewTab,
|
||||||
|
initialData
|
||||||
|
) {
|
||||||
|
const connection = await getConnectionInfo({ conid });
|
||||||
|
const tooltip = `${connection.displayName || connection.server}\n${database}\n${fullDisplayName({
|
||||||
|
schemaName,
|
||||||
|
pureName,
|
||||||
|
})}`;
|
||||||
|
|
||||||
|
openNewTab(
|
||||||
|
{
|
||||||
|
title: sqlTemplate ? 'Query #' : pureName,
|
||||||
|
tooltip,
|
||||||
|
icon: sqlTemplate ? 'img sql-file' : icons[objectTypeField],
|
||||||
|
tabComponent: sqlTemplate ? 'QueryTab' : tabComponent,
|
||||||
props: {
|
props: {
|
||||||
schemaName,
|
schemaName,
|
||||||
pureName,
|
pureName,
|
||||||
conid,
|
conid,
|
||||||
database,
|
database,
|
||||||
objectTypeField,
|
objectTypeField,
|
||||||
|
initialArgs: sqlTemplate ? { sqlTemplate } : null,
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
|
initialData,
|
||||||
|
{ forceNewTab }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import _ from 'lodash';
|
||||||
|
import AppObjectCore from './AppObjectCore.svelte';
|
||||||
|
import { currentDatabase, openedConnections } from '../stores';
|
||||||
|
import openNewTab from '../utility/openNewTab';
|
||||||
|
import { filterName } from 'dbgate-datalib';
|
||||||
|
import { getConnectionInfo } from '../utility/metadataLoaders';
|
||||||
|
import fullDisplayName from '../utility/fullDisplayName';
|
||||||
|
|
||||||
|
export let data;
|
||||||
|
|
||||||
|
function handleClick() {
|
||||||
|
const { schemaName, pureName, conid, database, objectTypeField } = data;
|
||||||
|
|
||||||
|
openDatabaseObjectDetail(
|
||||||
|
defaultTabs[objectTypeField],
|
||||||
|
defaultTabs[objectTypeField] ? null : 'CREATE OBJECT',
|
||||||
|
{
|
||||||
|
schemaName,
|
||||||
|
pureName,
|
||||||
|
conid,
|
||||||
|
database,
|
||||||
|
objectTypeField,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
// openNewTab({
|
||||||
|
// title: data.pureName,
|
||||||
|
// icon: 'img table',
|
||||||
|
// tabComponent: 'TableDataTab',
|
||||||
|
// props: {
|
||||||
|
// schemaName,
|
||||||
|
// pureName,
|
||||||
|
// conid,
|
||||||
|
// database,
|
||||||
|
// objectTypeField,
|
||||||
|
// },
|
||||||
|
// });
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
import splitterDrag from '../utility/splitterDrag';
|
import splitterDrag from '../utility/splitterDrag';
|
||||||
|
|
||||||
import ColumnLabel from './ColumnLabel.svelte';
|
import ColumnLabel from './ColumnLabel.svelte';
|
||||||
|
import { isTypeDateTime } from 'dbgate-tools';
|
||||||
|
import { openDatabaseObjectDetail } from '../appobj/DatabaseObjectAppObject.svelte';
|
||||||
|
|
||||||
export let column;
|
export let column;
|
||||||
export let conid = undefined;
|
export let conid = undefined;
|
||||||
@@ -12,6 +14,41 @@
|
|||||||
export let grouping = undefined;
|
export let grouping = undefined;
|
||||||
export let order = undefined;
|
export let order = undefined;
|
||||||
export let setGrouping;
|
export let setGrouping;
|
||||||
|
|
||||||
|
const openReferencedTable = () => {
|
||||||
|
openDatabaseObjectDetail('TableDataTab', null, {
|
||||||
|
schemaName: column.foreignKey.refSchemaName,
|
||||||
|
pureName: column.foreignKey.refTableName,
|
||||||
|
conid,
|
||||||
|
database,
|
||||||
|
objectTypeField: 'tables',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function getMenu() {
|
||||||
|
return [
|
||||||
|
{ onClick: () => setSort('ASC'), text: 'Sort ascending' },
|
||||||
|
{ onClick: () => setSort('DESC'), text: 'Sort descending' },
|
||||||
|
|
||||||
|
column.foreignKey && [{ divider: true }, { onClick: openReferencedTable, text: column.foreignKey.refTableName }],
|
||||||
|
|
||||||
|
{ divider: true },
|
||||||
|
{ onClick: () => setGrouping('GROUP'), text: 'Group by' },
|
||||||
|
{ onClick: () => setGrouping('MAX'), text: 'MAX' },
|
||||||
|
{ onClick: () => setGrouping('MIN'), text: 'MIN' },
|
||||||
|
{ onClick: () => setGrouping('SUM'), text: 'SUM' },
|
||||||
|
{ onClick: () => setGrouping('AVG'), text: 'AVG' },
|
||||||
|
{ onClick: () => setGrouping('COUNT'), text: 'COUNT' },
|
||||||
|
{ onClick: () => setGrouping('COUNT DISTINCT'), text: 'COUNT DISTINCT' },
|
||||||
|
|
||||||
|
isTypeDateTime(column.dataType) && [
|
||||||
|
{ divider: true },
|
||||||
|
{ onClick: () => setGrouping('GROUP:YEAR'), text: 'Group by YEAR' },
|
||||||
|
{ onClick: () => setGrouping('GROUP:MONTH'), text: 'Group by MONTH' },
|
||||||
|
{ onClick: () => setGrouping('GROUP:DAY'), text: 'Group by DAY' },
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="header">
|
<div class="header">
|
||||||
@@ -33,7 +70,7 @@
|
|||||||
<FontIcon icon="img sort-desc" />
|
<FontIcon icon="img sort-desc" />
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
<DropDownButton />
|
<DropDownButton menu={getMenu} />
|
||||||
<div class="horizontal-split-handle resizeHandleControl" use:splitterDrag={'clientX'} on:resizeSplitter />
|
<div class="horizontal-split-handle resizeHandleControl" use:splitterDrag={'clientX'} on:resizeSplitter />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -51,6 +88,8 @@
|
|||||||
}
|
}
|
||||||
.icon {
|
.icon {
|
||||||
margin-left: 3px;
|
margin-left: 3px;
|
||||||
|
align-self: center;
|
||||||
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
/* .resizer {
|
/* .resizer {
|
||||||
background-color: var(--theme-border);
|
background-color: var(--theme-border);
|
||||||
|
|||||||
@@ -46,6 +46,7 @@
|
|||||||
import ColumnHeaderControl from './ColumnHeaderControl.svelte';
|
import ColumnHeaderControl from './ColumnHeaderControl.svelte';
|
||||||
import DataGridRow from './DataGridRow.svelte';
|
import DataGridRow from './DataGridRow.svelte';
|
||||||
import { getFilterType, getFilterValueExpression } from 'dbgate-filterparser';
|
import { getFilterType, getFilterValueExpression } from 'dbgate-filterparser';
|
||||||
|
import stableStringify from 'json-stable-stringify';
|
||||||
import { tick } from 'svelte';
|
import { tick } from 'svelte';
|
||||||
import {
|
import {
|
||||||
cellIsSelected,
|
cellIsSelected,
|
||||||
@@ -71,6 +72,7 @@
|
|||||||
export let isLoading = false;
|
export let isLoading = false;
|
||||||
export let allRowCount = undefined;
|
export let allRowCount = undefined;
|
||||||
export let onReferenceSourceChanged = undefined;
|
export let onReferenceSourceChanged = undefined;
|
||||||
|
export let onReferenceClick = undefined;
|
||||||
|
|
||||||
export let isLoadedAll;
|
export let isLoadedAll;
|
||||||
export let loadedTime;
|
export let loadedTime;
|
||||||
@@ -159,6 +161,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// $: console.log('DISPLAY.config', display.config);
|
||||||
|
$: {
|
||||||
|
if (display.groupColumns && display.baseTable) {
|
||||||
|
onReferenceClick({
|
||||||
|
referenceId: stableStringify(display && display.groupColumns),
|
||||||
|
schemaName: display.baseTable.schemaName,
|
||||||
|
pureName: display.baseTable.pureName,
|
||||||
|
columns: display.groupColumns.map(col => ({
|
||||||
|
baseName: col,
|
||||||
|
refName: col,
|
||||||
|
dataType: _.get(display.baseTable && display.baseTable.columns.find(x => x.columnName == col), 'dataType'),
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function scrollIntoView(cell) {
|
function scrollIntoView(cell) {
|
||||||
const [row, col] = cell;
|
const [row, col] = cell;
|
||||||
|
|
||||||
|
|||||||
@@ -98,6 +98,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleCloseReference = () => {
|
const handleCloseReference = () => {
|
||||||
|
display.clearGrouping();
|
||||||
setChildConfig(null, null);
|
setChildConfig(null, null);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -112,7 +113,13 @@
|
|||||||
gridCoreComponent={SqlDataGridCore}
|
gridCoreComponent={SqlDataGridCore}
|
||||||
{display}
|
{display}
|
||||||
onReferenceSourceChanged={reference ? handleReferenceSourceChanged : null}
|
onReferenceSourceChanged={reference ? handleReferenceSourceChanged : null}
|
||||||
onReferenceClick={reference => setChildConfig(createGridConfig(), reference)}
|
onReferenceClick={value => {
|
||||||
|
if (value && value.referenceId && reference && reference.referenceId == value.referenceId) {
|
||||||
|
// reference not changed
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setChildConfig(createGridConfig(), value);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div slot="2" class="reference-container">
|
<div slot="2" class="reference-container">
|
||||||
|
|||||||
@@ -1,20 +1,60 @@
|
|||||||
|
<script context="module">
|
||||||
|
function getElementOffset(element) {
|
||||||
|
var de = document.documentElement;
|
||||||
|
var box = element.getBoundingClientRect();
|
||||||
|
var top = box.top + window.pageYOffset - de.clientTop;
|
||||||
|
var left = box.left + window.pageXOffset - de.clientLeft;
|
||||||
|
return { top: top, left: left };
|
||||||
|
}
|
||||||
|
|
||||||
|
function fixPopupPlacement(element) {
|
||||||
|
const { width, height } = element.getBoundingClientRect();
|
||||||
|
let offset = getElementOffset(element);
|
||||||
|
|
||||||
|
let newLeft = null;
|
||||||
|
let newTop = null;
|
||||||
|
|
||||||
|
if (offset.left + width > window.innerWidth) {
|
||||||
|
newLeft = offset.left - width;
|
||||||
|
}
|
||||||
|
if (offset.top + height > window.innerHeight) {
|
||||||
|
newTop = offset.top - height;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newLeft != null) element.style.left = `${newLeft}px`;
|
||||||
|
if (newTop != null) element.style.top = `${newTop}px`;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import clickOutside from '../utility/clickOutside';
|
import clickOutside from '../utility/clickOutside';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
export let items;
|
export let items;
|
||||||
export let top;
|
export let top;
|
||||||
export let left;
|
export let left;
|
||||||
|
|
||||||
|
let element;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
function handleClick(item) {
|
function handleClick(item) {
|
||||||
dispatch('close');
|
dispatch('close');
|
||||||
if (item.onClick) item.onClick();
|
if (item.onClick) item.onClick();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
fixPopupPlacement(element);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ul style={`left: ${left}px; top: ${top}px`} use:clickOutside on:clickOutside={() => dispatch('close')}>
|
<ul
|
||||||
|
style={`left: ${left}px; top: ${top}px`}
|
||||||
|
use:clickOutside
|
||||||
|
on:clickOutside={() => dispatch('close')}
|
||||||
|
bind:this={element}
|
||||||
|
>
|
||||||
{#each items as item}
|
{#each items as item}
|
||||||
{#if item.divider}
|
{#if item.divider}
|
||||||
<li class="divider" />
|
<li class="divider" />
|
||||||
|
|||||||
4
packages/web/src/utility/fullDisplayName.js
Normal file
4
packages/web/src/utility/fullDisplayName.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export default function fullDisplayName({ schemaName, pureName }) {
|
||||||
|
if (schemaName) return `${schemaName}.${pureName}`;
|
||||||
|
return pureName;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user