Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions OpenOrchestrator/database/db_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -899,6 +899,62 @@ def _apply_filters(query):
return elements_tuple


def get_queue_element(element_id: UUID | str) -> QueueElement:
"""Get a specific QueueElement from id.

Args:
element_id: ID of QueueElement to get.

Returns:
QueueElement with the requested ID.
"""
if isinstance(element_id, str):
element_id = UUID(element_id)
with _get_session() as session:
query = select(QueueElement).where(QueueElement.id == element_id)
return session.scalar(query)


def update_queue_element(element_id: str, reference: str | None = None, status: QueueStatus | None = None, data: str | None = None, message: str | None = None,
created_by: str | None = None, created_date: datetime | None = None, start_date: datetime | None = None, end_date: datetime | None = None):
"""Update fields of specific QueueElement.

Args:
element_id: ID of QueueElement to update.
reference: New value for reference. Defaults to None.
status: New value for status. Defaults to None.
data: New value for data. Defaults to None.
message: New value for message. Defaults to None.
created_by: New value for created_by. Defaults to None.
created_date: New value for created_date. Defaults to None.
start_date: New value for start_date. Defaults to None.
end_date: New value for end_date. Defaults to None.
"""
with _get_session() as session:
query = select(QueueElement).where(QueueElement.id == element_id)
q_element: QueueElement = session.scalar(query)

if q_element:
if reference:
q_element.reference = reference
if status:
q_element.status = status
if data:
q_element.data = data
if message:
q_element.message = message
if created_date:
q_element.created_date = created_date
if start_date:
q_element.start_date = start_date
if end_date:
q_element.end_date = end_date
if created_by:
q_element.created_by = created_by
session.commit()
session.refresh(q_element)


def get_queue_count() -> dict[str, dict[QueueStatus, int]]:
"""Count the number of queue elements of each status for every queue.

Expand Down
6 changes: 4 additions & 2 deletions OpenOrchestrator/orchestrator/datetime_input.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@

class DatetimeInput(ui.input):
"""A datetime input with a button to show a date and time picker dialog."""
PY_FORMAT = "%d-%m-%Y %H:%M"
VUE_FORMAT = "DD-MM-YYYY HH:mm"
PY_FORMAT = "%d-%m-%Y %H:%M:%S"
VUE_FORMAT = "DD-MM-YYYY HH:mm:ss"

def __init__(self, label: str, on_change: Optional[Callable[..., Any]] = None, allow_empty: bool = False) -> None:
"""Create a new DatetimeInput.
Expand Down Expand Up @@ -75,6 +75,8 @@ def set_datetime(self, value: datetime) -> None:
Args:
value: The new datetime value.
"""
if not value:
return
self.value = value.strftime(self.PY_FORMAT)

def _on_change(self, func: Optional[Callable[..., Any]]) -> None:
Expand Down
4 changes: 2 additions & 2 deletions OpenOrchestrator/orchestrator/popups/constant_popup.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def __init__(self, constant_tab: ConstantTab, constant: Constant | None = None):
self.cancel_button = ui.button("Cancel", on_click=self.dialog.close)

if constant:
self.delete_button = ui.button("Delete", color='red', on_click=self._delete_constant)
self.delete_button = ui.button("Delete", color='negative', on_click=self._delete_constant)

self._define_validation()
self._pre_populate()
Expand Down Expand Up @@ -92,7 +92,7 @@ def _create_constant(self):
async def _delete_constant(self):
if not self.constant:
return
if await question_popup(f"Delete constant '{self.constant.name}?", "Delete", "Cancel", color1='red'):
if await question_popup(f"Delete constant '{self.constant.name}?", "Delete", "Cancel", color1='negative'):
db_util.delete_constant(self.constant.name)
self.dialog.close()
self.constant_tab.update()
4 changes: 2 additions & 2 deletions OpenOrchestrator/orchestrator/popups/credential_popup.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def __init__(self, constant_tab: ConstantTab, credential: Credential | None = No
self.cancel_button = ui.button("Cancel", on_click=self.dialog.close)

if credential:
self.delete_button = ui.button("Delete", color='red', on_click=self._delete_credential)
self.delete_button = ui.button("Delete", color='negative', on_click=self._delete_credential)

self._define_validation()
self._pre_populate()
Expand Down Expand Up @@ -96,7 +96,7 @@ async def _delete_credential(self):
"""Delete the selected credential."""
if not self.credential:
return
if await question_popup(f"Delete credential '{self.credential.name}'?", "Delete", "Cancel", color1='red'):
if await question_popup(f"Delete credential '{self.credential.name}'?", "Delete", "Cancel", color1='negative'):
db_util.delete_credential(self.credential.name)
self.dialog.close()
self.constant_tab.update()
145 changes: 96 additions & 49 deletions OpenOrchestrator/orchestrator/popups/queue_element_popup.py
Original file line number Diff line number Diff line change
@@ -1,74 +1,121 @@
"""Class for queue element popups."""
import json
from typing import Callable

from nicegui import ui

from OpenOrchestrator.orchestrator import test_helper

# Styling constants
LABEL = 'text-subtitle2 text-grey-7'
VALUE = 'text-body1 gap-0'
SECTION = 'gap-0'
from OpenOrchestrator.database import db_util
from OpenOrchestrator.orchestrator.popups import generic_popups
from OpenOrchestrator.orchestrator.datetime_input import DatetimeInput
from OpenOrchestrator.database.queues import QueueStatus, QueueElement


# pylint: disable-next=too-few-public-methods, too-many-instance-attributes
class QueueElementPopup():
"""A popup to display queue element data.
"""
def __init__(self, row_data: ui.row):
def __init__(self, queue_element: QueueElement | None, on_dialog_close_callback: Callable, queue_name: str):
"""Show a dialogue with details of the row selected.

Args:
row_data: Data from the row selected.
"""
with ui.dialog() as dialog:
with ui.card().style('min-width: 37.5rem; max-width: 50rem'):

with ui.row().classes('w-full justify-between items-start mb-4'):
with ui.column().classes(SECTION + ' mb-4'):
ui.label("Reference:").classes(LABEL)
self.reference_text = ui.label(row_data.get('Reference', 'N/A')).classes('text-h5')
with ui.column().classes(SECTION + ' items-end'):
ui.label('Status').classes(LABEL)
self.status_text = ui.label(row_data.get('Status', 'N/A')).classes('text-h5')
ui.separator()

with ui.column().classes('gap-1'):
self.on_dialog_close_callback = on_dialog_close_callback
self.queue_element = queue_element
self.queue_name = queue_name
with ui.dialog() as self.dialog:
with ui.card().style('min-width: 37.5rem; max-width: 50rem').classes('gap-0'):

data_text = row_data.get('Data', None)
if data_text and len(data_text) > 0:
with ui.row().classes('w-full'):
ui.label('Data').classes(LABEL)
try:
data = json.loads(data_text)
formatted_data = json.dumps(data, indent=2, ensure_ascii=False)
self.data_text = ui.code(formatted_data).classes('h-12.5rem w-full').style('max-width: 37.5rem;')
except (json.JSONDecodeError, TypeError):
self.data_text = ui.code(data_text).classes('h-12.5rem w-full').style('max-width: 37.5rem;')

message_text = row_data.get('Message')
if message_text and len(message_text) > 0:
with ui.row().classes('w-full mt-4'):
ui.label('Message').classes(LABEL)
self.message_text = ui.label(message_text).classes(VALUE)
with ui.row().classes('w-full justify-between items-start'):
with ui.column().classes('gap-0'):
ui.label("ID:").classes('text-subtitle2 text-grey-7')
self.id_text = ui.label()
self.reference = ui.input("Reference")
with ui.column().classes('gap-0 items-end'):
self.created_by_label = ui.label("Created by:").classes('text-subtitle2 text-grey-7')
self.created_by = ui.label()
self.status = ui.select(options={status.name: status.value for status in QueueStatus}, label="Status").classes("w-32")

with ui.column().classes('gap-0'):
with ui.row().classes('w-full'):
self.data_field = ui.textarea("Data").classes('w-full')
with ui.row().classes('w-full mt-4'):
self.message = ui.input('Message').classes('w-full')
with ui.row().classes('w-full mt-4'):
with ui.column().classes('flex-1'):
ui.label("Created Date:").classes(LABEL)
self.created_date = ui.label(row_data.get('Created Date', 'N/A')).classes(VALUE)
self.created_date = DatetimeInput("Created Date", allow_empty=True)
with ui.column().classes('flex-1'):
ui.label("Start Date:").classes(LABEL)
self.start_date = ui.label(row_data.get('Start Date', 'N/A')).classes(VALUE)
self.start_date = DatetimeInput("Start Date", allow_empty=True)
with ui.column().classes('flex-1'):
ui.label("End Date:").classes(LABEL)
self.end_date = ui.label(row_data.get('End Date', 'N/A')).classes(VALUE)
self.end_date = DatetimeInput("End Date", allow_empty=True)

with ui.row().classes('w-full mt-4'):
ui.label("Created By:").classes(LABEL)
self.created_by = ui.label(row_data.get('Created By', 'N/A')).classes(VALUE)
with ui.row().classes('w-full'):
ui.label("ID:").classes(LABEL)
self.id_text = ui.label(row_data.get('ID', 'N/A')).classes(VALUE)
with ui.row().classes('w-full mt-4'):
self.save_button = ui.button(text='Save', on_click=self._save_and_close).classes('mt-4')
self.close_button = ui.button('Close', on_click=self._close_dialog).classes('mt-4')
self.delete_button = ui.button(text='Delete', on_click=self._delete_element, color="negative").classes('mt-4')

ui.button('Close', on_click=dialog.close).classes('mt-4')
test_helper.set_automation_ids(self, "queue_element_popup")
dialog.open()
self.dialog.open()
self._pre_populate()

def _pre_populate(self):
"""Pre populate the inputs with an existing credential."""
if self.queue_element:
if not self.queue_element.created_by:
self.created_by_label.visible = False
else:
self.created_by.text = self.queue_element.created_by
self.id_text.text = self.queue_element.id
self.reference.value = self.queue_element.reference
self.status.value = self.queue_element.status.name
self.message.value = self.queue_element.message
self.data_field.value = self._prettify_json(self.queue_element.data)
self.created_date.set_datetime(self.queue_element.created_date)
self.start_date.set_datetime(self.queue_element.start_date)
self.end_date.set_datetime(self.queue_element.end_date)
else:
self.id_text.text = "NOT SAVED"
self.created_by.text = "Orchestrator UI"
self.status.value = "NEW"
self.delete_button.visible = False

def _prettify_json(self, json_string: str) -> str:
if not json_string:
return None
try:
data = json.loads(json_string)
return json.dumps(data, indent=2, ensure_ascii=False)
except ValueError:
return json_string

def _close_dialog(self):
self.on_dialog_close_callback()
self.dialog.close()

async def _delete_element(self):
if not self.queue_element:
return
if await generic_popups.question_popup(f"Delete element '{self.queue_element.id}'?", "Delete", "Cancel", color1='negative'):
db_util.delete_queue_element(self.queue_element.id)
ui.notify("Queue element deleted", type='positive')
self._close_dialog()

def _save_element(self):
if not self.queue_element:
self.queue_element = db_util.create_queue_element(self.queue_name)
self.id_text.text = self.queue_element.id
ui.notify("New queue element created", type="positive")
db_util.update_queue_element(self.queue_element.id,
reference=self.reference.value,
status=self.status.value,
data=self.data_field.value,
message=self.message.value,
created_by=self.created_by.text,
created_date=self.created_date.get_datetime(),
start_date=self.start_date.get_datetime(),
end_date=self.end_date.get_datetime())

def _save_and_close(self):
self._save_element()
self._close_dialog()
27 changes: 24 additions & 3 deletions OpenOrchestrator/orchestrator/tabs/queue_tab.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,19 +62,20 @@ def update(self):
def _row_click(self, event):
row = event.args[1]
queue_name = row["Queue Name"]
QueuePopup(queue_name)
QueuePopup(queue_name, self.update)


# pylint: disable-next=too-few-public-methods
class QueuePopup():
"""A popup that displays queue elements in a queue."""
def __init__(self, queue_name: str):
def __init__(self, queue_name: str, update_callback):
self.queue_name = queue_name
self.order_by = "Created Date"
self.order_descending = False
self.page = 1
self.rows_per_page = 25
self.queue_count = 100
self.update_callback = update_callback # To make sure the main table updates changes to queue elements.

with ui.dialog(value=True).props('full-width full-height') as dialog, ui.card():
with ui.row().classes("w-full"):
Expand All @@ -95,10 +96,16 @@ def __init__(self, queue_name: str):
self.close_button = ui.button(icon="close", on_click=dialog.close)
with ui.scroll_area().classes("h-full"):
self.table = ui.table(columns=ELEMENT_COLUMNS, rows=[], row_key='ID', title=queue_name, pagination={'rowsPerPage': self.rows_per_page, 'rowsNumber': self.queue_count}).classes("w-full sticky-header h-[calc(100vh-200px)] overflow-auto")
self.table.on('rowClick', lambda e: QueueElementPopup(e.args[1]))
self.table.on('rowClick', lambda e: self._open_queue_element_popup(e.args[1]))
self.table.on('request', self._on_table_request)

with self.table.add_slot("top"):
ui.label(self.queue_name).classes("text-xl")
ui.space()
self.new_button = ui.button(icon='playlist_add', on_click=self._open_create_dialog)

self._update()
self.update_callback()
test_helper.set_automation_ids(self, "queue_popup")

def _dense_table(self, value: bool):
Expand Down Expand Up @@ -158,3 +165,17 @@ def _update_pagination(self, queue_count):
"""
self.queue_count = queue_count
self.table.pagination = {"rowsNumber": self.queue_count, "page": self.page, "rowsPerPage": self.rows_per_page, "sortBy": self.order_by, "descending": self.order_descending}

def _open_queue_element_popup(self, row_data: ui.row):
"""Open editable popup for specified row.

Args:
row_data: Row data from row clicked.
"""
queue_element = db_util.get_queue_element(row_data.get("ID"))
print("Queue element:", vars(queue_element))
print("Queue element __dict__:", queue_element.__dict__)
QueueElementPopup(queue_element=queue_element, on_dialog_close_callback=self._update, queue_name=self.queue_name)

def _open_create_dialog(self):
QueueElementPopup(None, on_dialog_close_callback=self._update, queue_name=self.queue_name)
4 changes: 2 additions & 2 deletions OpenOrchestrator/tests/ui_tests/test_logging_tab.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,10 +140,10 @@ def _set_date_filter(self, from_date: datetime | None, to_date: datetime | None)
to_input.send_keys(Keys.CONTROL, "a", Keys.DELETE)

if from_date:
from_input.send_keys(from_date.strftime("%d-%m-%Y %H:%M"))
from_input.send_keys(from_date.strftime("%d-%m-%Y %H:%M:%S"))

if to_date:
to_input.send_keys(to_date.strftime("%d-%m-%Y %H:%M"))
to_input.send_keys(to_date.strftime("%d-%m-%Y %H:%M:%S"))

def _set_process_filter(self, index: int):
"""Select a process in the process filter.
Expand Down
Loading
Loading