From 3d3521feedb9fcf80d9330b6a9a4480a942a5ace Mon Sep 17 00:00:00 2001 From: Gary Xue <105693507+gxueatlassian@users.noreply.github.com> Date: Tue, 28 Nov 2023 16:07:48 +1100 Subject: [PATCH] ARC-2643 allow multi workflow entries in audit log during backfill (#2579) --- src/github/branch.ts | 2 +- src/github/pull-request.ts | 2 +- src/github/workflow.ts | 2 +- src/interfaces/jira.ts | 4 +- .../jira-client-audit-log-helper.test.ts | 102 ++++++++++++++---- .../client/jira-client-audit-log-helper.ts | 74 ++++++++----- src/jira/client/jira-client.test.ts | 2 +- src/jira/client/jira-client.ts | 9 +- 8 files changed, 142 insertions(+), 55 deletions(-) diff --git a/src/github/branch.ts b/src/github/branch.ts index 362eeb82e8..901d4752e9 100644 --- a/src/github/branch.ts +++ b/src/github/branch.ts @@ -74,7 +74,7 @@ export const processBranch = async ( operationType: "NORMAL", auditLogsource: "WEBHOOK", entityAction: "BRANCH_CREATE", - subscriptionId: subscription?.id + subscriptionId: subscription?.id || 0 }); emitWebhookProcessedMetrics( diff --git a/src/github/pull-request.ts b/src/github/pull-request.ts index 4a4da4e59f..ea98f18e0d 100644 --- a/src/github/pull-request.ts +++ b/src/github/pull-request.ts @@ -81,7 +81,7 @@ export const pullRequestWebhookHandler = async (context: WebhookContext, jiraCli operationType: "NORMAL", auditLogsource: "WEBHOOK", entityAction: context?.name === "pull_request_review" ? "PR_REVIEW" : `PR_${context?.action?.toUpperCase()}`, - subscriptionId: subscription?.id + subscriptionId: subscription.id }); const { webhookReceived, name, log } = context; diff --git a/src/github/workflow.ts b/src/github/workflow.ts index efc9221f34..85ceaeee10 100644 --- a/src/github/workflow.ts +++ b/src/github/workflow.ts @@ -37,7 +37,7 @@ export const workflowWebhookHandler = async (context: WebhookContext, jiraClient operationType: "NORMAL", auditLogsource: "WEBHOOK", entityAction: `WORKFLOW_RUN_${context.action}`.toUpperCase(), - subscriptionId: subscription?.id + subscriptionId: subscription.id }); const { webhookReceived, name, log } = context; diff --git a/src/interfaces/jira.ts b/src/interfaces/jira.ts index b6bc7ad107..6f51ba06fe 100644 --- a/src/interfaces/jira.ts +++ b/src/interfaces/jira.ts @@ -254,6 +254,6 @@ export interface JiraSubmitOptions { preventTransitions: boolean; operationType: JiraOperationType; auditLogsource: AuditLogSourceType; - entityAction?: AuditEntityType; - subscriptionId?: number; + entityAction: AuditEntityType; + subscriptionId: number; } diff --git a/src/jira/client/jira-client-audit-log-helper.test.ts b/src/jira/client/jira-client-audit-log-helper.test.ts index 1fb087af98..b3d6a7fcbc 100644 --- a/src/jira/client/jira-client-audit-log-helper.test.ts +++ b/src/jira/client/jira-client-audit-log-helper.test.ts @@ -1,5 +1,6 @@ import { processBatchedBulkUpdateResp, processWorkflowSubmitResp } from "./jira-client-audit-log-helper"; import { getLogger } from "config/logger"; +import { JiraSubmitOptions } from "interfaces/jira"; describe("processBatchedBulkUpdateResp", () => { const mockLogger = getLogger("mock-logger"); @@ -421,9 +422,9 @@ describe("processBatchedBulkUpdateResp", () => { describe("processWorkflowSubmitResp", () => { const mockLogger = getLogger("mock-logger"); it("should return isSuccess as false when status code is anything other than 202", () => { - const reqBuildData = { + const reqBuildDataArray = [{ schemaVersion: "1.0", - pipelineId: 77164279, + pipelineId: "77164279", buildNumber: 6, updateSequenceNumber: 1700991428371, displayName: "CI", @@ -431,7 +432,7 @@ describe("processWorkflowSubmitResp", () => { state: "successful", lastUpdated: "2023-11-26T09:37:07Z", issueKeys: ["KAM-5"] - }; + }]; const response = { status: 400, data: { @@ -440,10 +441,10 @@ describe("processWorkflowSubmitResp", () => { rejectedBuilds: [] } }; - const options = { preventTransitions:false, operationType: "WEBHOOK", auditLogsource: "WEBHOOK", entityAction: "WORKFLOW_RUN", subscriptionId: 1122334455 }; + const options: JiraSubmitOptions = { preventTransitions:false, operationType: "NORMAL", auditLogsource: "WEBHOOK", entityAction: "WORKFLOW_RUN", subscriptionId: 1122334455 }; const result = processWorkflowSubmitResp({ - reqBuildData, + reqBuildDataArray, response, options, logger: mockLogger @@ -454,9 +455,9 @@ describe("processWorkflowSubmitResp", () => { }); }); it("should return isSuccess as false when status code is 202 but there is no acceptedBuilds", () => { - const reqBuildData = { + const reqBuildDataArray = [{ schemaVersion: "1.0", - pipelineId: 77164279, + pipelineId: "77164279", buildNumber: 6, updateSequenceNumber: 1700991428371, displayName: "CI", @@ -464,7 +465,7 @@ describe("processWorkflowSubmitResp", () => { state: "successful", lastUpdated: "2023-11-26T09:37:07Z", issueKeys: ["KAM-5"] - }; + }]; const response = { status: 202, data: { @@ -473,10 +474,10 @@ describe("processWorkflowSubmitResp", () => { rejectedBuilds: [] } }; - const options = { preventTransitions:false, operationType: "WEBHOOK", auditLogsource: "WEBHOOK", entityAction: "WORKFLOW_RUN", subscriptionId: 1122334455 }; + const options: JiraSubmitOptions = { preventTransitions:false, operationType: "NORMAL", auditLogsource: "WEBHOOK", entityAction: "WORKFLOW_RUN", subscriptionId: 1122334455 }; const result = processWorkflowSubmitResp({ - reqBuildData, + reqBuildDataArray, response, options, logger: mockLogger @@ -487,9 +488,9 @@ describe("processWorkflowSubmitResp", () => { }); }); it("should return isSuccess as true when status code is 202 and there are/is acceptedBuilds but the result has rejectedBuilds as well", () => { - const reqBuildData = { + const reqBuildDataArray = [{ schemaVersion: "1.0", - pipelineId: 77164279, + pipelineId: "77164279", buildNumber: 6, updateSequenceNumber: 1700991428371, displayName: "CI", @@ -497,7 +498,7 @@ describe("processWorkflowSubmitResp", () => { state: "successful", lastUpdated: "2023-11-26T09:37:07Z", issueKeys: ["KAM-5"] - }; + }]; const response = { status: 202, data: { @@ -506,10 +507,10 @@ describe("processWorkflowSubmitResp", () => { rejectedBuilds: [{ pipelineId: "11223434", buildNumber: 10 }] } }; - const options = { preventTransitions:false, operationType: "WEBHOOK", auditLogsource: "WEBHOOK", entityAction: "WORKFLOW_RUN", subscriptionId: 1122334455 }; + const options: JiraSubmitOptions = { preventTransitions:false, operationType: "NORMAL", auditLogsource: "WEBHOOK", entityAction: "WORKFLOW_RUN", subscriptionId: 1122334455 }; const result = processWorkflowSubmitResp({ - reqBuildData, + reqBuildDataArray, response, options, logger: mockLogger @@ -529,9 +530,9 @@ describe("processWorkflowSubmitResp", () => { }); }); it("should extract the build with 2 issue keys linked - audit info for logging", () => { - const reqBuildData = { + const reqBuildDataArray = [{ schemaVersion: "1.0", - pipelineId: 77164279, + pipelineId: "77164279", buildNumber: 6, updateSequenceNumber: 1700991428371, displayName: "CI", @@ -539,7 +540,7 @@ describe("processWorkflowSubmitResp", () => { state: "successful", lastUpdated: "2023-11-26T09:37:07Z", issueKeys: ["KAM-5", "KAM-6"] - }; + }]; const response = { status: 202, data: { @@ -548,10 +549,10 @@ describe("processWorkflowSubmitResp", () => { rejectedBuilds: [] } }; - const options = { preventTransitions:false, operationType: "WEBHOOK", auditLogsource: "WEBHOOK", entityAction: "WORKFLOW_RUN", subscriptionId: 1122334455 }; + const options: JiraSubmitOptions = { preventTransitions:false, operationType: "NORMAL", auditLogsource: "WEBHOOK", entityAction: "WORKFLOW_RUN", subscriptionId: 1122334455 }; const result = processWorkflowSubmitResp({ - reqBuildData, + reqBuildDataArray, response, options, logger: mockLogger @@ -579,4 +580,65 @@ describe("processWorkflowSubmitResp", () => { }] }); }); + it("should extract the two builds with 1 issue keys linked - audit info for logging", () => { + const reqBuildDataArray = [{ + schemaVersion: "1.0", + pipelineId: "77164279", + buildNumber: 6, + updateSequenceNumber: 1700991428371, + displayName: "CI", + url: "https://github.com/kamaOrgOne/repo_react/actions/runs/6994742701", + state: "successful", + lastUpdated: "2023-11-26T09:37:07Z", + issueKeys: ["KAM-5"] + }, { + schemaVersion: "1.0", + pipelineId: "77164280", + buildNumber: 7, + updateSequenceNumber: 1700991428372, + displayName: "CI2", + url: "https://github.com/kamaOrgOne/repo_react/actions/runs/6994742701", + state: "successful", + lastUpdated: "2023-11-26T09:37:07Z", + issueKeys: ["KAM-6"] + }]; + const response = { + status: 202, + data: { + unknownIssueKeys: [], + acceptedBuilds: [{ pipelineId: "77164279", buildNumber: 6 }, { pipelineId: "77164280", buildNumber: 7 }], + rejectedBuilds: [] + } + }; + const options: JiraSubmitOptions = { preventTransitions:false, operationType: "NORMAL", auditLogsource: "WEBHOOK", entityAction: "WORKFLOW_RUN", subscriptionId: 1122334455 }; + + const result = processWorkflowSubmitResp({ + reqBuildDataArray, + response, + options, + logger: mockLogger + }); + + expect(result).toEqual({ + isSuccess: true, + auditInfo:[{ + "createdAt": expect.anything(), + "entityAction": "WORKFLOW_RUN", + "entityId": "6_77164279", + "entityType": "builds", + "issueKey": "KAM-5", + "source": "WEBHOOK", + "subscriptionId": 1122334455 + }, + { + "createdAt": expect.anything(), + "entityAction": "WORKFLOW_RUN", + "entityId": "7_77164280", + "entityType": "builds", + "issueKey": "KAM-6", + "source": "WEBHOOK", + "subscriptionId": 1122334455 + }] + }); + }); }); diff --git a/src/jira/client/jira-client-audit-log-helper.ts b/src/jira/client/jira-client-audit-log-helper.ts index 2ad6727683..ce89b85f72 100644 --- a/src/jira/client/jira-client-audit-log-helper.ts +++ b/src/jira/client/jira-client-audit-log-helper.ts @@ -1,5 +1,10 @@ import { AuditInfo, saveAuditLog } from "../../services/audit-log-service"; import { isArray, isObject } from "lodash"; +import { + JiraBuild, + JiraSubmitOptions +} from "interfaces/jira"; +import Logger from "bunyan"; const getAuditInfo = ({ acceptedGithubEntities, @@ -99,10 +104,15 @@ export const processBatchedBulkUpdateResp = ({ }; export const processWorkflowSubmitResp = ({ - reqBuildData, + reqBuildDataArray, response, options, logger +}: { + reqBuildDataArray: JiraBuild[], + response: { status: number, data: any }, + options: JiraSubmitOptions, + logger: Logger }): { isSuccess: boolean; auditInfo?: Array; @@ -116,27 +126,29 @@ export const processWorkflowSubmitResp = ({ acceptedBuilds.length > 0; const auditInfo: Array = []; if (isSuccess && hasAcceptedBuilds) { - const reqBuildNo = reqBuildData.buildNumber; - const reqBuildPipelineId = reqBuildData.pipelineId; - const createdAt = new Date(); - const acceptedBuildFound = acceptedBuilds.some(acceptedBuild => acceptedBuild?.buildNumber.toString() === reqBuildNo.toString() && acceptedBuild.pipelineId.toString() === reqBuildPipelineId.toString()); - if (acceptedBuildFound) { - const issueKeys = reqBuildData?.issueKeys; - issueKeys.map((issueKey) => { - const obj: AuditInfo = { - createdAt, - entityId: `${reqBuildNo}_${reqBuildPipelineId}`, - entityType: "builds", - issueKey, - subscriptionId: options?.subscriptionId, - source: options?.auditLogsource || "WEBHOOK", - entityAction: options?.entityAction || "null" - }; - if (obj.subscriptionId && obj.entityId) { - auditInfo.push(obj); - } - }); - } + reqBuildDataArray.forEach((reqBuildData) => { + const reqBuildNo = reqBuildData.buildNumber; + const reqBuildPipelineId = reqBuildData.pipelineId; + const createdAt = new Date(); + const acceptedBuildFound = acceptedBuilds.some(acceptedBuild => acceptedBuild?.buildNumber.toString() === reqBuildNo.toString() && acceptedBuild.pipelineId.toString() === reqBuildPipelineId.toString()); + if (acceptedBuildFound) { + const issueKeys = reqBuildData?.issueKeys; + issueKeys.map((issueKey) => { + const obj: AuditInfo = { + createdAt, + entityId: `${reqBuildNo}_${reqBuildPipelineId}`, + entityType: "builds", + issueKey, + subscriptionId: options.subscriptionId, + source: options.auditLogsource || "WEBHOOK", + entityAction: options.entityAction || "null" + }; + if (obj.subscriptionId && obj.entityId) { + auditInfo.push(obj); + } + }); + } + }); return { isSuccess: true, auditInfo }; } return { isSuccess: false }; @@ -168,12 +180,24 @@ export const processAuditLogsForDevInfoBulkUpdate = ({ reqRepoData, response, op } }; -export const processAuditLogsForWorkflowSubmit = ({ reqBuildData, response, options, logger }) => { +export const processAuditLogsForWorkflowSubmit = ( + { reqBuildDataArray, response, options, logger }: { + reqBuildDataArray: JiraBuild[], + response: { status: number, data: any }, + options: JiraSubmitOptions, + logger: Logger + } +) => { try { + + if (!options) { + logger.debug("Skip sending to audit log as options are undefined"); + } + const { isSuccess, auditInfo } = processWorkflowSubmitResp({ - reqBuildData, + reqBuildDataArray, response, - options, + options: options, logger }); if (isSuccess) { diff --git a/src/jira/client/jira-client.test.ts b/src/jira/client/jira-client.test.ts index 3e64f1b6a8..2ef09e0e8e 100644 --- a/src/jira/client/jira-client.test.ts +++ b/src/jira/client/jira-client.test.ts @@ -206,7 +206,7 @@ describe("Test getting a jira client", () => { const response = await client.workflow.submit({ builds: [{}] - }); + }, {}); expect(response).toEqual({ status: 200, diff --git a/src/jira/client/jira-client.ts b/src/jira/client/jira-client.ts index 14a4c65da5..f5d6cd8efe 100644 --- a/src/jira/client/jira-client.ts +++ b/src/jira/client/jira-client.ts @@ -9,6 +9,7 @@ import { createHashWithSharedSecret } from "utils/encryption"; import { JiraAssociation, JiraBuildBulkSubmitData, + JiraBuild, JiraCommit, JiraDeploymentBulkSubmitData, JiraIssue, @@ -72,7 +73,7 @@ export interface JiraClient { }, }, workflow: { - submit: (data: JiraBuildBulkSubmitData, repositoryId: number, options?: JiraSubmitOptions) => Promise; + submit: (data: JiraBuildBulkSubmitData, repositoryId: number, options: JiraSubmitOptions) => Promise; }, deployment: { submit: ( @@ -382,7 +383,7 @@ export const getJiraClient = async ( } }, workflow: { - submit: async (data: JiraBuildBulkSubmitData, repositoryId: number, options?: JiraSubmitOptions) => { + submit: async (data: JiraBuildBulkSubmitData, repositoryId: number, options: JiraSubmitOptions) => { updateIssueKeysFor(data.builds, uniq); if (!withinIssueKeyLimit(data.builds)) { logger.warn({ @@ -411,9 +412,9 @@ export const getJiraClient = async ( status: response.status, data:response.data }; - const reqBuildData = data?.builds[0]; + const reqBuildDataArray: JiraBuild[] = data?.builds || []; if (await booleanFlag(BooleanFlags.USE_DYNAMODB_TO_PERSIST_AUDIT_LOG, jiraHost)) { - processAuditLogsForWorkflowSubmit({ reqBuildData, response:responseData, options, logger }); + processAuditLogsForWorkflowSubmit({ reqBuildDataArray, response:responseData, options, logger }); } return response; }