SYNC: Merge pull request #80 from dbgate/feature/loading-fix
This commit is contained in:
@@ -95,10 +95,12 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
handle_response(conid, database, { msgid, ...response }) {
|
||||
const [resolve, reject, additionalData] = this.requests[msgid];
|
||||
resolve(response);
|
||||
if (additionalData?.auditLogger) {
|
||||
additionalData?.auditLogger(response);
|
||||
const [resolve, reject, additionalData] = this.requests[msgid] || [];
|
||||
if (resolve) {
|
||||
resolve(response);
|
||||
if (additionalData?.auditLogger) {
|
||||
additionalData?.auditLogger(response);
|
||||
}
|
||||
}
|
||||
delete this.requests[msgid];
|
||||
},
|
||||
@@ -239,7 +241,7 @@ module.exports = {
|
||||
sendRequest(conn, message, additionalData = {}) {
|
||||
const msgid = crypto.randomUUID();
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
this.requests[msgid] = [resolve, reject, additionalData];
|
||||
this.requests[msgid] = [resolve, reject, additionalData, conn.conid, conn.database];
|
||||
try {
|
||||
const serializedMessage = serializeJsTypesForJsonStringify({ msgid, ...message });
|
||||
conn.subprocess.send(serializedMessage);
|
||||
@@ -264,12 +266,12 @@ module.exports = {
|
||||
},
|
||||
|
||||
sqlSelect_meta: true,
|
||||
async sqlSelect({ conid, database, select, auditLogSessionGroup }, req) {
|
||||
async sqlSelect({ conid, database, select, commandTimeout, auditLogSessionGroup }, req) {
|
||||
await testConnectionPermission(conid, req);
|
||||
const opened = await this.ensureOpened(conid, database);
|
||||
const res = await this.sendRequest(
|
||||
opened,
|
||||
{ msgtype: 'sqlSelect', select },
|
||||
{ msgtype: 'sqlSelect', select, commandTimeout },
|
||||
{
|
||||
auditLogger:
|
||||
auditLogSessionGroup && select?.from?.name?.pureName
|
||||
@@ -344,9 +346,12 @@ module.exports = {
|
||||
},
|
||||
|
||||
collectionData_meta: true,
|
||||
async collectionData({ conid, database, options, auditLogSessionGroup }, req) {
|
||||
async collectionData({ conid, database, options, commandTimeout, auditLogSessionGroup }, req) {
|
||||
await testConnectionPermission(conid, req);
|
||||
const opened = await this.ensureOpened(conid, database);
|
||||
if (commandTimeout && options) {
|
||||
options.commandTimeout = commandTimeout;
|
||||
}
|
||||
const res = await this.sendRequest(
|
||||
opened,
|
||||
{ msgtype: 'collectionData', options },
|
||||
@@ -580,6 +585,24 @@ module.exports = {
|
||||
};
|
||||
},
|
||||
|
||||
pingDatabases_meta: true,
|
||||
async pingDatabases({ databases }, req) {
|
||||
if (!databases || !Array.isArray(databases)) return { status: 'ok' };
|
||||
for (const { conid, database } of databases) {
|
||||
if (!conid || !database) continue;
|
||||
const existing = this.opened.find(x => x.conid == conid && x.database == database);
|
||||
if (existing) {
|
||||
try {
|
||||
existing.subprocess.send({ msgtype: 'ping' });
|
||||
} catch (err) {
|
||||
logger.error(extractErrorLogData(err), 'DBGM-00000 Error pinging DB connection');
|
||||
this.close(conid, database);
|
||||
}
|
||||
}
|
||||
}
|
||||
return { status: 'ok' };
|
||||
},
|
||||
|
||||
refresh_meta: true,
|
||||
async refresh({ conid, database, keepOpen }, req) {
|
||||
await testConnectionPermission(conid, req);
|
||||
@@ -622,6 +645,15 @@ module.exports = {
|
||||
structure: existing.structure,
|
||||
};
|
||||
socket.emitChanged(`database-status-changed`, { conid, database });
|
||||
|
||||
// Reject all pending requests for this connection
|
||||
for (const [msgid, entry] of Object.entries(this.requests)) {
|
||||
const [resolve, reject, additionalData, reqConid, reqDatabase] = entry;
|
||||
if (reqConid === conid && reqDatabase === database) {
|
||||
reject('DBGM-00000 Database connection closed');
|
||||
delete this.requests[msgid];
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -234,12 +234,12 @@ async function handleRunOperation({ msgid, operation, useTransaction }, skipRead
|
||||
}
|
||||
}
|
||||
|
||||
async function handleQueryData({ msgid, sql, range }, skipReadonlyCheck = false) {
|
||||
async function handleQueryData({ msgid, sql, range, commandTimeout }, skipReadonlyCheck = false) {
|
||||
await waitConnected();
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
try {
|
||||
if (!skipReadonlyCheck) ensureExecuteCustomScript(driver);
|
||||
const res = await driver.query(dbhan, sql, { range });
|
||||
const res = await driver.query(dbhan, sql, { range, commandTimeout });
|
||||
process.send({ msgtype: 'response', msgid, ...serializeJsTypesForJsonStringify(res) });
|
||||
} catch (err) {
|
||||
process.send({
|
||||
@@ -250,11 +250,11 @@ async function handleQueryData({ msgid, sql, range }, skipReadonlyCheck = false)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSqlSelect({ msgid, select }) {
|
||||
async function handleSqlSelect({ msgid, select, commandTimeout }) {
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
const dmp = driver.createDumper();
|
||||
dumpSqlSelect(dmp, select);
|
||||
return handleQueryData({ msgid, sql: dmp.s, range: select.range }, true);
|
||||
return handleQueryData({ msgid, sql: dmp.s, range: select.range, commandTimeout }, true);
|
||||
}
|
||||
|
||||
async function handleDriverDataCore(msgid, callMethod, { logName }) {
|
||||
|
||||
Vendored
+1
@@ -59,6 +59,7 @@ export interface QueryOptions {
|
||||
importSqlDump?: boolean;
|
||||
range?: { offset: number; limit: number };
|
||||
readonly?: boolean;
|
||||
commandTimeout?: number;
|
||||
}
|
||||
|
||||
export interface WriteTableOptions {
|
||||
|
||||
@@ -1,286 +1,307 @@
|
||||
<script context="module" lang="ts">
|
||||
import { __t } from '../translations';
|
||||
const getCurrentEditor = () => getActiveComponent('CollectionDataGridCore');
|
||||
|
||||
registerCommand({
|
||||
id: 'collectionDataGrid.openQuery',
|
||||
category: __t('command.dataGrid', { defaultMessage: 'Data grid' }),
|
||||
name: __t('command.dataGrid.openQuery', { defaultMessage: 'Open query' }),
|
||||
testEnabled: () => getCurrentEditor() != null,
|
||||
onClick: () => getCurrentEditor().openQuery(),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'collectionDataGrid.export',
|
||||
category: __t('command.dataGrid', { defaultMessage: 'Data grid' }),
|
||||
name: __t('command.dataGrid.export', { defaultMessage: 'Export' }),
|
||||
keyText: 'CtrlOrCommand+E',
|
||||
icon: 'icon export',
|
||||
testEnabled: () => getCurrentEditor() != null,
|
||||
onClick: () => getCurrentEditor().exportGrid(),
|
||||
});
|
||||
|
||||
function buildConditionForGrid(props) {
|
||||
const filters = props?.display?.config?.filters;
|
||||
const filterBehaviour =
|
||||
props?.display?.driver?.getFilterBehaviour(null, standardFilterBehaviours) ?? mongoFilterBehaviour;
|
||||
|
||||
// console.log('USED FILTER BEHAVIOUR', filterBehaviour);
|
||||
|
||||
const conditions = [];
|
||||
for (const uniqueName in filters || {}) {
|
||||
if (!filters[uniqueName]) continue;
|
||||
try {
|
||||
const ast = parseFilter(filters[uniqueName], filterBehaviour);
|
||||
// console.log('AST', ast);
|
||||
const cond = _.cloneDeepWith(ast, expr => {
|
||||
if (expr.exprType == 'placeholder') {
|
||||
return {
|
||||
exprType: 'column',
|
||||
columnName: uniqueName,
|
||||
};
|
||||
}
|
||||
|
||||
// if (expr.__placeholder__) {
|
||||
// return {
|
||||
// [uniqueName]: expr.__placeholder__,
|
||||
// };
|
||||
// }
|
||||
});
|
||||
conditions.push(cond);
|
||||
} catch (err) {
|
||||
// error in filter
|
||||
}
|
||||
}
|
||||
|
||||
return conditions.length > 0
|
||||
? {
|
||||
conditionType: 'and',
|
||||
conditions,
|
||||
}
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function buildSortForGrid(props) {
|
||||
const sort = props?.display?.config?.sort;
|
||||
|
||||
if (sort?.length > 0) {
|
||||
return sort.map(col => ({
|
||||
columnName: col.uniqueName,
|
||||
direction: col.order,
|
||||
}));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function loadCollectionDataPage(props, offset, limit) {
|
||||
const { conid, database } = props;
|
||||
|
||||
const response = await apiCall('database-connections/collection-data', {
|
||||
conid,
|
||||
database,
|
||||
options: {
|
||||
pureName: props.pureName,
|
||||
limit,
|
||||
skip: offset,
|
||||
condition: buildConditionForGrid(props),
|
||||
sort: buildSortForGrid(props),
|
||||
},
|
||||
auditLogSessionGroup: 'data-grid',
|
||||
});
|
||||
|
||||
if (response.errorMessage) return response;
|
||||
return response.rows;
|
||||
}
|
||||
|
||||
function dataPageAvailable(props) {
|
||||
return true;
|
||||
// const { display } = props;
|
||||
// const sql = display.getPageQuery(0, 1);
|
||||
// return !!sql;
|
||||
}
|
||||
|
||||
async function loadRowCount(props) {
|
||||
const { conid, database } = props;
|
||||
|
||||
const response = await apiCall('database-connections/collection-data', {
|
||||
conid,
|
||||
database,
|
||||
options: {
|
||||
pureName: props.pureName,
|
||||
countDocuments: true,
|
||||
condition: buildConditionForGrid(props),
|
||||
},
|
||||
});
|
||||
|
||||
return response.count;
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { parseFilter } from 'dbgate-filterparser';
|
||||
import _ from 'lodash';
|
||||
import { registerQuickExportHandler } from '../buttons/ToolStripExportButton.svelte';
|
||||
import registerCommand from '../commands/registerCommand';
|
||||
import {
|
||||
extractShellConnection,
|
||||
extractShellConnectionHostable,
|
||||
extractShellHostConnection,
|
||||
} from '../impexp/createImpExpScript';
|
||||
import { apiCall } from '../utility/api';
|
||||
|
||||
import { registerMenu } from '../utility/contextMenu';
|
||||
import createActivator, { getActiveComponent } from '../utility/createActivator';
|
||||
import createQuickExportMenu from '../utility/createQuickExportMenu';
|
||||
import { exportQuickExportFile } from '../utility/exportFileTools';
|
||||
import { getConnectionInfo } from '../utility/metadataLoaders';
|
||||
import openNewTab from '../utility/openNewTab';
|
||||
import ChangeSetGrider from './ChangeSetGrider';
|
||||
|
||||
import LoadingDataGridCore from './LoadingDataGridCore.svelte';
|
||||
import { mongoFilterBehaviour, standardFilterBehaviours } from 'dbgate-tools';
|
||||
import { openImportExportTab } from '../utility/importExportTools';
|
||||
export let conid;
|
||||
export let display;
|
||||
export let database;
|
||||
export let schemaName;
|
||||
export let pureName;
|
||||
export let config;
|
||||
export let changeSetState;
|
||||
export let dispatchChangeSet;
|
||||
|
||||
export let macroPreview;
|
||||
export let macroValues;
|
||||
export let setLoadedRows = null;
|
||||
export let onPublishedCellsChanged;
|
||||
|
||||
// export let onChangeGrider = undefined;
|
||||
|
||||
let loadedRows = [];
|
||||
let publishedCells = [];
|
||||
|
||||
export const activator = createActivator('CollectionDataGridCore', false);
|
||||
|
||||
// $: console.log('loadedRows BIND', loadedRows);
|
||||
$: grider = new ChangeSetGrider(
|
||||
loadedRows,
|
||||
changeSetState,
|
||||
dispatchChangeSet,
|
||||
display,
|
||||
macroPreview,
|
||||
macroValues,
|
||||
publishedCells
|
||||
);
|
||||
// $: console.log('GRIDER', grider);
|
||||
// $: if (onChangeGrider) onChangeGrider(grider);
|
||||
|
||||
function getExportQuery() {
|
||||
return display?.driver?.getCollectionExportQueryScript?.(
|
||||
pureName,
|
||||
buildConditionForGrid($$props),
|
||||
buildSortForGrid($$props)
|
||||
);
|
||||
// return `db.collection('${pureName}')
|
||||
// .find(${JSON.stringify(buildConditionForGrid($$props) || {})})
|
||||
// .sort(${JSON.stringify(buildMongoSort($$props) || {})})`;
|
||||
}
|
||||
|
||||
function getExportQueryJson() {
|
||||
return display?.driver?.getCollectionExportQueryJson?.(
|
||||
pureName,
|
||||
buildConditionForGrid($$props),
|
||||
buildSortForGrid($$props)
|
||||
);
|
||||
// return {
|
||||
// collection: pureName,
|
||||
// condition: buildConditionForGrid($$props) || {},
|
||||
// sort: buildMongoSort($$props) || {},
|
||||
// };
|
||||
}
|
||||
|
||||
export async function exportGrid() {
|
||||
const coninfo = await getConnectionInfo({ conid });
|
||||
const initialValues: any = {};
|
||||
initialValues.sourceStorageType = 'query';
|
||||
initialValues.sourceConnectionId = conid;
|
||||
initialValues.sourceDatabaseName = database;
|
||||
initialValues.sourceQuery = coninfo.isReadOnly
|
||||
? JSON.stringify(getExportQueryJson(), undefined, 2)
|
||||
: getExportQuery();
|
||||
initialValues.sourceQueryType = coninfo.isReadOnly ? 'json' : 'native';
|
||||
initialValues.sourceList = [pureName];
|
||||
initialValues[`columns_${pureName}`] = display.getExportColumnMap();
|
||||
openImportExportTab(initialValues);
|
||||
// showModal(ImportExportModal, { initialValues });
|
||||
}
|
||||
|
||||
export function openQuery() {
|
||||
openNewTab(
|
||||
{
|
||||
title: 'Query #',
|
||||
icon: 'img sql-file',
|
||||
tabComponent: 'QueryTab',
|
||||
focused: true,
|
||||
props: {
|
||||
conid,
|
||||
database,
|
||||
},
|
||||
},
|
||||
{
|
||||
editor: getExportQuery(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const quickExportHandler = fmt => async () => {
|
||||
const coninfo = await getConnectionInfo({ conid });
|
||||
exportQuickExportFile(
|
||||
pureName || 'Data',
|
||||
{
|
||||
functionName: 'queryReader',
|
||||
props: {
|
||||
...extractShellConnectionHostable(coninfo, database),
|
||||
queryType: coninfo.isReadOnly ? 'json' : 'native',
|
||||
query: coninfo.isReadOnly ? getExportQueryJson() : getExportQuery(),
|
||||
},
|
||||
hostConnection: extractShellHostConnection(coninfo, database),
|
||||
},
|
||||
fmt,
|
||||
display.getExportColumnMap()
|
||||
);
|
||||
};
|
||||
|
||||
registerQuickExportHandler(quickExportHandler);
|
||||
|
||||
registerMenu({ command: 'collectionDataGrid.openQuery', tag: 'export' }, () =>
|
||||
createQuickExportMenu(
|
||||
quickExportHandler,
|
||||
{
|
||||
command: 'collectionDataGrid.export',
|
||||
},
|
||||
{ tag: 'export' }
|
||||
)
|
||||
);
|
||||
|
||||
function handleSetLoadedRows(rows) {
|
||||
loadedRows = rows;
|
||||
if (setLoadedRows) setLoadedRows(rows);
|
||||
}
|
||||
</script>
|
||||
|
||||
<LoadingDataGridCore
|
||||
{...$$props}
|
||||
loadDataPage={loadCollectionDataPage}
|
||||
{dataPageAvailable}
|
||||
{loadRowCount}
|
||||
setLoadedRows={handleSetLoadedRows}
|
||||
onPublishedCellsChanged={value => {
|
||||
publishedCells = value;
|
||||
if (onPublishedCellsChanged) {
|
||||
onPublishedCellsChanged(value);
|
||||
}
|
||||
}}
|
||||
frameSelection={!!macroPreview}
|
||||
onOpenQuery={openQuery}
|
||||
{grider}
|
||||
const getCurrentEditor = () => getActiveComponent('CollectionDataGridCore');
|
||||
|
||||
registerCommand({
|
||||
id: 'collectionDataGrid.openQuery',
|
||||
category: __t('command.dataGrid', { defaultMessage: 'Data grid' }),
|
||||
name: __t('command.dataGrid.openQuery', { defaultMessage: 'Open query' }),
|
||||
testEnabled: () => getCurrentEditor() != null,
|
||||
onClick: () => getCurrentEditor().openQuery(),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'collectionDataGrid.export',
|
||||
category: __t('command.dataGrid', { defaultMessage: 'Data grid' }),
|
||||
name: __t('command.dataGrid.export', { defaultMessage: 'Export' }),
|
||||
keyText: 'CtrlOrCommand+E',
|
||||
icon: 'icon export',
|
||||
testEnabled: () => getCurrentEditor() != null,
|
||||
onClick: () => getCurrentEditor().exportGrid(),
|
||||
});
|
||||
|
||||
function buildConditionForGrid(props) {
|
||||
const filters = props?.display?.config?.filters;
|
||||
const filterBehaviour =
|
||||
props?.display?.driver?.getFilterBehaviour(null, standardFilterBehaviours) ?? mongoFilterBehaviour;
|
||||
|
||||
// console.log('USED FILTER BEHAVIOUR', filterBehaviour);
|
||||
|
||||
const conditions = [];
|
||||
for (const uniqueName in filters || {}) {
|
||||
if (!filters[uniqueName]) continue;
|
||||
try {
|
||||
const ast = parseFilter(filters[uniqueName], filterBehaviour);
|
||||
// console.log('AST', ast);
|
||||
const cond = _.cloneDeepWith(ast, expr => {
|
||||
if (expr.exprType == 'placeholder') {
|
||||
return {
|
||||
exprType: 'column',
|
||||
columnName: uniqueName,
|
||||
};
|
||||
}
|
||||
|
||||
// if (expr.__placeholder__) {
|
||||
// return {
|
||||
// [uniqueName]: expr.__placeholder__,
|
||||
// };
|
||||
// }
|
||||
});
|
||||
conditions.push(cond);
|
||||
} catch (err) {
|
||||
// error in filter
|
||||
}
|
||||
}
|
||||
|
||||
return conditions.length > 0
|
||||
? {
|
||||
conditionType: 'and',
|
||||
conditions,
|
||||
}
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function buildSortForGrid(props) {
|
||||
const sort = props?.display?.config?.sort;
|
||||
|
||||
if (sort?.length > 0) {
|
||||
return sort.map(col => ({
|
||||
columnName: col.uniqueName,
|
||||
direction: col.order,
|
||||
}));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function loadCollectionDataPage(props, offset, limit) {
|
||||
const { conid, database } = props;
|
||||
|
||||
const response = await apiCall('database-connections/collection-data', {
|
||||
conid,
|
||||
database,
|
||||
options: {
|
||||
pureName: props.pureName,
|
||||
limit,
|
||||
skip: offset,
|
||||
condition: buildConditionForGrid(props),
|
||||
sort: buildSortForGrid(props),
|
||||
},
|
||||
auditLogSessionGroup: 'data-grid',
|
||||
});
|
||||
|
||||
if (response.errorMessage) return response;
|
||||
return response.rows;
|
||||
}
|
||||
|
||||
function dataPageAvailable(props) {
|
||||
return true;
|
||||
// const { display } = props;
|
||||
// const sql = display.getPageQuery(0, 1);
|
||||
// return !!sql;
|
||||
}
|
||||
|
||||
async function loadRowCount(props) {
|
||||
const { conid, database } = props;
|
||||
|
||||
const timeoutPromise = new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Row count query timed out')), 3000)
|
||||
);
|
||||
|
||||
try {
|
||||
const response = await Promise.race([
|
||||
apiCall('database-connections/collection-data', {
|
||||
conid,
|
||||
database,
|
||||
commandTimeout: 3000,
|
||||
options: {
|
||||
pureName: props.pureName,
|
||||
countDocuments: true,
|
||||
condition: buildConditionForGrid(props),
|
||||
},
|
||||
}),
|
||||
timeoutPromise,
|
||||
]);
|
||||
|
||||
if (response && typeof response === 'object' && (response as any).errorMessage) {
|
||||
return { errorMessage: (response as any).errorMessage };
|
||||
}
|
||||
|
||||
if (response && typeof response === 'object' && typeof (response as any).count === 'number') {
|
||||
return (response as any).count;
|
||||
}
|
||||
|
||||
return { errorMessage: 'Error loading row count' };
|
||||
} catch (err) {
|
||||
return { errorMessage: err.message || 'Error loading row count' };
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { parseFilter } from 'dbgate-filterparser';
|
||||
import _ from 'lodash';
|
||||
import { registerQuickExportHandler } from '../buttons/ToolStripExportButton.svelte';
|
||||
import registerCommand from '../commands/registerCommand';
|
||||
import {
|
||||
extractShellConnection,
|
||||
extractShellConnectionHostable,
|
||||
extractShellHostConnection,
|
||||
} from '../impexp/createImpExpScript';
|
||||
import { apiCall } from '../utility/api';
|
||||
|
||||
import { registerMenu } from '../utility/contextMenu';
|
||||
import createActivator, { getActiveComponent } from '../utility/createActivator';
|
||||
import createQuickExportMenu from '../utility/createQuickExportMenu';
|
||||
import { exportQuickExportFile } from '../utility/exportFileTools';
|
||||
import { getConnectionInfo } from '../utility/metadataLoaders';
|
||||
import openNewTab from '../utility/openNewTab';
|
||||
import ChangeSetGrider from './ChangeSetGrider';
|
||||
|
||||
import LoadingDataGridCore from './LoadingDataGridCore.svelte';
|
||||
import { mongoFilterBehaviour, standardFilterBehaviours } from 'dbgate-tools';
|
||||
import { openImportExportTab } from '../utility/importExportTools';
|
||||
export let conid;
|
||||
export let display;
|
||||
export let database;
|
||||
export let schemaName;
|
||||
export let pureName;
|
||||
export let config;
|
||||
export let changeSetState;
|
||||
export let dispatchChangeSet;
|
||||
|
||||
export let macroPreview;
|
||||
export let macroValues;
|
||||
export let setLoadedRows = null;
|
||||
export let onPublishedCellsChanged;
|
||||
|
||||
// export let onChangeGrider = undefined;
|
||||
|
||||
let loadedRows = [];
|
||||
let publishedCells = [];
|
||||
|
||||
export const activator = createActivator('CollectionDataGridCore', false);
|
||||
|
||||
// $: console.log('loadedRows BIND', loadedRows);
|
||||
$: grider = new ChangeSetGrider(
|
||||
loadedRows,
|
||||
changeSetState,
|
||||
dispatchChangeSet,
|
||||
display,
|
||||
macroPreview,
|
||||
macroValues,
|
||||
publishedCells
|
||||
);
|
||||
// $: console.log('GRIDER', grider);
|
||||
// $: if (onChangeGrider) onChangeGrider(grider);
|
||||
|
||||
function getExportQuery() {
|
||||
return display?.driver?.getCollectionExportQueryScript?.(
|
||||
pureName,
|
||||
buildConditionForGrid($$props),
|
||||
buildSortForGrid($$props)
|
||||
);
|
||||
// return `db.collection('${pureName}')
|
||||
// .find(${JSON.stringify(buildConditionForGrid($$props) || {})})
|
||||
// .sort(${JSON.stringify(buildMongoSort($$props) || {})})`;
|
||||
}
|
||||
|
||||
function getExportQueryJson() {
|
||||
return display?.driver?.getCollectionExportQueryJson?.(
|
||||
pureName,
|
||||
buildConditionForGrid($$props),
|
||||
buildSortForGrid($$props)
|
||||
);
|
||||
// return {
|
||||
// collection: pureName,
|
||||
// condition: buildConditionForGrid($$props) || {},
|
||||
// sort: buildMongoSort($$props) || {},
|
||||
// };
|
||||
}
|
||||
|
||||
export async function exportGrid() {
|
||||
const coninfo = await getConnectionInfo({ conid });
|
||||
const initialValues: any = {};
|
||||
initialValues.sourceStorageType = 'query';
|
||||
initialValues.sourceConnectionId = conid;
|
||||
initialValues.sourceDatabaseName = database;
|
||||
initialValues.sourceQuery = coninfo.isReadOnly
|
||||
? JSON.stringify(getExportQueryJson(), undefined, 2)
|
||||
: getExportQuery();
|
||||
initialValues.sourceQueryType = coninfo.isReadOnly ? 'json' : 'native';
|
||||
initialValues.sourceList = [pureName];
|
||||
initialValues[`columns_${pureName}`] = display.getExportColumnMap();
|
||||
openImportExportTab(initialValues);
|
||||
// showModal(ImportExportModal, { initialValues });
|
||||
}
|
||||
|
||||
export function openQuery() {
|
||||
openNewTab(
|
||||
{
|
||||
title: 'Query #',
|
||||
icon: 'img sql-file',
|
||||
tabComponent: 'QueryTab',
|
||||
focused: true,
|
||||
props: {
|
||||
conid,
|
||||
database,
|
||||
},
|
||||
},
|
||||
{
|
||||
editor: getExportQuery(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const quickExportHandler = fmt => async () => {
|
||||
const coninfo = await getConnectionInfo({ conid });
|
||||
exportQuickExportFile(
|
||||
pureName || 'Data',
|
||||
{
|
||||
functionName: 'queryReader',
|
||||
props: {
|
||||
...extractShellConnectionHostable(coninfo, database),
|
||||
queryType: coninfo.isReadOnly ? 'json' : 'native',
|
||||
query: coninfo.isReadOnly ? getExportQueryJson() : getExportQuery(),
|
||||
},
|
||||
hostConnection: extractShellHostConnection(coninfo, database),
|
||||
},
|
||||
fmt,
|
||||
display.getExportColumnMap()
|
||||
);
|
||||
};
|
||||
|
||||
registerQuickExportHandler(quickExportHandler);
|
||||
|
||||
registerMenu({ command: 'collectionDataGrid.openQuery', tag: 'export' }, () =>
|
||||
createQuickExportMenu(
|
||||
quickExportHandler,
|
||||
{
|
||||
command: 'collectionDataGrid.export',
|
||||
},
|
||||
{ tag: 'export' }
|
||||
)
|
||||
);
|
||||
|
||||
function handleSetLoadedRows(rows) {
|
||||
loadedRows = rows;
|
||||
if (setLoadedRows) setLoadedRows(rows);
|
||||
}
|
||||
</script>
|
||||
|
||||
<LoadingDataGridCore
|
||||
{...$$props}
|
||||
loadDataPage={loadCollectionDataPage}
|
||||
{dataPageAvailable}
|
||||
{loadRowCount}
|
||||
setLoadedRows={handleSetLoadedRows}
|
||||
onPublishedCellsChanged={value => {
|
||||
publishedCells = value;
|
||||
if (onPublishedCellsChanged) {
|
||||
onPublishedCellsChanged(value);
|
||||
}
|
||||
}}
|
||||
frameSelection={!!macroPreview}
|
||||
onOpenQuery={openQuery}
|
||||
{grider}
|
||||
/>
|
||||
|
||||
@@ -461,6 +461,8 @@
|
||||
export let frameSelection = undefined;
|
||||
export let isLoading = false;
|
||||
export let allRowCount = undefined;
|
||||
export let allRowCountError = undefined;
|
||||
export let onReloadRowCount = undefined;
|
||||
export let onReferenceSourceChanged = undefined;
|
||||
export let onPublishedCellsChanged = undefined;
|
||||
export let onReferenceClick = undefined;
|
||||
@@ -2400,6 +2402,15 @@
|
||||
<div class="row-count-label">
|
||||
{_t('datagrid.rows', { defaultMessage: 'Rows' })}: {allRowCount.toLocaleString()}
|
||||
</div>
|
||||
{:else if allRowCountError && multipleGridsOnTab}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="row-count-label row-count-error"
|
||||
title={allRowCountError}
|
||||
on:click={onReloadRowCount}
|
||||
>
|
||||
{_t('datagrid.rows', { defaultMessage: 'Rows' })}: {_t('datagrid.rowCountMany', { defaultMessage: 'Many' })}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if isLoading}
|
||||
@@ -2408,6 +2419,13 @@
|
||||
|
||||
{#if !tabControlHiddenTab && !multipleGridsOnTab && allRowCount != null}
|
||||
<StatusBarTabItem text={`${_t('datagrid.rows', { defaultMessage: 'Rows' })}: ${allRowCount.toLocaleString()}`} />
|
||||
{:else if !tabControlHiddenTab && !multipleGridsOnTab && allRowCountError}
|
||||
<StatusBarTabItem
|
||||
text={`${_t('datagrid.rows', { defaultMessage: 'Rows' })}: ${_t('datagrid.rowCountMany', { defaultMessage: 'Many' })}`}
|
||||
title={allRowCountError}
|
||||
clickable
|
||||
onClick={onReloadRowCount}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -2472,6 +2490,15 @@
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.row-count-error {
|
||||
cursor: pointer;
|
||||
color: var(--theme-font-3);
|
||||
}
|
||||
|
||||
.row-count-error:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.selection-menu {
|
||||
position: absolute;
|
||||
background-color: var(--theme-datagrid-corner-label-background);
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
let isLoadedAll = false;
|
||||
let loadedTime = new Date().getTime();
|
||||
let allRowCount = null;
|
||||
let allRowCountError = null;
|
||||
let errorMessage = null;
|
||||
let domGrid;
|
||||
|
||||
@@ -37,8 +38,14 @@
|
||||
}
|
||||
|
||||
const handleLoadRowCount = async () => {
|
||||
const rowCount = await loadRowCount($$props);
|
||||
allRowCount = rowCount;
|
||||
const result = await loadRowCount($$props);
|
||||
if (result != null && typeof result === 'object' && result.errorMessage) {
|
||||
allRowCount = null;
|
||||
allRowCountError = result.errorMessage;
|
||||
} else {
|
||||
allRowCount = result;
|
||||
allRowCountError = null;
|
||||
}
|
||||
};
|
||||
|
||||
async function loadNextData() {
|
||||
@@ -103,6 +110,7 @@
|
||||
|
||||
function reload() {
|
||||
allRowCount = null;
|
||||
allRowCountError = null;
|
||||
isLoading = false;
|
||||
loadedRows = [];
|
||||
isLoadedAll = false;
|
||||
@@ -132,6 +140,8 @@
|
||||
{errorMessage}
|
||||
{isLoading}
|
||||
allRowCount={rowCountLoaded || allRowCount}
|
||||
allRowCountError={allRowCountError}
|
||||
onReloadRowCount={handleLoadRowCount}
|
||||
{isLoadedAll}
|
||||
{loadedTime}
|
||||
{grider}
|
||||
|
||||
@@ -2,238 +2,253 @@
|
||||
import { getActiveComponent } from '../utility/createActivator';
|
||||
import registerCommand from '../commands/registerCommand';
|
||||
import hasPermission from '../utility/hasPermission';
|
||||
import { __t, _t } from '../translations'
|
||||
const getCurrentEditor = () => getActiveComponent('SqlDataGridCore');
|
||||
|
||||
registerCommand({
|
||||
id: 'sqlDataGrid.openQuery',
|
||||
category: __t('command.datagrid', { defaultMessage: 'Data grid' }),
|
||||
name: __t('command.openQuery', { defaultMessage : 'Open query' }),
|
||||
testEnabled: () => getCurrentEditor() != null && hasPermission('dbops/query'),
|
||||
onClick: () => getCurrentEditor().openQuery(),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'sqlDataGrid.export',
|
||||
category: __t('command.datagrid', { defaultMessage: 'Data grid' }),
|
||||
name: __t('common.export', { defaultMessage : 'Export' }),
|
||||
icon: 'icon export',
|
||||
keyText: 'CtrlOrCommand+E',
|
||||
testEnabled: () => getCurrentEditor() != null && hasPermission('dbops/export'),
|
||||
onClick: () => getCurrentEditor().exportGrid(),
|
||||
});
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import _ from 'lodash';
|
||||
import { registerQuickExportHandler } from '../buttons/ToolStripExportButton.svelte';
import {
|
||||
extractShellConnection,
|
||||
extractShellConnectionHostable,
|
||||
extractShellHostConnection,
|
||||
} from '../impexp/createImpExpScript';
|
||||
import { apiCall } from '../utility/api';
|
||||
|
||||
import { registerMenu } from '../utility/contextMenu';
|
||||
import createActivator from '../utility/createActivator';
|
||||
import createQuickExportMenu from '../utility/createQuickExportMenu';
|
||||
import { exportQuickExportFile } from '../utility/exportFileTools';
|
||||
import { getConnectionInfo } from '../utility/metadataLoaders';
|
||||
import openNewTab from '../utility/openNewTab';
|
||||
import ChangeSetGrider from './ChangeSetGrider';
|
||||
|
||||
import LoadingDataGridCore from './LoadingDataGridCore.svelte';
import { openImportExportTab } from '../utility/importExportTools';
|
||||
import { getIntSettingsValue } from '../settings/settingsTools';
|
||||
import OverlayDiffGrider from './OverlayDiffGrider';
|
||||
|
||||
export let conid;
|
||||
export let display;
|
||||
export let database;
|
||||
export let schemaName;
|
||||
export let pureName;
|
||||
export let config;
|
||||
export let changeSetState;
|
||||
export let dispatchChangeSet;
|
||||
export let overlayDefinition = null;
|
||||
|
||||
export let macroPreview;
|
||||
export let macroValues;
|
||||
export let onPublishedCellsChanged;
|
||||
|
||||
let publishedCells = [];
|
||||
|
||||
// export let onChangeGrider = undefined;
|
||||
|
||||
export const activator = createActivator('SqlDataGridCore', false);
|
||||
|
||||
let loadedRows = [];
|
||||
|
||||
let grider;
|
||||
|
||||
// $: console.log('loadedRows BIND', loadedRows);
|
||||
|
||||
$: {
|
||||
if (!overlayDefinition && macroPreview) {
|
||||
grider = new ChangeSetGrider(
|
||||
loadedRows,
|
||||
changeSetState,
|
||||
dispatchChangeSet,
|
||||
display,
|
||||
macroPreview,
|
||||
macroValues,
|
||||
publishedCells
|
||||
);
|
||||
}
|
||||
}
|
||||
// prevent recreate grider, if no macro is selected, so there is no need to selectedcells in macro
|
||||
$: {
|
||||
if (!overlayDefinition && !macroPreview) {
|
||||
grider = new ChangeSetGrider(loadedRows, changeSetState, dispatchChangeSet, display);
|
||||
}
|
||||
}
|
||||
// $: console.log('GRIDER', grider);
|
||||
// $: if (onChangeGrider) onChangeGrider(grider);
|
||||
|
||||
$: {
|
||||
if (overlayDefinition) {
|
||||
grider = new OverlayDiffGrider(
|
||||
loadedRows,
|
||||
display,
|
||||
overlayDefinition.matchColumns,
|
||||
overlayDefinition.overlayData,
|
||||
overlayDefinition.matchedDbKeys
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function exportGrid() {
|
||||
const coninfo = await getConnectionInfo({ conid });
|
||||
|
||||
const initialValues: any = {};
|
||||
initialValues.sourceStorageType = 'query';
|
||||
initialValues.sourceConnectionId = conid;
|
||||
initialValues.sourceDatabaseName = database;
|
||||
initialValues.sourceQuery = coninfo.isReadOnly
|
||||
? JSON.stringify(display.getExportQueryJson(), undefined, 2)
|
||||
: display.getExportQuery();
|
||||
initialValues.sourceQueryType = coninfo.isReadOnly ? 'json' : 'native';
|
||||
initialValues.sourceList = display.baseTableOrSimilar ? [display.baseTableOrSimilar.pureName] : [];
|
||||
initialValues[`columns_${pureName}`] = display.getExportColumnMap();
|
||||
openImportExportTab(initialValues);
|
||||
// showModal(ImportExportModal, { initialValues });
|
||||
}
|
||||
|
||||
export function openQuery(sql?) {
|
||||
openNewTab(
|
||||
{
|
||||
title: _t('common.queryNumber', { defaultMessage: 'Query #' }),
|
||||
icon: 'img sql-file',
|
||||
tabComponent: 'QueryTab',
|
||||
focused: true,
|
||||
props: {
|
||||
schemaName: display.baseTableOrSimilar?.schemaName,
|
||||
pureName: display.baseTableOrSimilar?.pureName,
|
||||
conid,
|
||||
database,
|
||||
},
|
||||
},
|
||||
{
|
||||
editor: sql ?? display.getExportQuery(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function openQueryOnError() {
|
||||
openQuery(display.getPageQueryText(0, getIntSettingsValue('dataGrid.pageSize', 100, 5, 50000)));
|
||||
}
|
||||
|
||||
const quickExportHandler = fmt => async () => {
|
||||
const coninfo = await getConnectionInfo({ conid });
|
||||
exportQuickExportFile(
|
||||
pureName || 'Data',
|
||||
{
|
||||
functionName: 'queryReader',
|
||||
props: {
|
||||
...extractShellConnectionHostable(coninfo, database),
|
||||
queryType: coninfo.isReadOnly ? 'json' : 'native',
|
||||
query: coninfo.isReadOnly ? display.getExportQueryJson() : display.getExportQuery(),
|
||||
},
|
||||
hostConnection: extractShellHostConnection(coninfo, database),
|
||||
},
|
||||
fmt,
|
||||
display.getExportColumnMap()
|
||||
);
|
||||
};
|
||||
registerQuickExportHandler(quickExportHandler);
|
||||
|
||||
registerMenu(
|
||||
{ command: 'sqlDataGrid.openActiveChart', tag: 'chart' },
|
||||
{ command: 'sqlDataGrid.openQuery', tag: 'export' },
|
||||
() =>
|
||||
createQuickExportMenu(
|
||||
quickExportHandler,
|
||||
{
|
||||
command: 'sqlDataGrid.export',
|
||||
},
|
||||
{ tag: 'export' }
|
||||
)
|
||||
);
|
||||
|
||||
function handleSetLoadedRows(rows) {
|
||||
loadedRows = rows;
|
||||
}
|
||||
|
||||
async function loadDataPage(props, offset, limit) {
|
||||
const { display, conid, database } = props;
|
||||
|
||||
const select = display.getPageQuery(offset, limit);
|
||||
|
||||
const response = await apiCall('database-connections/sql-select', {
|
||||
conid,
|
||||
database,
|
||||
select,
|
||||
auditLogSessionGroup: 'data-grid',
|
||||
});
|
||||
|
||||
if (response.errorMessage) return response;
|
||||
return response.rows;
|
||||
}
|
||||
|
||||
function dataPageAvailable(props) {
|
||||
const { display } = props;
|
||||
const select = display.getPageQuery(0, 1);
|
||||
return !!select;
|
||||
}
|
||||
|
||||
async function loadRowCount(props) {
|
||||
const { display, conid, database } = props;
|
||||
|
||||
const select = display.getCountQuery();
|
||||
|
||||
const response = await apiCall('database-connections/sql-select', {
|
||||
conid,
|
||||
database,
|
||||
select,
|
||||
});
|
||||
|
||||
return parseInt(response.rows[0].count);
|
||||
}
|
||||
</script>
|
||||
|
||||
<LoadingDataGridCore
|
||||
{...$$props}
|
||||
{loadDataPage}
|
||||
{dataPageAvailable}
|
||||
{loadRowCount}
|
||||
setLoadedRows={handleSetLoadedRows}
|
||||
onPublishedCellsChanged={value => {
|
||||
publishedCells = value;
|
||||
if (onPublishedCellsChanged) {
|
||||
onPublishedCellsChanged(value);
|
||||
}
|
||||
}}
|
||||
frameSelection={!!macroPreview}
|
||||
{grider}
|
||||
{display}
|
||||
onOpenQuery={openQuery}
|
||||
onOpenQueryOnError={openQueryOnError}
|
||||
/>
|
||||
import { __t, _t } from '../translations'
|
||||
const getCurrentEditor = () => getActiveComponent('SqlDataGridCore');
|
||||
|
||||
registerCommand({
|
||||
id: 'sqlDataGrid.openQuery',
|
||||
category: __t('command.datagrid', { defaultMessage: 'Data grid' }),
|
||||
name: __t('command.openQuery', { defaultMessage : 'Open query' }),
|
||||
testEnabled: () => getCurrentEditor() != null && hasPermission('dbops/query'),
|
||||
onClick: () => getCurrentEditor().openQuery(),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'sqlDataGrid.export',
|
||||
category: __t('command.datagrid', { defaultMessage: 'Data grid' }),
|
||||
name: __t('common.export', { defaultMessage : 'Export' }),
|
||||
icon: 'icon export',
|
||||
keyText: 'CtrlOrCommand+E',
|
||||
testEnabled: () => getCurrentEditor() != null && hasPermission('dbops/export'),
|
||||
onClick: () => getCurrentEditor().exportGrid(),
|
||||
});
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import _ from 'lodash';
|
||||
import { registerQuickExportHandler } from '../buttons/ToolStripExportButton.svelte';
|
||||
import {
|
||||
extractShellConnection,
|
||||
extractShellConnectionHostable,
|
||||
extractShellHostConnection,
|
||||
} from '../impexp/createImpExpScript';
|
||||
import { apiCall } from '../utility/api';
|
||||
|
||||
import { registerMenu } from '../utility/contextMenu';
|
||||
import createActivator from '../utility/createActivator';
|
||||
import createQuickExportMenu from '../utility/createQuickExportMenu';
|
||||
import { exportQuickExportFile } from '../utility/exportFileTools';
|
||||
import { getConnectionInfo } from '../utility/metadataLoaders';
|
||||
import openNewTab from '../utility/openNewTab';
|
||||
import ChangeSetGrider from './ChangeSetGrider';
|
||||
|
||||
import LoadingDataGridCore from './LoadingDataGridCore.svelte';
|
||||
import { openImportExportTab } from '../utility/importExportTools';
|
||||
import { getIntSettingsValue } from '../settings/settingsTools';
|
||||
import OverlayDiffGrider from './OverlayDiffGrider';
|
||||
|
||||
export let conid;
|
||||
export let display;
|
||||
export let database;
|
||||
export let schemaName;
|
||||
export let pureName;
|
||||
export let config;
|
||||
export let changeSetState;
|
||||
export let dispatchChangeSet;
|
||||
export let overlayDefinition = null;
|
||||
|
||||
export let macroPreview;
|
||||
export let macroValues;
|
||||
export let onPublishedCellsChanged;
|
||||
|
||||
let publishedCells = [];
|
||||
|
||||
// export let onChangeGrider = undefined;
|
||||
|
||||
export const activator = createActivator('SqlDataGridCore', false);
|
||||
|
||||
let loadedRows = [];
|
||||
|
||||
let grider;
|
||||
|
||||
// $: console.log('loadedRows BIND', loadedRows);
|
||||
|
||||
$: {
|
||||
if (!overlayDefinition && macroPreview) {
|
||||
grider = new ChangeSetGrider(
|
||||
loadedRows,
|
||||
changeSetState,
|
||||
dispatchChangeSet,
|
||||
display,
|
||||
macroPreview,
|
||||
macroValues,
|
||||
publishedCells
|
||||
);
|
||||
}
|
||||
}
|
||||
// prevent recreate grider, if no macro is selected, so there is no need to selectedcells in macro
|
||||
$: {
|
||||
if (!overlayDefinition && !macroPreview) {
|
||||
grider = new ChangeSetGrider(loadedRows, changeSetState, dispatchChangeSet, display);
|
||||
}
|
||||
}
|
||||
// $: console.log('GRIDER', grider);
|
||||
// $: if (onChangeGrider) onChangeGrider(grider);
|
||||
|
||||
$: {
|
||||
if (overlayDefinition) {
|
||||
grider = new OverlayDiffGrider(
|
||||
loadedRows,
|
||||
display,
|
||||
overlayDefinition.matchColumns,
|
||||
overlayDefinition.overlayData,
|
||||
overlayDefinition.matchedDbKeys
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function exportGrid() {
|
||||
const coninfo = await getConnectionInfo({ conid });
|
||||
|
||||
const initialValues: any = {};
|
||||
initialValues.sourceStorageType = 'query';
|
||||
initialValues.sourceConnectionId = conid;
|
||||
initialValues.sourceDatabaseName = database;
|
||||
initialValues.sourceQuery = coninfo.isReadOnly
|
||||
? JSON.stringify(display.getExportQueryJson(), undefined, 2)
|
||||
: display.getExportQuery();
|
||||
initialValues.sourceQueryType = coninfo.isReadOnly ? 'json' : 'native';
|
||||
initialValues.sourceList = display.baseTableOrSimilar ? [display.baseTableOrSimilar.pureName] : [];
|
||||
initialValues[`columns_${pureName}`] = display.getExportColumnMap();
|
||||
openImportExportTab(initialValues);
|
||||
// showModal(ImportExportModal, { initialValues });
|
||||
}
|
||||
|
||||
export function openQuery(sql?) {
|
||||
openNewTab(
|
||||
{
|
||||
title: _t('common.queryNumber', { defaultMessage: 'Query #' }),
|
||||
icon: 'img sql-file',
|
||||
tabComponent: 'QueryTab',
|
||||
focused: true,
|
||||
props: {
|
||||
schemaName: display.baseTableOrSimilar?.schemaName,
|
||||
pureName: display.baseTableOrSimilar?.pureName,
|
||||
conid,
|
||||
database,
|
||||
},
|
||||
},
|
||||
{
|
||||
editor: sql ?? display.getExportQuery(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function openQueryOnError() {
|
||||
openQuery(display.getPageQueryText(0, getIntSettingsValue('dataGrid.pageSize', 100, 5, 50000)));
|
||||
}
|
||||
|
||||
const quickExportHandler = fmt => async () => {
|
||||
const coninfo = await getConnectionInfo({ conid });
|
||||
exportQuickExportFile(
|
||||
pureName || 'Data',
|
||||
{
|
||||
functionName: 'queryReader',
|
||||
props: {
|
||||
...extractShellConnectionHostable(coninfo, database),
|
||||
queryType: coninfo.isReadOnly ? 'json' : 'native',
|
||||
query: coninfo.isReadOnly ? display.getExportQueryJson() : display.getExportQuery(),
|
||||
},
|
||||
hostConnection: extractShellHostConnection(coninfo, database),
|
||||
},
|
||||
fmt,
|
||||
display.getExportColumnMap()
|
||||
);
|
||||
};
|
||||
registerQuickExportHandler(quickExportHandler);
|
||||
|
||||
registerMenu(
|
||||
{ command: 'sqlDataGrid.openActiveChart', tag: 'chart' },
|
||||
{ command: 'sqlDataGrid.openQuery', tag: 'export' },
|
||||
() =>
|
||||
createQuickExportMenu(
|
||||
quickExportHandler,
|
||||
{
|
||||
command: 'sqlDataGrid.export',
|
||||
},
|
||||
{ tag: 'export' }
|
||||
)
|
||||
);
|
||||
|
||||
function handleSetLoadedRows(rows) {
|
||||
loadedRows = rows;
|
||||
}
|
||||
|
||||
async function loadDataPage(props, offset, limit) {
|
||||
const { display, conid, database } = props;
|
||||
|
||||
const select = display.getPageQuery(offset, limit);
|
||||
|
||||
const response = await apiCall('database-connections/sql-select', {
|
||||
conid,
|
||||
database,
|
||||
select,
|
||||
auditLogSessionGroup: 'data-grid',
|
||||
});
|
||||
|
||||
if (response.errorMessage) return response;
|
||||
return response.rows;
|
||||
}
|
||||
|
||||
function dataPageAvailable(props) {
|
||||
const { display } = props;
|
||||
const select = display.getPageQuery(0, 1);
|
||||
return !!select;
|
||||
}
|
||||
|
||||
async function loadRowCount(props) {
|
||||
const { display, conid, database } = props;
|
||||
|
||||
const select = display.getCountQuery();
|
||||
|
||||
const timeoutPromise = new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Row count query timed out')), 3000)
|
||||
);
|
||||
|
||||
try {
|
||||
const response = await Promise.race([
|
||||
apiCall('database-connections/sql-select', {
|
||||
conid,
|
||||
database,
|
||||
select,
|
||||
commandTimeout: 3000,
|
||||
}),
|
||||
timeoutPromise,
|
||||
]);
|
||||
|
||||
if (response.errorMessage) return { errorMessage: response.errorMessage };
|
||||
return parseInt(response.rows[0].count);
|
||||
} catch (err) {
|
||||
return { errorMessage: err.message || 'Error loading row count' };
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<LoadingDataGridCore
|
||||
{...$$props}
|
||||
{loadDataPage}
|
||||
{dataPageAvailable}
|
||||
{loadRowCount}
|
||||
setLoadedRows={handleSetLoadedRows}
|
||||
onPublishedCellsChanged={value => {
|
||||
publishedCells = value;
|
||||
if (onPublishedCellsChanged) {
|
||||
onPublishedCellsChanged(value);
|
||||
}
|
||||
}}
|
||||
frameSelection={!!macroPreview}
|
||||
{grider}
|
||||
{display}
|
||||
onOpenQuery={openQuery}
|
||||
onOpenQueryOnError={openQueryOnError}
|
||||
/>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -22,6 +22,7 @@
|
||||
let isLoadedCount = false;
|
||||
let loadedTime = new Date().getTime();
|
||||
let allRowCount = null;
|
||||
let allRowCountError = null;
|
||||
let errorMessage = null;
|
||||
|
||||
const handleLoadCurrentRow = async () => {
|
||||
@@ -38,7 +39,14 @@
|
||||
|
||||
const handleLoadRowCount = async () => {
|
||||
isLoadingCount = true;
|
||||
allRowCount = await loadRowCountFunc();
|
||||
const result = await loadRowCountFunc();
|
||||
if (result != null && typeof result === 'object' && result.errorMessage) {
|
||||
allRowCount = null;
|
||||
allRowCountError = result.errorMessage;
|
||||
} else {
|
||||
allRowCount = result;
|
||||
allRowCountError = null;
|
||||
}
|
||||
isLoadedCount = true;
|
||||
isLoadingCount = false;
|
||||
};
|
||||
@@ -55,6 +63,7 @@
|
||||
rowData = null;
|
||||
loadedTime = new Date().getTime();
|
||||
allRowCount = null;
|
||||
allRowCountError = null;
|
||||
errorMessage = null;
|
||||
}
|
||||
|
||||
@@ -82,4 +91,4 @@
|
||||
$: if (onReferenceSourceChanged && rowData) onReferenceSourceChanged([rowData], loadedTime);
|
||||
</script>
|
||||
|
||||
<FormView {...$$props} {grider} isLoading={isLoadingData} {allRowCount} onNavigate={handleNavigate} />
|
||||
<FormView {...$$props} {grider} isLoading={isLoadingData} {allRowCount} {allRowCountError} onReloadRowCount={handleLoadRowCount} onNavigate={handleNavigate} />
|
||||
|
||||
@@ -1,35 +1,47 @@
|
||||
<script lang="ts" context="module">
|
||||
import { apiCall } from '../utility/api';
|
||||
async function loadRow(props, select) {
|
||||
const { conid, database } = props;
|
||||
|
||||
if (!select) return null;
|
||||
|
||||
const response = await apiCall('database-connections/sql-select', {
|
||||
conid,
|
||||
database,
|
||||
select,
|
||||
auditLogSessionGroup: 'data-form',
|
||||
});
|
||||
|
||||
if (response.errorMessage) return response;
|
||||
return response.rows[0];
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
import _ from 'lodash';
|
||||
import LoadingFormView from './LoadingFormView.svelte';
|
||||
|
||||
export let display;
|
||||
|
||||
async function handleLoadRow() {
|
||||
return await loadRow($$props, display.getPageQuery(display.config.formViewRecordNumber || 0, 1));
|
||||
}
|
||||
|
||||
async function handleLoadRowCount() {
|
||||
const countRow = await loadRow($$props, display.getCountQuery());
|
||||
return countRow ? parseInt(countRow.count) : null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<LoadingFormView {...$$props} loadRowFunc={handleLoadRow} loadRowCountFunc={handleLoadRowCount} />
|
||||
async function loadRow(props, select, options = {}) {
|
||||
const { conid, database } = props;
|
||||
|
||||
if (!select) return null;
|
||||
|
||||
const response = await apiCall('database-connections/sql-select', {
|
||||
conid,
|
||||
database,
|
||||
select,
|
||||
auditLogSessionGroup: 'data-form',
|
||||
...options,
|
||||
});
|
||||
|
||||
if (response.errorMessage) return response;
|
||||
return response.rows[0];
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import _ from 'lodash';
|
||||
import LoadingFormView from './LoadingFormView.svelte';
|
||||
|
||||
export let display;
|
||||
|
||||
async function handleLoadRow() {
|
||||
return await loadRow($$props, display.getPageQuery(display.config.formViewRecordNumber || 0, 1));
|
||||
}
|
||||
|
||||
async function handleLoadRowCount() {
|
||||
const timeoutPromise = new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Row count query timed out')), 3000)
|
||||
);
|
||||
try {
|
||||
const countRow = await Promise.race([
|
||||
loadRow($$props, display.getCountQuery(), { commandTimeout: 3000 }),
|
||||
timeoutPromise,
|
||||
]);
|
||||
return countRow ? parseInt(countRow.count) : null;
|
||||
} catch (err) {
|
||||
return { errorMessage: err.message || 'Error loading row count' };
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<LoadingFormView {...$$props} loadRowFunc={handleLoadRow} loadRowCountFunc={handleLoadRowCount} />
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import _ from 'lodash';
|
||||
import { currentDatabase, openedConnectionsWithTemporary, getCurrentConfig, getOpenedConnections } from '../stores';
|
||||
import {
|
||||
currentDatabase,
|
||||
openedConnectionsWithTemporary,
|
||||
getCurrentConfig,
|
||||
getOpenedConnections,
|
||||
getOpenedTabs,
|
||||
} from '../stores';
|
||||
import { apiCall, getVolatileConnections, strmid } from './api';
|
||||
import hasPermission from '../utility/hasPermission';
|
||||
import { getConfig } from './metadataLoaders';
|
||||
@@ -37,9 +43,29 @@ const doDatabasePing = value => {
|
||||
}
|
||||
};
|
||||
|
||||
function pingAllOpenedDatabases() {
|
||||
const tabs = getOpenedTabs() || [];
|
||||
const allDbs = tabs
|
||||
.filter(tab => !tab.closedTime && tab.props?.conid && tab.props?.database)
|
||||
.map(tab => ({ conid: tab.props.conid as string, database: tab.props.database as string }));
|
||||
const seen = new Set<string>();
|
||||
const databases: { conid: string; database: string }[] = [];
|
||||
for (const db of allDbs) {
|
||||
const key = `${db.conid}/${db.database}`;
|
||||
if (!seen.has(key)) {
|
||||
seen.add(key);
|
||||
databases.push(db);
|
||||
}
|
||||
}
|
||||
if (databases.length > 0) {
|
||||
apiCall('database-connections/ping-databases', { databases });
|
||||
}
|
||||
}
|
||||
|
||||
let openedConnectionsHandle = null;
|
||||
|
||||
let currentDatabaseHandle = null;
|
||||
let allDatabasesHandle = null;
|
||||
|
||||
export function subscribeConnectionPingers() {
|
||||
openedConnectionsWithTemporary.subscribe(value => {
|
||||
@@ -53,6 +79,11 @@ export function subscribeConnectionPingers() {
|
||||
if (currentDatabaseHandle) window.clearInterval(currentDatabaseHandle);
|
||||
currentDatabaseHandle = window.setInterval(() => doDatabasePing(value), 20 * 1000);
|
||||
});
|
||||
|
||||
// Ping all databases that have open (non-closed) tabs, not just the current one
|
||||
pingAllOpenedDatabases();
|
||||
if (allDatabasesHandle) window.clearInterval(allDatabasesHandle);
|
||||
allDatabasesHandle = window.setInterval(() => pingAllOpenedDatabases(), 20 * 1000);
|
||||
}
|
||||
|
||||
export function callServerPing() {
|
||||
|
||||
@@ -181,7 +181,7 @@
|
||||
</div>
|
||||
<div class="container">
|
||||
{#each contextItems || [] as item}
|
||||
<div class="item" class:clickable={item.clickable} on:click={item.onClick}>
|
||||
<div class="item" class:clickable={item.clickable} on:click={item.onClick} title={item.title || null}>
|
||||
{#if item.icon}
|
||||
<FontIcon icon={item.icon} padRight />
|
||||
{/if}
|
||||
|
||||
@@ -8,14 +8,15 @@
|
||||
export let clickable = false;
|
||||
export let icon = null;
|
||||
export let onClick = null;
|
||||
export let title = null;
|
||||
|
||||
const key = uuidv1();
|
||||
const tabid = getContext('tabid');
|
||||
|
||||
onMount(() => {
|
||||
updateStatuBarInfoItem(tabid, key, { text, icon, clickable, onClick });
|
||||
updateStatuBarInfoItem(tabid, key, { text, icon, clickable, onClick, title });
|
||||
});
|
||||
onDestroy(() => updateStatuBarInfoItem(tabid, key, null));
|
||||
|
||||
$: updateStatuBarInfoItem(tabid, key, { text, icon, clickable, onClick });
|
||||
$: updateStatuBarInfoItem(tabid, key, { text, icon, clickable, onClick, title });
|
||||
</script>
|
||||
|
||||
@@ -71,14 +71,19 @@ const driver = {
|
||||
// called for retrieve data (eg. browse in data grid) and for update database
|
||||
async query(dbhan, query, options) {
|
||||
const offset = options?.range?.offset;
|
||||
const commandTimeout = options?.commandTimeout;
|
||||
const executeOptions = {};
|
||||
if (commandTimeout) {
|
||||
executeOptions.readTimeout = parseInt(commandTimeout);
|
||||
}
|
||||
if (options?.discardResult) {
|
||||
await dbhan.client.execute(query);
|
||||
await dbhan.client.execute(query, [], executeOptions);
|
||||
return {
|
||||
rows: [],
|
||||
columns: [],
|
||||
};
|
||||
}
|
||||
const result = await dbhan.client.execute(query);
|
||||
const result = await dbhan.client.execute(query, [], executeOptions);
|
||||
if (!result.rows?.[0]) {
|
||||
return {
|
||||
rows: [],
|
||||
|
||||
@@ -25,6 +25,7 @@ const driver = {
|
||||
},
|
||||
// called for retrieve data (eg. browse in data grid) and for update database
|
||||
async query(dbhan, query, options) {
|
||||
const commandTimeout = options?.commandTimeout;
|
||||
if (options?.discardResult) {
|
||||
await dbhan.client.command({
|
||||
query,
|
||||
@@ -34,10 +35,14 @@ const driver = {
|
||||
columns: [],
|
||||
};
|
||||
} else {
|
||||
const resultSet = await dbhan.client.query({
|
||||
const queryOptions = {
|
||||
query,
|
||||
format: 'JSONCompactEachRowWithNamesAndTypes',
|
||||
});
|
||||
};
|
||||
if (commandTimeout) {
|
||||
queryOptions.settings = { max_execution_time: Math.ceil(parseInt(commandTimeout) / 1000) };
|
||||
}
|
||||
const resultSet = await dbhan.client.query(queryOptions);
|
||||
|
||||
const dataSet = await resultSet.json();
|
||||
if (!dataSet?.[0]) {
|
||||
|
||||
@@ -487,7 +487,11 @@ const drivers = driverBases.map((driverBase) => ({
|
||||
|
||||
const collection = dbhan.getDatabase().collection(options.pureName);
|
||||
if (options.countDocuments) {
|
||||
const count = await collection.countDocuments(deserializeMongoData(mongoCondition) || {});
|
||||
const countOptions = {};
|
||||
if (options.commandTimeout) {
|
||||
countOptions.maxTimeMS = parseInt(options.commandTimeout);
|
||||
}
|
||||
const count = await collection.countDocuments(deserializeMongoData(mongoCondition) || {}, countOptions);
|
||||
return { count };
|
||||
} else if (options.aggregate) {
|
||||
let cursor = await collection.aggregate(deserializeMongoData(convertToMongoAggregate(options.aggregate)));
|
||||
|
||||
@@ -119,7 +119,7 @@ async function tediousQueryCore(dbhan, sql, options) {
|
||||
columns: [],
|
||||
});
|
||||
}
|
||||
const { addDriverNativeColumn, discardResult } = options || {};
|
||||
const { addDriverNativeColumn, discardResult, commandTimeout } = options || {};
|
||||
return new Promise((resolve, reject) => {
|
||||
const result = {
|
||||
rows: [],
|
||||
@@ -129,6 +129,9 @@ async function tediousQueryCore(dbhan, sql, options) {
|
||||
if (err) reject(err);
|
||||
else resolve(result);
|
||||
});
|
||||
if (commandTimeout) {
|
||||
request.setTimeout(parseInt(commandTimeout));
|
||||
}
|
||||
request.on('columnMetadata', function (columns) {
|
||||
result.columns = extractTediousColumns(columns, addDriverNativeColumn);
|
||||
});
|
||||
|
||||
@@ -105,9 +105,18 @@ const drivers = driverBases.map(driverBase => ({
|
||||
};
|
||||
}
|
||||
|
||||
const commandTimeout = options?.commandTimeout;
|
||||
const queryOptions = {};
|
||||
if (commandTimeout) {
|
||||
queryOptions.timeout = parseInt(commandTimeout);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
dbhan.client.query(sql, function (error, results, fields) {
|
||||
if (error) reject(error);
|
||||
dbhan.client.query({ sql, ...queryOptions }, function (error, results, fields) {
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
const columns = extractColumns(fields);
|
||||
resolve({ rows: results && columns && results.map && results.map(row => modifyRow(zipDataRow(row, columns), columns)), columns });
|
||||
});
|
||||
|
||||
@@ -107,7 +107,7 @@ const driver = {
|
||||
async close(dbhan) {
|
||||
return dbhan.client.close();
|
||||
},
|
||||
async query(dbhan, sql) {
|
||||
async query(dbhan, sql, options) {
|
||||
if (sql == null || sql.trim() == '') {
|
||||
return {
|
||||
rows: [],
|
||||
@@ -120,7 +120,21 @@ const driver = {
|
||||
sql = mtrim[1];
|
||||
}
|
||||
|
||||
const res = await dbhan.client.execute(sql);
|
||||
const commandTimeout = options?.commandTimeout;
|
||||
let previousCallTimeout;
|
||||
if (commandTimeout) {
|
||||
previousCallTimeout = dbhan.client.callTimeout;
|
||||
dbhan.client.callTimeout = parseInt(commandTimeout);
|
||||
}
|
||||
|
||||
let res;
|
||||
try {
|
||||
res = await dbhan.client.execute(sql);
|
||||
} finally {
|
||||
if (commandTimeout) {
|
||||
dbhan.client.callTimeout = previousCallTimeout || 0;
|
||||
}
|
||||
}
|
||||
try {
|
||||
const columns = extractOracleColumns(res.metaData);
|
||||
return { rows: (res.rows || []).map(row => modifyRow(zipDataRow(row, columns), columns)), columns };
|
||||
|
||||
@@ -178,14 +178,25 @@ const drivers = driverBases.map(driverBase => ({
|
||||
async close(dbhan) {
|
||||
return dbhan.client.end();
|
||||
},
|
||||
async query(dbhan, sql) {
|
||||
async query(dbhan, sql, options) {
|
||||
if (sql == null) {
|
||||
return {
|
||||
rows: [],
|
||||
columns: [],
|
||||
};
|
||||
}
|
||||
const res = await dbhan.client.query({ text: sql, rowMode: 'array' });
|
||||
const commandTimeout = options?.commandTimeout;
|
||||
if (commandTimeout) {
|
||||
await dbhan.client.query({ text: `SET statement_timeout = ${parseInt(commandTimeout)}` });
|
||||
}
|
||||
let res;
|
||||
try {
|
||||
res = await dbhan.client.query({ text: sql, rowMode: 'array' });
|
||||
} finally {
|
||||
if (commandTimeout) {
|
||||
await dbhan.client.query({ text: 'SET statement_timeout = 0' }).catch(() => {});
|
||||
}
|
||||
}
|
||||
const columns = extractPostgresColumns(res, dbhan);
|
||||
|
||||
const transormableTypeNames = Object.values(dbhan.typeIdToName ?? {});
|
||||
|
||||
@@ -7247,6 +7247,11 @@ doctrine@^3.0.0:
|
||||
dependencies:
|
||||
esutils "^2.0.2"
|
||||
|
||||
dom-to-image@^2.6.0:
|
||||
version "2.6.0"
|
||||
resolved "https://registry.yarnpkg.com/dom-to-image/-/dom-to-image-2.6.0.tgz#8a503608088c87b1c22f9034ae032e1898955867"
|
||||
integrity sha512-Dt0QdaHmLpjURjU7Tnu3AgYSF2LuOmksSGsUcE6ItvJoCWTBEmiMXcqBdNSAm9+QbbwD7JMoVsuuKX6ZVQv1qA==
|
||||
|
||||
dotenv@^16.0.0:
|
||||
version "16.4.5"
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f"
|
||||
|
||||
Reference in New Issue
Block a user