import _ from "lodash" ; import issueKeyParser from "jira-issue-key-parser" ; import { JiraDeploymentData } from "../interfaces/jira" ; import { GitHubAPI } from "probot" ; import { WebhookPayloadDeploymentStatus } from "@octokit/webhooks" ; import { LoggerWithTarget } from "probot/lib/wrap-logger" ; import { Octokit } from "@octokit/rest" ; import { booleanFlag , BooleanFlags } from "../config/feature-flags" ; import { compareCommitsBetweenBaseAndHeadBranches } from "./util/githubApiRequests" ; async function getLastSuccessfulDeployCommitSha ( owner , repoName , github , deployments , logger ? ) { try { for ( const deployment of deployments ) { const listDeploymentStatusResponse = await github . repos . listDeploymentStatuses ( { owner : owner , repo : repoName , deployment_id : deployment . id , per_page : 100 } ) ; const lastSuccessful = listDeploymentStatusResponse . data . find ( deployment => deployment . state === "success" ) ; if ( lastSuccessful !== undefined ) { return deployment . sha ; } } } catch ( e ) { logger ?. error ( `Failed to get deployment statuses.` ) ; } return deployments [ deployments . length - 1 ] . sha ; } async function getCommitMessagesSinceLastSuccessfulDeployment ( owner , repoName , currentDeploySha , currentDeployId , currentDeployEnv , github , logger ) { const deployments = await github . repos . listDeployments ( { owner : owner , repo : repoName , environment : currentDeployEnv , per_page : 10 } ) ; const filteredDeployments = deployments . data . filter ( deployment => deployment . id !== currentDeployId ) ; if ( ! filteredDeployments . length ) { return undefined ; } const lastSuccessfullyDeployedCommit = await getLastSuccessfulDeployCommitSha ( owner , repoName , github , filteredDeployments , logger ) ; const compareCommitsPayload = { owner : owner , repo : repoName , base : lastSuccessfullyDeployedCommit , head : currentDeploySha } ; const allCommitMessages = await compareCommitsBetweenBaseAndHeadBranches ( compareCommitsPayload , github , logger ) ; return allCommitMessages ; } function mapState ( state ) { switch ( state ) { case "queued" : return "pending" ; case "pending" : case "in_progress" : return "in_progress" ; case "success" : return "successful" ; case "error" : case "failure" : return "failed" ; default : return "unknown" ; } } export function mapEnvironment ( environment ) { const isEnvironment = ( envNames ) : boolean => { const exactMatch = envNames . join ( "|" ) ; const separator = "[^a-z0-9]" ; const envNamesPattern = RegExp ( `^(.*${ separator })?(${ exactMatch })(${ separator }.*)?$` , "i" ) ; return envNamesPattern . test ( _ . deburr ( environment ) ) ; } ; const environmentMapping = { development : [ "development" , "dev" , "trunk" ] , testing : [ "testing" , "test" , "tests" , "tst" , "integration" , "integ" , "intg" , "int" , "acceptance" , "accept" , "acpt" , "qa" , "qc" , "control" , "quality" ] , staging : [ "staging" , "stage" , "stg" , "preprod" , "model" , "internal" ] , production : [ "production" , "prod" , "prd" , "live" ] , } ; const jiraEnv = Object . keys ( environmentMapping ) . find ( key => isEnvironment ( environmentMapping [ key ] ) ) ; if ( ! jiraEnv ) { return "unmapped" ; } return jiraEnv ; } export default async ( githubClient , payload , jiraHost , logger ) : Promise < JiraDeploymentData | undefined > => { const deployment = payload . deployment ; const deployment_status = payload . deployment_status ; const { data : { commit : { message } } } = await githubClient . repos . getCommit ( { owner : payload . repository . owner . login , repo : payload . repository . name , ref : deployment . sha } ) ; let issueKeys ; if ( await booleanFlag ( BooleanFlags . SUPPORT_BRANCH_AND_MERGE_WORKFLOWS_FOR_DEPLOYMENTS , false , jiraHost ) ) { const allCommitsMessages = await getCommitMessagesSinceLastSuccessfulDeployment ( payload . repository . owner . login , payload . repository . name , deployment . sha , deployment . id , deployment_status . environment , githubClient , logger ) ; issueKeys = issueKeyParser ( ) . parse ( `${ deployment . ref }\n${ message }\n${ allCommitsMessages }` ) || [ ] ; } else { issueKeys = issueKeyParser ( ) . parse ( `${ deployment . ref }\n${ message }` ) || [ ] ; } if ( _ . isEmpty ( issueKeys ) ) { return undefined ; } const environment = mapEnvironment ( deployment_status . environment ) ; if ( environment === "unmapped" ) { logger ?. info ( `Unmapped environment detected for deployment. Unmapped value is ${ deployment_status }. Sending it as unmapped to Jira.` ) ; } return { deployments : [ { schemaVersion : "1.0" , deploymentSequenceNumber : deployment . id , updateSequenceNumber : deployment_status . id , issueKeys , displayName : deployment . task , url : deployment_status . target_url || deployment . url , description : deployment . description || deployment_status . description || deployment . task , lastUpdated : new Date ( deployment_status . updated_at ) , state : mapState ( deployment_status . state ) , pipeline : { id : deployment . task , displayName : deployment . task , url : deployment_status . target_url || deployment . url , } , environment : { id : deployment_status . environment , displayName : deployment_status . environment , type : environment , } , } ] , } ; } ;