Skip to content

Commit d23ede0

Browse files
authored
Merge pull request #4658 from akto-api-security/feature/jira-agentic-attachments
Feature/jira agentic attachments
2 parents 7a3a5ef + 330caef commit d23ede0

3 files changed

Lines changed: 106 additions & 8 deletions

File tree

apps/dashboard/src/main/java/com/akto/action/JiraIntegrationAction.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
import javax.servlet.http.HttpServletRequest;
6464
import okhttp3.*;
6565

66+
import org.apache.commons.io.FileUtils;
6667
import org.apache.commons.lang3.StringUtils;
6768
import org.apache.struts2.interceptor.ServletRequestAware;
6869
import org.bson.Document;
@@ -101,6 +102,7 @@ public class JiraIntegrationAction extends UserAction implements ServletRequestA
101102
private String origReq;
102103
private String testReq;
103104
private String issueId;
105+
private boolean agenticResult;
104106

105107
private String dashboardUrl;
106108

@@ -878,7 +880,21 @@ public String attachFileToIssue() {
878880
jiraType = jiraIntegration.getJiraType();
879881

880882
String url = jiraIntegration.getBaseUrl() + getCreateIssueEndpoint() + "/" + issueId + ATTACH_FILE_ENDPOINT;
881-
File tmpOutputFile = createRequestFile(origReq, testReq);
883+
File tmpOutputFile;
884+
if (agenticResult) {
885+
if (origReq == null || origReq.isEmpty()) {
886+
return Action.SUCCESS.toUpperCase();
887+
}
888+
try {
889+
tmpOutputFile = File.createTempFile("agentic_conversation", ".txt");
890+
FileUtils.writeStringToFile(tmpOutputFile, origReq, (String) null);
891+
} catch (Exception e) {
892+
loggerMaker.errorAndAddToDb(e, "Error creating agentic conversation file", LogDb.DASHBOARD);
893+
return Action.SUCCESS.toUpperCase();
894+
}
895+
} else {
896+
tmpOutputFile = createRequestFile(origReq, testReq);
897+
}
882898
if(tmpOutputFile == null) {
883899
return Action.SUCCESS.toUpperCase();
884900
}
@@ -1353,6 +1369,14 @@ public void setIssueId(String issueId) {
13531369
this.issueId = issueId;
13541370
}
13551371

1372+
public boolean isAgenticResult() {
1373+
return agenticResult;
1374+
}
1375+
1376+
public void setAgenticResult(boolean agenticResult) {
1377+
this.agenticResult = agenticResult;
1378+
}
1379+
13561380
public Map<String, List<BasicDBObject>> getProjectAndIssueMap() {
13571381
return projectAndIssueMap;
13581382
}

apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/TestRunResultPage/TestRunResultPage.jsx

Lines changed: 79 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState, useEffect } from 'react'
1+
import React, { useState, useEffect, useRef } from 'react'
22
import { CircleTickMajor, ArchiveMinor, LinkMinor } from '@shopify/polaris-icons';
33
import TestingStore from '../testingStore';
44
import api from '../api';
@@ -83,6 +83,7 @@ function TestRunResultPage(props) {
8383
const [conversations, setConversations] = useState([])
8484
const [conversationRemediationText, setConversationRemediationText] = useState(null)
8585
const [validationFailed, setValidationFailed] = useState(false)
86+
const agenticConversationsRef = useRef([])
8687
const [showForbidden, setShowForbidden] = useState(false)
8788

8889
// AI Chat state
@@ -140,8 +141,59 @@ function TestRunResultPage(props) {
140141
return jiraInteg.jiraTicketKey
141142
}
142143

143-
async function attachFileToIssue(origReq, testReq, issueId) {
144-
let jiraInteg = await api.attachFileToIssue(origReq, testReq, issueId);
144+
async function attachFileToIssue(origReq, testReq, issueId, agenticResult = false) {
145+
await api.attachFileToIssue(origReq, testReq, issueId, agenticResult);
146+
}
147+
148+
function formatHttpMessage(rawMessage) {
149+
try {
150+
// The message is stored with literal backslash-escaped quotes ({\"request\":...})
151+
// Wrap in quotes so JSON.parse treats it as a JSON string and unescapes it
152+
let parsed;
153+
try {
154+
parsed = JSON.parse(rawMessage);
155+
} catch (_) {
156+
parsed = JSON.parse('"' + rawMessage + '"');
157+
}
158+
// Handle double-stringified case
159+
if (typeof parsed === 'string') {
160+
parsed = JSON.parse(parsed);
161+
}
162+
const req = parsed.request || {};
163+
const res = parsed.response || {};
164+
165+
let reqHeaders = {};
166+
let resHeaders = {};
167+
try { reqHeaders = typeof req.headers === 'string' ? JSON.parse(req.headers) : (req.headers || {}); } catch (_) {}
168+
try { resHeaders = typeof res.headers === 'string' ? JSON.parse(res.headers) : (res.headers || {}); } catch (_) {}
169+
170+
let reqBody = req.body || '';
171+
let resBody = res.body || '';
172+
try { reqBody = JSON.stringify(JSON.parse(reqBody), null, 2); } catch (_) {}
173+
try { resBody = JSON.stringify(JSON.parse(resBody), null, 2); } catch (_) {}
174+
175+
const formatHeaders = (headers) =>
176+
Object.entries(headers).map(([k, v]) => ` ${k}: ${v}`).join('\n') || ' (none)';
177+
178+
return [
179+
'=== REQUEST ===',
180+
`${req.method || 'GET'} ${req.url || ''}`,
181+
req.queryParams ? `Query Params: ${req.queryParams}` : null,
182+
'\nHeaders:',
183+
formatHeaders(reqHeaders),
184+
'\nBody:',
185+
reqBody || '(empty)',
186+
'',
187+
'=== RESPONSE ===',
188+
`Status: ${res.statusCode || ''}`,
189+
'\nHeaders:',
190+
formatHeaders(resHeaders),
191+
'\nBody:',
192+
resBody || '(empty)',
193+
].filter(line => line !== null).join('\n');
194+
} catch (_) {
195+
return rawMessage;
196+
}
145197
}
146198

147199
function buildTestResultMetadata() {
@@ -246,9 +298,9 @@ function TestRunResultPage(props) {
246298
if (res && res.length > 0) {
247299
const result = transform.prepareConversationsList(res)
248300
setConversations(result.conversations);
249-
// Store remediation text from conversations if available
250301
setConversationRemediationText(result.remediationText || null)
251302
setValidationFailed(result.validationFailed)
303+
agenticConversationsRef.current = res;
252304
}
253305
}
254306
}
@@ -311,7 +363,29 @@ function TestRunResultPage(props) {
311363
}
312364

313365
let sampleData = selectedTestRunResult.testResults[0]
314-
attachFileToIssue(sampleData.originalMessage, sampleData.message, jiraTicketKey)
366+
if (sampleData.resultTypeAgentic) {
367+
const agenticConversations = agenticConversationsRef.current;
368+
if (agenticConversations && agenticConversations.length > 0) {
369+
const separator = "\n\n" + "=".repeat(60) + "\n\n";
370+
371+
// File 1: Prompt/conversation messages
372+
const conversationText = agenticConversations.map((conv, idx) => {
373+
let turn = `Turn ${idx + 1}\n\nTested Interaction:\n${conv.finalSentPrompt}\n\nAI Agent:\n${conv.response}`;
374+
if (conv.validationMessage && conv.validationMessage !== "null") {
375+
turn += `\n\nValidation Message:\n${conv.validationMessage}`;
376+
}
377+
return turn;
378+
}).join(separator);
379+
attachFileToIssue(conversationText, null, jiraTicketKey, true);
380+
381+
// File 2: HTTP request/response from testResults message
382+
if (sampleData.message) {
383+
attachFileToIssue(formatHttpMessage(sampleData.message), null, jiraTicketKey, true);
384+
}
385+
}
386+
} else {
387+
attachFileToIssue(sampleData.originalMessage, sampleData.message, jiraTicketKey)
388+
}
315389

316390
}
317391

apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/api.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,12 +139,12 @@ export default {
139139
}
140140
})
141141
},
142-
attachFileToIssue(origReq, testReq, issueId) {
142+
attachFileToIssue(origReq, testReq, issueId, agenticResult = false) {
143143
return request({
144144
url: '/api/attachFileToIssue',
145145
method: 'post',
146146
data: {
147-
origReq, testReq, issueId
147+
origReq, testReq, issueId, agenticResult
148148
}
149149
})
150150
},

0 commit comments

Comments
 (0)