Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions docs/content/docs/reference/components/jira_v1.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,86 @@ Type: OBJECT
```


### List Issue Comments
Name: listIssueComments

`Return all comments for an issue.`

#### Properties

| Name | Label | Type | Description | Required |
|:---------------:|:--------------:|:------------:|:-------------------:|:--------:|
| project | Project ID | STRING | ID of the project where the issue is located. | false |
| issueId | Issue ID | STRING <details> <summary> Depends On </summary> project </details> | ID of the issue. | true |
| orderBy | Order By | STRING <details> <summary> Options </summary> <span title="Order ascending by created date.">+created</span>, <span title="Order descending by created date.">-created</span> </details> | Order the results by a field. | false |
| maxResults | Max Results | INTEGER | The maximum number of items to return per page. | false |

#### Example JSON Structure
```json
{
"label" : "List Issue Comments",
"name" : "listIssueComments",
"parameters" : {
"project" : "",
"issueId" : "",
"orderBy" : "",
"maxResults" : 1
},
"type" : "jira/v1/listIssueComments"
}
```

#### Output



Type: OBJECT


#### Properties

| Name | Type | Description |
|:------------:|:------------:|:-------------------:|
| maxResults | INTEGER | The maximum number of items that could be returned. |
| startAt | INTEGER | The index of the first item returned. |
| total | INTEGER | The number of items returned. |
| comments | ARRAY <details> <summary> Items </summary> [&#123;STRING\(id), STRING\(self), &#123;STRING\(type), INTEGER\(version), [&#123;STRING\(text)&#125;]\(content)&#125;\(body), &#123;STRING\(accountId), STRING\(accountType), BOOLEAN\(active), STRING\(emailAddress), STRING\(displayName), STRING\(self), STRING\(timezone)&#125;\(author), STRING\(created), STRING\(updated)&#125;] </details> | List of comments on the issue |




#### Output Example
```json
{
"maxResults" : 1,
"startAt" : 1,
"total" : 1,
"comments" : [ {
"id" : "",
"self" : "",
"body" : {
"type" : "",
"version" : 1,
"content" : [ {
"text" : ""
} ]
},
"author" : {
"accountId" : "",
"accountType" : "",
"active" : false,
"emailAddress" : "",
"displayName" : "",
"self" : "",
"timezone" : ""
},
"created" : "",
"updated" : ""
} ]
}
```


### Search Issues
Name: searchForIssuesUsingJql

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import com.bytechef.component.jira.action.JiraCreateIssueAction;
import com.bytechef.component.jira.action.JiraCreateIssueCommentAction;
import com.bytechef.component.jira.action.JiraGetIssueAction;
import com.bytechef.component.jira.action.JiraListIssueCommentsAction;
import com.bytechef.component.jira.action.JiraSearchForIssuesUsingJqlAction;
import com.bytechef.component.jira.connection.JiraConnection;
import com.bytechef.component.jira.trigger.JiraNewIssueTrigger;
Expand Down Expand Up @@ -55,12 +56,14 @@ public class JiraComponentHandler implements ComponentHandler {
JiraCreateIssueAction.ACTION_DEFINITION,
JiraCreateIssueCommentAction.ACTION_DEFINITION,
JiraGetIssueAction.ACTION_DEFINITION,
JiraListIssueCommentsAction.ACTION_DEFINITION,
JiraSearchForIssuesUsingJqlAction.ACTION_DEFINITION)
.clusterElements(
tool(JiraAssignIssueAction.ACTION_DEFINITION),
tool(JiraCreateIssueAction.ACTION_DEFINITION),
tool(JiraCreateIssueCommentAction.ACTION_DEFINITION),
tool(JiraGetIssueAction.ACTION_DEFINITION),
tool(JiraListIssueCommentsAction.ACTION_DEFINITION),
tool(JiraSearchForIssuesUsingJqlAction.ACTION_DEFINITION))
.triggers(
JiraNewIssueTrigger.TRIGGER_DEFINITION,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
* Copyright 2025 ByteChef
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.bytechef.component.jira.action;

import static com.bytechef.component.definition.ComponentDsl.action;
import static com.bytechef.component.definition.ComponentDsl.array;
import static com.bytechef.component.definition.ComponentDsl.bool;
import static com.bytechef.component.definition.ComponentDsl.integer;
import static com.bytechef.component.definition.ComponentDsl.object;
import static com.bytechef.component.definition.ComponentDsl.option;
import static com.bytechef.component.definition.ComponentDsl.outputSchema;
import static com.bytechef.component.definition.ComponentDsl.string;
import static com.bytechef.component.jira.constant.JiraConstants.ISSUE_ID;
import static com.bytechef.component.jira.constant.JiraConstants.MAX_RESULTS;
import static com.bytechef.component.jira.constant.JiraConstants.ORDER_BY;
import static com.bytechef.component.jira.constant.JiraConstants.PROJECT;

import com.bytechef.component.definition.ActionDefinition.OptionsFunction;
import com.bytechef.component.definition.ComponentDsl.ModifiableActionDefinition;
import com.bytechef.component.definition.Context;
import com.bytechef.component.definition.Context.Http;
import com.bytechef.component.definition.Parameters;
import com.bytechef.component.definition.TypeReference;
import com.bytechef.component.jira.util.JiraOptionsUtils;

/**
* @author Ivona Pavela
*/
public class JiraListIssueCommentsAction {

public static final ModifiableActionDefinition ACTION_DEFINITION = action("listIssueComments")
.title("List Issue Comments")
.description("Return all comments for an issue.")
.properties(
string(PROJECT)
.label("Project ID")
.description("ID of the project where the issue is located.")
.options((OptionsFunction<String>) JiraOptionsUtils::getProjectIdOptions)
.required(false),
string(ISSUE_ID)
.label("Issue ID")
.description("ID of the issue.")
.options((OptionsFunction<String>) JiraOptionsUtils::getIssueIdOptions)
.optionsLookupDependsOn(PROJECT)
.required(true),
string(ORDER_BY)
.label("Order By")
.description("Order the results by a field.")
.options(
option("Created (Ascending)", "+created", "Order ascending by created date."),
option("Created (Descending)", "-created", "Order descending by created date."))
.required(false),
integer(MAX_RESULTS)
.label("Max Results")
.description("The maximum number of items to return per page.")
.required(false))
.output(
outputSchema(
object()
.properties(
integer("maxResults")
.description("The maximum number of items that could be returned."),
integer("startAt")
.description("The index of the first item returned."),
integer("total")
.description("The number of items returned."),
array("comments")
.description("List of comments on the issue")
.items(
object()
.properties(
string("id")
.description("The ID of the comment."),
string("self")
.description("The URL of the comment."),
object("body")
.properties(
string("type")
.description(
"Defines the type of block node such as paragraph, table, and alike."),
integer("version")
.description(
"Defines the version of ADF used in this representation."),
array("content")
.description(
"An array containing inline and block nodes that define the content of a section of the document.")
.items(
object()
.properties(string("text")))),
object("author")
.properties(
string("accountId")
.description(
"The account ID of the user, which uniquely identifies the user across all Atlassian products."),
string("accountType")
.description(
"The type of account represented by this user. This will be one of 'atlassian' (normal users), 'app' (application user) or 'customer' (Jira Service Desk customer user)"),
bool("active")
.description("Whether the user is active."),
string("emailAddress")
.description(
"The email address of the user. Depending on the user’s privacy settings, this may be returned as null."),
string("displayName")
.description(
"The display name of the user. Depending on the user’s privacy settings, this may return an alternative value."),
string("self")
.description("The URL of the user."),
string("timezone")
.description(
"The time zone specified in the user's profile. Depending on the user’s privacy settings, this may be returned as null.")),
string("created")
.description("The date and time at which the comment was created."),
string("updated")
.description(
"The date and time at which the comment was updated last."))))))
.perform(JiraListIssueCommentsAction::perform);

public static Object perform(Parameters inputParameters, Parameters connectionParameters, Context context) {
return context
.http(http -> http.get("/issue/" + inputParameters.getRequiredString(ISSUE_ID) + "/comment"))
.configuration(Http.responseType(Http.ResponseType.JSON))
.queryParameters(
ORDER_BY, inputParameters.getString(ORDER_BY),
MAX_RESULTS, inputParameters.getInteger(MAX_RESULTS))
.execute()
.getBody(new TypeReference<>() {});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public class JiraConstants {
public static final String SUMMARY = "summary";
public static final String TEXT = "text";
public static final String TYPE = "type";
public static final String ORDER_BY = "orderBy";

public static final ModifiableObjectProperty ISSUE_OUTPUT_PROPERTY = object()
.properties(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Copyright 2025 ByteChef
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.bytechef.component.jira.action;

import static com.bytechef.component.jira.constant.JiraConstants.ISSUE_ID;
import static com.bytechef.component.jira.constant.JiraConstants.MAX_RESULTS;
import static com.bytechef.component.jira.constant.JiraConstants.ORDER_BY;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentCaptor.forClass;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import com.bytechef.component.definition.ActionContext;
import com.bytechef.component.definition.Context;
import com.bytechef.component.definition.Context.ContextFunction;
import com.bytechef.component.definition.Context.Http;
import com.bytechef.component.definition.Context.Http.Configuration.ConfigurationBuilder;
import com.bytechef.component.definition.Parameters;
import com.bytechef.component.definition.TypeReference;
import com.bytechef.component.test.definition.MockParametersFactory;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;

/**
* @author Ivona Pavela
*/
class JiraListIssueCommentsActionTest {

private final ArgumentCaptor<ConfigurationBuilder> configurationBuilderArgumentCaptor =
forClass(ConfigurationBuilder.class);
@SuppressWarnings("unchecked")
private final ArgumentCaptor<ContextFunction<Http, Http.Executor>> httpFunctionArgumentCaptor =
forClass(ContextFunction.class);
private final ActionContext mockedActionContext = mock(ActionContext.class);
private final Http.Executor mockedExecutor = mock(Http.Executor.class);
private final Http mockedHttp = mock(Http.class);
private final Parameters mockedParameters = MockParametersFactory.create(
Map.of(ISSUE_ID, "xy", ORDER_BY, "+created", MAX_RESULTS, 50));
private final Http.Response mockedResponse = mock(Http.Response.class);
private final Map<String, Object> responseMap = Map.of("comments", "example");
private final ArgumentCaptor<String> stringArgumentCaptor = forClass(String.class);

@Test
void testPerform() {
when(mockedActionContext.http(httpFunctionArgumentCaptor.capture()))
.thenAnswer(_ -> httpFunctionArgumentCaptor.getValue()
.apply(mockedHttp));
when(mockedHttp.get(stringArgumentCaptor.capture()))
.thenReturn(mockedExecutor);
when(mockedExecutor.configuration(configurationBuilderArgumentCaptor.capture()))
.thenReturn(mockedExecutor);
when(mockedExecutor.queryParameters(ORDER_BY, "+created", MAX_RESULTS, 50))
.thenReturn(mockedExecutor);
when(mockedExecutor.execute())
.thenReturn(mockedResponse);
when(mockedResponse.getBody(any(TypeReference.class)))
.thenReturn(responseMap);

Object result = JiraListIssueCommentsAction.perform(mockedParameters, null, mockedActionContext);
assertEquals(responseMap, result);

ContextFunction<Context.Http, Http.Executor> capturedFunction = httpFunctionArgumentCaptor.getValue();

assertNotNull(capturedFunction);

ConfigurationBuilder configurationBuilder = configurationBuilderArgumentCaptor.getValue();

Http.Configuration configuration = configurationBuilder.build();

Http.ResponseType responseType = configuration.getResponseType();

assertEquals(Http.ResponseType.Type.JSON, responseType.getType());

assertEquals("/issue/xy/comment", stringArgumentCaptor.getValue());
}
}
Loading