Skip to content

Commit

Permalink
ci: Copy properties from issue to pull request
Browse files Browse the repository at this point in the history
  • Loading branch information
Piotr Adamczyk committed Nov 10, 2020
1 parent 68d52d8 commit fe8d175
Show file tree
Hide file tree
Showing 10 changed files with 315 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ data class GithubPullRequest(
@SerialName("html_url") val htmlUrl: String,
val title: String,
val number: Int,
val assignees: List<GithubUser>
val assignees: List<GithubUser> = emptyList(),
val labels: List<GitHubLabel> = emptyList(),
val body: String = "",
val head: GitHubHead? = null
)

@Serializable
Expand All @@ -22,3 +25,17 @@ data class GithubUser(
object GithubPullRequestDeserializer : ResponseDeserializable<List<GithubPullRequest>> {
override fun deserialize(content: String): List<GithubPullRequest> = content.toObject()
}

@Serializable
data class GitHubLabel(
val name: String
)

object GitHubLabelDeserializable : ResponseDeserializable<List<GitHubLabel>> {
override fun deserialize(content: String): List<GitHubLabel> = content.toObject()
}

@Serializable
data class GitHubHead(
val ref: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package flank.scripts.github

import com.github.kittinunf.fuel.core.ResponseDeserializable
import flank.scripts.utils.toObject
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class GithubPullRequest(
@SerialName("html_url") val htmlUrl: String,
val title: String,
val number: Int,
val assignees: List<GithubUser>,
val labels: List<GitHubLabel> = emptyList(),
val body: String = "",
val head: GitHubHead?
)

@Serializable
data class GithubUser(
val login: String,
@SerialName("html_url") val htmlUrl: String
)

object GithubPullRequestDeserializer : ResponseDeserializable<List<GithubPullRequest>> {
override fun deserialize(content: String): List<GithubPullRequest> = content.toObject()
}

@Serializable
data class GitHubLabel(
val name: String
)

object GitHubLabelDeserializable : ResponseDeserializable<List<GitHubLabel>> {
override fun deserialize(content: String): List<GitHubLabel> = content.toObject()
}

@Serializable
data class GitHubHead(
val ref: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package flank.scripts.pullrequest

fun GitHubPullRequest.findReferenceNumber() =
(tryGetReferenceNumberFromBody() ?: tryGetReferenceNumberFromBranch())
?.trim()
?.replace("#", "")
?.toInt()

private fun GitHubPullRequest.tryGetReferenceNumberFromBody() = bodyReferenceRegex.find(body)?.value

private fun GitHubPullRequest.tryGetReferenceNumberFromBranch() = branchReferenceRegex.find(head?.ref.orEmpty())?.value

private val bodyReferenceRegex = "#\\d+\\s".toRegex()
private val branchReferenceRegex = "#\\d+".toRegex()
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package flank.scripts.pullrequest

import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.options.default
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.options.required
import com.github.ajalt.clikt.parameters.types.int
import com.github.kittinunf.result.onError
import com.github.kittinunf.result.success
import flank.scripts.testartifacts.core.GITHUB_TOKEN_ENV_KEY
import flank.scripts.utils.getEnv
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

object CopyProperties :
CliktCommand(name = "copyProperties", help = "Copy properties from referanced issue to pull request") {

private val githubToken by option(help = "Git Token").default(getEnv(GITHUB_TOKEN_ENV_KEY))
private val zenhubToken by option(help = "ZenHub api Token").required()
private val prNumber by option(help = "Pull request number").int().required()

override fun run() {
runBlocking {
getGitHubPullRequest(githubToken, prNumber)
.onError {
println("Could not copy properties, because of ${it.message}")
}
.success { pullRequest ->
val issueNumber = pullRequest.findReferenceNumber()
checkNotNull(issueNumber) { "Reference issue not found on description and branch" }
println("Found referenced issue #$issueNumber")
launch(Dispatchers.IO) { pullRequest.copyGitHubProperties(githubToken, issueNumber, prNumber) }
launch(Dispatchers.IO) { copyZenhubProperties(zenhubToken, issueNumber, prNumber) }
}
}
}
}

private suspend fun GitHubPullRequest.copyGitHubProperties(
githubToken: String,
baseIssueNumber: Int,
prNumber: Int
) = coroutineScope {
val assignees = this@copyGitHubProperties.assignees.map { it.login }
listOf(
launch { setAssigneesToPullRequest(githubToken, prNumber, assignees) },
launch { copyLabels(githubToken, baseIssueNumber, prNumber) },
).joinAll()
}

private suspend fun copyZenhubProperties(
zenhubToken: String,
baseIssueNumber: Int,
prNumber: Int
) {
copyEstimations(zenhubToken, baseIssueNumber, prNumber)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package flank.scripts.pullrequest

import com.github.kittinunf.fuel.Fuel
import com.github.kittinunf.fuel.coroutines.awaitResult
import flank.scripts.exceptions.mapClientError
import flank.scripts.exceptions.toGithubException

suspend fun getGitHubPullRequest(githubToken: String, issueNumber: Int) =
Fuel.get("https://api.github.com/repos/Flank/flank/pulls/$issueNumber")
.appendHeader("Accept", "application/vnd.github.v3+json")
.appendHeader("Authorization", "token $githubToken")
.awaitResult(GitHubPullRequestDeserializable)
.mapClientError { it.toGithubException() }
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package flank.scripts.pullrequest

import com.github.kittinunf.fuel.core.ResponseDeserializable
import flank.scripts.utils.toObject
import kotlinx.serialization.Serializable

@Serializable
data class GitHubPullRequest(
val assignees: List<GitHubUser> = emptyList(),
val labels: List<GitHubLabel> = emptyList(),
val body: String = "",
val head: GitHubHead?
)

object GitHubPullRequestDeserializable : ResponseDeserializable<GitHubPullRequest> {
override fun deserialize(content: String): GitHubPullRequest = content.toObject()
}

@Serializable
data class GitHubLabel(
val name: String
)

object GitHubLabelDeserializable : ResponseDeserializable<List<GitHubLabel>> {
override fun deserialize(content: String): List<GitHubLabel> = content.toObject()
}

@Serializable
data class GitHubUser(
val login: String
)

@Serializable
data class GitHubHead(
val ref: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package flank.scripts.pullrequest

import com.github.kittinunf.fuel.Fuel
import com.github.kittinunf.fuel.coroutines.awaitStringResult
import com.github.kittinunf.result.onError
import com.github.kittinunf.result.success
import flank.scripts.utils.toJson
import kotlinx.serialization.Serializable

suspend fun setAssigneesToPullRequest(githubToken: String, pullRequestNumber: Int, assignees: List<String>) {
Fuel.post("https://api.github.com/repos/Flank/flank/issues/$pullRequestNumber/assignees")
.appendHeader("Accept", "application/vnd.github.v3+json")
.appendHeader("Authorization", "token $githubToken")
.body(SetAssigneesRequest(assignees).toJson())
.awaitStringResult()
.onError {
println("Could not set assignees because of ${it.message}")
it.printStackTrace()
}
.success { println("$assignees set to pull request #$pullRequestNumber") }
}

@Serializable
private data class SetAssigneesRequest(
val assignees: List<String>
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package flank.scripts.pullrequest

import com.github.kittinunf.fuel.Fuel
import com.github.kittinunf.fuel.coroutines.awaitResult
import com.github.kittinunf.fuel.coroutines.awaitStringResult
import com.github.kittinunf.result.map
import com.github.kittinunf.result.onError
import com.github.kittinunf.result.success
import flank.scripts.exceptions.mapClientError
import flank.scripts.exceptions.toGithubException
import flank.scripts.utils.toJson
import kotlinx.serialization.Serializable

suspend fun copyLabels(githubToken: String, issueNumber: Int, pullRequestNumber: Int) {
getLabelsFromIssue(githubToken, issueNumber)
.onError { println("Could not copy labels because of ${it.message}") }
.map { it.map { label -> label.name } }
.get()
.run {
setLabelsToPullRequest(githubToken, pullRequestNumber, this)
}
}

private suspend fun getLabelsFromIssue(githubToken: String, issueNumber: Int) =
Fuel.get("https://api.github.com/repos/Flank/flank/issues/$issueNumber/labels")
.appendHeader("Accept", "application/vnd.github.v3+json")
.appendHeader("Authorization", "token $githubToken")
.awaitResult(GitHubLabelDeserializable)
.mapClientError { it.toGithubException() }

private suspend fun setLabelsToPullRequest(githubToken: String, pullRequestNumber: Int, labels: List<String>) {
Fuel.post("https://api.github.com/repos/Flank/flank/issues/$pullRequestNumber/labels")
.appendHeader("Accept", "application/vnd.github.v3+json")
.appendHeader("Authorization", "token $githubToken")
.body(SetLabelsRequest(labels).toJson())
.awaitStringResult()
.onError { println("Could not set assignees because of ${it.message}") }
.success { println("$labels set to pull request #$pullRequestNumber") }
}

@Serializable
private data class SetLabelsRequest(
val labels: List<String>
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package flank.scripts.pullrequest

import com.github.kittinunf.fuel.Fuel
import com.github.kittinunf.fuel.core.Request
import com.github.kittinunf.fuel.coroutines.awaitResult
import com.github.kittinunf.fuel.coroutines.awaitStringResult
import com.github.kittinunf.result.onError
import com.github.kittinunf.result.success
import flank.scripts.utils.toJson
import kotlinx.serialization.Serializable

private const val FLANK_REPO_ID = 84221974
private const val ZENHUB_BASE_URL = "https://api.zenhub.com/p1/repositories/$FLANK_REPO_ID"

suspend fun copyEstimations(zenhubToken: String, issueNumber: Int, pullRequestNumber: Int) {
getEstimation(zenhubToken, issueNumber)
.run { setEstimation(zenhubToken, pullRequestNumber, estimate.value) }
}

suspend fun getEstimation(zenhubToken: String, issueNumber: Int) =
Fuel.get("$ZENHUB_BASE_URL/issues/$issueNumber")
.withZenhubHeaders(zenhubToken)
.awaitResult(ZenHubIssueDeserializable)
.onError { println("Could not get estimations because of ${it.message}") }
.get()

private suspend fun setEstimation(zenhubToken: String, pullRequestNumber: Int, estimate: Int) {
Fuel.put("$ZENHUB_BASE_URL/issues/$pullRequestNumber/estimate")
.withZenhubHeaders(zenhubToken)
.body(ZenHubEstimateRequest(estimate).toJson())
.awaitStringResult()
.onError {
it.printStackTrace()
println("Could not set estimations because of ${it.message}")
}
.success { println("Estimate $estimate set to pull request #$pullRequestNumber") }
}

private fun Request.withZenhubHeaders(zenhubToken: String) =
appendHeader("Content-Type", "application/json")
.appendHeader("X-Authentication-Token", zenhubToken)

@Serializable
private data class ZenHubEstimateRequest(val estimate: Int)
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package flank.scripts.pullrequest

import com.github.kittinunf.fuel.core.ResponseDeserializable
import flank.scripts.utils.toObject
import kotlinx.serialization.Serializable

@Serializable
data class ZenHubIssue(
val estimate: ZenHubEstimate
)

@Serializable
data class ZenHubEstimate(
val value: Int
)

object ZenHubIssueDeserializable : ResponseDeserializable<ZenHubIssue> {
override fun deserialize(content: String): ZenHubIssue = content.toObject()
}

0 comments on commit fe8d175

Please sign in to comment.