From 23934e691a8f723ed48ac7ea7cb361fecd44f675 Mon Sep 17 00:00:00 2001 From: cpl-jonathan Date: Thu, 19 Jun 2025 09:00:32 +1200 Subject: [PATCH 01/10] Renamed path. --- components/{ClearPoint GitHUb => bob}/github_get_pr_component.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename components/{ClearPoint GitHUb => bob}/github_get_pr_component.py (100%) diff --git a/components/ClearPoint GitHUb/github_get_pr_component.py b/components/bob/github_get_pr_component.py similarity index 100% rename from components/ClearPoint GitHUb/github_get_pr_component.py rename to components/bob/github_get_pr_component.py From 6c5ecc1c0add54f5b978bdb3976fb85b776fb848 Mon Sep 17 00:00:00 2001 From: cpl-jonathan Date: Thu, 19 Jun 2025 09:01:01 +1200 Subject: [PATCH 02/10] Fixed path name. --- components/{bob => ClearPoint GitHub}/github_get_pr_component.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename components/{bob => ClearPoint GitHub}/github_get_pr_component.py (100%) diff --git a/components/bob/github_get_pr_component.py b/components/ClearPoint GitHub/github_get_pr_component.py similarity index 100% rename from components/bob/github_get_pr_component.py rename to components/ClearPoint GitHub/github_get_pr_component.py From efb27a915e868dab370b65c9c52955a191c4d1dd Mon Sep 17 00:00:00 2001 From: cpl-jonathan Date: Thu, 19 Jun 2025 11:04:14 +1200 Subject: [PATCH 03/10] WIP search Jira component. --- .../jira_search_issues_component.py | 75 +++++++++++++++++++ comps_requirements.txt | 3 +- 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 components/ClearPoint Jira/jira_search_issues_component.py diff --git a/components/ClearPoint Jira/jira_search_issues_component.py b/components/ClearPoint Jira/jira_search_issues_component.py new file mode 100644 index 0000000..41969b4 --- /dev/null +++ b/components/ClearPoint Jira/jira_search_issues_component.py @@ -0,0 +1,75 @@ +from langflow.custom import Component +from langflow.io import Output, SecretStrInput, MessageTextInput,StrInput,BoolInput +from jira import JIRA +from typing import List + + +class JiraSearchIssuesComponent(Component): + display_name = "Jira Search Issues" + description = ( + "Returns issues in Jira based on the provided JQL query." + ) + icon = "jira" + name = "JiraSearchIssuesComponent" + + inputs = [ + StrInput( + name="JIRA_SERVER_URL", + display_name="Jira Server URL", + required=True, + ), + StrInput( + name="JIRA_USERNAME", + display_name="Jira Username", + required=True, + ), + SecretStrInput( + name="JIRA_API_KEY", + display_name="Jira API Key", + required=True, + ), + MessageTextInput( + name="jql_query", + display_name="JQL query ", + info="The JQL query to use to search for issues in Jira.", + tool_mode=True, + ), + MessageTextInput( + name="issue_fields", + display_name="Issue fields", + info="A comma-seperated list of issues fields to return. If not provided, all fields will be returned.", + tool_mode=True, + ), + BoolInput( + name="flatten_fields", + display_name="Flatten Fields", + info="If true, the fields will be flattened as top level attributes.", + value=True, + ) + ] + + outputs = [ + Output( + name="issues", display_name="List of Issues", method="search_issues" + ) + ] + + def search_issues(self) -> List[Data]: + jira = JIRA(server=self.JIRA_SERVER_URL, basic_auth=(self.JIRA_USERNAME, self.JIRA_API_KEY)) + + + issues = jira.search_issues(self.jql_query, fields=self.issue_fields,json_result=True) + + issues_list = [] + for issue in issues["issues"]: + + if self.flatten_fields: + # Flatten the fields to top level attributes, prefixed with "fields_" + flattened_issue = {f"fields_{k}": v for k, v in issue["fields"].items()} + issue.update(flattened_issue) + del issue["fields"] + + issues_list.append(Data(data=issue)) + + self.status = issues_list + return issues_list diff --git a/comps_requirements.txt b/comps_requirements.txt index 7f9f593..2b3d648 100644 --- a/comps_requirements.txt +++ b/comps_requirements.txt @@ -1,2 +1,3 @@ PyGithub -atlassian-python-api \ No newline at end of file +atlassian-python-api +jira \ No newline at end of file From ff90e92238034493a5aaf59c662e01dd12717add Mon Sep 17 00:00:00 2001 From: cpl-jonathan Date: Thu, 19 Jun 2025 15:30:57 +1200 Subject: [PATCH 04/10] Added JSON to data component. --- .../json_to_data_component.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 components/ClearPoint General/json_to_data_component.py diff --git a/components/ClearPoint General/json_to_data_component.py b/components/ClearPoint General/json_to_data_component.py new file mode 100644 index 0000000..3f79ed7 --- /dev/null +++ b/components/ClearPoint General/json_to_data_component.py @@ -0,0 +1,24 @@ +from langflow.custom import Component +from langflow.io import Output, MultilineInput +from langflow.schema import Data +import json + + +class JsonStringToDataComponent(Component): + display_name = "JSON to Data" + description = "This component converts a JSON string to a Data object." + icon = "braces" + name = "JsonStringToData" + + inputs = [ + MultilineInput(name="json_string", display_name="JSON string"), + ] + + outputs = [ + Output(name="json_as_data", display_name="JSON as Data", method="convert_json_to_data") + ] + + def convert_json_to_data(self) -> Data: + data = Data(data=json.loads(self.json_string)) + self.status = data + return data From d3b8470107f4f54dbeb7e73ddf86ab7a49799917 Mon Sep 17 00:00:00 2001 From: cpl-jonathan Date: Thu, 19 Jun 2025 16:07:58 +1200 Subject: [PATCH 05/10] Working data conditional router. --- .../data_conditional_router_component.py | 85 +++++++++++++++++++ .../json_to_data_component.py | 2 +- comps_requirements.txt | 3 +- 3 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 components/ClearPoint General/data_conditional_router_component.py diff --git a/components/ClearPoint General/data_conditional_router_component.py b/components/ClearPoint General/data_conditional_router_component.py new file mode 100644 index 0000000..119df12 --- /dev/null +++ b/components/ClearPoint General/data_conditional_router_component.py @@ -0,0 +1,85 @@ +from langflow.custom import Component +from langflow.io import DropdownInput, IntInput, Output, DataInput, StrInput +from langflow.schema.message import Data +from evalidate import Expr + + +class DataConditionalRouterComponent(Component): + display_name = "Data If-Else" + description = "Routes an Data to a corresponding output based on a boolean expression." + icon = "split" + name = "DataConditionalRouter" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.__iteration_updated = False + + inputs = [ + DataInput( + name="input_data", + display_name="Input", + info="The primary text input for the operation.", + required=True, + ), + StrInput( + name="eval_expression", + display_name="Eval Expression", + info="The boolean expression to eval the input data against.", + required=True, + ), + IntInput( + name="max_iterations", + display_name="Max Iterations", + info="The maximum number of iterations for the conditional router.", + value=10, + advanced=True, + ), + DropdownInput( + name="default_route", + display_name="Default Route", + options=["true_result", "false_result"], + info="The default route to take when max iterations are reached.", + value="false_result", + advanced=True, + ), + ] + + outputs = [ + Output(display_name="True", name="true_result", method="true_response"), + Output(display_name="False", name="false_result", method="false_response"), + ] + + def _pre_run_setup(self): + self.__iteration_updated = False + + def iterate_and_stop_once(self, route_to_stop: str): + if not self.__iteration_updated: + self.update_ctx({f"{self._id}_iteration": self.ctx.get(f"{self._id}_iteration", 0) + 1}) + self.__iteration_updated = True + if self.ctx.get(f"{self._id}_iteration", 0) >= self.max_iterations and route_to_stop == self.default_route: + route_to_stop = "true_result" if route_to_stop == "false_result" else "false_result" + self.stop(route_to_stop) + + def true_response(self) -> Data: + # TODO: Proper error handling. Should check for bool result and handle EvalException + result = Expr(self.eval_expression).eval(self.input_data.data) + + if result: + self.status = self.input_data + self.iterate_and_stop_once("false_result") + return self.input_data + self.iterate_and_stop_once("true_result") + return Data(text="Eval is not true,.") + + def false_response(self) -> Data: + # TODO: Proper error handling. Should check for bool result and handle EvalException + result = Expr(self.eval_expression).eval(self.input_data.data) + + if not result: + self.status = self.input_data + self.iterate_and_stop_once("true_result") + return self.input_data + self.iterate_and_stop_once("false_result") + return Data(text="Eval is not false.") + + \ No newline at end of file diff --git a/components/ClearPoint General/json_to_data_component.py b/components/ClearPoint General/json_to_data_component.py index 3f79ed7..6aca0e3 100644 --- a/components/ClearPoint General/json_to_data_component.py +++ b/components/ClearPoint General/json_to_data_component.py @@ -7,7 +7,7 @@ class JsonStringToDataComponent(Component): display_name = "JSON to Data" description = "This component converts a JSON string to a Data object." - icon = "braces" + icon = "file-json" name = "JsonStringToData" inputs = [ diff --git a/comps_requirements.txt b/comps_requirements.txt index 2b3d648..7677087 100644 --- a/comps_requirements.txt +++ b/comps_requirements.txt @@ -1,3 +1,4 @@ PyGithub atlassian-python-api -jira \ No newline at end of file +jira +evalidate \ No newline at end of file From e336ebe0aae223f4923a236487145bd8cefd294b Mon Sep 17 00:00:00 2001 From: cpl-jonathan Date: Thu, 26 Jun 2025 16:39:38 +1200 Subject: [PATCH 06/10] Updated to also return a dataframe. --- .../jira_search_issues_component.py | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/components/ClearPoint Jira/jira_search_issues_component.py b/components/ClearPoint Jira/jira_search_issues_component.py index 41969b4..0eacd3a 100644 --- a/components/ClearPoint Jira/jira_search_issues_component.py +++ b/components/ClearPoint Jira/jira_search_issues_component.py @@ -2,6 +2,7 @@ from langflow.io import Output, SecretStrInput, MessageTextInput,StrInput,BoolInput from jira import JIRA from typing import List +from langflow.schema import Data, DataFrame class JiraSearchIssuesComponent(Component): @@ -50,8 +51,17 @@ class JiraSearchIssuesComponent(Component): outputs = [ Output( - name="issues", display_name="List of Issues", method="search_issues" - ) + name="issues", + display_name="List of Issues", + method="search_issues", + info="Matching issues as a list of Data objects." + ), + Output( + display_name="DataFrame of Issues", + name="dataframe", + method="build_dataframe", + info="Matching issues in DataFrame.", + ), ] def search_issues(self) -> List[Data]: @@ -73,3 +83,13 @@ def search_issues(self) -> List[Data]: self.status = issues_list return issues_list + + def build_dataframe(self) -> DataFrame: + issues = self.search_issues() + rows = [] + for issue in issues: + rows.append(dict(issue.data)) + + df_result = DataFrame(rows) + self.status = df_result # store in self.status for logs + return df_result From 958de15306781cd4fea8c7b8196c6a10a9c52f5d Mon Sep 17 00:00:00 2001 From: cpl-jonathan Date: Mon, 30 Jun 2025 11:01:33 +1200 Subject: [PATCH 07/10] Added first row component. --- .../ClearPoint General/first_row_component.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 components/ClearPoint General/first_row_component.py diff --git a/components/ClearPoint General/first_row_component.py b/components/ClearPoint General/first_row_component.py new file mode 100644 index 0000000..d0e3ea8 --- /dev/null +++ b/components/ClearPoint General/first_row_component.py @@ -0,0 +1,27 @@ +from langflow.custom import Component +from langflow.io import DataFrameInput, Output +from langflow.schema import Data + + +class FirstRowComponent(Component): + display_name = "First row" + description = "Takes the first row of a Dataframe and converts it to a Data object" + icon = "list-start" + name = "FirstRowComponent" + + inputs = [ + DataFrameInput( + name="input_dataframe", + display_name="Dataframe", + info="This is the input DataFrame" + ), + ] + + outputs = [ + Output(display_name="Data", name="output_data", method="build_output"), + ] + + def build_output(self) -> Data: + data = self.input_dataframe.to_data_list()[0] # TODO empty list checking and handling + self.status = data + return data From 514e8e52b140d633477a9578c572e83e1dea9efb Mon Sep 17 00:00:00 2001 From: cpl-jonathan Date: Mon, 30 Jun 2025 12:29:57 +1200 Subject: [PATCH 08/10] Added logger. --- .../ClearPoint General/logger_component.py | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 components/ClearPoint General/logger_component.py diff --git a/components/ClearPoint General/logger_component.py b/components/ClearPoint General/logger_component.py new file mode 100644 index 0000000..2b64a63 --- /dev/null +++ b/components/ClearPoint General/logger_component.py @@ -0,0 +1,56 @@ +from langflow.custom import Component +from langflow.io import HandleInput, Output, StrInput +from langflow.schema import Data, DataFrame, Message + + +class LoggerComponent(Component): + display_name = "Logger" + description = "Logs the passed in object and then passes it on unchanged." + icon = "bug" + name = "LoggerComponent" + + inputs = [ + StrInput( + name="log_message", + display_name="Log Message", + info="The message to log before the object.", + required=True, + ), + HandleInput( + name="input_object", + display_name="Input", + input_types=["DataFrame", "Data", "Message"], + info="Accepts either a DataFrame, a Data object or a Message.", + required=True, + ), + ] + + outputs = [ + Output(display_name="Data", name="output_data", method="build_data_output"), + Output(display_name="DataFrame", name="output_dataframe", method="build_dataframe_output"), + Output(display_name="Message", name="output_message", method="build_message_output"), + ] + + def _log_object(self): + log_msg = f"{self.log_message} {self.input_object}" + self.log(log_msg,"LoggerComponent") + print(log_msg) + self.status = self.log_message + + def build_data_output(self) -> Data: + if not isinstance(self.input_object,Data): + raise Exception("Input was not a Data object!") + self._log_object() + return self.input_object + + def build_dataframe_output(self) -> DataFrame: + if not isinstance(self.input_object,DataFrame): + raise Exception("Input was not a DataFrame object!") + self._log_object() + return self.input_object + + def build_message_output(self) -> Message: + if not isinstance(self.input_object,Message): + raise Exception("Input was not a Message object!") + self._log_object() + return self.input_object From 67dcc1c412ce17201ccf6f556b67971878c69ad9 Mon Sep 17 00:00:00 2001 From: cpl-jonathan Date: Mon, 30 Jun 2025 12:58:30 +1200 Subject: [PATCH 09/10] Added Jira add comment component. --- .../ClearPoint Jira/jira_add_comment.py | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 components/ClearPoint Jira/jira_add_comment.py diff --git a/components/ClearPoint Jira/jira_add_comment.py b/components/ClearPoint Jira/jira_add_comment.py new file mode 100644 index 0000000..8b4c2a0 --- /dev/null +++ b/components/ClearPoint Jira/jira_add_comment.py @@ -0,0 +1,62 @@ +from langflow.custom import Component +from langflow.io import Output, SecretStrInput, MessageTextInput,StrInput,BoolInput,MultilineInput +from jira import JIRA +from typing import List +from langflow.schema import Data, DataFrame + + +class JiraAddCommentComponent(Component): + display_name = "Jira Add comment" + description = ( + "Adds a comment to a Jira issue." + ) + icon = "jira" + name = "JiraAddCommentComponent" + + inputs = [ + StrInput( + name="JIRA_SERVER_URL", + display_name="Jira Server URL", + required=True, + ), + StrInput( + name="JIRA_USERNAME", + display_name="Jira Username", + required=True, + ), + SecretStrInput( + name="JIRA_API_KEY", + display_name="Jira API Key", + required=True, + ), + MessageTextInput( + name="issue_key", + display_name="Issue Key", + info="The key of the issue to update.", + required=True, + tool_mode=True, + ), + MultilineInput(name="comment_text", + display_name="Comment Text", + info="Text of the comment to add to the issue.", + required=True, + tool_mode=True), + ] + + outputs = [ + Output( + name="results", + display_name="Results", + method="add_issue", + info="The results of the Jira comment add operation." + ) + ] + + def add_issue(self) -> Data: + jira = JIRA(server=self.JIRA_SERVER_URL, basic_auth=(self.JIRA_USERNAME, self.JIRA_API_KEY)) + + results = jira.add_comment(self.issue_key,self.comment_text) + + self.status = results.raw + return Data(data=results.raw) + \ No newline at end of file From a653fe1c95b90bc327d52a8b0cd6f60668b64990 Mon Sep 17 00:00:00 2001 From: cpl-jonathan Date: Mon, 30 Jun 2025 13:24:58 +1200 Subject: [PATCH 10/10] Added Jira add label to issue. --- components/ClearPoint Jira/jira_add_label.py | 72 ++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 components/ClearPoint Jira/jira_add_label.py diff --git a/components/ClearPoint Jira/jira_add_label.py b/components/ClearPoint Jira/jira_add_label.py new file mode 100644 index 0000000..b6d2ff0 --- /dev/null +++ b/components/ClearPoint Jira/jira_add_label.py @@ -0,0 +1,72 @@ +from langflow.custom import Component +from langflow.io import Output, SecretStrInput, MessageTextInput,StrInput,BoolInput,MultilineInput +from jira import JIRA +from typing import List +from langflow.schema import Data, DataFrame + + +class JiraAddLabelToIssueComponent(Component): + display_name = "Jira Add label to issue" + description = ( + "Adds a label to a Jira issue." + ) + icon = "jira" + name = "JiraAddLabelToIssueComponent" + + inputs = [ + StrInput( + name="JIRA_SERVER_URL", + display_name="Jira Server URL", + required=True, + ), + StrInput( + name="JIRA_USERNAME", + display_name="Jira Username", + required=True, + ), + SecretStrInput( + name="JIRA_API_KEY", + display_name="Jira API Key", + required=True, + ), + MessageTextInput( + name="issue_key", + display_name="Issue Key", + info="The key of the issue to add the label to.", + required=True, + tool_mode=True, + ), + MessageTextInput( + name="label", + display_name="Label", + info="The label to add to the issue.", + required=True, + tool_mode=True, + ), + ] + + outputs = [ + Output( + name="results", + display_name="Results", + method="add_label", + info="The results of the Jira label add operation." + ) + ] + + def add_label(self) -> Data: + jira = JIRA(server=self.JIRA_SERVER_URL, basic_auth=(self.JIRA_USERNAME, self.JIRA_API_KEY)) + + # grab the issue + issue = jira.issue(self.issue_key) + + # add the label to the issue + issue.fields.labels.append(self.label) + # update issue with the new label + issue.update(fields={"labels": issue.fields.labels}) + + issue = jira.issue(self.issue_key) # load the updated issue so we can get raw data TODO: add logic to avoid this second call + + self.status = issue.raw + return Data(data=issue.raw) + \ No newline at end of file