mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2026-10-26 06:30:06 -05:00
1) Added support to show all background processes in separate panel. Fixes #3709
2) Port process watcher to React. Fixes #7404
This commit is contained in:
1 parent
271b6d91fc
commit
c2b23465cc
100 files changed
+1949
-1638
No files matched your search
@@ -86,8 +86,10 @@ class NavMenuLocators:
|
||||
"//div[contains(@class,'wcFrameTitleBar')]" \
|
||||
"//div[contains(text(),'Process Watcher')]"
|
||||
|
||||
process_watcher_detailed_command_canvas_css = \
|
||||
".bg-process-details .bg-detailed-desc"
|
||||
process_watcher_detailed_message_css = \
|
||||
"div[data-test='process-details'] div[data-test='process-message']"
|
||||
process_watcher_detailed_command_css = \
|
||||
"div[data-test='process-details'] div[data-test='process-cmd']"
|
||||
|
||||
process_watcher_close_button_xpath = \
|
||||
"//div[contains(@class,'wcFloating')]//" \
|
||||
@@ -107,6 +109,10 @@ class NavMenuLocators:
|
||||
|
||||
rcdock_tab = "div.dock-tab-btn[id$='{0}']"
|
||||
|
||||
process_start_close_selector = \
|
||||
"div[data-test='process-popup-start'] button[data-label='Close']"
|
||||
process_end_close_selector = \
|
||||
"div[data-test='process-popup-end'] button[data-label='Close']"
|
||||
process_watcher_error_close_xpath = \
|
||||
".btn.btn-sm-sq.btn-primary.pg-bg-close > i"
|
||||
|
||||
|
||||
@@ -36,5 +36,9 @@ define(function () {
|
||||
'file_manager.save_file_dialog_view': '/file_manager/save_file_dialog_view/<int:trans_id>',
|
||||
'file_manager.save_show_hidden_file_option': '/file_manager/save_show_hidden_file_option/<int:trans_id>',
|
||||
'settings.save_file_format_setting': '/settings/save_file_format_setting/',
|
||||
'bgprocess.detailed_status': '/misc/bgprocess/<pid>/<int:out>/<int:err>/',
|
||||
'bgprocess.list': '/misc/bgprocess/',
|
||||
'bgprocess.stop_process': '/misc/bgprocess/stop/<pid>',
|
||||
'bgprocess.acknowledge': '/misc/bgprocess/<pid>'
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,120 @@
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import axios from 'axios/index';
|
||||
import BgProcessManager, { BgProcessManagerProcessState } from '../../../pgadmin/misc/bgprocess/static/js/BgProcessManager';
|
||||
import * as BgProcessNotify from '../../../pgadmin/misc/bgprocess/static/js/BgProcessNotify';
|
||||
|
||||
|
||||
|
||||
describe('BgProcessManager', ()=>{
|
||||
let obj;
|
||||
let networkMock;
|
||||
const pgBrowser = jasmine.createSpyObj('pgBrowser', [], {
|
||||
docker: {
|
||||
findPanels: ()=>{/* dummy */},
|
||||
addPanel: ()=>{/* dummy */}
|
||||
}
|
||||
});
|
||||
|
||||
beforeAll(()=>{
|
||||
networkMock = new MockAdapter(axios);
|
||||
networkMock.onGet('/misc/bgprocess/').reply(200, [{}]);
|
||||
networkMock.onPut('/misc/bgprocess/stop/12345').reply(200,{});
|
||||
networkMock.onPut('/misc/bgprocess/12345').reply(200,{});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
networkMock.restore();
|
||||
});
|
||||
|
||||
beforeEach(()=>{
|
||||
obj = new BgProcessManager(pgBrowser);
|
||||
});
|
||||
|
||||
it('init', ()=>{
|
||||
spyOn(obj, 'startWorker');
|
||||
obj.init();
|
||||
expect(obj.startWorker).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('procList', ()=>{
|
||||
obj._procList = [{a: '1'}];
|
||||
expect(obj.procList).toEqual([{a: '1'}]);
|
||||
});
|
||||
|
||||
it('startWorker', (done)=>{
|
||||
spyOn(obj, 'syncProcesses');
|
||||
obj._pendingJobId = ['123123123123'];
|
||||
obj.startWorker();
|
||||
|
||||
setTimeout(()=>{
|
||||
expect(obj.syncProcesses).toHaveBeenCalled();
|
||||
done();
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
it('startProcess', ()=>{
|
||||
let nSpy = spyOn(BgProcessNotify, 'processStarted');
|
||||
obj.startProcess('12345', 'process desc');
|
||||
expect(obj._pendingJobId).toEqual(['12345']);
|
||||
expect(nSpy.calls.mostRecent().args[0]).toBe('process desc');
|
||||
});
|
||||
|
||||
|
||||
it('stopProcess', (done)=>{
|
||||
obj._procList = [{
|
||||
id: '12345',
|
||||
process_state: BgProcessManagerProcessState.PROCESS_STARTED,
|
||||
}];
|
||||
|
||||
obj.stopProcess('12345');
|
||||
setTimeout(()=>{
|
||||
expect(obj._procList[0].process_state).toBe(BgProcessManagerProcessState.PROCESS_TERMINATED);
|
||||
done();
|
||||
}, 500);
|
||||
});
|
||||
|
||||
it('acknowledge', (done)=>{
|
||||
obj._procList = [{
|
||||
id: '12345',
|
||||
process_state: BgProcessManagerProcessState.PROCESS_FINISHED,
|
||||
}];
|
||||
|
||||
obj.acknowledge(['12345']);
|
||||
setTimeout(()=>{
|
||||
expect(obj._procList.length).toBe(0);
|
||||
done();
|
||||
}, 500);
|
||||
});
|
||||
|
||||
|
||||
it('checkPending', ()=>{
|
||||
obj._procList = [{
|
||||
id: '12345',
|
||||
process_state: BgProcessManagerProcessState.PROCESS_FINISHED,
|
||||
}];
|
||||
obj._pendingJobId = ['12345'];
|
||||
let nSpy = spyOn(BgProcessNotify, 'processCompleted');
|
||||
obj.checkPending();
|
||||
expect(nSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
it('openProcessesPanel', ()=>{
|
||||
const panel = {
|
||||
focus: ()=>{/* dummy */}
|
||||
};
|
||||
spyOn(pgBrowser.docker, 'addPanel').and.returnValue(panel);
|
||||
|
||||
/* panel open */
|
||||
spyOn(pgBrowser.docker, 'findPanels').and.returnValue([panel]);
|
||||
obj.openProcessesPanel();
|
||||
expect(pgBrowser.docker.addPanel).not.toHaveBeenCalled();
|
||||
|
||||
/* panel closed */
|
||||
spyOn(pgBrowser.docker, 'findPanels')
|
||||
.withArgs('processes').and.returnValue([])
|
||||
.withArgs('properties').and.returnValue([panel]);
|
||||
obj.openProcessesPanel();
|
||||
expect(pgBrowser.docker.addPanel).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,75 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import jasmineEnzyme from 'jasmine-enzyme';
|
||||
import '../helper/enzyme.helper';
|
||||
import { createMount } from '@material-ui/core/test-utils';
|
||||
import BgProcessManager, { BgProcessManagerProcessState } from '../../../pgadmin/misc/bgprocess/static/js/BgProcessManager';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import * as BgProcessNotify from '../../../pgadmin/misc/bgprocess/static/js/BgProcessNotify';
|
||||
import Notifier from '../../../pgadmin/static/js/helpers/Notifier';
|
||||
|
||||
describe('BgProcessNotify', ()=>{
|
||||
let mount;
|
||||
|
||||
/* Use createMount so that material ui components gets the required context */
|
||||
/* https://material-ui.com/guides/testing/#api */
|
||||
beforeAll(()=>{
|
||||
mount = createMount();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
mount.cleanUp();
|
||||
});
|
||||
|
||||
beforeEach(()=>{
|
||||
jasmineEnzyme();
|
||||
pgAdmin.Browser = pgAdmin.Browser || {};
|
||||
pgAdmin.Browser.BgProcessManager = new BgProcessManager(pgAdmin.Browser);
|
||||
});
|
||||
|
||||
it('processStarted', ()=>{
|
||||
const nspy = spyOn(Notifier, 'notify');
|
||||
BgProcessNotify.processStarted('some desc', ()=>{/* dummy */});
|
||||
expect(nspy.calls.mostRecent().args[0].props).toEqual(jasmine.objectContaining({
|
||||
title: 'Process started',
|
||||
desc: 'some desc',
|
||||
}));
|
||||
});
|
||||
|
||||
it('processCompleted success', ()=>{
|
||||
const nspy = spyOn(Notifier, 'notify');
|
||||
BgProcessNotify.processCompleted('some desc', BgProcessManagerProcessState.PROCESS_FINISHED, ()=>{/* dummy */});
|
||||
expect(nspy.calls.mostRecent().args[0].props).toEqual(jasmine.objectContaining({
|
||||
title: 'Process completed',
|
||||
desc: 'some desc',
|
||||
success: true,
|
||||
}));
|
||||
});
|
||||
|
||||
it('processCompleted failed', ()=>{
|
||||
const nspy = spyOn(Notifier, 'notify');
|
||||
BgProcessNotify.processCompleted('some desc', BgProcessManagerProcessState.PROCESS_FAILED, ()=>{/* dummy */});
|
||||
expect(nspy.calls.mostRecent().args[0].props).toEqual(jasmine.objectContaining({
|
||||
title: 'Process failed',
|
||||
desc: 'some desc',
|
||||
success: false,
|
||||
}));
|
||||
});
|
||||
|
||||
it('processCompleted terminated', ()=>{
|
||||
const nspy = spyOn(Notifier, 'notify');
|
||||
BgProcessNotify.processCompleted('some desc', BgProcessManagerProcessState.PROCESS_TERMINATED, ()=>{/* dummy */});
|
||||
expect(nspy.calls.mostRecent().args[0].props).toEqual(jasmine.objectContaining({
|
||||
title: 'Process terminated',
|
||||
desc: 'some desc',
|
||||
success: false,
|
||||
}));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import jasmineEnzyme from 'jasmine-enzyme';
|
||||
import React from 'react';
|
||||
import '../helper/enzyme.helper';
|
||||
import { createMount } from '@material-ui/core/test-utils';
|
||||
import Theme from '../../../pgadmin/static/js/Theme';
|
||||
import BgProcessManager, { BgProcessManagerProcessState } from '../../../pgadmin/misc/bgprocess/static/js/BgProcessManager';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import Processes from '../../../pgadmin/misc/bgprocess/static/js/Processes';
|
||||
|
||||
|
||||
const processData = {
|
||||
acknowledge: null,
|
||||
current_storage_dir: null,
|
||||
desc: 'Doing some operation on the server \'PostgreSQL 12 (localhost:5432)\'',
|
||||
details: {
|
||||
cmd: '/Library/PostgreSQL/12/bin/mybin --testing',
|
||||
message: 'Doing some detailed operation on the server \'PostgreSQL 12 (localhost:5432)\'...'
|
||||
},
|
||||
etime: null,
|
||||
execution_time: 0.09,
|
||||
exit_code: null,
|
||||
id: '220803091429498400',
|
||||
process_state: BgProcessManagerProcessState.PROCESS_STARTED,
|
||||
stime: '2022-08-03T09:14:30.191940+00:00',
|
||||
type_desc: 'Operation on the server',
|
||||
utility_pid: 140391
|
||||
};
|
||||
|
||||
describe('Proceses', ()=>{
|
||||
let mount;
|
||||
|
||||
/* Use createMount so that material ui components gets the required context */
|
||||
/* https://material-ui.com/guides/testing/#api */
|
||||
beforeAll(()=>{
|
||||
mount = createMount();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
mount.cleanUp();
|
||||
});
|
||||
|
||||
beforeEach(()=>{
|
||||
jasmineEnzyme();
|
||||
pgAdmin.Browser = pgAdmin.Browser || {};
|
||||
pgAdmin.Browser.BgProcessManager = new BgProcessManager(pgAdmin.Browser);
|
||||
pgAdmin.Browser.BgProcessManager._procList = [processData];
|
||||
});
|
||||
|
||||
describe('ProcessDetails', ()=>{
|
||||
let ctrlMount = (props)=>{
|
||||
return mount(<Theme>
|
||||
<Processes
|
||||
{...props}
|
||||
/>
|
||||
</Theme>);
|
||||
};
|
||||
|
||||
it('init', (done)=>{
|
||||
let ctrl = ctrlMount({});
|
||||
setTimeout(()=>{
|
||||
ctrl.update();
|
||||
expect(ctrl.find('PgTable').length).toBe(1);
|
||||
done();
|
||||
}, 1000);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,116 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import jasmineEnzyme from 'jasmine-enzyme';
|
||||
import React from 'react';
|
||||
import '../helper/enzyme.helper';
|
||||
import { createMount } from '@material-ui/core/test-utils';
|
||||
import Theme from '../../../pgadmin/static/js/Theme';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import axios from 'axios/index';
|
||||
import ProcessDetails from '../../../pgadmin/misc/bgprocess/static/js/ProcessDetails';
|
||||
import BgProcessManager, { BgProcessManagerProcessState } from '../../../pgadmin/misc/bgprocess/static/js/BgProcessManager';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import { MESSAGE_TYPE } from '../../../pgadmin/static/js/components/FormComponents';
|
||||
import _ from 'lodash';
|
||||
|
||||
|
||||
const processData = {
|
||||
acknowledge: null,
|
||||
current_storage_dir: null,
|
||||
desc: 'Doing some operation on the server \'PostgreSQL 12 (localhost:5432)\'',
|
||||
details: {
|
||||
cmd: '/Library/PostgreSQL/12/bin/mybin --testing',
|
||||
message: 'Doing some detailed operation on the server \'PostgreSQL 12 (localhost:5432)\'...'
|
||||
},
|
||||
etime: null,
|
||||
execution_time: 0.09,
|
||||
exit_code: null,
|
||||
id: '220803091429498400',
|
||||
process_state: BgProcessManagerProcessState.PROCESS_STARTED,
|
||||
stime: '2022-08-03T09:14:30.191940+00:00',
|
||||
type_desc: 'Operation on the server',
|
||||
utility_pid: 140391
|
||||
};
|
||||
|
||||
const detailsResponse = {
|
||||
err: {
|
||||
done: true,
|
||||
lines: [['220803091259931276', 'INFO: operation log err']],
|
||||
pos: 123
|
||||
},
|
||||
execution_time: 1.27,
|
||||
exit_code: 0,
|
||||
out: {
|
||||
done: true,
|
||||
lines: [['220803091259931276', 'INFO: operation log out']],
|
||||
pos: 123
|
||||
},
|
||||
start_time: '2022-08-03 09:12:59.774503 +0000'
|
||||
};
|
||||
|
||||
describe('ProcessDetails', ()=>{
|
||||
let mount;
|
||||
let networkMock;
|
||||
|
||||
/* Use createMount so that material ui components gets the required context */
|
||||
/* https://material-ui.com/guides/testing/#api */
|
||||
beforeAll(()=>{
|
||||
mount = createMount();
|
||||
networkMock = new MockAdapter(axios);
|
||||
let initialResp = _.cloneDeep(detailsResponse);
|
||||
initialResp.err.done = false;
|
||||
initialResp.out.done = false;
|
||||
initialResp.exit_code = null;
|
||||
networkMock.onGet(`/misc/bgprocess/${processData.id}/0/0/`).reply(200, initialResp);
|
||||
networkMock.onGet(`/misc/bgprocess/${processData.id}/123/123/`).reply(200, detailsResponse);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
mount.cleanUp();
|
||||
networkMock.restore();
|
||||
});
|
||||
|
||||
beforeEach(()=>{
|
||||
jasmineEnzyme();
|
||||
pgAdmin.Browser = pgAdmin.Browser || {};
|
||||
pgAdmin.Browser.BgProcessManager = new BgProcessManager(pgAdmin.Browser);
|
||||
});
|
||||
|
||||
describe('ProcessDetails', ()=>{
|
||||
let ctrlMount = (props)=>{
|
||||
return mount(<Theme>
|
||||
<ProcessDetails
|
||||
data={processData}
|
||||
{...props}
|
||||
/>
|
||||
</Theme>);
|
||||
};
|
||||
|
||||
it('running and success', (done)=>{
|
||||
let ctrl = ctrlMount({});
|
||||
setTimeout(()=>{
|
||||
ctrl.update();
|
||||
expect(ctrl.find('NotifierMessage').props()).toEqual(jasmine.objectContaining({
|
||||
type: MESSAGE_TYPE.INFO,
|
||||
message: 'Running...',
|
||||
}));
|
||||
setTimeout(()=>{
|
||||
ctrl.update();
|
||||
expect(ctrl.find('NotifierMessage').props()).toEqual(jasmine.objectContaining({
|
||||
type: MESSAGE_TYPE.SUCCESS,
|
||||
message: 'Successfully completed.',
|
||||
}));
|
||||
ctrl.unmount();
|
||||
done();
|
||||
}, 2000);
|
||||
}, 500);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -6,48 +6,60 @@
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
import time
|
||||
from regression.feature_utils.locators import NavMenuLocators
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
|
||||
|
||||
def close_bgprocess_popup(tester):
|
||||
def close_bgprocess_start_popup(tester):
|
||||
"""
|
||||
Allows us to close the background process popup window
|
||||
"""
|
||||
# In cases where backup div is not closed (sometime due to some error)
|
||||
try:
|
||||
tester.page.wait_for_element_to_disappear(
|
||||
lambda x: tester.driver.find_element(
|
||||
By.XPATH, ".ajs-message.ajs-bg-bgprocess.ajs-visible"))
|
||||
except Exception:
|
||||
tester.driver.find_element(
|
||||
By.CSS_SELECTOR,
|
||||
NavMenuLocators.process_watcher_error_close_xpath).click()
|
||||
tester.driver.find_element(
|
||||
By.CSS_SELECTOR,
|
||||
NavMenuLocators.process_start_close_selector).click()
|
||||
|
||||
# In cases where restore div is not closed (sometime due to some error)
|
||||
try:
|
||||
tester.page.wait_for_element_to_disappear(
|
||||
lambda x: tester.driver.find_element(
|
||||
By.XPATH,
|
||||
"//div[@class='card-header bg-primary d-flex']/div"
|
||||
"[contains(text(), 'Restoring backup')]"))
|
||||
except Exception:
|
||||
tester.driver.find_element(
|
||||
By.CSS_SELECTOR,
|
||||
NavMenuLocators.process_watcher_error_close_xpath).click()
|
||||
|
||||
# In cases where maintenance window is not closed (sometime due to some
|
||||
# error)
|
||||
try:
|
||||
tester.page.wait_for_element_to_disappear(
|
||||
lambda x: tester.driver.find_element(
|
||||
By.XPATH,
|
||||
"//div[@class='card-header bg-primary d-flex']/div"
|
||||
"[contains(text(), 'Maintenance')]"))
|
||||
except Exception:
|
||||
tester.driver.find_element(
|
||||
By.CSS_SELECTOR,
|
||||
NavMenuLocators.process_watcher_error_close_xpath).click()
|
||||
def wait_for_process_start(tester):
|
||||
tester.wait.until(EC.visibility_of_element_located(
|
||||
(By.CSS_SELECTOR,
|
||||
NavMenuLocators.process_start_close_selector)))
|
||||
close_bgprocess_start_popup(tester)
|
||||
|
||||
|
||||
def wait_for_process_end(self):
|
||||
"""This will wait for process to complete dialogue status"""
|
||||
attempts = 120
|
||||
status = False
|
||||
while attempts > 0:
|
||||
try:
|
||||
button = self.page.find_by_css_selector(
|
||||
NavMenuLocators.process_end_close_selector)
|
||||
status = True
|
||||
button.click()
|
||||
break
|
||||
except Exception:
|
||||
attempts -= 1
|
||||
time.sleep(.5)
|
||||
return status
|
||||
|
||||
|
||||
def open_process_details(tester):
|
||||
status = wait_for_process_end(tester)
|
||||
if not status:
|
||||
raise RuntimeError("Process not completed")
|
||||
|
||||
tester.page.click_tab("Processes")
|
||||
time.sleep(3)
|
||||
tester.page.find_by_css_selector(
|
||||
"div[data-test='processes'] "
|
||||
"div[data-test='row-container']:nth-child(1) "
|
||||
"div[role='row'] div[role='cell']:nth-child(3) button").click()
|
||||
|
||||
tester.page.wait_for_element_to_disappear(
|
||||
lambda driver: driver.find_element(
|
||||
By.CSS_SELECTOR, "span[data-test='loading-logs']"))
|
||||
|
||||
|
||||
def close_process_watcher(tester):
|
||||
|
||||
@@ -1185,28 +1185,6 @@ def check_binary_path_or_skip_test(cls, utility_name):
|
||||
cls.skipTest(ret_val)
|
||||
|
||||
|
||||
def get_watcher_dialogue_status(self):
|
||||
"""This will get watcher dialogue status"""
|
||||
import time
|
||||
attempts = 120
|
||||
status = None
|
||||
while attempts > 0:
|
||||
try:
|
||||
status = self.page.find_by_css_selector(
|
||||
".pg-bg-status-text").text
|
||||
|
||||
if 'Failed' in status:
|
||||
break
|
||||
if status == 'Started' or status == 'Running...':
|
||||
attempts -= 1
|
||||
time.sleep(.5)
|
||||
else:
|
||||
break
|
||||
except Exception:
|
||||
attempts -= 1
|
||||
return status
|
||||
|
||||
|
||||
def get_driver_version():
|
||||
version = getattr(psycopg2, '__version__', None)
|
||||
return version
|
||||
|
||||
Reference in new issue
Block a user