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:
Aditya Toshniwal authored and Akshay Joshi committed 2022-08-11 10:49:45 +05:30
1 parent 271b6d91fc
commit c2b23465cc
100 files changed
+1949 -1638

No files matched your search

+8 -2
View File
@@ -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