Added Jira connectivity testing. Added alert management in AdminForm
This commit is contained in:
@@ -66,4 +66,10 @@ def post(session, _id: str, args: dict):
|
||||
def post(session, _id: str):
|
||||
logger.debug(f"Entering {Routes.ConfigureJiraCancel} with args {debug_session(session)}, {_id=}")
|
||||
instance = InstanceManager.get(session, _id)
|
||||
return instance.cancel_jira_settings()
|
||||
return instance.cancel_jira_settings()
|
||||
|
||||
@rt(Routes.ConfigureJiraTest)
|
||||
def post(session, _id: str, args: dict):
|
||||
logger.debug(f"Entering {Routes.ConfigureJiraTest} with args {debug_session(session)}, {_id=}, {args=}")
|
||||
instance = InstanceManager.get(session, _id)
|
||||
return instance.test_jira_settings(args)
|
||||
@@ -1,10 +1,31 @@
|
||||
from fastcore.basics import NotStr
|
||||
|
||||
icon_jira = NotStr("""<svg name="jara" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
|
||||
icon_jira = NotStr("""<svg name="jira" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<style>.a{fill:none;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;}</style>
|
||||
</defs>
|
||||
<path class="a" d="M5.5,22.9722h0a8.7361,8.7361,0,0,0,8.7361,8.7361h2.0556v2.0556A8.7361,8.7361,0,0,0,25.0278,42.5h0V22.9722Z"/>
|
||||
<path class="a" d="M14.2361,14.2361h0a8.7361,8.7361,0,0,0,8.7361,8.7361h2.0556v2.0556a8.7361,8.7361,0,0,0,8.7361,8.7361h0V14.2361Z"/>
|
||||
<path class="a" d="M22.9722,5.5h0a8.7361,8.7361,0,0,0,8.7361,8.7361h2.0556v2.0556A8.7361,8.7361,0,0,0,42.5,25.0278h0V5.5Z"/>
|
||||
</svg>""")
|
||||
</svg>""")
|
||||
|
||||
|
||||
icon_msg_info = NotStr("""<svg name="info" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="h-6 w-6 shrink-0 stroke-current">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
""")
|
||||
|
||||
icon_msg_success = NotStr("""<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
""")
|
||||
|
||||
icon_msg_warning = NotStr("""<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||
</svg>
|
||||
""")
|
||||
|
||||
icon_msg_error = NotStr("""<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
""")
|
||||
@@ -63,6 +63,14 @@ class AdminCommandManager(BaseCommandManager):
|
||||
"hx-swap": "outerHTML",
|
||||
"hx-vals": f'js:{{"_id": "{self._id}"}}',
|
||||
}
|
||||
|
||||
def test_jira(self):
|
||||
return {
|
||||
"hx-post": f"{ROUTE_ROOT}{Routes.ConfigureJiraTest}",
|
||||
"hx-target": f"#{self._owner.tabs_manager.get_id()}",
|
||||
"hx-swap": "outerHTML",
|
||||
"hx-vals": f'js:{{"_id": "{self._id}"}}',
|
||||
}
|
||||
|
||||
class ImportHolidaysCommandManager(BaseCommandManager):
|
||||
def __init__(self, owner):
|
||||
|
||||
@@ -6,7 +6,7 @@ from components.BaseComponent import BaseComponent
|
||||
from components.admin.admin_db_manager import AdminDbManager
|
||||
from components.admin.assets.icons import icon_jira
|
||||
from components.admin.commands import AdminCommandManager
|
||||
from components.admin.components.AdminForm import AdminFormItem, AdminFormType, AdminForm
|
||||
from components.admin.components.AdminForm import AdminFormItem, AdminFormType, AdminForm, AdminButton, AdminMessageType
|
||||
from components.admin.components.ImportHolidays import ImportHolidays
|
||||
from components.admin.constants import ADMIN_INSTANCE_ID, ADMIN_AI_BUDDY_INSTANCE_ID, ADMIN_JIRA_INSTANCE_ID
|
||||
from components.aibuddy.assets.icons import icon_brain_ok
|
||||
@@ -14,6 +14,7 @@ from components.hoildays.assets.icons import icon_holidays
|
||||
from components.tabs.components.MyTabs import MyTabs
|
||||
from components_helpers import mk_ellipsis, mk_icon
|
||||
from core.instance_manager import InstanceManager
|
||||
from core.jira import Jira
|
||||
|
||||
|
||||
class Admin(BaseComponent):
|
||||
@@ -36,7 +37,7 @@ class Admin(BaseComponent):
|
||||
hooks = {
|
||||
"on_ok": self.commands.save_ai_buddy(),
|
||||
"on_cancel": self.commands.cancel_ai_buddy(),
|
||||
"ok_title": "Apply"
|
||||
"ok_title": "Apply",
|
||||
}
|
||||
form = InstanceManager.get(self._session,
|
||||
AdminForm.create_component_id(self._session, prefix=self._id),
|
||||
@@ -68,7 +69,8 @@ class Admin(BaseComponent):
|
||||
hooks = {
|
||||
"on_ok": self.commands.save_configure_jira(),
|
||||
"on_cancel": self.commands.cancel_configure_jira(),
|
||||
"ok_title": "Apply"
|
||||
"ok_title": "Apply",
|
||||
"extra_buttons": [AdminButton("Test", self.commands.test_jira)]
|
||||
}
|
||||
|
||||
form = InstanceManager.get(self._session,
|
||||
@@ -85,7 +87,7 @@ class Admin(BaseComponent):
|
||||
return self._add_tab(ADMIN_JIRA_INSTANCE_ID, "Admin - Jira Configuration", form)
|
||||
|
||||
def update_ai_buddy_settings(self, values: dict):
|
||||
values = self.manage_lists(values)
|
||||
values = AdminForm.get_fields_values(values)
|
||||
self.db.ai_buddy.update(values, ignore_missing=True)
|
||||
return self.tabs_manager.render()
|
||||
|
||||
@@ -95,15 +97,25 @@ class Admin(BaseComponent):
|
||||
return self.tabs_manager.render()
|
||||
|
||||
def update_jira_settings(self, values: dict):
|
||||
values = self.manage_lists(values)
|
||||
values = AdminForm.get_fields_values(values)
|
||||
self.db.jira.update(values, ignore_missing=True)
|
||||
return self.tabs_manager.render()
|
||||
|
||||
|
||||
def cancel_jira_settings(self):
|
||||
tab_id = self.tabs_manager.get_tab_id(ADMIN_JIRA_INSTANCE_ID)
|
||||
self.tabs_manager.remove_tab(tab_id)
|
||||
return self.tabs_manager.render()
|
||||
|
||||
|
||||
def test_jira_settings(self, values: dict):
|
||||
values = AdminForm.get_fields_values(values)
|
||||
jira = Jira(values["user_name"], values["api_token"])
|
||||
form = self.tabs_manager.get_tab_content_by_key(ADMIN_JIRA_INSTANCE_ID)
|
||||
res = jira.test()
|
||||
if res.status_code == 200:
|
||||
form.set_message("Success !", AdminMessageType.SUCCESS)
|
||||
else:
|
||||
form.set_message(f"Error {res.status_code} - {res.text}", AdminMessageType.ERROR)
|
||||
return self.tabs_manager.render()
|
||||
|
||||
def __ft__(self):
|
||||
return Div(
|
||||
@@ -138,40 +150,3 @@ class Admin(BaseComponent):
|
||||
@staticmethod
|
||||
def create_component_id(session):
|
||||
return f"{ADMIN_INSTANCE_ID}{session['user_id']}"
|
||||
|
||||
@staticmethod
|
||||
def manage_lists(data_dict):
|
||||
"""
|
||||
Processes a dictionary of key-value pairs to reorganize keys based on specific
|
||||
criteria. If a key ends with its corresponding string value, the method extracts
|
||||
the prefix of the key (the portion of the key before the value) and groups the
|
||||
value under this prefix in a list. Otherwise, the original key-value pair is
|
||||
preserved in the resulting dictionary.
|
||||
|
||||
:param data_dict: Dictionary where each key is a string and its corresponding
|
||||
value can be of any type.
|
||||
:type data_dict: dict
|
||||
:return: A dictionary where the keys have been categorized into groups
|
||||
based on whether they end with the same string value, reorganized into
|
||||
lists, while preserving other key-value pairs as they are.
|
||||
:rtype: dict
|
||||
"""
|
||||
|
||||
result_dict = {}
|
||||
|
||||
for key, value in data_dict.items():
|
||||
# Check if the value is a string and the key ends with the value
|
||||
if isinstance(value, str) and key.endswith(value):
|
||||
# Find the beginning part of the key (before the value)
|
||||
prefix = key.replace(value, '').rstrip('_')
|
||||
|
||||
# Add the value to the list under the prefix key
|
||||
if prefix not in result_dict:
|
||||
result_dict[prefix] = []
|
||||
|
||||
result_dict[prefix].append(value)
|
||||
|
||||
else:
|
||||
result_dict[key] = value
|
||||
|
||||
return result_dict
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
from typing import Any, Callable
|
||||
|
||||
from fasthtml.components import *
|
||||
|
||||
from assets.icons import icon_error
|
||||
from components.BaseComponent import BaseComponent
|
||||
from components_helpers import apply_boundaries, mk_dialog_buttons, safe_get_dialog_buttons_parameters
|
||||
from components.admin.assets.icons import icon_msg_success, icon_msg_info, icon_msg_error, icon_msg_warning
|
||||
from components_helpers import apply_boundaries, mk_dialog_buttons, safe_get_dialog_buttons_parameters, mk_icon
|
||||
from core.utils import get_unique_id
|
||||
|
||||
|
||||
@@ -18,6 +20,14 @@ class AdminFormType:
|
||||
TEXTAREA = "textarea"
|
||||
|
||||
|
||||
class AdminMessageType:
|
||||
NONE = "none"
|
||||
SUCCESS = "success"
|
||||
ERROR = "error"
|
||||
INFO = "info"
|
||||
WARNING = "warning"
|
||||
|
||||
|
||||
@dataclass
|
||||
class AdminFormItem:
|
||||
name: str
|
||||
@@ -27,6 +37,12 @@ class AdminFormItem:
|
||||
possible_values: list[str] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class AdminButton:
|
||||
title: str
|
||||
on_click: Callable = None
|
||||
|
||||
|
||||
class AdminForm(BaseComponent):
|
||||
def __init__(self, session, _id, owner, title: str, obj: Any, form_fields: list[AdminFormItem], hooks=None, key=None,
|
||||
boundaries=None):
|
||||
@@ -38,6 +54,21 @@ class AdminForm(BaseComponent):
|
||||
self.title = title
|
||||
self.obj = obj
|
||||
self.form_fields = form_fields
|
||||
self.message = None
|
||||
|
||||
def set_message(self, message, msg_type: AdminMessageType.NONE):
|
||||
if msg_type == AdminMessageType.NONE:
|
||||
self.message = message
|
||||
else:
|
||||
if msg_type == AdminMessageType.SUCCESS:
|
||||
icon = icon_msg_success
|
||||
elif msg_type == AdminMessageType.ERROR:
|
||||
icon = icon_msg_error
|
||||
elif msg_type == AdminMessageType.WARNING:
|
||||
icon = icon_msg_warning
|
||||
else:
|
||||
icon = icon_msg_info
|
||||
self.message = Div(icon, Span(message), role=msg_type, cls=f"alert alert-{msg_type} mr-2")
|
||||
|
||||
def mk_input(self, item: AdminFormItem):
|
||||
return Input(
|
||||
@@ -62,7 +93,7 @@ class AdminForm(BaseComponent):
|
||||
cls="checkbox checkbox-xs",
|
||||
checked=value in current_values
|
||||
),
|
||||
|
||||
|
||||
cls="checkbox-item") for value in item.possible_values]
|
||||
|
||||
return Div(*checkbox_items, cls="adm-items-group")
|
||||
@@ -95,9 +126,20 @@ class AdminForm(BaseComponent):
|
||||
else:
|
||||
return self.mk_input(item)
|
||||
|
||||
def mk_extra_buttons(self):
|
||||
extra_buttons = self._hooks.get("extra_buttons", None)
|
||||
if not extra_buttons:
|
||||
return None
|
||||
|
||||
return Div(
|
||||
*[Button(btn.title, cls="btn btn-ghost btn-sm", **btn.on_click()) for btn in extra_buttons],
|
||||
cls="flex justify-end"
|
||||
)
|
||||
|
||||
def __ft__(self):
|
||||
return Form(
|
||||
Fieldset(Legend(self.title, cls="fieldset-legend"),
|
||||
Div(self.message),
|
||||
*[
|
||||
Div(
|
||||
Label(item.title, cls="label"),
|
||||
@@ -107,6 +149,7 @@ class AdminForm(BaseComponent):
|
||||
|
||||
for item in self.form_fields
|
||||
],
|
||||
self.mk_extra_buttons(),
|
||||
mk_dialog_buttons(**safe_get_dialog_buttons_parameters(self._hooks)),
|
||||
**apply_boundaries(self._boundaries),
|
||||
cls="fieldset bg-base-200 border-base-300 rounded-box w-xs border p-4"
|
||||
@@ -119,3 +162,40 @@ class AdminForm(BaseComponent):
|
||||
suffix = get_unique_id()
|
||||
|
||||
return f"{prefix}{suffix}"
|
||||
|
||||
@staticmethod
|
||||
def get_fields_values(data_dict):
|
||||
"""
|
||||
Processes a dictionary of key-value pairs to reorganize keys based on specific
|
||||
criteria. If a key ends with its corresponding string value, the method extracts
|
||||
the prefix of the key (the portion of the key before the value) and groups the
|
||||
value under this prefix in a list. Otherwise, the original key-value pair is
|
||||
preserved in the resulting dictionary.
|
||||
|
||||
:param data_dict: Dictionary where each key is a string and its corresponding
|
||||
value can be of any type.
|
||||
:type data_dict: dict
|
||||
:return: A dictionary where the keys have been categorized into groups
|
||||
based on whether they end with the same string value, reorganized into
|
||||
lists, while preserving other key-value pairs as they are.
|
||||
:rtype: dict
|
||||
"""
|
||||
|
||||
result_dict = {}
|
||||
|
||||
for key, value in data_dict.items():
|
||||
# Check if the value is a string and the key ends with the value
|
||||
if isinstance(value, str) and key.endswith(value):
|
||||
# Find the beginning part of the key (before the value)
|
||||
prefix = key.replace(value, '').rstrip('_')
|
||||
|
||||
# Add the value to the list under the prefix key
|
||||
if prefix not in result_dict:
|
||||
result_dict[prefix] = []
|
||||
|
||||
result_dict[prefix].append(value)
|
||||
|
||||
else:
|
||||
result_dict[key] = value
|
||||
|
||||
return result_dict
|
||||
|
||||
@@ -12,3 +12,4 @@ class Routes:
|
||||
PasteHolidays = "/paste-holidays"
|
||||
ConfigureJira = "/configure-jira"
|
||||
ConfigureJiraCancel = "/configure-jira-cancel"
|
||||
ConfigureJiraTest = "/configure-jira-test"
|
||||
|
||||
@@ -35,7 +35,7 @@ def post(session, _id: str, name: str, tab_boundaries: str):
|
||||
|
||||
|
||||
@rt(Routes.AddComponent)
|
||||
def post(session, _id: str, component_type: str, x: int, y: int):
|
||||
def post(session, _id: str, component_type: str, x: float, y: float):
|
||||
logger.debug(
|
||||
f"Entering {Routes.AddComponent} with args {debug_session(session)}, {_id=}, {component_type=}, {x=}, {y=}")
|
||||
instance = InstanceManager.get(session, _id)
|
||||
|
||||
@@ -39,6 +39,18 @@ class Jira:
|
||||
self.api_token = api_token
|
||||
self.auth = HTTPBasicAuth(self.user_name, self.api_token)
|
||||
|
||||
def test(self):
|
||||
url = f"{JIRA_ROOT}/myself"
|
||||
|
||||
response = requests.request(
|
||||
"GET",
|
||||
url,
|
||||
headers=DEFAULT_HEADERS,
|
||||
auth=self.auth
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
def issue(self, issue_id: str) -> Expando:
|
||||
"""
|
||||
Retrieve an issue
|
||||
|
||||
Reference in New Issue
Block a user