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/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 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..6aca0e3 --- /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 = "file-json" + 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 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 diff --git a/components/ClearPoint GitHUb/github_get_pr_component.py b/components/ClearPoint GitHub/github_get_pr_component.py similarity index 100% rename from components/ClearPoint GitHUb/github_get_pr_component.py rename to components/ClearPoint GitHub/github_get_pr_component.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 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 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..0eacd3a --- /dev/null +++ b/components/ClearPoint Jira/jira_search_issues_component.py @@ -0,0 +1,95 @@ +from langflow.custom import Component +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): + 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", + 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]: + 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 + + 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 diff --git a/comps_requirements.txt b/comps_requirements.txt index 7f9f593..7677087 100644 --- a/comps_requirements.txt +++ b/comps_requirements.txt @@ -1,2 +1,4 @@ PyGithub -atlassian-python-api \ No newline at end of file +atlassian-python-api +jira +evalidate \ No newline at end of file