From 64e7c44a7d534374a44ac586bb71e09256f8969f Mon Sep 17 00:00:00 2001 From: Kodjo Sossouvi Date: Tue, 5 Aug 2025 10:42:26 +0200 Subject: [PATCH] Added other Jira resources --- src/components/workflows/assets/Workflows.js | 2 +- .../workflows/components/WorkflowDesigner.py | 8 +- .../components/WorkflowDesignerProperties.py | 31 +++++-- .../workflows/components/WorkflowPlayer.py | 5 +- src/core/jira.py | 92 ++++++++++++------- src/logging.yaml | 5 + 6 files changed, 94 insertions(+), 49 deletions(-) diff --git a/src/components/workflows/assets/Workflows.js b/src/components/workflows/assets/Workflows.js index 16ed0c6..9f94180 100644 --- a/src/components/workflows/assets/Workflows.js +++ b/src/components/workflows/assets/Workflows.js @@ -629,7 +629,7 @@ function bindWorkflowProperties(elementId) { const totalWidth = properties_component.getBoundingClientRect().width console.debug("totalWidth", totalWidth) - const minPropertiesWidth = 340; // this value avoid scroll bars + const minPropertiesWidth = 352; // this value avoid scroll bars const inputSection = document.getElementById(`pi_${elementId}`); const propertiesSection = document.getElementById(`pp_${elementId}`); diff --git a/src/components/workflows/components/WorkflowDesigner.py b/src/components/workflows/components/WorkflowDesigner.py index 6a3cf03..ac6fda3 100644 --- a/src/components/workflows/components/WorkflowDesigner.py +++ b/src/components/workflows/components/WorkflowDesigner.py @@ -196,7 +196,7 @@ class WorkflowDesigner(BaseComponent): def save_properties(self, component_id: str, details: dict): if component_id in self._state.components: component = self._state.components[component_id] - component.properties = details + component.properties |= details undo_redo_attrs = UndoRedoAttrs(f"Set properties for {component.title}", on_undo=self.refresh_state) self._db.save_state(self._key, self._state, undo_redo_attrs) @@ -518,7 +518,7 @@ class WorkflowDesigner(BaseComponent): selected="selected" if name.value == request_type else None) def _mk_input_group(): - if request_type == JiraRequestTypes.Issues.value: + if request_type == JiraRequestTypes.Search.value: return Div( Input(type="text", name="request", @@ -538,7 +538,7 @@ class WorkflowDesigner(BaseComponent): ) def _mk_extra_parameters(): - if request_type == JiraRequestTypes.Issues.value: + if request_type == JiraRequestTypes.Search.value: return Input(type="text", name="fields", value=component.properties.get("fields", DEFAULT_SEARCH_FIELDS), @@ -547,7 +547,7 @@ class WorkflowDesigner(BaseComponent): else: return None - request_type = component.properties.get("request_type", JiraRequestTypes.Issues.value) + request_type = component.properties.get("request_type", JiraRequestTypes.Search.value) return Div( Fieldset( Legend("JQL", cls="fieldset-legend"), diff --git a/src/components/workflows/components/WorkflowDesignerProperties.py b/src/components/workflows/components/WorkflowDesignerProperties.py index 2e524bb..800d0d2 100644 --- a/src/components/workflows/components/WorkflowDesignerProperties.py +++ b/src/components/workflows/components/WorkflowDesignerProperties.py @@ -186,40 +186,51 @@ class WorkflowDesignerProperties(BaseComponent): selected="selected" if name.value == request_type else None) def _mk_input_group(): - if request_type == JiraRequestTypes.Issues.value: + if request_type == JiraRequestTypes.Search.value or request_type == "issues": # remove issues at some point return [ Div( Input(type="text", - name="fields", - value=self._component.properties.get("fields", DEFAULT_SEARCH_FIELDS), + name=f"{request_type}_fields", + value=self._component.properties.get(f"{request_type}_fields", DEFAULT_SEARCH_FIELDS), placeholder="default fields", cls="input w-full"), P("Jira fields to retrieve"), ), Div( Input(type="text", - name="request", - value=self._component.properties.get("request", ""), + name=f"{request_type}_request", + value=self._component.properties.get(f"{request_type}_request", ""), placeholder="Enter JQL", cls="input w-full"), P("Write your jql code"), ) ] - elif request_type == JiraRequestTypes.Comments.value: + elif request_type in (JiraRequestTypes.Issue.value, JiraRequestTypes.Comments.value): return [ Div( Input(type="text", - name="request", - value=self._component.properties.get("request", ""), + name=f"{request_type}_request", + value=self._component.properties.get(f"{request_type}_request", ""), placeholder="Issue id", cls="input w-full"), P("Put the issue id here"), ) ] + elif request_type == JiraRequestTypes.Versions.value: + return [ + Div( + Input(type="text", + name=f"{request_type}_request", + value=self._component.properties.get(f"{request_type}_request", ""), + placeholder="Project key", + cls="input w-full"), + P("Enter the project key"), + ) + ] else: - return [Div("** Not Implemented **")] + return [Div(f"** Not Implemented ** ('{request_type}' not supported yet)")] - request_type = self._component.properties.get("request_type", JiraRequestTypes.Issues.value) + request_type = self._component.properties.get("request_type", JiraRequestTypes.Search.value) return Div( Fieldset( Legend("Jira", cls="fieldset-legend"), diff --git a/src/components/workflows/components/WorkflowPlayer.py b/src/components/workflows/components/WorkflowPlayer.py index d5d385c..5e16952 100644 --- a/src/components/workflows/components/WorkflowPlayer.py +++ b/src/components/workflows/components/WorkflowPlayer.py @@ -191,13 +191,14 @@ class WorkflowPlayer(BaseComponent): component.properties["repository"], component.properties["table"])) elif key == (ProcessorTypes.Producer, "Jira"): + request_type = component.properties["request_type"] engine.add_processor( JiraDataProducer(self._session, self._settings_manager, component.id, component.properties["request_type"], - component.properties["request"], - component.properties["fields"])) + component.properties[f"{request_type}_request"], + component.properties.get(f"{request_type}_fields", None))) elif key == (ProcessorTypes.Filter, "Default"): engine.add_processor(DefaultDataFilter(component.id, component.properties["filter"])) elif key == (ProcessorTypes.Presenter, "Default"): diff --git a/src/core/jira.py b/src/core/jira.py index c053b0c..8a02258 100644 --- a/src/core/jira.py +++ b/src/core/jira.py @@ -10,7 +10,7 @@ from core.Expando import Expando JIRA_ROOT = "https://altares.atlassian.net/rest/api/3" DEFAULT_HEADERS = {"Accept": "application/json"} DEFAULT_SEARCH_FIELDS = "summary,status,assignee" -logger = logging.getLogger("jql") +logger = logging.getLogger("Jira") class NotFound(Exception): @@ -18,8 +18,10 @@ class NotFound(Exception): class JiraRequestTypes(Enum): - Issues = "issues" + Search = "search" + Issue = "issue" Comments = "comments" + Versions = "versions" class Jira: @@ -41,7 +43,10 @@ class Jira: self.fields = fields def test(self): + logger.debug(f"test with no parameters") + url = f"{JIRA_ROOT}/myself" + logger.debug(f" url: {url}") response = requests.request( "GET", @@ -49,16 +54,21 @@ class Jira: headers=DEFAULT_HEADERS, auth=self.auth ) + logger.debug(f" response: {response}") + logger.debug(f" response.text: {response.text}") return response - def issue(self, issue_id: str) -> Expando: + def issue(self, issue_id: str) -> list[Expando]: """ Retrieve an issue :param issue_id: :return: """ + logger.debug(f"comments with {issue_id=}") + url = f"{JIRA_ROOT}/issue/{issue_id}" + logger.debug(f" url: {url}") response = requests.request( "GET", @@ -66,8 +76,10 @@ class Jira: headers=DEFAULT_HEADERS, auth=self.auth ) + logger.debug(f" response: {response}") + logger.debug(f" response.text: {response.text}") - return Expando(json.loads(response.text)) + return [Expando(json.loads(response.text))] def fields(self) -> list[Expando]: """ @@ -86,14 +98,14 @@ class Jira: as_dict = json.loads(response.text) return [Expando(field) for field in as_dict] - def issues(self, jql: str, fields=None) -> list[Expando]: + def search(self, jql: str, fields=None) -> list[Expando]: """ Executes a JQL and returns the list of issues :param jql: :param fields: list of fields to retrieve :return: """ - logger.debug(f"Processing jql '{jql}'") + logger.debug(f"search with {jql=}, {fields=}") if not jql: raise ValueError("Jql cannot be empty.") @@ -102,6 +114,7 @@ class Jira: fields = self.fields url = f"{JIRA_ROOT}/search" + logger.debug(f" url: {url}") headers = DEFAULT_HEADERS.copy() headers["Content-Type"] = "application/json" @@ -113,15 +126,19 @@ class Jira: "maxResults": 500, # Does not seem to be used. It's always 100 ! "startAt": 0 } + logger.debug(f" payload: {payload}") result = [] while True: - logger.debug(f"Request startAt '{payload['startAt']}'") + logger.debug(f" Request startAt '{payload['startAt']}'") response = requests.request("POST", url, data=json.dumps(payload), headers=headers, auth=self.auth) + logger.debug(f" response: {response}") + logger.debug(f" response.text: {response.text}") + if response.status_code != 200: raise Exception(self._format_error(response)) @@ -130,6 +147,7 @@ class Jira: result += as_dict["issues"] if as_dict["startAt"] + as_dict["maxResults"] >= as_dict["total"]: + logger.debug(f" response: {response}") # We retrieve more than the total nuber of items break @@ -143,12 +161,18 @@ class Jira: :param issue_id: :return: """ + logger.debug(f"comments with {issue_id=}") + url = f"{JIRA_ROOT}/issue/{issue_id}/comment" + logger.debug(f" url: {url}") response = requests.request("GET", url, headers=DEFAULT_HEADERS, auth=self.auth) + logger.debug(f" response: {response}") + logger.debug(f" response.text: {response.text}") + if response.status_code != 200: raise Exception(self._format_error(response)) @@ -156,6 +180,34 @@ class Jira: result = as_dict["comments"] return [Expando(issue) for issue in result] + def versions(self, project_key): + """ + Given a project name and a version name + returns fixVersion number in JIRA + :param project_key: + :return: + """ + logger.debug(f"versions with {project_key=}") + + url = f"{JIRA_ROOT}/project/{project_key}/versions" + logger.debug(f" url: {url}") + + response = requests.request( + "GET", + url, + headers=DEFAULT_HEADERS, + auth=self.auth + ) + + logger.debug(f" response: {response}") + logger.debug(f" response.text: {response.text}") + + if response.status_code != 200: + raise NotFound() + + as_list = json.loads(response.text) + return [Expando(version) for version in as_list] + def extract(self, jql, mappings, updates=None) -> list[dict]: """ Executes a jql and returns list of dict @@ -188,30 +240,6 @@ class Jira: row = {cvs_col: issue.get(jira_path) for jira_path, cvs_col in mappings.items() if cvs_col is not None} yield row - def get_versions(self, project_key): - """ - Given a project name and a version name - returns fixVersion number in JIRA - :param project_key: - :param version_name: - :return: - """ - - url = f"{JIRA_ROOT}/project/{project_key}/versions" - - response = requests.request( - "GET", - url, - headers=DEFAULT_HEADERS, - auth=self.auth - ) - - if response.status_code != 200: - raise NotFound() - - as_list = json.loads(response.text) - return [Expando(version) for version in as_list] - def get_version(self, project_key, version_name): """ Given a project name and a version name @@ -221,7 +249,7 @@ class Jira: :return: """ - for version in self.get_versions(project_key): + for version in self.versions(project_key): if version.name == version_name: return version diff --git a/src/logging.yaml b/src/logging.yaml index 214389c..90289c0 100644 --- a/src/logging.yaml +++ b/src/logging.yaml @@ -47,4 +47,9 @@ loggers: AddStuffApp: level: INFO handlers: [ console ] + propagate: False + + Jira: + level: DEBUG + handlers: [ console ] propagate: False \ No newline at end of file