diff --git a/src/components/admin/AdminApp.py b/src/components/admin/AdminApp.py
index fb307dc..46ca798 100644
--- a/src/components/admin/AdminApp.py
+++ b/src/components/admin/AdminApp.py
@@ -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()
\ No newline at end of file
+ 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)
\ No newline at end of file
diff --git a/src/components/admin/assets/icons.py b/src/components/admin/assets/icons.py
index 6caaea7..45097b8 100644
--- a/src/components/admin/assets/icons.py
+++ b/src/components/admin/assets/icons.py
@@ -1,10 +1,31 @@
from fastcore.basics import NotStr
-icon_jira = NotStr("""""")
+
+
+icon_msg_info = NotStr("""
+
+
+""")
+
+icon_msg_success = NotStr("""
+
+
+""")
+
+icon_msg_warning = NotStr("""
+
+
+""")
+
+icon_msg_error = NotStr("""
+
+
+""")
\ No newline at end of file
diff --git a/src/components/admin/commands.py b/src/components/admin/commands.py
index 4b6e0df..eaaf9a6 100644
--- a/src/components/admin/commands.py
+++ b/src/components/admin/commands.py
@@ -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):
diff --git a/src/components/admin/components/Admin.py b/src/components/admin/components/Admin.py
index c7623df..9157d4f 100644
--- a/src/components/admin/components/Admin.py
+++ b/src/components/admin/components/Admin.py
@@ -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
diff --git a/src/components/admin/components/AdminForm.py b/src/components/admin/components/AdminForm.py
index eda596f..fcf3033 100644
--- a/src/components/admin/components/AdminForm.py
+++ b/src/components/admin/components/AdminForm.py
@@ -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
diff --git a/src/components/admin/constants.py b/src/components/admin/constants.py
index cbde2ba..7294484 100644
--- a/src/components/admin/constants.py
+++ b/src/components/admin/constants.py
@@ -12,3 +12,4 @@ class Routes:
PasteHolidays = "/paste-holidays"
ConfigureJira = "/configure-jira"
ConfigureJiraCancel = "/configure-jira-cancel"
+ ConfigureJiraTest = "/configure-jira-test"
diff --git a/src/components/workflows/WorkflowsApp.py b/src/components/workflows/WorkflowsApp.py
index da45fb0..a77342f 100644
--- a/src/components/workflows/WorkflowsApp.py
+++ b/src/components/workflows/WorkflowsApp.py
@@ -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)
diff --git a/src/core/jira.py b/src/core/jira.py
index a7e1741..10923b2 100644
--- a/src/core/jira.py
+++ b/src/core/jira.py
@@ -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