Enabled large file downloads for desktop users within the query tool. #3369

pull/8763/head
Aditya Toshniwal 2025-05-14 15:30:17 +05:30 committed by GitHub
parent ebf4963758
commit 126e1fb53d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 354 additions and 110 deletions

View File

@ -20,6 +20,7 @@ Bundled PostgreSQL Utilities
New features
************
| `Issue #3369 <https://github.com/pgadmin-org/pgadmin4/issues/3369>`_ - Enabled large file downloads for desktop users within the query tool.
| `Issue #8583 <https://github.com/pgadmin-org/pgadmin4/issues/8583>`_ - Add all missing options to the Import/Export Data functionality, and update the syntax of the COPY command to align with the latest standards.
| `Issue #8681 <https://github.com/pgadmin-org/pgadmin4/issues/8681>`_ - Add support for exporting table data based on a custom query.

View File

@ -8,6 +8,7 @@
//////////////////////////////////////////////////////////////
import globals from 'globals';
import js from '@eslint/js';
import unusedImports from 'eslint-plugin-unused-imports';
export default [
js.configs.recommended,
@ -34,6 +35,9 @@ export default [
'platform': 'readonly',
},
},
'plugins': {
'unused-imports': unusedImports,
},
'rules': {
'indent': [
'error',
@ -55,6 +59,17 @@ export default [
'no-console': ['error', { allow: ['warn', 'error'] }],
// We need to exclude below for RegEx case
'no-useless-escape': 0,
'no-unused-vars': 'off',
'unused-imports/no-unused-imports': 'error',
'unused-imports/no-unused-vars': [
'warn',
{
'vars': 'all',
'varsIgnorePattern': '^_',
'args': 'after-used',
'argsIgnorePattern': '^_',
},
],
},
},
];

View File

@ -13,12 +13,12 @@
"packageManager": "yarn@3.8.7",
"devDependencies": {
"electron": "36.2.0",
"eslint": "^9.26.0"
"eslint": "^9.26.0",
"eslint-plugin-unused-imports": "^4.1.4"
},
"dependencies": {
"axios": "^1.9.0",
"electron-context-menu": "^4.0.5",
"electron-dl": "^4.0.0",
"electron-store": "^10.0.0"
"electron-store": "^10.0.1"
}
}

View File

@ -0,0 +1,121 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2025, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import { app, ipcMain, dialog, BrowserWindow, shell } from 'electron';
import fs from 'fs';
import path from 'path';
import { setBadge, clearBadge, clearProgress, setProgress } from './progress.js';
import { writeServerLog } from './misc.js';
class DownloadItem {
constructor(filePath, onUpdate, onRemove) {
this.filePath = filePath;
this.currentLoaded = 0;
this.total = null;
this.stream = fs.createWriteStream(filePath);;
this.onUpdate = onUpdate;
this.onRemove = onRemove;
}
write(chunk) {
this.stream.write(chunk);
this.currentLoaded += chunk.length;
this.onUpdate?.();
}
setTotal(total) {
this.total = total;
}
remove() {
this.stream.end();
this.onRemove?.();
}
}
const downloadQueue = {};
function updateProgress(callerWindow) {
let count = Object.keys(downloadQueue).length;
if (count === 0) {
clearBadge();
clearProgress.call(callerWindow);
return;
}
setBadge(Object.keys(downloadQueue).length);
let progress = 0;
if(Object.values(downloadQueue).some((item) => item.total === null)) {
// If any of the items in the queue does not have a total, we cannot calculate progress
// so we return 2 to indicate that the progress is indeterminate.
progress = 2;
} else {
const total = Object.values(downloadQueue).reduce((acc, item) => {
if (item.total) {
return acc + item.currentLoaded / item.total;
}
return acc + item.currentLoaded;
}, 0);
progress = total / Object.keys(downloadQueue).length;
}
setProgress.call(callerWindow, progress);
}
export function setupDownloader() {
// Listen for the renderer's request to show the open dialog
ipcMain.handle('get-download-path', async (event, options, prompt=true) => {
try {
let filePath = path.join(app.getPath('downloads'), options.defaultPath);
const callerWindow = BrowserWindow.fromWebContents(event.sender);
// prompt is true when the user has set the preference to prompt for download location
if(prompt) {
const result = await dialog.showSaveDialog(callerWindow, {
title: 'Save File',
...options,
});
if (result.canceled) {
return;
}
filePath = result.filePath;
}
downloadQueue[filePath] = new DownloadItem(filePath, () => {
updateProgress(callerWindow);
}, () => {
delete downloadQueue[filePath];
updateProgress(callerWindow);
});
updateProgress(callerWindow);
return filePath;
} catch (error) {
writeServerLog(`Error in get-download-path: ${error}`);
}
});
ipcMain.on('download-data-save-total', (event, filePath, total) => {
const item = downloadQueue[filePath];
if (item) {
item.setTotal(total);
}
});
ipcMain.on('download-data-save-chunk', (event, filePath, chunk) => {
const item = downloadQueue[filePath];
if (item) {
item.write(chunk);
}
});
ipcMain.on('download-data-save-end', (event, filePath, openFile=false) => {
const item = downloadQueue[filePath];
if (item) {
item.remove();
openFile && shell.openPath(filePath);
}
});
}

View File

@ -16,7 +16,7 @@ import { spawn } from 'child_process';
import { fileURLToPath } from 'url';
import { setupMenu } from './menu.js';
import contextMenu from 'electron-context-menu';
import { CancelError, download } from 'electron-dl';
import { setupDownloader } from './downloader.js';
const configStore = new Store({
defaults: {
@ -153,28 +153,6 @@ function reloadApp() {
currWin.webContents.reload();
}
async function desktopFileDownload(payload) {
const currWin = BrowserWindow.getFocusedWindow();
try {
await download(currWin, payload.downloadUrl, {
filename: payload.fileName,
saveAs: payload.prompt_for_download_location,
onProgress: (progress) => {
currWin.webContents.send('download-progress', progress);
},
onCompleted: (item) => {
currWin.webContents.send('download-complete', item);
if (payload.automatically_open_downloaded_file)
shell.openPath(item.path);
},
});
} catch (error) {
if (!(error instanceof CancelError)) {
misc.writeServerLog(error);
}
}
}
// This functions is used to start the pgAdmin4 server by spawning a
// separate process.
function startDesktopMode() {
@ -192,8 +170,9 @@ function startDesktopMode() {
process.env.PGADMIN_SERVER_MODE = 'OFF';
// Start Page URL
startPageUrl = 'http://127.0.0.1:' + serverPort + '/?key=' + UUID;
serverCheckUrl = 'http://127.0.0.1:' + serverPort + '/misc/ping?key=' + UUID;
const baseUrl = `http://127.0.0.1:${serverPort}`;
startPageUrl = `${baseUrl}/?key=${UUID}`;
serverCheckUrl = `${baseUrl}/misc/ping?key=${UUID}`;
// Write Python Path, pgAdmin file path and command in log file.
misc.writeServerLog('pgAdmin Runtime Environment');
@ -356,6 +335,8 @@ function launchPgAdminWindow() {
'reloadApp': reloadApp,
});
setupDownloader();
pgAdminMainScreen.loadURL(startPageUrl);
const bounds = configStore.get('bounds');
@ -429,7 +410,6 @@ ipcMain.on('log', (text) => ()=>{
misc.writeServerLog(text);
});
ipcMain.on('reloadApp', reloadApp);
ipcMain.on('onFileDownload', (_, payload) => desktopFileDownload(payload));
ipcMain.handle('checkPortAvailable', async (_e, fixedPort)=>{
try {
await misc.getAvailablePort(fixedPort);

View File

@ -24,5 +24,9 @@ contextBridge.exposeInMainWorld('electronUI', {
showSaveDialog: (options) => ipcRenderer.invoke('showSaveDialog', options),
log: (text)=> ipcRenderer.send('log', text),
reloadApp: ()=>{ipcRenderer.send('reloadApp');},
onFileDownload: (payload) => ipcRenderer.send('onFileDownload', payload),
// Download related functions
getDownloadPath: (...args) => ipcRenderer.invoke('get-download-path', ...args),
downloadDataSaveChunk: (...args) => ipcRenderer.send('download-data-save-chunk', ...args),
downloadDataSaveTotal: (...args) => ipcRenderer.send('download-data-save-total', ...args),
downloadDataSaveEnd: (...args) => ipcRenderer.send('download-data-save-end', ...args),
});

View File

@ -0,0 +1,39 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2025, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import { app } from 'electron';
export function setBadge(count) {
const badgeCount = parseInt(count, 10);
if (!isNaN(badgeCount)) {
app.setBadgeCount(badgeCount);
}
}
// Function to clear badge
export function clearBadge() {
app.setBadgeCount(0);
}
// Function to set progress bar
export function setProgress(progress) {
const progressValue = parseFloat(progress);
if (this && !isNaN(progressValue) && progressValue >= 0 && progressValue <= 1) {
this.setProgressBar(progressValue);
} else if (this && progress === -1) {
this.setProgressBar(-1);
}
}
// Function to clear progress
export function clearProgress() {
if (this) {
this.setProgressBar(-1);
}
}

View File

@ -751,7 +751,7 @@ __metadata:
languageName: node
linkType: hard
"electron-store@npm:^10.0.0":
"electron-store@npm:^10.0.1":
version: 10.0.1
resolution: "electron-store@npm:10.0.1"
dependencies:
@ -881,6 +881,19 @@ __metadata:
languageName: node
linkType: hard
"eslint-plugin-unused-imports@npm:^4.1.4":
version: 4.1.4
resolution: "eslint-plugin-unused-imports@npm:4.1.4"
peerDependencies:
"@typescript-eslint/eslint-plugin": ^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0
eslint: ^9.0.0 || ^8.0.0
peerDependenciesMeta:
"@typescript-eslint/eslint-plugin":
optional: true
checksum: 1f4ce3e3972699345513840f3af1b783033dbc3a3e85b62ce12b3f6a89fd8c92afe46d0c00af40bacb14465445983ba0ccc326a6fd5132553061fb0e47bcba19
languageName: node
linkType: hard
"eslint-scope@npm:^8.3.0":
version: 8.3.0
resolution: "eslint-scope@npm:8.3.0"
@ -1886,9 +1899,9 @@ __metadata:
axios: ^1.9.0
electron: 36.2.0
electron-context-menu: ^4.0.5
electron-dl: ^4.0.0
electron-store: ^10.0.0
electron-store: ^10.0.1
eslint: ^9.26.0
eslint-plugin-unused-imports: ^4.1.4
languageName: unknown
linkType: soft

View File

@ -40,7 +40,7 @@ import Replication from './Replication';
import { getExpandCell } from '../../../static/js/components/PgReactTableStyled';
import CodeMirror from '../../../static/js/components/ReactCodeMirror';
import GetAppRoundedIcon from '@mui/icons-material/GetAppRounded';
import { downloadFile } from '../../../static/js/utils';
import { downloadTextData } from '../../../static/js/download_utils';
import RefreshButton from './components/RefreshButtons';
function parseData(data) {
@ -451,7 +451,7 @@ function Dashboard({
let fileName = 'data-' + new Date().getTime() + extension;
try {
downloadFile(respData, fileName, `text/${type}`);
downloadTextData(respData, fileName, `text/${type}`);
} catch {
setSsMsg(gettext('Failed to download the logs.'));
}

View File

@ -32,7 +32,7 @@ import Uploader from './Uploader';
import GridView from './GridView';
import convert from 'convert-units';
import PropTypes from 'prop-types';
import { downloadBlob } from '../../../../../static/js/utils';
import { downloadBlob } from '../../../../../static/js/download_utils';
import ErrorBoundary from '../../../../../static/js/helpers/ErrorBoundary';
import { MY_STORAGE } from './FileManagerConstants';
import _ from 'lodash';

View File

@ -7,7 +7,7 @@
//
//////////////////////////////////////////////////////////////
import getApiInstance from '../api_instance';
import { downloadFile } from '../utils';
import { downloadTextData } from '../download_utils';
function convertImageURLtoDataURI(api, image) {
return new Promise(function(resolve, reject) {
@ -43,6 +43,6 @@ export function downloadSvg(svg, svgName) {
}
Promise.all(image_promises).then(function() {
downloadFile(svgElement.outerHTML, svgName, 'image/svg+xml');
downloadTextData(svgElement.outerHTML, svgName, 'image/svg+xml');
});
}

View File

@ -0,0 +1,112 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2025, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////////////////
import usePreferences from '../../preferences/static/js/store';
import { callFetch, parseApiError } from './api_instance';
import { getBrowser, toPrettySize } from './utils';
// This function is used to download the base64 data
// and create a link to download the file.
export async function downloadBase64UrlData(downloadUrl, fileName) {
let link = document.createElement('a');
link.setAttribute('href', downloadUrl);
link.setAttribute('download', fileName);
link.style.setProperty('visibility ', 'hidden');
document.body.appendChild(link);
link.click();
link.remove();
}
// This function is used to download the blob data
export async function downloadBlob(blob, fileName) {
const urlCreator = window.URL || window.webkitURL;
const downloadUrl = urlCreator.createObjectURL(blob);
downloadBase64UrlData(downloadUrl, fileName);
window.URL.revokeObjectURL(downloadUrl);
}
// This function is used to download the text data
export function downloadTextData(textData, fileName, fileType) {
const respBlob = new Blob([textData], {type : fileType});
downloadBlob(respBlob, fileName);
}
// This function is used to download the file from the given URL
// and use streaming to download the file in chunks where there
// is no limit on the file size.
export async function downloadFileStream(allOptions, fileName, fileType, onProgress) {
const {automatically_open_downloaded_file, prompt_for_download_location} = usePreferences.getState().getPreferencesForModule('misc');
const start = async (filePath, writer) => {
const response = await callFetch(allOptions.url, allOptions.options);
if(!response.ok) {
throw new Error(parseApiError(await response.json()));
}
if (!response.body) {
throw new Error(response.statusText);
}
const contentLength = response.headers.get('Content-Length');
writer.downloadDataSaveTotal(filePath, contentLength ? parseInt(contentLength, 10) : null);
const reader = response.body.getReader();
let done = false;
let receivedLength = 0; // received bytes
while (!done) {
const { value, done: doneReading } = await reader.read();
done = doneReading;
if (value) {
writer.downloadDataSaveChunk(filePath, value);
receivedLength += value.length;
onProgress?.(toPrettySize(receivedLength, 'B', 2));
}
}
writer.downloadDataSaveEnd(filePath, automatically_open_downloaded_file);
};
let writer;
let filePath = '';
try {
if(getBrowser().name != 'Electron') {
// In other browsers, we use the blob to download the file.
const data = [];
writer = {
downloadDataSaveChunk: (_fp, chunk) => {
data.push(chunk);
},
downloadDataSaveTotal: () => {
// This is not used in the browser
},
downloadDataSaveEnd: () => {
// This is not used in the browser
},
};
await start(filePath, writer);
const blob = new Blob(data, {type: fileType});
downloadBlob(blob, fileName);
} else {
writer = window.electronUI;
filePath = await window.electronUI.getDownloadPath({
defaultPath: fileName,
}, prompt_for_download_location);
// If the user cancels the download, we will not proceed
if(!filePath) {
return;
}
await start(filePath, writer);
}
} catch (error) {
writer.downloadDataSaveEnd(filePath);
throw new Error('Download failed: ' + error.message);
}
}

View File

@ -369,46 +369,7 @@ export function checkTrojanSource(content, isPasteEvent) {
}
}
export async function downloadBlob(blob, fileName) {
const {automatically_open_downloaded_file, prompt_for_download_location} = usePreferences.getState().getPreferencesForModule('misc');
const urlCreator = window.URL || window.webkitURL;
const downloadUrl = urlCreator.createObjectURL(blob);
if (getBrowser().name == 'IE' && window.navigator.msSaveBlob) {
// IE10+ : (has Blob, but not a[download] or URL)
window.navigator.msSaveBlob(blob, fileName);
} else if (getBrowser().name == 'Electron') {
await window.electronUI.onFileDownload({downloadUrl, fileName, automatically_open_downloaded_file, prompt_for_download_location});
} else {
const link = document.createElement('a');
link.setAttribute('href', downloadUrl);
link.setAttribute('download', fileName);
link.style.setProperty('visibility ', 'hidden');
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
export async function downloadUrlData(downloadUrl, fileName) {
const {automatically_open_downloaded_file, prompt_for_download_location} = usePreferences.getState().getPreferencesForModule('misc');
if (getBrowser().name == 'Electron') {
window.electronUI.onFileDownload({downloadUrl, fileName, automatically_open_downloaded_file, prompt_for_download_location});
} else {
let link = document.createElement('a');
link.setAttribute('href', downloadUrl);
link.setAttribute('download', fileName);
link.click();
link.remove();
}
}
export function downloadFile(textData, fileName, fileType) {
const respBlob = new Blob([textData], {type : fileType});
downloadBlob(respBlob, fileName);
}
export function toPrettySize(rawSize, from='B') {
export function toPrettySize(rawSize, from='B', decimalFixed=null) {
try {
//if the integer need to be converted to K for thousands, M for millions , B for billions only
if (from == '') {
@ -416,6 +377,9 @@ export function toPrettySize(rawSize, from='B') {
}
let conVal = convert(rawSize).from(from).toBest();
conVal.val = Math.round(conVal.val * 100) / 100;
if(decimalFixed) {
conVal.val = conVal.val.toFixed(decimalFixed);
}
return `${conVal.val} ${conVal.unit}`;
}
catch {

View File

@ -37,7 +37,7 @@ import pgAdmin from 'sources/pgadmin';
import { styled } from '@mui/material/styles';
import BeforeUnload from './BeforeUnload';
import { isMac } from '../../../../../../static/js/keyboard_shortcuts';
import { downloadUrlData } from '../../../../../../static/js/utils';
import { downloadBase64UrlData } from '../../../../../../static/js/download_utils';
/* Custom react-diagram action for keyboard events */
export class KeyboardShortcutAction extends Action {
@ -601,7 +601,7 @@ export default class ERDTool extends React.Component {
this.closeOnSave = closeOnSave;
if(this.state.current_file && !isSaveAs) {
this.saveFile(this.state.current_file);
} else if (this.diagram.getNodesData().length > 0){ {
} else if (this.diagram.getNodesData().length > 0){
let params = {
'supported_types': ['*','pgerd'],
'dialog_type': 'create_file',
@ -610,7 +610,6 @@ export default class ERDTool extends React.Component {
};
this.props.pgAdmin.Tools.FileManager.show(params, this.saveFile.bind(this), null, this.context);
}
}
}
saveFile(fileName) {
@ -761,7 +760,7 @@ export default class ERDTool extends React.Component {
}
toPng(this.canvasEle, {width, height})
.then((dataUrl)=>{
downloadUrlData(dataUrl, `${this.getCurrentProjectName()}.png`);
downloadBase64UrlData(dataUrl, `${this.getCurrentProjectName()}.png`);
}).catch((err)=>{
console.error(err);
let msg = gettext('Unknown error. Check console logs');

View File

@ -26,7 +26,8 @@ import { LineChart, BarChart, PieChart, DATA_POINT_STYLE, DATA_POINT_SIZE,
LightenDarkenColor} from 'sources/chartjs';
import { QueryToolEventsContext, QueryToolContext } from '../QueryToolComponent';
import { QUERY_TOOL_EVENTS, PANELS } from '../QueryToolConstants';
import { downloadUrlData, getChartColor } from '../../../../../../static/js/utils';
import { downloadBase64UrlData } from '../../../../../../static/js/download_utils';
import { getChartColor } from '../../../../../../static/js/utils';
const StyledBox = styled(Box)(({theme}) => ({
width: '100%',
@ -380,7 +381,7 @@ export function GraphVisualiser({initColumns}) {
const onDownloadGraph = async ()=> {
let downloadUrl = chartObjRef.current.toBase64Image(),
fileName = 'graph_visualiser-' + new Date().getTime() + '.png';
downloadUrlData(downloadUrl, fileName);
downloadBase64UrlData(downloadUrl, fileName);
};
// This plugin is used to set the background color of the canvas. Very useful

View File

@ -10,7 +10,7 @@ import _ from 'lodash';
import { styled } from '@mui/material/styles';
import React, { useContext, useEffect, useRef, useState } from 'react';
import QueryToolDataGrid, { GRID_ROW_SELECT_KEY } from '../QueryToolDataGrid';
import {CONNECTION_STATUS, PANELS, QUERY_TOOL_EVENTS} from '../QueryToolConstants';
import {CONNECTION_STATUS, PANELS, QUERY_TOOL_EVENTS, MODAL_DIALOGS} from '../QueryToolConstants';
import url_for from 'sources/url_for';
import getApiInstance, { parseApiError } from '../../../../../../static/js/api_instance';
import { QueryToolContext, QueryToolEventsContext } from '../QueryToolComponent';
@ -22,7 +22,7 @@ import { LayoutDockerContext } from '../../../../../../static/js/helpers/Layout'
import { GeometryViewer } from './GeometryViewer';
import Explain from '../../../../../../static/js/Explain';
import { QuerySources } from './QueryHistory';
import { downloadFile } from '../../../../../../static/js/utils';
import { downloadFileStream } from '../../../../../../static/js/download_utils';
import CopyData from '../QueryToolDataGrid/CopyData';
import moment from 'moment';
import ConfirmSaveContent from '../../../../../../static/js/Dialogs/ConfirmSaveContent';
@ -31,7 +31,6 @@ import { GraphVisualiser } from './GraphVisualiser';
import { usePgAdmin } from '../../../../../../static/js/PgAdminProvider';
import pgAdmin from 'sources/pgadmin';
import ConnectServerContent from '../../../../../../static/js/Dialogs/ConnectServerContent';
import { MODAL_DIALOGS } from '../QueryToolConstants';
const StyledBox = styled(Box)(({theme}) => ({
display: 'flex',
@ -514,23 +513,17 @@ export class ResultSetUtils {
});
}
async saveResultsToFile(fileName) {
async saveResultsToFile(fileName, onProgress) {
try {
let {data: respData} = await this.api.post(
url_for('sqleditor.query_tool_download', {
this.hasQueryCommitted = false;
await downloadFileStream({
url: url_for('sqleditor.query_tool_download', {
'trans_id': this.transId,
}),
{filename: fileName, query_commited: this.hasQueryCommitted}
);
if(!_.isUndefined(respData.data)) {
if(!respData.status) {
this.eventBus.fireEvent(QUERY_TOOL_EVENTS.SET_MESSAGE, respData.data.result);
}
} else {
this.hasQueryCommitted = false;
downloadFile(respData, fileName, 'text/csv');
}
options: {
method: 'POST',
body: JSON.stringify({filename: fileName, query_commited: this.hasQueryCommitted})
}}, fileName, 'text/csv', onProgress);
this.eventBus.fireEvent(QUERY_TOOL_EVENTS.TRIGGER_SAVE_RESULTS_END);
} catch (error) {
this.eventBus.fireEvent(QUERY_TOOL_EVENTS.TRIGGER_SAVE_RESULTS_END);
@ -1049,7 +1042,9 @@ export function ResultSet() {
fileName = queryToolCtx.params.node_name + extension;
}
setLoaderText(gettext('Downloading results...'));
await rsu.current.saveResultsToFile(fileName);
await rsu.current.saveResultsToFile(fileName, (p)=>{
setLoaderText(gettext('Downloading results(%s)...', p));
});
setLoaderText('');
});

View File

@ -16,7 +16,7 @@ import FileManager, { FileManagerUtils, getComparator } from '../../../pgadmin/m
import MockAdapter from 'axios-mock-adapter';
import axios from 'axios';
import getApiInstance from '../../../pgadmin/static/js/api_instance';
import * as pgUtils from '../../../pgadmin/static/js/utils';
import * as downloadUtils from '../../../pgadmin/static/js/download_utils';
import userEvent from '@testing-library/user-event';
const files = [
@ -116,8 +116,8 @@ describe('FileManger', ()=>{
let closeModal=jest.fn(),
onOK=jest.fn(),
onCancel=jest.fn(),
ctrlMount = async (props)=>{
return await render(<Theme>
ctrlMount = (props)=>{
return render(<Theme>
<FileManager
params={params}
closeModal={closeModal}
@ -135,7 +135,7 @@ describe('FileManger', ()=>{
networkMock.onPost(`/file_manager/save_last_dir/${transId}`).reply(200, {'success':1,'errormsg':'','info':'','result':null,'data':null});
let ctrl;
await act(async ()=>{
ctrl = await ctrlMount({});
ctrl = ctrlMount({});
});
const user = userEvent.setup();
await user.click(ctrl.container.querySelector('[name="menu-options"]'));
@ -157,7 +157,7 @@ describe('FileManger', ()=>{
let ctrl;
const user = userEvent.setup();
await act(async ()=>{
ctrl = await ctrlMount({});
ctrl = ctrlMount({});
});
await user.click(ctrl.container.querySelector('[name="menu-shared-storage"]'));
@ -171,7 +171,7 @@ describe('FileManger', ()=>{
let ctrl;
const user = userEvent.setup();
await act(async ()=>{
ctrl = await ctrlMount({});
ctrl = ctrlMount({});
});
await user.click(ctrl.container.querySelector('[name="menu-shared-storage"]'));
@ -345,9 +345,9 @@ describe('FileManagerUtils', ()=>{
});
it('downloadFile', async ()=>{
jest.spyOn(pgUtils, 'downloadBlob').mockImplementation(() => {});
jest.spyOn(downloadUtils, 'downloadBlob').mockImplementation(() => {});
let row = {Filename: 'newfile1', Path: '/home/newfile1', 'storage_folder': 'my_storage'};
await fmObj.downloadFile(row);
expect(pgUtils.downloadBlob).toHaveBeenCalledWith('blobdata', 'newfile1');
expect(downloadUtils.downloadBlob).toHaveBeenCalledWith('blobdata', 'newfile1');
});
});