OpenOrchestrator 1.3.1__py3-none-any.whl → 2.0.0rc2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. OpenOrchestrator/__main__.py +66 -12
  2. OpenOrchestrator/common/connection_frame.py +10 -4
  3. OpenOrchestrator/database/base.py +8 -0
  4. OpenOrchestrator/database/constants.py +3 -15
  5. OpenOrchestrator/database/db_util.py +110 -37
  6. OpenOrchestrator/database/logs.py +3 -15
  7. OpenOrchestrator/database/queues.py +3 -15
  8. OpenOrchestrator/database/schedulers.py +32 -0
  9. OpenOrchestrator/database/triggers.py +7 -16
  10. OpenOrchestrator/orchestrator/application.py +13 -8
  11. OpenOrchestrator/orchestrator/datetime_input.py +2 -2
  12. OpenOrchestrator/orchestrator/popups/constant_popup.py +8 -6
  13. OpenOrchestrator/orchestrator/popups/credential_popup.py +10 -8
  14. OpenOrchestrator/orchestrator/popups/generic_popups.py +15 -2
  15. OpenOrchestrator/orchestrator/popups/trigger_popup.py +25 -14
  16. OpenOrchestrator/orchestrator/tabs/constants_tab.py +5 -2
  17. OpenOrchestrator/orchestrator/tabs/logging_tab.py +3 -0
  18. OpenOrchestrator/orchestrator/tabs/queue_tab.py +4 -1
  19. OpenOrchestrator/orchestrator/tabs/schedulers_tab.py +45 -0
  20. OpenOrchestrator/orchestrator/tabs/settings_tab.py +2 -5
  21. OpenOrchestrator/orchestrator/tabs/trigger_tab.py +9 -5
  22. OpenOrchestrator/orchestrator/test_helper.py +17 -0
  23. OpenOrchestrator/orchestrator_connection/connection.py +21 -4
  24. OpenOrchestrator/scheduler/application.py +3 -2
  25. OpenOrchestrator/scheduler/run_tab.py +16 -6
  26. OpenOrchestrator/scheduler/runner.py +52 -64
  27. OpenOrchestrator/scheduler/settings_tab.py +90 -14
  28. OpenOrchestrator/scheduler/util.py +8 -0
  29. OpenOrchestrator/tests/__init__.py +0 -0
  30. OpenOrchestrator/tests/db_test_util.py +40 -0
  31. OpenOrchestrator/tests/test_db_util.py +372 -0
  32. OpenOrchestrator/tests/test_orchestrator_connection.py +142 -0
  33. OpenOrchestrator/tests/test_trigger_polling.py +143 -0
  34. OpenOrchestrator/tests/ui_tests/__init__.py +0 -0
  35. OpenOrchestrator/tests/ui_tests/test_constants_tab.py +167 -0
  36. OpenOrchestrator/tests/ui_tests/test_logging_tab.py +180 -0
  37. OpenOrchestrator/tests/ui_tests/test_queues_tab.py +126 -0
  38. OpenOrchestrator/tests/ui_tests/test_schedulers_tab.py +47 -0
  39. OpenOrchestrator/tests/ui_tests/test_trigger_tab.py +243 -0
  40. OpenOrchestrator/tests/ui_tests/ui_util.py +151 -0
  41. openorchestrator-2.0.0rc2.dist-info/METADATA +158 -0
  42. openorchestrator-2.0.0rc2.dist-info/RECORD +55 -0
  43. {openorchestrator-1.3.1.dist-info → openorchestrator-2.0.0rc2.dist-info}/WHEEL +1 -1
  44. OpenOrchestrator/scheduler/connection_frame.py +0 -96
  45. openorchestrator-1.3.1.dist-info/METADATA +0 -60
  46. openorchestrator-1.3.1.dist-info/RECORD +0 -39
  47. {openorchestrator-1.3.1.dist-info → openorchestrator-2.0.0rc2.dist-info/licenses}/LICENSE +0 -0
  48. {openorchestrator-1.3.1.dist-info → openorchestrator-2.0.0rc2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,243 @@
1
+ """Tests relating to the trigger tab in Orchestrator."""
2
+
3
+ import unittest
4
+ from datetime import datetime, timedelta
5
+ import time
6
+
7
+ from selenium.webdriver.common.by import By
8
+
9
+ from OpenOrchestrator.common import datetime_util
10
+ from OpenOrchestrator.tests import db_test_util
11
+ from OpenOrchestrator.database import db_util
12
+ from OpenOrchestrator.database.triggers import SingleTrigger, ScheduledTrigger, QueueTrigger, TriggerStatus
13
+ from OpenOrchestrator.tests.ui_tests import ui_util
14
+
15
+
16
+ class TestTriggerTab(unittest.TestCase):
17
+ """Test functionality of the trigger tab ui."""
18
+ def setUp(self) -> None:
19
+ self.browser = ui_util.open_orchestrator()
20
+ db_test_util.establish_clean_database()
21
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_tab]").click()
22
+ ui_util.refresh_ui(self.browser)
23
+
24
+ def tearDown(self) -> None:
25
+ self.browser.quit()
26
+
27
+ @ui_util.screenshot_on_error
28
+ def test_single_trigger_creation(self):
29
+ """Test creation of a single trigger."""
30
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_tab_single_button]").click()
31
+
32
+ # Fill form
33
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_popup_trigger_input]").send_keys("Trigger name")
34
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_popup_name_input]").send_keys("Process name")
35
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_popup_time_input]").send_keys("12-11-2020 12:34")
36
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_popup_path_input]").send_keys("Process Path")
37
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_popup_git_check]").click()
38
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_popup_args_input]").send_keys("Process args")
39
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_popup_priority_input]").send_keys("5")
40
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_popup_whitelist_input]").send_keys("Scheduler1\nScheduler2\n")
41
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_popup_save_button]").click()
42
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=popup_option1_button]").click()
43
+
44
+ # Check result
45
+ time.sleep(5)
46
+ triggers = db_util.get_all_triggers()
47
+ self.assertEqual(len(triggers), 1)
48
+ trigger: SingleTrigger = triggers[0]
49
+ self.assertIsInstance(trigger, SingleTrigger)
50
+
51
+ self.assertEqual(trigger.trigger_name, "Trigger name")
52
+ self.assertEqual(trigger.process_name, "Process name")
53
+ self.assertEqual(trigger.next_run, datetime.strptime("12-11-2020 12:34", "%d-%m-%Y %H:%M"))
54
+ self.assertEqual(trigger.process_path, "Process Path")
55
+ self.assertEqual(trigger.is_git_repo, True)
56
+ self.assertEqual(trigger.process_args, "Process args")
57
+ self.assertEqual(trigger.is_blocking, True)
58
+ self.assertEqual(trigger.priority, 5)
59
+ self.assertEqual(trigger.scheduler_whitelist, '["Scheduler1", "Scheduler2"]')
60
+
61
+ @ui_util.screenshot_on_error
62
+ def test_scheduled_trigger_creation(self):
63
+ """Test creation of a scheduled trigger."""
64
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_tab_scheduled_button]").click()
65
+
66
+ # Fill form
67
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_popup_trigger_input]").send_keys("Trigger name")
68
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_popup_name_input]").send_keys("Process name")
69
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_popup_cron_input]").send_keys("1 1 * * *")
70
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_popup_path_input]").send_keys("Process Path")
71
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_popup_git_check]").click()
72
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_popup_args_input]").send_keys("Process args")
73
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_popup_blocking_check]").click()
74
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_popup_priority_input]").send_keys("5")
75
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_popup_whitelist_input]").send_keys("Scheduler1\nScheduler2\n")
76
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_popup_save_button]").click()
77
+
78
+ # Check result
79
+ time.sleep(2)
80
+ triggers = db_util.get_all_triggers()
81
+ self.assertEqual(len(triggers), 1)
82
+ trigger: ScheduledTrigger = triggers[0]
83
+ self.assertIsInstance(trigger, ScheduledTrigger)
84
+
85
+ self.assertEqual(trigger.trigger_name, "Trigger name")
86
+ self.assertEqual(trigger.process_name, "Process name")
87
+ self.assertEqual(trigger.cron_expr, "1 1 * * *")
88
+ self.assertEqual(trigger.process_path, "Process Path")
89
+ self.assertEqual(trigger.is_git_repo, True)
90
+ self.assertEqual(trigger.process_args, "Process args")
91
+ self.assertEqual(trigger.is_blocking, False)
92
+ self.assertEqual(trigger.priority, 5)
93
+ self.assertEqual(trigger.scheduler_whitelist, '["Scheduler1", "Scheduler2"]')
94
+
95
+ @ui_util.screenshot_on_error
96
+ def test_queue_trigger_creation(self):
97
+ """Test creation of a queue trigger."""
98
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_tab_queue_button]").click()
99
+
100
+ # Fill form
101
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_popup_trigger_input]").send_keys("Trigger name")
102
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_popup_name_input]").send_keys("Process name")
103
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_popup_queue_input]").send_keys("Queue Name")
104
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_popup_batch_input]").send_keys("1")
105
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_popup_path_input]").send_keys("Process Path")
106
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_popup_git_check]").click()
107
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_popup_args_input]").send_keys("Process args")
108
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_popup_blocking_check]").click()
109
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_popup_priority_input]").send_keys("5")
110
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_popup_whitelist_input]").send_keys("Scheduler1\nScheduler2\n")
111
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_popup_save_button]").click()
112
+
113
+ # Check result
114
+ time.sleep(1)
115
+ triggers = db_util.get_all_triggers()
116
+ self.assertEqual(len(triggers), 1)
117
+ trigger: QueueTrigger = triggers[0]
118
+ self.assertIsInstance(trigger, QueueTrigger)
119
+
120
+ self.assertEqual(trigger.trigger_name, "Trigger name")
121
+ self.assertEqual(trigger.process_name, "Process name")
122
+ self.assertEqual(trigger.queue_name, "Queue Name")
123
+ self.assertEqual(trigger.min_batch_size, 11)
124
+ self.assertEqual(trigger.process_path, "Process Path")
125
+ self.assertEqual(trigger.is_git_repo, True)
126
+ self.assertEqual(trigger.process_args, "Process args")
127
+ self.assertEqual(trigger.is_blocking, False)
128
+ self.assertEqual(trigger.priority, 5)
129
+ self.assertEqual(trigger.scheduler_whitelist, '["Scheduler1", "Scheduler2"]')
130
+
131
+ @ui_util.screenshot_on_error
132
+ def test_trigger_table(self):
133
+ """Test that data is shown correctly in the trigger table."""
134
+ # Create some triggers
135
+ yesterday = datetime.today() - timedelta(days=1)
136
+ tomorrow = datetime.today() + timedelta(days=1)
137
+ db_util.create_single_trigger("A Single trigger", "Single Process", yesterday, "Single path", "Single args", False, False, 0, [])
138
+ db_util.create_scheduled_trigger("B Scheduled trigger", "Scheduled Process", "1 2 3 4 5", tomorrow, "Scheduled path", "Scheduled args", False, False, 0, [])
139
+ db_util.create_queue_trigger("C Queue trigger", "Queue Process", "Queue Name", "Queue path", "Queue args", False, False, 25, 0, [])
140
+
141
+ ui_util.refresh_ui(self.browser)
142
+
143
+ table_data = ui_util.get_table_data(self.browser, "trigger_tab_trigger_table")
144
+
145
+ # Check single trigger
146
+ self.assertEqual(table_data[0][0], "A Single trigger")
147
+ self.assertEqual(table_data[0][1], "0")
148
+ self.assertEqual(table_data[0][2], "Single")
149
+ self.assertEqual(table_data[0][3], "Idle")
150
+ self.assertEqual(table_data[0][4], "Single Process")
151
+ self.assertEqual(table_data[0][5], "Never")
152
+ self.assertEqual(table_data[0][6], f"{datetime_util.format_datetime(yesterday)}\nOverdue")
153
+
154
+ # Check scheduled trigger
155
+ self.assertEqual(table_data[1][0], "B Scheduled trigger")
156
+ self.assertEqual(table_data[1][1], "0")
157
+ self.assertEqual(table_data[1][2], "Scheduled")
158
+ self.assertEqual(table_data[1][3], "Idle")
159
+ self.assertEqual(table_data[1][4], "Scheduled Process")
160
+ self.assertEqual(table_data[1][5], "Never")
161
+ self.assertEqual(table_data[1][6], f"{datetime_util.format_datetime(tomorrow)}")
162
+
163
+ # Check queue trigger
164
+ self.assertEqual(table_data[2][0], "C Queue trigger")
165
+ self.assertEqual(table_data[2][1], "0")
166
+ self.assertEqual(table_data[2][2], "Queue")
167
+ self.assertEqual(table_data[2][3], "Idle")
168
+ self.assertEqual(table_data[2][4], "Queue Process")
169
+ self.assertEqual(table_data[2][5], "Never")
170
+ self.assertEqual(table_data[2][6], "N/A")
171
+
172
+ @ui_util.screenshot_on_error
173
+ def test_delete_trigger(self):
174
+ """Test deleting a trigger."""
175
+ # Create a trigger
176
+ db_util.create_queue_trigger("Queue trigger", "Queue Process", "Queue Name", "Queue path", "Queue args", False, False, 25, 0, [])
177
+ ui_util.refresh_ui(self.browser)
178
+
179
+ # Click trigger
180
+ ui_util.click_table_row(self.browser, "trigger_tab_trigger_table", 0)
181
+
182
+ # Delete trigger
183
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_popup_delete_button]").click()
184
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=popup_option1_button]").click()
185
+
186
+ # Check result
187
+ time.sleep(1)
188
+ triggers = db_util.get_all_triggers()
189
+ self.assertEqual(len(triggers), 0)
190
+
191
+ @ui_util.screenshot_on_error
192
+ def test_enable_disable(self):
193
+ """Test disabling and enabling a trigger."""
194
+ # Create a trigger
195
+ db_util.create_queue_trigger("Queue trigger", "Queue Process", "Queue Name", "Queue path", "Queue args", False, False, 25, 0, [])
196
+ ui_util.refresh_ui(self.browser)
197
+
198
+ # Click trigger
199
+ ui_util.click_table_row(self.browser, "trigger_tab_trigger_table", 0)
200
+
201
+ # Disable trigger
202
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_popup_disable_button]").click()
203
+ time.sleep(1)
204
+
205
+ trigger = db_util.get_all_triggers()[0]
206
+ self.assertEqual(trigger.process_status, TriggerStatus.PAUSED)
207
+
208
+ # Enable trigger
209
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_popup_enable_button]").click()
210
+ time.sleep(1)
211
+
212
+ trigger = db_util.get_all_triggers()[0]
213
+ self.assertEqual(trigger.process_status, TriggerStatus.IDLE)
214
+
215
+ # Close trigger
216
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_popup_cancel_button]").click()
217
+
218
+ @ui_util.screenshot_on_error
219
+ def test_edit_trigger(self):
220
+ """Test editing a trigger."""
221
+ # Create a trigger
222
+ db_util.create_queue_trigger("Queue trigger", "Queue Process", "Queue Name", "Queue path", "Queue args", False, False, 25, 0, [])
223
+ ui_util.refresh_ui(self.browser)
224
+
225
+ # Click trigger
226
+ ui_util.click_table_row(self.browser, "trigger_tab_trigger_table", 0)
227
+
228
+ # Edit trigger
229
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_popup_trigger_input]").send_keys(" Edit")
230
+ self.browser.find_element(By.CSS_SELECTOR, "[auto-id=trigger_popup_save_button]").click()
231
+ time.sleep(1)
232
+
233
+ # Check result
234
+ trigger = db_util.get_all_triggers()[0]
235
+ self.assertEqual(trigger.trigger_name, "Queue trigger Edit")
236
+
237
+
238
+ if __name__ == '__main__':
239
+ # unittest.main(failfast=True)
240
+ t = TestTriggerTab()
241
+ t.setUp()
242
+ t.test_single_trigger_creation()
243
+ t.tearDown()
@@ -0,0 +1,151 @@
1
+ """This module contains functions to help with ui tests."""
2
+
3
+ import subprocess
4
+ import os
5
+ import sys
6
+ import time
7
+ from typing import Callable
8
+ from pathlib import Path
9
+
10
+ from selenium import webdriver
11
+ from selenium.webdriver.common.by import By
12
+ from selenium.webdriver.common.keys import Keys
13
+ from selenium.common.exceptions import WebDriverException
14
+ from selenium.webdriver.remote.webelement import WebElement
15
+
16
+ from OpenOrchestrator.orchestrator.application import get_free_port
17
+
18
+
19
+ ENCRYPTION_KEY = None
20
+
21
+
22
+ def open_orchestrator() -> webdriver.Chrome:
23
+ """Open Orchestrator in a Selenium Chrome browser.
24
+
25
+ Raises:
26
+ RuntimeError: If the Orchestrator app didn't start.
27
+
28
+ Returns:
29
+ The Chrome browser logged in to Orchestrator.
30
+ """
31
+ conn_string = os.environ['CONN_STRING']
32
+ os.environ['ORCHESTRATOR_TEST'] = "True"
33
+
34
+ port = get_free_port()
35
+ subprocess.Popen([sys.executable, "-m", "OpenOrchestrator", "o", "--port", str(port), "--dont_show"]) # pylint: disable=consider-using-with
36
+
37
+ chrome_options = webdriver.ChromeOptions()
38
+ chrome_options.add_argument("--disable-search-engine-choice-screen")
39
+ browser = webdriver.Chrome(options=chrome_options)
40
+ browser.implicitly_wait(2)
41
+ browser.maximize_window()
42
+ for _ in range(5):
43
+ try:
44
+ browser.get(f"http://localhost:{port}")
45
+ break
46
+ except WebDriverException:
47
+ pass
48
+ else:
49
+ raise RuntimeError("Couldn't connect to app after 5 tries.")
50
+
51
+ # Wait for the ui to load
52
+ time.sleep(1)
53
+
54
+ conn_input = browser.find_element(By.CSS_SELECTOR, "input[auto-id=connection_frame_conn_input]")
55
+ conn_input.send_keys(Keys.CONTROL, "a", Keys.DELETE)
56
+ conn_input.send_keys(conn_string)
57
+
58
+ browser.find_element(By.CSS_SELECTOR, "button[auto-id=settings_tab_key_button]").click()
59
+ browser.find_element(By.CSS_SELECTOR, "button[auto-id=connection_frame_conn_button]").click()
60
+
61
+ global ENCRYPTION_KEY # pylint: disable=global-statement
62
+ ENCRYPTION_KEY = browser.find_element(By.CSS_SELECTOR, "input[auto-id=connection_frame_key_input]").get_attribute("value")
63
+
64
+ time.sleep(1)
65
+
66
+ return browser
67
+
68
+
69
+ def refresh_ui(browser: webdriver.Chrome):
70
+ """Press the refresh button."""
71
+ browser.find_element(By.CSS_SELECTOR, "[auto-id=refresh_button").click()
72
+ time.sleep(0.5)
73
+
74
+
75
+ def monkey_patch_send_keys():
76
+ """Monkey patch the WebElement.send_keys function with a
77
+ slower version. This works better with NiceGui elements.
78
+ """
79
+ old_send_keys = WebElement.send_keys
80
+
81
+ def send_slow_keys(element: WebElement, *value: str | list):
82
+ """Send keys to an element one at a time.
83
+ If "value" is a list fall back to the default behavior.
84
+ """
85
+ if len(value) == 1 and isinstance(value[0], str):
86
+ for c in value[0]:
87
+ old_send_keys(element, c)
88
+ else:
89
+ old_send_keys(element, value)
90
+
91
+ WebElement.send_keys = send_slow_keys
92
+
93
+
94
+ monkey_patch_send_keys()
95
+
96
+
97
+ def get_table_data(browser: webdriver.Chrome, auto_id: str) -> list[list[str]]:
98
+ """Get the data from a table.
99
+
100
+ Args:
101
+ browser: The browser object to perform the action.
102
+ auto_id: The automation id of the table.
103
+
104
+ Returns:
105
+ A 2D list of strings of all the data in the table.
106
+ """
107
+ table = browser.find_element(By.CSS_SELECTOR, f"[auto-id={auto_id}]")
108
+ rows = table.find_element(By.TAG_NAME, "tbody").find_elements(By.TAG_NAME, "tr")
109
+
110
+ data = []
111
+ for row in rows:
112
+ fields = row.find_elements(By.TAG_NAME, "td")
113
+ data.append([f.text for f in fields])
114
+
115
+ return data
116
+
117
+
118
+ def click_table_row(browser: webdriver.Chrome, auto_id: str, index: int):
119
+ """Click a row in a table.
120
+
121
+ Args:
122
+ browser: The browser to perform the action.
123
+ auto_id: The automation id of the table.
124
+ index: The 0-based index of the row to click.
125
+ """
126
+ table = browser.find_element(By.CSS_SELECTOR, f"[auto-id={auto_id}]")
127
+ rows = table.find_element(By.TAG_NAME, "tbody").find_elements(By.TAG_NAME, "tr")
128
+ rows[index].click()
129
+ time.sleep(0.5)
130
+
131
+
132
+ def screenshot_on_error(test_func: Callable) -> Callable:
133
+ """A decorator that can be used on test functions to take a Selenium screenshot on errors.
134
+ The unittest class must have a instance variable called "browser" which is the Selenium webdriver.
135
+
136
+ Args:
137
+ test_func: The test function to decorate.
138
+ """
139
+ def wrapper(*args, **kwargs):
140
+ browser: webdriver.Chrome = args[0].browser
141
+
142
+ try:
143
+ test_func(*args, **kwargs)
144
+ except Exception:
145
+ folder = Path("error_screenshots")
146
+ folder.mkdir(exist_ok=True)
147
+ path = folder / Path(f"{test_func.__name__}.png")
148
+ browser.save_screenshot(str(path))
149
+ raise
150
+
151
+ return wrapper
@@ -0,0 +1,158 @@
1
+ Metadata-Version: 2.4
2
+ Name: OpenOrchestrator
3
+ Version: 2.0.0rc2
4
+ Summary: A package containing OpenOrchestrator and OpenOrchestrator Scheduler
5
+ Author-email: ITK Development <itk-rpa@mkb.aarhus.dk>
6
+ Project-URL: Homepage, https://github.com/itk-dev-rpa/OpenOrchestrator
7
+ Project-URL: Bug Tracker, https://github.com/itk-dev-rpa/OpenOrchestrator/issues
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: Microsoft :: Windows
11
+ Requires-Python: >=3.11
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: cryptography==45.0.*
15
+ Requires-Dist: cronsim==2.6
16
+ Requires-Dist: SQLAlchemy==2.0.*
17
+ Requires-Dist: pyodbc==5.2.*
18
+ Requires-Dist: nicegui==2.22.*
19
+ Provides-Extra: dev
20
+ Requires-Dist: pylint; extra == "dev"
21
+ Requires-Dist: flake8; extra == "dev"
22
+ Requires-Dist: selenium; extra == "dev"
23
+ Provides-Extra: alembic
24
+ Requires-Dist: alembic==1.16.*; extra == "alembic"
25
+ Dynamic: license-file
26
+
27
+ # OpenOrchestrator
28
+
29
+ Read the documentation [here](https://itk-dev-rpa.github.io/OpenOrchestrator-docs).
30
+
31
+ Package is located at [Pypi](https://pypi.org/project/OpenOrchestrator/).
32
+
33
+ ## Usage for Orchestrator admins
34
+
35
+ OpenOrchestrator provides a commandline interface (cli) to start the different parts
36
+ of the application.
37
+
38
+ For a full explanation run `python -m OpenOrchestrator -h` in the command line.
39
+
40
+ The most common commands would be:
41
+
42
+ `python -m OpenOrchestrator o` for orchestrator.
43
+
44
+ `python -m OpenOrchestrator s` for scheduler.
45
+
46
+ ## Usage for RPA developers
47
+
48
+ Import the connection module to your RPA code and get access to the orchestrator methods;
49
+
50
+ - logging status to OpenOrchestrator
51
+ - getting credentials and constants from OpenOrchestrator
52
+ - creating, getting and updating job elements in a queue
53
+
54
+ Run the code with arguments
55
+
56
+ ```bash
57
+ python run.py "<process name>" "<connection string>" "<secret key>" "<arguments>"
58
+ ```
59
+
60
+ ```python
61
+ # run.py
62
+ # connect to OpenOrchestrator and log something
63
+ from OpenOrchestrator.orchestrator_connection.connection import OrchestratorConnection
64
+
65
+ oc = OrchestratorConnection.create_connection_from_args()
66
+ oc.log_trace("open orchestrator connected.")
67
+ ```
68
+
69
+ ## Installation
70
+
71
+ Requires Python 3.10 or later.
72
+
73
+ Install using `pip install OpenOrchestrator`
74
+
75
+ ### Creating or upgrading a database
76
+
77
+ If you need to create a new database or upgrade to a new revision follow these steps:
78
+
79
+ 1. Download this repository either via Github or Git.
80
+ 2. Open a command line in the project folder.
81
+ 3. Run the following commands:
82
+
83
+ ```bash
84
+ python -m venv .venv
85
+ .venv\scripts\activate
86
+ pip install .[alembic]
87
+
88
+ python -m OpenOrchestrator upgrade "<Your connection string here>"
89
+ ```
90
+
91
+ This will automatically bring you to the newest revision of the database.
92
+
93
+ ## Contributing
94
+
95
+ ### Setup
96
+
97
+ To start developing for OpenOrchestrator pull the current develop branch using GIT.
98
+
99
+ In the new folder run the following commands in the command line:
100
+
101
+ ```bash
102
+ python -m venv .venv
103
+ .venv\scripts\activate
104
+ pip install -e .
105
+ ```
106
+
107
+ This will create a new virtual environment and install the project in 'editable' mode.
108
+ This means that any changes to the code is automatically included in the installation.
109
+
110
+ ### Automated Tests
111
+
112
+ OpenOrchestrator contains automated tests.
113
+
114
+ Before running the tests you need to define the following environment variables:
115
+
116
+ ```bash
117
+ SET CONN_STRING="<connection string to test db>"
118
+ ```
119
+
120
+ Examples of connection strings:
121
+
122
+ - mssql+pyodbc://localhost\SQLEXPRESS/OO_Unittest?driver=ODBC+Driver+17+for+SQL+Server
123
+ - sqlite+pysqlite:///test_db.db
124
+
125
+ To run tests execute the following command from the main directory:
126
+
127
+ ```bash
128
+ python -m unittest discover
129
+ ```
130
+
131
+ ### Manual Tests
132
+
133
+ Not all functionality is covered by automated tests. Especially the Scheduler app is not covered well.
134
+
135
+ Refer to the `manual_tests.txt` file for a list of things that should be tested.
136
+
137
+ ### Creating new database revisions
138
+
139
+ If your update requires a change to the database schemas you need to create a new revision schema in the `alembic` folder.
140
+
141
+ This can mostly be done automatically by the following steps:
142
+
143
+ 1. First make sure your database is on the __previous__ version of the database schema.
144
+
145
+ 2. Make sure any new ORM classes are imported in `alembic/env.py`.
146
+
147
+ 3. Then run the following command (replacing the connection string and message):
148
+
149
+ ```bash
150
+ alembic -x "connection_string" revision --autogenerate -m "Some useful message"
151
+ ```
152
+
153
+ This will create a new file in the `alembic/versions` folder with a random prefix and then your message as the name.
154
+
155
+ 4. Open the file and make sure the contents make sense. Obvious changes are detected automatically
156
+ but some changes might not be.
157
+
158
+ 5. Update the expected revision number in `db_util.py > check_database_revision`.
@@ -0,0 +1,55 @@
1
+ OpenOrchestrator/__init__.py,sha256=i4Ir68mBu7rrqhlEE6Qh_uyble599jaEWCEMf8g58tI,179
2
+ OpenOrchestrator/__main__.py,sha256=BohTEX2clH9DZVAVVW1uVGC_MJlvvyoKcNugXhSahEY,3072
3
+ OpenOrchestrator/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ OpenOrchestrator/common/connection_frame.py,sha256=sjfx1NUVBnlHW0krh05HzFohMcg2KLEi1QNWQN4M9E0,3373
5
+ OpenOrchestrator/common/crypto_util.py,sha256=VJ7fgxyjrW-bGQmsGVKXLUk98pcZGXG4faHtwxCWDDk,2440
6
+ OpenOrchestrator/common/datetime_util.py,sha256=4I70tz6WZGZ3xdxodJhdHJID14Y9myJTShFArOWCSpA,534
7
+ OpenOrchestrator/database/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ OpenOrchestrator/database/base.py,sha256=6GaBNOagnzW2eoYeB_uhBMBAxqeMac9yTkzHASWgLAE,235
9
+ OpenOrchestrator/database/constants.py,sha256=wg1kGa0zk9iBHoeiwauHTcndB8w0MVfq6CHemKh1U5k,2091
10
+ OpenOrchestrator/database/db_util.py,sha256=K1_LGFSlYkslCklLYA5wdN0G7Otq20RZYsB5ccn0TzI,31507
11
+ OpenOrchestrator/database/logs.py,sha256=EGbHnD4wGzsNDa5N7N_7xuTyGXcbNApapBUsQnwAX5M,1310
12
+ OpenOrchestrator/database/queues.py,sha256=GLHJz194j2EctYlgN1paZecDPHlJokPCo3Q7qQZM8PQ,1979
13
+ OpenOrchestrator/database/schedulers.py,sha256=7eims2tMjKO6sl255_u9eXcVG6662ZSp-v_VQMKd0Vk,1372
14
+ OpenOrchestrator/database/triggers.py,sha256=xNNKSanSoyIEcQgiBF91Bch4YsMOWeMoQ1ZE1PcL3dE,3878
15
+ OpenOrchestrator/database/truncated_string.py,sha256=v5TvMn3Sof9sG3RzHZx5_wRGBZ5IcJTsiyIO20MjehA,521
16
+ OpenOrchestrator/orchestrator/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ OpenOrchestrator/orchestrator/application.py,sha256=rs5GdM6lkePjGxnUQVSeSEQWHtf2_MaieE0GaWaV-gE,3726
18
+ OpenOrchestrator/orchestrator/datetime_input.py,sha256=zLYjlS3t8BQfyzlXkyQSpM1erC2dOBggwCwi9wq8b1Y,3085
19
+ OpenOrchestrator/orchestrator/test_helper.py,sha256=NDO7pcJg1GXK7R89WveQ3f-95T-FaOnnm_prB8UylmQ,660
20
+ OpenOrchestrator/orchestrator/popups/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
+ OpenOrchestrator/orchestrator/popups/constant_popup.py,sha256=n6RNpzQYpaLi8w-6tQTp64teobmX9JKc7seuzUve_QA,3717
22
+ OpenOrchestrator/orchestrator/popups/credential_popup.py,sha256=ncTas5zKJYxGFc7x9YUkINb2B6w7dNAQB5IHjLn1FbA,4254
23
+ OpenOrchestrator/orchestrator/popups/generic_popups.py,sha256=fBtl4vYRhKccoz1QViVjt-uZ6VrE2YrGCSleKzI_rm8,1530
24
+ OpenOrchestrator/orchestrator/popups/trigger_popup.py,sha256=w2rkJBSPb8cEsf5DPHIThxQuc8FCyCHci35Adr0__Gg,11061
25
+ OpenOrchestrator/orchestrator/tabs/constants_tab.py,sha256=wuMMAxcDXKFXbwCYbFXV0e-ZPBLuknNmQL-YDCDtRik,2650
26
+ OpenOrchestrator/orchestrator/tabs/logging_tab.py,sha256=p6dnEZfj9mxsDOyREzOq0Pnnx38IuGd57hex-zYEYb0,3701
27
+ OpenOrchestrator/orchestrator/tabs/queue_tab.py,sha256=HZTCkxA1-nTd9XPToupXAc4pDO-VIVR7IWpEli_Hekw,6013
28
+ OpenOrchestrator/orchestrator/tabs/schedulers_tab.py,sha256=YVWH0hjOWWvzYjVbFUvS7M2xwzXNrY9VLm8hdG-rsa8,2059
29
+ OpenOrchestrator/orchestrator/tabs/settings_tab.py,sha256=j6j9NYi2L6br--Dk1LP4UnUll8NtMVEjhWp_i3RnHDM,695
30
+ OpenOrchestrator/orchestrator/tabs/trigger_tab.py,sha256=eW8aJuJzCAy-93-EQdpfzDcsShPMJEZPSWV9uDHuIso,4405
31
+ OpenOrchestrator/orchestrator_connection/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
+ OpenOrchestrator/orchestrator_connection/connection.py,sha256=dDKyJI7PBxcbT0_6swLUvKT0myO3o-eTWrUUeXh4pxg,9675
33
+ OpenOrchestrator/scheduler/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
+ OpenOrchestrator/scheduler/application.py,sha256=TM9iXuFGW27ULESCzcGKcprGthWxOfArbCn3Qp6TI5Q,1632
35
+ OpenOrchestrator/scheduler/run_tab.py,sha256=WkfePFe1bXda2LdiMW8f4Y4AmgaB_BcW3jh4GobZpw8,6117
36
+ OpenOrchestrator/scheduler/runner.py,sha256=eNN6K6qxdQjiXZAHquqJ2vBnnv9TA2bfaP-yQgLYk0w,8470
37
+ OpenOrchestrator/scheduler/settings_tab.py,sha256=AUIM60IZKSUIn3yKjhG5qRLlEluj8zV0BzvF7dD52ig,3980
38
+ OpenOrchestrator/scheduler/util.py,sha256=VEQ9dRgP60sOoL6uvTlvqSm0DRAGBbDIYVwPGn7krP8,185
39
+ OpenOrchestrator/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
+ OpenOrchestrator/tests/db_test_util.py,sha256=nr6FHXGvQl-0rkpRtnE3sZLk7MKZ4g5tLMoWyUey0Sc,1498
41
+ OpenOrchestrator/tests/test_db_util.py,sha256=p3XOELnU5xhPoYhm61z4bQB_H3UyF_vRjXMz5tJz_G8,13532
42
+ OpenOrchestrator/tests/test_orchestrator_connection.py,sha256=Bd_i03tZmY_dNzEfin7pT552xiivH8o7ayR0GS1xHwU,5786
43
+ OpenOrchestrator/tests/test_trigger_polling.py,sha256=sIMLLtcmHIrBlP8JvSU3HoPZQnJD5c0nnGH567M4hoE,6593
44
+ OpenOrchestrator/tests/ui_tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
45
+ OpenOrchestrator/tests/ui_tests/test_constants_tab.py,sha256=Xw4JIi61-tItpAy_b3joSWjDIMTv7MN79H-AlYx7pMM,7572
46
+ OpenOrchestrator/tests/ui_tests/test_logging_tab.py,sha256=rTtnY-DHAbRqGzuoJWkSWD1xU1Q4ZVaLKZ5lEx0pHdI,7067
47
+ OpenOrchestrator/tests/ui_tests/test_queues_tab.py,sha256=riZph2PapSnf88m8plCEV7UVHLVWrZ-zJz8RYDKoe0U,5491
48
+ OpenOrchestrator/tests/ui_tests/test_schedulers_tab.py,sha256=5-8Hx4qlzbs3chz9bew_JoeRpKZFmQ8iFVp0Y03MBlo,1763
49
+ OpenOrchestrator/tests/ui_tests/test_trigger_tab.py,sha256=9VgCQY0plOF8xjK5OCEN-07dMaOT_ObttKrSOdJW8p0,12521
50
+ OpenOrchestrator/tests/ui_tests/ui_util.py,sha256=1v4hmXiQ0Aunth8IO-iUFLDiROMpDm1iOLZUGmuBa4A,4822
51
+ openorchestrator-2.0.0rc2.dist-info/licenses/LICENSE,sha256=4-Kjm-gkbiOLCBYMzsVJZEepdsm2vk8QesNOASvi9mg,1068
52
+ openorchestrator-2.0.0rc2.dist-info/METADATA,sha256=MN1MIvn-lf40W51pLZin6P69PvDsyqSmWRhm0xuMg2o,4755
53
+ openorchestrator-2.0.0rc2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
54
+ openorchestrator-2.0.0rc2.dist-info/top_level.txt,sha256=2btKMQESHuRC_ICbCjHTHH_-us2G7CyeskeaSTTL07Y,17
55
+ openorchestrator-2.0.0rc2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (76.0.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5