From 45dd0c836a5245e016507ef85d31a9217a1ae9ad Mon Sep 17 00:00:00 2001 From: Ark2307 Date: Thu, 12 Dec 2024 11:15:57 +0530 Subject: [PATCH 1/8] Temp commit # Conflicts: # apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/observe/api_collections/ApiCollections.jsx # Conflicts: # apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/vulnerability_report/VulnerabilityReport.jsx --- .../com/akto/action/ApiCollectionsAction.java | 2 +- .../akto/action/JiraIntegrationAction.java | 11 ++++++++++ apps/dashboard/src/main/resources/struts.xml | 22 +++++++++++++++++++ .../pages/issues/IssuesPage/IssuesPage.jsx | 17 +++++++++++--- .../src/apps/dashboard/pages/issues/api.js | 7 ++++++ .../api_collections/ApiCollections.jsx | 11 ++++++---- .../VulnerabilityReport.jsx | 11 ++++++++-- 7 files changed, 71 insertions(+), 10 deletions(-) diff --git a/apps/dashboard/src/main/java/com/akto/action/ApiCollectionsAction.java b/apps/dashboard/src/main/java/com/akto/action/ApiCollectionsAction.java index 45b2194dc7..db9372f096 100644 --- a/apps/dashboard/src/main/java/com/akto/action/ApiCollectionsAction.java +++ b/apps/dashboard/src/main/java/com/akto/action/ApiCollectionsAction.java @@ -578,7 +578,7 @@ public String fetchSensitiveInfoInCollections(){ sensitiveSubtypes.addAll(SingleTypeInfoDao.instance.sensitiveSubTypeNames()); List sensitiveSubtypesInRequest = SingleTypeInfoDao.instance.sensitiveSubTypeInRequestNames(); - this.sensitiveUrlsInResponse = SingleTypeInfoDao.instance.getSensitiveApisCount(sensitiveSubtypes, true, Filters.empty()); + this.sensitiveUrlsInResponse = SingleTypeInfoDao.instance.getSensitiveApisCount(sensitiveSubtypes, true, Filters.nin(SingleTypeInfo._COLLECTION_IDS, deactivatedCollections)); sensitiveSubtypes.addAll(sensitiveSubtypesInRequest); this.sensitiveSubtypesInCollection = SingleTypeInfoDao.instance.getSensitiveSubtypesDetectedForCollection(sensitiveSubtypes); diff --git a/apps/dashboard/src/main/java/com/akto/action/JiraIntegrationAction.java b/apps/dashboard/src/main/java/com/akto/action/JiraIntegrationAction.java index 8c59b635f7..df7d847954 100644 --- a/apps/dashboard/src/main/java/com/akto/action/JiraIntegrationAction.java +++ b/apps/dashboard/src/main/java/com/akto/action/JiraIntegrationAction.java @@ -15,6 +15,7 @@ import com.akto.dto.OriginalHttpResponse; import com.akto.dto.jira_integration.JiraIntegration; import com.akto.dto.jira_integration.JiraMetaData; +import com.akto.dto.test_run_findings.TestingIssuesId; import com.akto.log.LoggerMaker; import com.akto.log.LoggerMaker.LogDb; import com.akto.parsers.HttpCallParser; @@ -376,6 +377,12 @@ private BasicDBObject buildContentDetails(String txt, String link) { return details; } + List issuesIds; + + public String bulkCreateJiraTickets (){ + + } + public String getBaseUrl() { return baseUrl; } @@ -467,5 +474,9 @@ public String getJiraTicketKey() { public void setJiraTicketKey(String jiraTicketKey) { this.jiraTicketKey = jiraTicketKey; } + + public void setIssuesIds(List issuesIds) { + this.issuesIds = issuesIds; + } } diff --git a/apps/dashboard/src/main/resources/struts.xml b/apps/dashboard/src/main/resources/struts.xml index 9c1cd9c624..c55eba1cc0 100644 --- a/apps/dashboard/src/main/resources/struts.xml +++ b/apps/dashboard/src/main/resources/struts.xml @@ -6343,6 +6343,28 @@ + + + + + ISSUES + READ_WRITE + User created jira issues in bulk + + + + 403 + false + ^actionErrors.* + + + + 422 + false + ^actionErrors.*, ^responses.* + + + diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/IssuesPage.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/IssuesPage.jsx index a0704569c9..4033fea8ce 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/IssuesPage.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/IssuesPage.jsx @@ -222,6 +222,13 @@ function IssuesPage() { resetResourcesSelected() }) } + + function createJiraTicketBulk () { + api.bulkCreateJiraTickets(items).then((res) => { + setToast(true, false, `${items.length} jira ticket${items.length === 1 ? "" : "s"} created.`) + resetResourcesSelected() + }) + } let issues = [{ content: 'False positive', @@ -234,6 +241,10 @@ function IssuesPage() { { content: 'No time to fix', onAction: () => { ignoreAction("No time to fix") } + }, + { + content: 'Create jira ticket', + onAction: () => { createJiraTicketBulk() } }] let reopen = [{ @@ -246,12 +257,12 @@ function IssuesPage() { switch (status) { case "OPEN": ret = [].concat(issues); break; - case "IGNORED": if (items.length == 1) { - ret = [].concat(issues); - } + case "IGNORED": ret = ret.concat(reopen); break; case "FIXED": + default: + ret = [] } return ret; diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/api.js b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/api.js index 23ab2ab8c4..1f3361559f 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/api.js +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/api.js @@ -51,4 +51,11 @@ export default { data: {startTimeStamp, endTimeStamp} }) }, + bulkCreateJiraTickets(issueIds){ + return request({ + url: 'api/bulkCreateJiraTickets', + method: 'post', + data: {issueIds} + }) + } } \ No newline at end of file diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/observe/api_collections/ApiCollections.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/observe/api_collections/ApiCollections.jsx index 389d192c68..3ed98c8a7a 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/observe/api_collections/ApiCollections.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/observe/api_collections/ApiCollections.jsx @@ -200,7 +200,7 @@ function ApiCollections() { const userRole = window.USER_ROLE const navigate = useNavigate(); - const [data, setData] = useState({'all': [], 'hostname':[], 'groups': [], 'custom': [], 'deactivated': []}) + const [data, setData] = useState({'all': [], 'hostname':[], 'groups': [], 'user_groups': [] , 'custom': [], 'deactivated': []}) const [active, setActive] = useState(false); const [loading, setLoading] = useState(false) @@ -219,7 +219,7 @@ function ApiCollections() { // const dummyData = dummyJson; - const definedTableTabs = ['All', 'Hostname', 'Groups', 'Custom', 'Deactivated'] + const definedTableTabs = ['All', 'Hostname', 'Groups', 'User groups' , 'Custom', 'Deactivated'] const { tabsInfo, selectItems } = useTable() const tableSelectedTab = PersistStore.getState().tableSelectedTab[window.location.pathname] @@ -286,6 +286,7 @@ function ApiCollections() { let dataObj = {} dataObj = convertToNewData(tmp, {}, {}, {}, {}, {}, true); let res = {} + let groupsCollections = dataObj.prettify.filter((c) => c.type === "API_GROUP" && !c.deactivated) res.all = dataObj.prettify res.hostname = dataObj.prettify.filter((c) => c.hostName !== null && c.hostName !== undefined && !c.deactivated) const allGroups = dataObj.prettify.filter((c) => c.type === "API_GROUP" && !c.deactivated); @@ -295,7 +296,7 @@ function ApiCollections() { if (res.hostname.length === 0 && (tableSelectedTab === undefined || tableSelectedTab.length === 0)) { setTimeout(() => { setSelectedTab("custom"); - setSelected(3); + setSelected(4); },[100]) } @@ -427,6 +428,8 @@ function ApiCollections() { const allHostNameMap = func.mapCollectionIdToHostName(tmp) setHostNameMap(allHostNameMap) + groupsCollections = dataObj.prettify.filter((c) => c.type === "API_GROUP" && !c.deactivated) + tmp = {} tmp.all = dataObj.prettify tmp.hostname = dataObj.prettify.filter((c) => c.hostName !== null && c.hostName !== undefined && !c.deactivated) @@ -763,7 +766,7 @@ function ApiCollections() { key={refreshData} pageLimit={100} data={data[selectedTab]} - sortOptions={ selectedTab === 'groups' ? [...tempSortOptions, ...sortOptions] : sortOptions} + sortOptions={ selectedTab === 'groups' ? [...tempSortOptions, ...sortOptions] : sortOptions} resourceName={resourceName} filters={[]} disambiguateLabel={disambiguateLabel} diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/vulnerability_report/VulnerabilityReport.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/vulnerability_report/VulnerabilityReport.jsx index 83e1044105..6b01fb13e6 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/vulnerability_report/VulnerabilityReport.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/vulnerability_report/VulnerabilityReport.jsx @@ -83,7 +83,7 @@ const VulnerabilityReport = () => { } setCurrentDate(startTimestamp) }) - + while (true) { let testingRunCountsFromDB = 0 await api.fetchVulnerableTestingRunResults(testingRunSummaryId, resultsCount).then(async(resp) => { @@ -139,6 +139,13 @@ const VulnerabilityReport = () => { shouldFetchSubcategoriesAndCategories = true } + if(vulnerableTestingRunResults.length > 0){ + if(vulnerableTestingRunResults[0]?.testRunResultSummaryId?.timestamp !== undefined){ + const dateOfTest = func.formatReportDate(new Date(vulnerableTestingRunResults[0]?.testRunResultSummaryId?.timestamp * 1000)) + setCurrentDate(dateOfTest) + } + } + let subCategories let categories if(shouldFetchSubcategoriesAndCategories) { @@ -289,7 +296,7 @@ const VulnerabilityReport = () => { const reportSummaryItems = [ { - title: "Total Vulnerable APIs", + title: "Vulnerable APIs found", data: totalApisTested, }, { From b5c052dc500f3ab59ee6e52c55d0c468d146c3e1 Mon Sep 17 00:00:00 2001 From: Umesh Kumar <166806589+TangoBeeAkto@users.noreply.github.com> Date: Mon, 16 Dec 2024 10:46:06 +0530 Subject: [PATCH 2/8] feat: user can now create jira issues in bulk --- .../akto/action/JiraIntegrationAction.java | 226 +++++++++++++++++- .../shared/JiraTicketCreationModal.jsx | 54 +++++ .../pages/issues/IssuesPage/IssuesPage.jsx | 50 +++- .../src/apps/dashboard/pages/issues/api.js | 4 +- .../TestRunResultPage/TestRunResultFlyout.jsx | 59 ++--- 5 files changed, 341 insertions(+), 52 deletions(-) create mode 100644 apps/dashboard/web/polaris_web/web/src/apps/dashboard/components/shared/JiraTicketCreationModal.jsx diff --git a/apps/dashboard/src/main/java/com/akto/action/JiraIntegrationAction.java b/apps/dashboard/src/main/java/com/akto/action/JiraIntegrationAction.java index df7d847954..b9f9cdb91f 100644 --- a/apps/dashboard/src/main/java/com/akto/action/JiraIntegrationAction.java +++ b/apps/dashboard/src/main/java/com/akto/action/JiraIntegrationAction.java @@ -4,6 +4,15 @@ import java.util.*; import java.util.concurrent.TimeUnit; +import com.akto.dao.test_editor.YamlTemplateDao; +import com.akto.dao.testing.TestingRunResultDao; +import com.akto.dto.ApiInfo; +import com.akto.dto.test_editor.Info; +import com.akto.dto.test_editor.YamlTemplate; +import com.akto.dto.test_run_findings.TestingRunIssues; +import com.akto.dto.testing.TestResult; +import com.akto.dto.testing.TestingRunResult; +import com.mongodb.client.model.Projections; import org.apache.commons.io.FileUtils; import org.bson.conversions.Bson; @@ -57,6 +66,7 @@ public class JiraIntegrationAction extends UserAction { private final String META_ENDPOINT = "/rest/api/3/issue/createmeta"; private final String CREATE_ISSUE_ENDPOINT = "/rest/api/3/issue"; + private final String CREATE_ISSUE_ENDPOINT_BULK = "/rest/api/3/issue/bulk"; private final String ATTACH_FILE_ENDPOINT = "/attachments"; private static final LoggerMaker loggerMaker = new LoggerMaker(ApiExecutor.class); private static final OkHttpClient client = CoreHTTPClient.client.newBuilder() @@ -377,10 +387,217 @@ private BasicDBObject buildContentDetails(String txt, String link) { return details; } + String aktoDashboardHost; List issuesIds; - public String bulkCreateJiraTickets (){ - + if(issuesIds == null || issuesIds.isEmpty()){ + addActionError("Cannot create an empty jira issue."); + return ERROR.toUpperCase(); + } + + if((projId == null || projId.isEmpty()) || (issueType == null || issueType.isEmpty())){ + addActionError("Project ID or Issue Type cannot be empty."); + return ERROR.toUpperCase(); + } + + JiraIntegration jiraInstance = JiraIntegrationDao.instance.findOne(new BasicDBObject()); + if(jiraInstance == null) { + addActionError("Jira is not integrated."); + return ERROR.toUpperCase(); + } + + List jiraMetaDataList = new ArrayList<>(); + Bson projection = Projections.include(YamlTemplate.INFO); + List yamlTemplateList = YamlTemplateDao.instance.findAll(Filters.empty(), projection); + Map testSubTypeToInfoMap = new HashMap<>(); + for(YamlTemplate yamlTemplate : yamlTemplateList) { + Info info = yamlTemplate.getInfo(); + testSubTypeToInfoMap.put(info.getSubCategory(), info); + } + + List testingRunResultList = new ArrayList<>(); + for(TestingIssuesId testingIssuesId : issuesIds) { + TestingRunIssues issue = TestingRunIssuesDao.instance.findOne(Filters.and( + Filters.in("_id.testSubCategory", testingIssuesId.getTestSubCategory()), + Filters.in("_id.apiInfoKey." + ApiInfo.ApiInfoKey.API_COLLECTION_ID, testingIssuesId.getApiInfoKey().getApiCollectionId()), + Filters.in("_id.apiInfoKey." + ApiInfo.ApiInfoKey.URL, testingIssuesId.getApiInfoKey().getUrl()), + Filters.in("_id.apiInfoKey." + ApiInfo.ApiInfoKey.METHOD, testingIssuesId.getApiInfoKey().getMethod()), + Filters.exists("jiraIssueUrl", true) + )); + + if(issue != null && (issue.getJiraIssueUrl() != null || !issue.getJiraIssueUrl().isEmpty())) { + addActionError("One of the selected issues has a Jira issue created for it."); + return Action.ERROR.toUpperCase(); + } + + Info info = testSubTypeToInfoMap.get(testingIssuesId.getTestSubCategory()); + if(info == null) { + loggerMaker.errorAndAddToDb("Error: Test sub category not found: " + testingIssuesId.getTestSubCategory(), LogDb.DASHBOARD); + continue; + } + + TestingRunResult testingRunResult = TestingRunResultDao.instance.findOne(Filters.and( + Filters.in(TestingRunResult.TEST_SUB_TYPE, testingIssuesId.getTestSubCategory()), + Filters.in(TestingRunResult.API_INFO_KEY + "." + ApiInfo.ApiInfoKey.API_COLLECTION_ID, testingIssuesId.getApiInfoKey().getApiCollectionId()), + Filters.in(TestingRunResult.API_INFO_KEY + "." + ApiInfo.ApiInfoKey.URL, testingIssuesId.getApiInfoKey().getUrl()), + Filters.in(TestingRunResult.API_INFO_KEY + "." + ApiInfo.ApiInfoKey.METHOD, testingIssuesId.getApiInfoKey().getMethod()) + ), Projections.include("_id", TestingRunResult.TEST_RESULTS)); + + if(testingRunResult == null) { + loggerMaker.errorAndAddToDb("Error: Testing Run Result not found", LogDb.DASHBOARD); + continue; + } + + testingRunResultList.add(testingRunResult); + + JiraMetaData jiraMetaData; + try { + String inputUrl = testingIssuesId.getApiInfoKey().getUrl(); + + URL url = new URL(inputUrl); + String hostname = url.getHost(); + String endpoint = url.getPath(); + + jiraMetaData = new JiraMetaData( + info.getName(), + "Host - "+hostname, + endpoint, + aktoDashboardHost+"/dashboard/issues?result="+testingRunResult.getId().toHexString(), + info.getDescription(), + testingIssuesId + ); + + } catch (Exception e) { + loggerMaker.errorAndAddToDb("Error while parsing the url: " + e.getMessage(), LogDb.DASHBOARD); + continue; + } + + jiraMetaDataList.add(jiraMetaData); + } + + + + BasicDBObject reqPayload = new BasicDBObject(); + BasicDBList issueUpdates = new BasicDBList(); + + for (JiraMetaData jiraMetaData : jiraMetaDataList) { + BasicDBObject fields = new BasicDBObject(); + + // Issue title + fields.put("summary", "Akto Report - " + jiraMetaData.getIssueTitle()); + jiraIntegration = JiraIntegrationDao.instance.findOne(new BasicDBObject()); + + // Issue type (TASK) + BasicDBObject issueTypeObj = new BasicDBObject(); + issueTypeObj.put("id", this.issueType); + fields.put("issuetype", issueTypeObj); + + // Project ID + BasicDBObject project = new BasicDBObject(); + project.put("key", this.projId); + fields.put("project", project); + + // Issue description + BasicDBObject description = new BasicDBObject(); + description.put("type", "doc"); + description.put("version", 1); + BasicDBList contentList = new BasicDBList(); + contentList.add(buildContentDetails(jiraMetaData.getHostStr(), null)); + contentList.add(buildContentDetails(jiraMetaData.getEndPointStr(), null)); + contentList.add(buildContentDetails("Issue link - Akto dashboard", jiraMetaData.getIssueUrl())); + contentList.add(buildContentDetails(jiraMetaData.getIssueDescription(), null)); + description.put("content", contentList); + + fields.put("description", description); + + // Prepare the issue object + BasicDBObject issueObject = new BasicDBObject(); + issueObject.put("fields", fields); + issueUpdates.add(issueObject); + } + + // Prepare the full request payload + reqPayload.put("issueUpdates", issueUpdates); + + // URL for bulk create issues + String url = jiraIntegration.getBaseUrl() + CREATE_ISSUE_ENDPOINT_BULK; + String authHeader = Base64.getEncoder().encodeToString((jiraIntegration.getUserEmail() + ":" + jiraIntegration.getApiToken()).getBytes()); + + Map> headers = new HashMap<>(); + headers.put("Authorization", Collections.singletonList("Basic " + authHeader)); + + OriginalHttpRequest request = new OriginalHttpRequest(url, "", "POST", reqPayload.toString(), headers, ""); + + try { + OriginalHttpResponse response = ApiExecutor.sendRequest(request, true, null, false, new ArrayList<>()); + String responsePayload = response.getBody(); + + if (response.getStatusCode() > 201 || responsePayload == null) { + loggerMaker.errorAndAddToDb("Error while creating Jira issues in bulk, URL not accessible, request body " + + request.getBody() + " ,response body " + response.getBody() + " ,response status " + response.getStatusCode(), + LoggerMaker.LogDb.DASHBOARD); + + if (responsePayload != null) { + try { + BasicDBObject obj = BasicDBObject.parse(responsePayload); + List errorMessages = (List) obj.get("errorMessages"); + String error; + if (errorMessages.size() == 0) { + BasicDBObject errObj = BasicDBObject.parse(obj.getString("errors")); + error = errObj.getString("project"); + } else { + error = errorMessages.get(0); + } + addActionError(error); + } catch (Exception e) { + // Handle exception + } + } + + return Action.ERROR.toUpperCase(); + } + + BasicDBObject payloadObj; + try { + payloadObj = BasicDBObject.parse(responsePayload); + List issues = (List) payloadObj.get("issues"); + for (BasicDBObject issue : issues) { + String issueKey = issue.getString("key"); + String jiraTicketUrl = jiraIntegration.getBaseUrl() + "/browse/" + issueKey; + + for (JiraMetaData metaData : jiraMetaDataList) { + TestingRunIssuesDao.instance.getMCollection().updateOne( + Filters.eq(Constants.ID, metaData.getTestingIssueId()), + Updates.combine(Updates.set("jiraIssueUrl", jiraTicketUrl)), + new UpdateOptions().upsert(false) + ); + } + } + + for (int i = 0; i < issues.size(); i++) { + BasicDBObject issue = issues.get(i); + TestingRunResult testingRunResult = testingRunResultList.get(i); + String issueKey = issue.getString("key"); + TestResult genericTestResult = (TestResult) testingRunResult.getTestResults().get(testingRunResult.getTestResults().size() - 1); + setIssueId(issueKey); + setOrigReq(genericTestResult.getOriginalMessage()); + setTestReq(genericTestResult.getMessage()); + String status = attachFileToIssue(); + if (status.equals(ERROR.toUpperCase())) { + return ERROR.toUpperCase(); + } + } + } catch (Exception e) { + loggerMaker.errorAndAddToDb(e, "Error processing Jira bulk issue response " + e.getMessage(), LoggerMaker.LogDb.DASHBOARD); + return Action.ERROR.toUpperCase(); + } + + } catch (Exception e) { + loggerMaker.errorAndAddToDb(e, "Error making Jira bulk create request: " + e.getMessage(), LoggerMaker.LogDb.DASHBOARD); + return Action.ERROR.toUpperCase(); + } + + return Action.SUCCESS.toUpperCase(); } public String getBaseUrl() { @@ -478,5 +695,8 @@ public void setJiraTicketKey(String jiraTicketKey) { public void setIssuesIds(List issuesIds) { this.issuesIds = issuesIds; } - + + public void setAktoDashboardHost(String aktoDashboardHost) { + this.aktoDashboardHost = aktoDashboardHost; + } } diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/components/shared/JiraTicketCreationModal.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/components/shared/JiraTicketCreationModal.jsx new file mode 100644 index 0000000000..247c91bf7e --- /dev/null +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/components/shared/JiraTicketCreationModal.jsx @@ -0,0 +1,54 @@ +import { Modal, Text, VerticalStack } from '@shopify/polaris' +import React from 'react' +import DropdownSearch from './DropdownSearch' + +const JiraTicketCreationModal = ({ activator, modalActive, setModalActive, handleSaveAction, createDisabled, jiraProjectMaps, setProjId, setIssueType, projId, issueType, issueId }) => { + const getValueFromIssueType = (projId, issueId) => { + if(Object.keys(jiraProjectMaps).length > 0 && projId.length > 0 && issueId.length > 0){ + const jiraTemp = jiraProjectMaps[projId].filter(x => x.issueId === issueId) + if(jiraTemp.length > 0){ + return jiraTemp[0].issueType + } + } + return issueType + } + + return ( + setModalActive(false)} + size="small" + title={Configure jira ticket details} + primaryAction={{ + content: 'Create ticket', + onAction: () => handleSaveAction(issueId), + disabled: createDisabled + }} + > + + + {return{label: x, value: x}}): []} + setSelected={setProjId} + preSelected={projId} + value={projId} + /> + + 0 ? jiraProjectMaps[projId].map((x) => {return{label: x.issueType, value: x.issueId}}) : []} + setSelected={setIssueType} + preSelected={issueType} + value={getValueFromIssueType(projId, issueType)} + /> + + + + ) +} + +export default JiraTicketCreationModal \ No newline at end of file diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/IssuesPage.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/IssuesPage.jsx index 4033fea8ce..67316b5c45 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/IssuesPage.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/IssuesPage.jsx @@ -6,7 +6,7 @@ import Store from "../../../store"; import func from "@/util/func"; import { MarkFulfilledMinor, ReportMinor, ExternalMinor } from '@shopify/polaris-icons'; import PersistStore from "../../../../main/PersistStore"; -import { Button, HorizontalGrid, HorizontalStack, IndexFiltersMode } from "@shopify/polaris"; +import { Button, HorizontalGrid, HorizontalStack, IndexFiltersMode, Modal, Text, VerticalStack } from "@shopify/polaris"; import EmptyScreensLayout from "../../../components/banners/EmptyScreensLayout"; import { ISSUES_PAGE_DOCS_URL } from "../../../../main/onboardingData"; import {SelectCollectionComponent} from "../../testing/TestRunsPage/TestrunsBannerComponent" @@ -27,6 +27,9 @@ import SpinnerCentered from "../../../components/progress/SpinnerCentered.jsx"; import TableStore from "../../../components/tables/TableStore.js"; import CriticalFindingsGraph from "./CriticalFindingsGraph.jsx"; import CriticalUnsecuredAPIsOverTimeGraph from "./CriticalUnsecuredAPIsOverTimeGraph.jsx"; +import DropdownSearch from "../../../components/shared/DropdownSearch.jsx"; +import settingFunctions from "../../settings/module.js"; +import JiraTicketCreationModal from "../../../components/shared/JiraTicketCreationModal.jsx"; const sortOptions = [ { label: 'Severity', value: 'severity asc', directionLabel: 'Highest', sortKey: 'severity', columnIndex: 2 }, @@ -127,6 +130,12 @@ function IssuesPage() { const [selected, setSelected] = useState(0) const [tableLoading, setTableLoading] = useState(false) const [issuesDataCount, setIssuesDataCount] = useState([]) + const [jiraModalActive, setJiraModalActive] = useState(false) + const [selectedIssuesItems, setSelectedIssuesItems] = useState([]) + const [jiraProjectMaps,setJiraProjectMap] = useState({}) + const [issueType, setIssueType] = useState(''); + const [projId, setProjId] = useState('') + const [isCreatingTicket, setIsCreatingTicket] = useState(false) const [currDateRange, dispatchCurrDateRange] = useReducer(produce((draft, action) => func.dateRangeReducer(draft, action)), values.ranges[5]) @@ -166,6 +175,7 @@ function IssuesPage() { TableStore.getState().setSelectedItems([]) selectItems([]) setKey(!key) + setSelectedIssuesItems([]) } useEffect(() => { @@ -200,6 +210,18 @@ function IssuesPage() { filtersOptions = func.getCollectionFilters(filtersOptions) + const handleSaveJiraAction = () => { + setToast(true, false, "Please wait while we create your Jira ticket.") + setIsCreatingTicket(true) + setJiraModalActive(false) + api.bulkCreateJiraTickets(selectedIssuesItems, window.location.origin, projId, issueType).then((res) => { + setToast(true, false, `${selectedIssuesItems.length} jira ticket${selectedIssuesItems.length === 1 ? "" : "s"} created.`) + resetResourcesSelected() + }).finally(() => { + setIsCreatingTicket(false) + }) + } + let promotedBulkActions = (selectedResources) => { let items if(selectedResources.length > 0 && typeof selectedResources[0][0] === 'string') { @@ -224,9 +246,18 @@ function IssuesPage() { } function createJiraTicketBulk () { - api.bulkCreateJiraTickets(items).then((res) => { - setToast(true, false, `${items.length} jira ticket${items.length === 1 ? "" : "s"} created.`) - resetResourcesSelected() + setSelectedIssuesItems(items) + settingFunctions.fetchJiraIntegration().then((jirIntegration) => { + if(jirIntegration.projectIdsMap !== null && Object.keys(jirIntegration.projectIdsMap).length > 0){ + setJiraProjectMap(jirIntegration.projectIdsMap) + if(Object.keys(jirIntegration.projectIdsMap).length > 0){ + setProjId(Object.keys(jirIntegration.projectIdsMap)[0]) + } + }else{ + setProjId(jirIntegration.projId) + setIssueType(jirIntegration.issueType) + } + setJiraModalActive(true) }) } @@ -485,6 +516,17 @@ function IssuesPage() { secondaryActions={ dispatchCurrDateRange({ type: "update", period: dateObj.period, title: dateObj.title, alias: dateObj.alias })} />} /> {(resultId !== null && resultId.length > 0) ? : null} + ) } diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/api.js b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/api.js index 1f3361559f..f262ab32c7 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/api.js +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/api.js @@ -51,11 +51,11 @@ export default { data: {startTimeStamp, endTimeStamp} }) }, - bulkCreateJiraTickets(issueIds){ + bulkCreateJiraTickets(issuesIds, aktoDashboardHost, projId, issueType){ return request({ url: 'api/bulkCreateJiraTickets', method: 'post', - data: {issueIds} + data: {issuesIds, aktoDashboardHost, projId, issueType} }) } } \ No newline at end of file diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/TestRunResultPage/TestRunResultFlyout.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/TestRunResultPage/TestRunResultFlyout.jsx index 95c4690ccd..9efe2752b8 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/TestRunResultPage/TestRunResultFlyout.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/TestRunResultPage/TestRunResultFlyout.jsx @@ -16,6 +16,7 @@ import ActivityTracker from '../../dashboard/components/ActivityTracker' import observeFunc from "../../observe/transform.js" import settingFunctions from '../../settings/module.js' import DropdownSearch from '../../../components/shared/DropdownSearch.jsx' +import JiraTicketCreationModal from '../../../components/shared/JiraTicketCreationModal.jsx' function TestRunResultFlyout(props) { @@ -28,6 +29,7 @@ function TestRunResultFlyout(props) { const [jiraProjectMaps,setJiraProjectMap] = useState({}) const [issueType, setIssueType] = useState(''); const [projId, setProjId] = useState('') + const [isCreatingTicket, setIsCreatingTicket] = useState(false) // modify testing run result and headers const infoStateFlyout = infoState && infoState.length > 0 ? infoState.filter((item) => item.title !== 'Jira') : [] const fetchApiInfo = useCallback( async(apiInfoKey) => { @@ -94,7 +96,9 @@ function TestRunResultFlyout(props) { const handleSaveAction = (id) => { if(projId.length > 0 && issueType.length > 0){ + setIsCreatingTicket(true) createJiraTicket(id, projId, issueType) + setIsCreatingTicket(false) setModalActive(false) }else{ func.setToast(true, true, "Invalid project id or issue type") @@ -129,17 +133,6 @@ function TestRunResultFlyout(props) { window.open(navUrl, "_blank") } - const getValueFromIssueType = (projId, issueId) => { - if(Object.keys(jiraProjectMaps).length > 0 && projId.length > 0 && issueId.length > 0){ - const jiraTemp = jiraProjectMaps[projId].filter(x => x.issueId === issueId) - if(jiraTemp.length > 0){ - return jiraTemp[0].issueType - } - } - return issueType - - } - function ActionsComp (){ const issuesActions = issueDetails?.testRunIssueStatus === "IGNORED" ? [...issues, ...reopen] : issues return( @@ -190,39 +183,19 @@ function TestRunResultFlyout(props) { {selectedTestRunResult && selectedTestRunResult.vulnerable && - Create Jira Ticket} - open={modalActive} - onClose={() => setModalActive(false)} - size="small" - title={Configure jira ticket details} - primaryAction={{ - content: 'Create ticket', - onAction: () => handleSaveAction(issueDetails.id) - }} - > - - - {return{label: x, value: x}}): []} - setSelected={setProjId} - preSelected={projId} - value={projId} - /> - - 0 ? jiraProjectMaps[projId].map((x) => {return{label: x.issueType, value: x.issueId}}) : []} - setSelected={setIssueType} - preSelected={issueType} - value={getValueFromIssueType(projId, issueType)} - /> - - - + modalActive={modalActive} + setModalActive={setModalActive} + handleSaveAction={handleSaveAction} + createDisabled={(!projId || !issueType || isCreatingTicket)} + jiraProjectMaps={jiraProjectMaps} + setProjId={setProjId} + setIssueType={setIssueType} + projId={projId} + issueType={issueType} + issueId={issueDetails.id} + /> } From 0013c3587823fb6b5c780423651b171ca33c6668 Mon Sep 17 00:00:00 2001 From: Umesh Kumar <166806589+TangoBeeAkto@users.noreply.github.com> Date: Mon, 16 Dec 2024 12:07:10 +0530 Subject: [PATCH 3/8] fix: removed few things --- .../pages/observe/api_collections/ApiCollections.jsx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/observe/api_collections/ApiCollections.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/observe/api_collections/ApiCollections.jsx index 3ed98c8a7a..f620e47144 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/observe/api_collections/ApiCollections.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/observe/api_collections/ApiCollections.jsx @@ -200,7 +200,7 @@ function ApiCollections() { const userRole = window.USER_ROLE const navigate = useNavigate(); - const [data, setData] = useState({'all': [], 'hostname':[], 'groups': [], 'user_groups': [] , 'custom': [], 'deactivated': []}) + const [data, setData] = useState({'all': [], 'hostname':[], 'groups': [], 'custom': [], 'deactivated': []}) const [active, setActive] = useState(false); const [loading, setLoading] = useState(false) @@ -219,7 +219,7 @@ function ApiCollections() { // const dummyData = dummyJson; - const definedTableTabs = ['All', 'Hostname', 'Groups', 'User groups' , 'Custom', 'Deactivated'] + const definedTableTabs = ['All', 'Hostname', 'Groups', 'Custom', 'Deactivated'] const { tabsInfo, selectItems } = useTable() const tableSelectedTab = PersistStore.getState().tableSelectedTab[window.location.pathname] @@ -286,7 +286,6 @@ function ApiCollections() { let dataObj = {} dataObj = convertToNewData(tmp, {}, {}, {}, {}, {}, true); let res = {} - let groupsCollections = dataObj.prettify.filter((c) => c.type === "API_GROUP" && !c.deactivated) res.all = dataObj.prettify res.hostname = dataObj.prettify.filter((c) => c.hostName !== null && c.hostName !== undefined && !c.deactivated) const allGroups = dataObj.prettify.filter((c) => c.type === "API_GROUP" && !c.deactivated); @@ -296,7 +295,7 @@ function ApiCollections() { if (res.hostname.length === 0 && (tableSelectedTab === undefined || tableSelectedTab.length === 0)) { setTimeout(() => { setSelectedTab("custom"); - setSelected(4); + setSelected(3); },[100]) } @@ -428,8 +427,6 @@ function ApiCollections() { const allHostNameMap = func.mapCollectionIdToHostName(tmp) setHostNameMap(allHostNameMap) - groupsCollections = dataObj.prettify.filter((c) => c.type === "API_GROUP" && !c.deactivated) - tmp = {} tmp.all = dataObj.prettify tmp.hostname = dataObj.prettify.filter((c) => c.hostName !== null && c.hostName !== undefined && !c.deactivated) From 46857c7e2daeacee0e9131c0247ad5d3fe41a7ac Mon Sep 17 00:00:00 2001 From: Umesh Kumar <166806589+TangoBeeAkto@users.noreply.github.com> Date: Mon, 16 Dec 2024 12:23:50 +0530 Subject: [PATCH 4/8] refactor: handling jira creation loading inside the modal comp --- .../components/shared/JiraTicketCreationModal.jsx | 14 ++++++++++---- .../pages/issues/IssuesPage/IssuesPage.jsx | 5 ----- .../TestRunResultPage/TestRunResultFlyout.jsx | 4 ---- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/components/shared/JiraTicketCreationModal.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/components/shared/JiraTicketCreationModal.jsx index 247c91bf7e..f367a87736 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/components/shared/JiraTicketCreationModal.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/components/shared/JiraTicketCreationModal.jsx @@ -1,8 +1,10 @@ import { Modal, Text, VerticalStack } from '@shopify/polaris' -import React from 'react' +import React, { useState } from 'react' import DropdownSearch from './DropdownSearch' -const JiraTicketCreationModal = ({ activator, modalActive, setModalActive, handleSaveAction, createDisabled, jiraProjectMaps, setProjId, setIssueType, projId, issueType, issueId }) => { +const JiraTicketCreationModal = ({ activator, modalActive, setModalActive, handleSaveAction, jiraProjectMaps, setProjId, setIssueType, projId, issueType, issueId }) => { + const [isCreatingTicket, setIsCreatingTicket] = useState(false) + const getValueFromIssueType = (projId, issueId) => { if(Object.keys(jiraProjectMaps).length > 0 && projId.length > 0 && issueId.length > 0){ const jiraTemp = jiraProjectMaps[projId].filter(x => x.issueId === issueId) @@ -22,8 +24,12 @@ const JiraTicketCreationModal = ({ activator, modalActive, setModalActive, handl title={Configure jira ticket details} primaryAction={{ content: 'Create ticket', - onAction: () => handleSaveAction(issueId), - disabled: createDisabled + onAction: () => { + setIsCreatingTicket(true) + handleSaveAction(issueId) + setIsCreatingTicket(false) + }, + disabled: (!projId || !issueType || isCreatingTicket) }} > diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/IssuesPage.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/IssuesPage.jsx index 67316b5c45..3de19e0a1e 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/IssuesPage.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/IssuesPage.jsx @@ -135,7 +135,6 @@ function IssuesPage() { const [jiraProjectMaps,setJiraProjectMap] = useState({}) const [issueType, setIssueType] = useState(''); const [projId, setProjId] = useState('') - const [isCreatingTicket, setIsCreatingTicket] = useState(false) const [currDateRange, dispatchCurrDateRange] = useReducer(produce((draft, action) => func.dateRangeReducer(draft, action)), values.ranges[5]) @@ -212,13 +211,10 @@ function IssuesPage() { const handleSaveJiraAction = () => { setToast(true, false, "Please wait while we create your Jira ticket.") - setIsCreatingTicket(true) setJiraModalActive(false) api.bulkCreateJiraTickets(selectedIssuesItems, window.location.origin, projId, issueType).then((res) => { setToast(true, false, `${selectedIssuesItems.length} jira ticket${selectedIssuesItems.length === 1 ? "" : "s"} created.`) resetResourcesSelected() - }).finally(() => { - setIsCreatingTicket(false) }) } @@ -520,7 +516,6 @@ function IssuesPage() { modalActive={jiraModalActive} setModalActive={setJiraModalActive} handleSaveAction={handleSaveJiraAction} - createDisabled={(!projId || !issueType || isCreatingTicket)} jiraProjectMaps={jiraProjectMaps} setProjId={setProjId} setIssueType={setIssueType} diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/TestRunResultPage/TestRunResultFlyout.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/TestRunResultPage/TestRunResultFlyout.jsx index 9efe2752b8..7ad8dbde32 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/TestRunResultPage/TestRunResultFlyout.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/TestRunResultPage/TestRunResultFlyout.jsx @@ -29,7 +29,6 @@ function TestRunResultFlyout(props) { const [jiraProjectMaps,setJiraProjectMap] = useState({}) const [issueType, setIssueType] = useState(''); const [projId, setProjId] = useState('') - const [isCreatingTicket, setIsCreatingTicket] = useState(false) // modify testing run result and headers const infoStateFlyout = infoState && infoState.length > 0 ? infoState.filter((item) => item.title !== 'Jira') : [] const fetchApiInfo = useCallback( async(apiInfoKey) => { @@ -96,9 +95,7 @@ function TestRunResultFlyout(props) { const handleSaveAction = (id) => { if(projId.length > 0 && issueType.length > 0){ - setIsCreatingTicket(true) createJiraTicket(id, projId, issueType) - setIsCreatingTicket(false) setModalActive(false) }else{ func.setToast(true, true, "Invalid project id or issue type") @@ -188,7 +185,6 @@ function TestRunResultFlyout(props) { modalActive={modalActive} setModalActive={setModalActive} handleSaveAction={handleSaveAction} - createDisabled={(!projId || !issueType || isCreatingTicket)} jiraProjectMaps={jiraProjectMaps} setProjId={setProjId} setIssueType={setIssueType} From eae0e898d6919604976dd55719a966395a2873b1 Mon Sep 17 00:00:00 2001 From: Umesh Kumar <166806589+TangoBeeAkto@users.noreply.github.com> Date: Mon, 16 Dec 2024 12:26:46 +0530 Subject: [PATCH 5/8] refactor: removed unwated code --- .../testing/vulnerability_report/VulnerabilityReport.jsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/vulnerability_report/VulnerabilityReport.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/vulnerability_report/VulnerabilityReport.jsx index 6b01fb13e6..c548053ce8 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/vulnerability_report/VulnerabilityReport.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/vulnerability_report/VulnerabilityReport.jsx @@ -139,13 +139,6 @@ const VulnerabilityReport = () => { shouldFetchSubcategoriesAndCategories = true } - if(vulnerableTestingRunResults.length > 0){ - if(vulnerableTestingRunResults[0]?.testRunResultSummaryId?.timestamp !== undefined){ - const dateOfTest = func.formatReportDate(new Date(vulnerableTestingRunResults[0]?.testRunResultSummaryId?.timestamp * 1000)) - setCurrentDate(dateOfTest) - } - } - let subCategories let categories if(shouldFetchSubcategoriesAndCategories) { From a13ff7265adbfdb8014dd2588e888de64fd12293 Mon Sep 17 00:00:00 2001 From: Umesh Kumar <166806589+TangoBeeAkto@users.noreply.github.com> Date: Mon, 16 Dec 2024 16:39:03 +0530 Subject: [PATCH 6/8] fix: handling jira creation errors in frontend --- .../akto/action/JiraIntegrationAction.java | 142 ++++++++---------- .../pages/issues/IssuesPage/IssuesPage.jsx | 6 +- .../TestRunResultPage/TestRunResultPage.jsx | 3 + 3 files changed, 74 insertions(+), 77 deletions(-) diff --git a/apps/dashboard/src/main/java/com/akto/action/JiraIntegrationAction.java b/apps/dashboard/src/main/java/com/akto/action/JiraIntegrationAction.java index b9f9cdb91f..3d288d77d1 100644 --- a/apps/dashboard/src/main/java/com/akto/action/JiraIntegrationAction.java +++ b/apps/dashboard/src/main/java/com/akto/action/JiraIntegrationAction.java @@ -1,6 +1,7 @@ package com.akto.action; import java.io.File; +import java.net.URL; import java.util.*; import java.util.concurrent.TimeUnit; @@ -200,42 +201,7 @@ public String fetchIntegration() { public String createIssue() { BasicDBObject reqPayload = new BasicDBObject(); - BasicDBObject fields = new BasicDBObject(); - - String endpoint = jiraMetaData.getEndPointStr().replace("Endpoint - ", ""); - String truncatedEndpoint = endpoint; - if(endpoint.length() > 30) { - truncatedEndpoint = endpoint.substring(0, 15) + "..." + endpoint.substring(endpoint.length() - 15); - } - - String endpointMethod = jiraMetaData.getTestingIssueId().getApiInfoKey().getMethod().name(); - - // issue title - fields.put("summary", "Akto Report - " + jiraMetaData.getIssueTitle() + " (" + endpointMethod + " - " + truncatedEndpoint + ")"); - jiraIntegration = JiraIntegrationDao.instance.findOne(new BasicDBObject()); - - // issue type (TASK) - BasicDBObject issueTypeObj = new BasicDBObject(); - issueTypeObj.put("id", this.issueType); - fields.put("issuetype", issueTypeObj); - - // project id - BasicDBObject project = new BasicDBObject(); - project.put("key", this.projId); - fields.put("project", project); - - // issue description - BasicDBObject description = new BasicDBObject(); - description.put("type", "doc"); - description.put("version", 1); - BasicDBList contentList = new BasicDBList(); - contentList.add(buildContentDetails(jiraMetaData.getHostStr(), null)); - contentList.add(buildContentDetails(jiraMetaData.getEndPointStr(), null)); - contentList.add(buildContentDetails("Issue link - Akto dashboard", jiraMetaData.getIssueUrl())); - contentList.add(buildContentDetails(jiraMetaData.getIssueDescription(), null)); - description.put("content", contentList); - - fields.put("description", description); + BasicDBObject fields = jiraTicketPayloadCreator(jiraMetaData); reqPayload.put("fields", fields); @@ -389,6 +355,7 @@ private BasicDBObject buildContentDetails(String txt, String link) { String aktoDashboardHost; List issuesIds; + private String errorMessage; public String bulkCreateJiraTickets (){ if(issuesIds == null || issuesIds.isEmpty()){ addActionError("Cannot create an empty jira issue."); @@ -408,7 +375,11 @@ public String bulkCreateJiraTickets (){ List jiraMetaDataList = new ArrayList<>(); Bson projection = Projections.include(YamlTemplate.INFO); - List yamlTemplateList = YamlTemplateDao.instance.findAll(Filters.empty(), projection); + List testingSubCategories = new ArrayList<>(); + for(TestingIssuesId testingIssuesId : issuesIds) { + testingSubCategories.add(testingIssuesId.getTestSubCategory()); + } + List yamlTemplateList = YamlTemplateDao.instance.findAll(Filters.in("_id", testingSubCategories), projection); Map testSubTypeToInfoMap = new HashMap<>(); for(YamlTemplate yamlTemplate : yamlTemplateList) { Info info = yamlTemplate.getInfo(); @@ -416,25 +387,19 @@ public String bulkCreateJiraTickets (){ } List testingRunResultList = new ArrayList<>(); + int existingIssues = 0; for(TestingIssuesId testingIssuesId : issuesIds) { TestingRunIssues issue = TestingRunIssuesDao.instance.findOne(Filters.and( - Filters.in("_id.testSubCategory", testingIssuesId.getTestSubCategory()), - Filters.in("_id.apiInfoKey." + ApiInfo.ApiInfoKey.API_COLLECTION_ID, testingIssuesId.getApiInfoKey().getApiCollectionId()), - Filters.in("_id.apiInfoKey." + ApiInfo.ApiInfoKey.URL, testingIssuesId.getApiInfoKey().getUrl()), - Filters.in("_id.apiInfoKey." + ApiInfo.ApiInfoKey.METHOD, testingIssuesId.getApiInfoKey().getMethod()), + Filters.in("_id", testingIssuesId), Filters.exists("jiraIssueUrl", true) )); if(issue != null && (issue.getJiraIssueUrl() != null || !issue.getJiraIssueUrl().isEmpty())) { - addActionError("One of the selected issues has a Jira issue created for it."); - return Action.ERROR.toUpperCase(); + existingIssues++; + continue; } Info info = testSubTypeToInfoMap.get(testingIssuesId.getTestSubCategory()); - if(info == null) { - loggerMaker.errorAndAddToDb("Error: Test sub category not found: " + testingIssuesId.getTestSubCategory(), LogDb.DASHBOARD); - continue; - } TestingRunResult testingRunResult = TestingRunResultDao.instance.findOne(Filters.and( Filters.in(TestingRunResult.TEST_SUB_TYPE, testingIssuesId.getTestSubCategory()), @@ -475,40 +440,21 @@ public String bulkCreateJiraTickets (){ jiraMetaDataList.add(jiraMetaData); } - - BasicDBObject reqPayload = new BasicDBObject(); BasicDBList issueUpdates = new BasicDBList(); - for (JiraMetaData jiraMetaData : jiraMetaDataList) { - BasicDBObject fields = new BasicDBObject(); + if(existingIssues == issuesIds.size()) { + errorMessage = "All selected issues already have existing Jira tickets. No new tickets were created."; + } else if(existingIssues > 0) { + errorMessage = "Jira tickets created for all selected issues, except for " + existingIssues + " issues that already have tickets."; + } - // Issue title - fields.put("summary", "Akto Report - " + jiraMetaData.getIssueTitle()); - jiraIntegration = JiraIntegrationDao.instance.findOne(new BasicDBObject()); + if(jiraMetaDataList.isEmpty()) { + return Action.SUCCESS.toUpperCase(); + } - // Issue type (TASK) - BasicDBObject issueTypeObj = new BasicDBObject(); - issueTypeObj.put("id", this.issueType); - fields.put("issuetype", issueTypeObj); - - // Project ID - BasicDBObject project = new BasicDBObject(); - project.put("key", this.projId); - fields.put("project", project); - - // Issue description - BasicDBObject description = new BasicDBObject(); - description.put("type", "doc"); - description.put("version", 1); - BasicDBList contentList = new BasicDBList(); - contentList.add(buildContentDetails(jiraMetaData.getHostStr(), null)); - contentList.add(buildContentDetails(jiraMetaData.getEndPointStr(), null)); - contentList.add(buildContentDetails("Issue link - Akto dashboard", jiraMetaData.getIssueUrl())); - contentList.add(buildContentDetails(jiraMetaData.getIssueDescription(), null)); - description.put("content", contentList); - - fields.put("description", description); + for (JiraMetaData jiraMetaData : jiraMetaDataList) { + BasicDBObject fields = jiraTicketPayloadCreator(jiraMetaData); // Prepare the issue object BasicDBObject issueObject = new BasicDBObject(); @@ -600,6 +546,46 @@ public String bulkCreateJiraTickets (){ return Action.SUCCESS.toUpperCase(); } + private BasicDBObject jiraTicketPayloadCreator(JiraMetaData jiraMetaData) { + BasicDBObject fields = new BasicDBObject(); + String endpoint = jiraMetaData.getEndPointStr().replace("Endpoint - ", ""); + String truncatedEndpoint = endpoint; + if(endpoint.length() > 30) { + truncatedEndpoint = endpoint.substring(0, 15) + "..." + endpoint.substring(endpoint.length() - 15); + } + + String endpointMethod = jiraMetaData.getTestingIssueId().getApiInfoKey().getMethod().name(); + + // issue title + fields.put("summary", "Akto Report - " + jiraMetaData.getIssueTitle() + " (" + endpointMethod + " - " + truncatedEndpoint + ")"); + jiraIntegration = JiraIntegrationDao.instance.findOne(new BasicDBObject()); + + // Issue type (TASK) + BasicDBObject issueTypeObj = new BasicDBObject(); + issueTypeObj.put("id", this.issueType); + fields.put("issuetype", issueTypeObj); + + // Project ID + BasicDBObject project = new BasicDBObject(); + project.put("key", this.projId); + fields.put("project", project); + + // Issue description + BasicDBObject description = new BasicDBObject(); + description.put("type", "doc"); + description.put("version", 1); + BasicDBList contentList = new BasicDBList(); + contentList.add(buildContentDetails(jiraMetaData.getHostStr(), null)); + contentList.add(buildContentDetails(jiraMetaData.getEndPointStr(), null)); + contentList.add(buildContentDetails("Issue link - Akto dashboard", jiraMetaData.getIssueUrl())); + contentList.add(buildContentDetails(jiraMetaData.getIssueDescription(), null)); + description.put("content", contentList); + + fields.put("description", description); + + return fields; + } + public String getBaseUrl() { return baseUrl; } @@ -699,4 +685,8 @@ public void setIssuesIds(List issuesIds) { public void setAktoDashboardHost(String aktoDashboardHost) { this.aktoDashboardHost = aktoDashboardHost; } + + public String getErrorMessage() { + return errorMessage; + } } diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/IssuesPage.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/IssuesPage.jsx index 3de19e0a1e..66e1d251cf 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/IssuesPage.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/IssuesPage.jsx @@ -213,7 +213,11 @@ function IssuesPage() { setToast(true, false, "Please wait while we create your Jira ticket.") setJiraModalActive(false) api.bulkCreateJiraTickets(selectedIssuesItems, window.location.origin, projId, issueType).then((res) => { - setToast(true, false, `${selectedIssuesItems.length} jira ticket${selectedIssuesItems.length === 1 ? "" : "s"} created.`) + if(res?.errorMessage) { + setToast(true, false, res?.errorMessage) + } else { + setToast(true, false, `${selectedIssuesItems.length} jira ticket${selectedIssuesItems.length === 1 ? "" : "s"} created.`) + } resetResourcesSelected() }) } diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/TestRunResultPage/TestRunResultPage.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/TestRunResultPage/TestRunResultPage.jsx index 02447bb8b1..0c45df452b 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/TestRunResultPage/TestRunResultPage.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/TestRunResultPage/TestRunResultPage.jsx @@ -169,6 +169,9 @@ function TestRunResultPage(props) { let jiraTicketKey = "" await createJiraTicketApiCall("Host - "+hostName, pathname, window.location.href, description, issueTitle, issueId, projId, issueType).then(async(res)=> { + if(res?.errorMessage) { + setToast(true, true, res?.errorMessage) + } jiraTicketKey = res await fetchData(); setToast(true,false,"Jira Ticket Created, scroll down to view") From e1e8d45ec6f4ffa17f9f0b11a90176a628a60a4d Mon Sep 17 00:00:00 2001 From: Umesh Kumar <166806589+TangoBeeAkto@users.noreply.github.com> Date: Wed, 18 Dec 2024 14:55:29 +0530 Subject: [PATCH 7/8] fix: fixed some bugs --- .../akto/action/JiraIntegrationAction.java | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/apps/dashboard/src/main/java/com/akto/action/JiraIntegrationAction.java b/apps/dashboard/src/main/java/com/akto/action/JiraIntegrationAction.java index 3d288d77d1..242c7fda3b 100644 --- a/apps/dashboard/src/main/java/com/akto/action/JiraIntegrationAction.java +++ b/apps/dashboard/src/main/java/com/akto/action/JiraIntegrationAction.java @@ -201,6 +201,7 @@ public String fetchIntegration() { public String createIssue() { BasicDBObject reqPayload = new BasicDBObject(); + jiraIntegration = JiraIntegrationDao.instance.findOne(new BasicDBObject()); BasicDBObject fields = jiraTicketPayloadCreator(jiraMetaData); reqPayload.put("fields", fields); @@ -367,8 +368,8 @@ public String bulkCreateJiraTickets (){ return ERROR.toUpperCase(); } - JiraIntegration jiraInstance = JiraIntegrationDao.instance.findOne(new BasicDBObject()); - if(jiraInstance == null) { + jiraIntegration = JiraIntegrationDao.instance.findOne(new BasicDBObject()); + if(jiraIntegration == null) { addActionError("Jira is not integrated."); return ERROR.toUpperCase(); } @@ -382,17 +383,28 @@ public String bulkCreateJiraTickets (){ List yamlTemplateList = YamlTemplateDao.instance.findAll(Filters.in("_id", testingSubCategories), projection); Map testSubTypeToInfoMap = new HashMap<>(); for(YamlTemplate yamlTemplate : yamlTemplateList) { + if(yamlTemplate == null || yamlTemplate.getInfo() == null) { + loggerMaker.errorAndAddToDb("ERROR: YamlTemplate or YamlTemplate.info is null", LogDb.DASHBOARD); + continue; + } Info info = yamlTemplate.getInfo(); testSubTypeToInfoMap.put(info.getSubCategory(), info); } List testingRunResultList = new ArrayList<>(); int existingIssues = 0; + List testingRunIssuesList = TestingRunIssuesDao.instance.findAll(Filters.and( + Filters.in("_id", issuesIds), + Filters.exists("jiraIssueUrl", true) + )); for(TestingIssuesId testingIssuesId : issuesIds) { - TestingRunIssues issue = TestingRunIssuesDao.instance.findOne(Filters.and( - Filters.in("_id", testingIssuesId), - Filters.exists("jiraIssueUrl", true) - )); + TestingRunIssues issue = null; + for(TestingRunIssues testingRunIssues : testingRunIssuesList) { + if(testingIssuesId.equals(testingRunIssues.getId())) { + issue = testingRunIssues; + break; + } + } if(issue != null && (issue.getJiraIssueUrl() != null || !issue.getJiraIssueUrl().isEmpty())) { existingIssues++; @@ -403,9 +415,7 @@ public String bulkCreateJiraTickets (){ TestingRunResult testingRunResult = TestingRunResultDao.instance.findOne(Filters.and( Filters.in(TestingRunResult.TEST_SUB_TYPE, testingIssuesId.getTestSubCategory()), - Filters.in(TestingRunResult.API_INFO_KEY + "." + ApiInfo.ApiInfoKey.API_COLLECTION_ID, testingIssuesId.getApiInfoKey().getApiCollectionId()), - Filters.in(TestingRunResult.API_INFO_KEY + "." + ApiInfo.ApiInfoKey.URL, testingIssuesId.getApiInfoKey().getUrl()), - Filters.in(TestingRunResult.API_INFO_KEY + "." + ApiInfo.ApiInfoKey.METHOD, testingIssuesId.getApiInfoKey().getMethod()) + Filters.in(TestingRunResult.API_INFO_KEY, testingIssuesId.getApiInfoKey()) ), Projections.include("_id", TestingRunResult.TEST_RESULTS)); if(testingRunResult == null) { @@ -558,7 +568,6 @@ private BasicDBObject jiraTicketPayloadCreator(JiraMetaData jiraMetaData) { // issue title fields.put("summary", "Akto Report - " + jiraMetaData.getIssueTitle() + " (" + endpointMethod + " - " + truncatedEndpoint + ")"); - jiraIntegration = JiraIntegrationDao.instance.findOne(new BasicDBObject()); // Issue type (TASK) BasicDBObject issueTypeObj = new BasicDBObject(); From c1ae78ab5b8132c5bf37f7aa3416aadf7795b59e Mon Sep 17 00:00:00 2001 From: Umesh Kumar <166806589+TangoBeeAkto@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:24:28 +0530 Subject: [PATCH 8/8] improved the code time comp --- .../akto/action/JiraIntegrationAction.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/apps/dashboard/src/main/java/com/akto/action/JiraIntegrationAction.java b/apps/dashboard/src/main/java/com/akto/action/JiraIntegrationAction.java index 242c7fda3b..9019620b34 100644 --- a/apps/dashboard/src/main/java/com/akto/action/JiraIntegrationAction.java +++ b/apps/dashboard/src/main/java/com/akto/action/JiraIntegrationAction.java @@ -202,6 +202,10 @@ public String createIssue() { BasicDBObject reqPayload = new BasicDBObject(); jiraIntegration = JiraIntegrationDao.instance.findOne(new BasicDBObject()); + if(jiraIntegration == null) { + addActionError("Jira is not integrated."); + return ERROR.toUpperCase(); + } BasicDBObject fields = jiraTicketPayloadCreator(jiraMetaData); reqPayload.put("fields", fields); @@ -397,16 +401,13 @@ public String bulkCreateJiraTickets (){ Filters.in("_id", issuesIds), Filters.exists("jiraIssueUrl", true) )); - for(TestingIssuesId testingIssuesId : issuesIds) { - TestingRunIssues issue = null; - for(TestingRunIssues testingRunIssues : testingRunIssuesList) { - if(testingIssuesId.equals(testingRunIssues.getId())) { - issue = testingRunIssues; - break; - } - } + Set testingRunIssueIds = new HashSet<>(); + for (TestingRunIssues testingRunIssues : testingRunIssuesList) { + testingRunIssueIds.add(testingRunIssues.getId()); + } - if(issue != null && (issue.getJiraIssueUrl() != null || !issue.getJiraIssueUrl().isEmpty())) { + for(TestingIssuesId testingIssuesId : issuesIds) { + if(testingRunIssueIds.contains(testingIssuesId)) { existingIssues++; continue; }