Skip to content

Commit 4f96f99

Browse files
Copilotphrocker
andcommitted
Integrate JIRA capabilities into ai-agent module with comprehensive testing
Co-authored-by: phrocker <[email protected]>
1 parent 5f6999e commit 4f96f99

File tree

9 files changed

+1063
-1
lines changed

9 files changed

+1063
-1
lines changed

ai-agent/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@
3737
<artifactId>sentrius-core</artifactId>
3838
<version>1.0.0-SNAPSHOT</version>
3939
</dependency>
40+
<dependency>
41+
<groupId>io.sentrius</groupId>
42+
<artifactId>sentrius-dataplane</artifactId>
43+
<version>1.0.0-SNAPSHOT</version>
44+
</dependency>
4045
<dependency>
4146
<groupId>io.sentrius</groupId>
4247
<artifactId>provenance-core</artifactId>

ai-agent/src/main/java/io/sentrius/agent/analysis/agents/agents/VerbRegistry.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public void scanClasspath() {
7373
try (
7474
ScanResult scanResult = new ClassGraph()
7575
.enableAllInfo()
76-
.acceptPackages("io.sentrius.agent.analysis.agents.verbs")
76+
.acceptPackages("io.sentrius.agent.analysis.agents.verbs", "io.sentrius.sso.core.integrations.ticketing")
7777
.scan()
7878
) {
7979

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package io.sentrius.agent.analysis.agents.integration;
2+
3+
import io.sentrius.sso.core.dto.TicketDTO;
4+
import io.sentrius.sso.core.integrations.ticketing.JiraVerbService;
5+
import io.sentrius.sso.core.model.users.User;
6+
import lombok.RequiredArgsConstructor;
7+
import lombok.extern.slf4j.Slf4j;
8+
import org.springframework.beans.factory.annotation.Autowired;
9+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
10+
import org.springframework.stereotype.Service;
11+
12+
import java.util.List;
13+
14+
/**
15+
* Service that provides AI agent integration with JIRA capabilities.
16+
* This service bridges the ai-agent module with the JIRA capabilities from the dataplane module.
17+
*/
18+
@Slf4j
19+
@Service
20+
@ConditionalOnBean(JiraVerbService.class)
21+
@RequiredArgsConstructor
22+
public class AIAgentJiraIntegrationService {
23+
24+
private final JiraVerbService jiraVerbService;
25+
26+
/**
27+
* Searches for JIRA tickets based on a query string.
28+
* This is a convenience method that delegates to the JiraVerbService.
29+
*
30+
* @param query The search query (can be JQL or simple text)
31+
* @return List of tickets matching the query
32+
*/
33+
public List<TicketDTO> searchForTickets(String query) {
34+
log.info("AI Agent requesting JIRA ticket search with query: {}", query);
35+
return jiraVerbService.searchForTickets(query);
36+
}
37+
38+
/**
39+
* Assigns a JIRA ticket to a user.
40+
* This is a convenience method that delegates to the JiraVerbService.
41+
*
42+
* @param ticketKey The JIRA ticket key (e.g., "PROJ-123")
43+
* @param user The user to assign the ticket to
44+
* @return true if assignment was successful, false otherwise
45+
*/
46+
public Boolean assignTicket(String ticketKey, User user) {
47+
log.info("AI Agent requesting JIRA ticket assignment: {} to user {}", ticketKey, user.getEmailAddress());
48+
return jiraVerbService.assignTicket(ticketKey, user);
49+
}
50+
51+
/**
52+
* Updates a JIRA ticket with a comment.
53+
* This is a convenience method that delegates to the JiraVerbService.
54+
*
55+
* @param ticketKey The JIRA ticket key (e.g., "PROJ-123")
56+
* @param user The user adding the comment
57+
* @param message The comment message
58+
* @return true if update was successful, false otherwise
59+
*/
60+
public Boolean updateTicket(String ticketKey, User user, String message) {
61+
log.info("AI Agent requesting JIRA ticket update: {} with comment from user {}", ticketKey, user.getEmailAddress());
62+
return jiraVerbService.updateTicket(ticketKey, user, message);
63+
}
64+
65+
/**
66+
* Checks if JIRA integration is configured and available.
67+
* This is a convenience method that delegates to the JiraVerbService.
68+
*
69+
* @return true if JIRA integration is available, false otherwise
70+
*/
71+
public Boolean isJiraAvailable() {
72+
log.info("AI Agent checking JIRA availability");
73+
return jiraVerbService.isJiraAvailable();
74+
}
75+
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package io.sentrius.agent.analysis.agents.verbs;
2+
3+
import io.sentrius.agent.analysis.agents.integration.AIAgentJiraIntegrationService;
4+
import io.sentrius.sso.core.dto.TicketDTO;
5+
import io.sentrius.sso.core.dto.ztat.AgentExecution;
6+
import io.sentrius.sso.core.model.users.User;
7+
import io.sentrius.sso.core.model.verbs.Verb;
8+
import lombok.RequiredArgsConstructor;
9+
import lombok.extern.slf4j.Slf4j;
10+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
11+
import org.springframework.stereotype.Service;
12+
13+
import java.util.List;
14+
import java.util.Map;
15+
16+
/**
17+
* Service that provides AI agent verbs for JIRA integration.
18+
* These verbs can be discovered and called by the AI agent system.
19+
*/
20+
@Slf4j
21+
@Service
22+
@ConditionalOnBean(AIAgentJiraIntegrationService.class)
23+
@RequiredArgsConstructor
24+
public class AIAgentJiraVerbService {
25+
26+
private final AIAgentJiraIntegrationService jiraIntegrationService;
27+
28+
/**
29+
* Searches for JIRA tickets based on a query string.
30+
* This verb allows AI agents to search for tickets using JQL or simple text.
31+
*
32+
* @param execution The agent execution context
33+
* @param args Map containing the search query
34+
* @return List of tickets matching the query
35+
*/
36+
@Verb(
37+
name = "searchJiraTickets",
38+
description = "Search for JIRA tickets using a query string. Can use JQL or simple text search.",
39+
returnType = List.class,
40+
isAiCallable = true,
41+
paramDescriptions = {"Search query string (JQL or simple text)"}
42+
)
43+
public List<TicketDTO> searchJiraTickets(AgentExecution execution, Map<String, Object> args) {
44+
log.info("AI Agent verb: searchJiraTickets called with args: {}", args);
45+
46+
String query = (String) args.get("query");
47+
if (query == null || query.trim().isEmpty()) {
48+
log.warn("No query provided for JIRA ticket search");
49+
return List.of();
50+
}
51+
52+
return jiraIntegrationService.searchForTickets(query);
53+
}
54+
55+
/**
56+
* Assigns a JIRA ticket to a user.
57+
* This verb allows AI agents to assign tickets to users.
58+
*
59+
* @param execution The agent execution context
60+
* @param args Map containing the ticket key and user
61+
* @return true if assignment was successful, false otherwise
62+
*/
63+
@Verb(
64+
name = "assignJiraTicket",
65+
description = "Assign a JIRA ticket to a user",
66+
returnType = Boolean.class,
67+
isAiCallable = true,
68+
paramDescriptions = {"JIRA ticket key (e.g., PROJ-123)", "User to assign the ticket to"}
69+
)
70+
public Boolean assignJiraTicket(AgentExecution execution, Map<String, Object> args) {
71+
log.info("AI Agent verb: assignJiraTicket called with args: {}", args);
72+
73+
String ticketKey = (String) args.get("ticketKey");
74+
User user = (User) args.get("user");
75+
76+
if (ticketKey == null || ticketKey.trim().isEmpty()) {
77+
log.warn("No ticket key provided for JIRA ticket assignment");
78+
return false;
79+
}
80+
81+
if (user == null) {
82+
log.warn("No user provided for JIRA ticket assignment");
83+
return false;
84+
}
85+
86+
return jiraIntegrationService.assignTicket(ticketKey, user);
87+
}
88+
89+
/**
90+
* Updates a JIRA ticket with a comment.
91+
* This verb allows AI agents to add comments to tickets.
92+
*
93+
* @param execution The agent execution context
94+
* @param args Map containing the ticket key, user, and message
95+
* @return true if update was successful, false otherwise
96+
*/
97+
@Verb(
98+
name = "updateJiraTicket",
99+
description = "Add a comment to a JIRA ticket",
100+
returnType = Boolean.class,
101+
isAiCallable = true,
102+
paramDescriptions = {"JIRA ticket key (e.g., PROJ-123)", "User adding the comment", "Comment message"}
103+
)
104+
public Boolean updateJiraTicket(AgentExecution execution, Map<String, Object> args) {
105+
log.info("AI Agent verb: updateJiraTicket called with args: {}", args);
106+
107+
String ticketKey = (String) args.get("ticketKey");
108+
User user = (User) args.get("user");
109+
String message = (String) args.get("message");
110+
111+
if (ticketKey == null || ticketKey.trim().isEmpty()) {
112+
log.warn("No ticket key provided for JIRA ticket update");
113+
return false;
114+
}
115+
116+
if (user == null) {
117+
log.warn("No user provided for JIRA ticket update");
118+
return false;
119+
}
120+
121+
if (message == null || message.trim().isEmpty()) {
122+
log.warn("No message provided for JIRA ticket update");
123+
return false;
124+
}
125+
126+
return jiraIntegrationService.updateTicket(ticketKey, user, message);
127+
}
128+
129+
/**
130+
* Checks if JIRA integration is configured and available.
131+
* This verb allows AI agents to check JIRA availability before attempting operations.
132+
*
133+
* @param execution The agent execution context
134+
* @param args Map (can be empty)
135+
* @return true if JIRA integration is available, false otherwise
136+
*/
137+
@Verb(
138+
name = "checkJiraAvailability",
139+
description = "Check if JIRA integration is configured and available",
140+
returnType = Boolean.class,
141+
isAiCallable = true,
142+
paramDescriptions = {}
143+
)
144+
public Boolean checkJiraAvailability(AgentExecution execution, Map<String, Object> args) {
145+
log.info("AI Agent verb: checkJiraAvailability called");
146+
return jiraIntegrationService.isJiraAvailable();
147+
}
148+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package io.sentrius.agent.analysis.agents.agents;
2+
3+
import io.sentrius.agent.analysis.agents.integration.AIAgentJiraIntegrationService;
4+
import io.sentrius.agent.analysis.agents.verbs.AIAgentJiraVerbService;
5+
import io.sentrius.agent.discovery.AgentEndpointDiscoveryService;
6+
import io.sentrius.sso.core.services.capabilities.EndpointScanningService;
7+
import io.sentrius.sso.core.services.agents.ZeroTrustClientService;
8+
import org.junit.jupiter.api.Test;
9+
import org.junit.jupiter.api.extension.ExtendWith;
10+
import org.mockito.Mock;
11+
import org.mockito.junit.jupiter.MockitoExtension;
12+
import org.springframework.context.ApplicationContext;
13+
14+
import static org.junit.jupiter.api.Assertions.*;
15+
import static org.mockito.Mockito.*;
16+
17+
@ExtendWith(MockitoExtension.class)
18+
class VerbRegistryJiraIntegrationTest {
19+
20+
@Mock
21+
private ApplicationContext applicationContext;
22+
23+
@Mock
24+
private ZeroTrustClientService zeroTrustClientService;
25+
26+
@Mock
27+
private EndpointScanningService endpointScanningService;
28+
29+
@Mock
30+
private AgentEndpointDiscoveryService agentEndpointDiscoveryService;
31+
32+
@Mock
33+
private AIAgentJiraIntegrationService aiAgentJiraIntegrationService;
34+
35+
@Test
36+
void testVerbRegistryScansJiraVerbs() {
37+
// Given
38+
AIAgentJiraVerbService jiraVerbService = new AIAgentJiraVerbService(aiAgentJiraIntegrationService);
39+
when(applicationContext.getBean(AIAgentJiraVerbService.class)).thenReturn(jiraVerbService);
40+
41+
VerbRegistry verbRegistry = new VerbRegistry(
42+
applicationContext,
43+
zeroTrustClientService,
44+
endpointScanningService,
45+
agentEndpointDiscoveryService
46+
);
47+
48+
// When
49+
verbRegistry.scanClasspath();
50+
51+
// Then
52+
// Verify that the registry contains the expected JIRA verbs
53+
assertTrue(verbRegistry.isVerbRegistered("searchJiraTickets"));
54+
assertTrue(verbRegistry.isVerbRegistered("assignJiraTicket"));
55+
assertTrue(verbRegistry.isVerbRegistered("updateJiraTicket"));
56+
assertTrue(verbRegistry.isVerbRegistered("checkJiraAvailability"));
57+
}
58+
59+
@Test
60+
void testVerbRegistryGetVerbs() {
61+
// Given
62+
AIAgentJiraVerbService jiraVerbService = new AIAgentJiraVerbService(aiAgentJiraIntegrationService);
63+
when(applicationContext.getBean(AIAgentJiraVerbService.class)).thenReturn(jiraVerbService);
64+
65+
VerbRegistry verbRegistry = new VerbRegistry(
66+
applicationContext,
67+
zeroTrustClientService,
68+
endpointScanningService,
69+
agentEndpointDiscoveryService
70+
);
71+
72+
// When
73+
verbRegistry.scanClasspath();
74+
var verbs = verbRegistry.getVerbs();
75+
76+
// Then
77+
assertNotNull(verbs);
78+
assertTrue(verbs.containsKey("searchJiraTickets"));
79+
assertTrue(verbs.containsKey("assignJiraTicket"));
80+
assertTrue(verbs.containsKey("updateJiraTicket"));
81+
assertTrue(verbs.containsKey("checkJiraAvailability"));
82+
}
83+
}

0 commit comments

Comments
 (0)