Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 103 additions & 10 deletions src/main/kotlin/com/wire/github/Routing.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import com.wire.github.util.KtxSerializer
import com.wire.github.util.SignatureValidator
import com.wire.github.util.TemplateHandler
import com.wire.sdk.WireAppSdk
import com.wire.sdk.model.QualifiedId
import com.wire.sdk.model.WireMessage
import com.wire.sdk.service.WireApplicationManager
import io.ktor.http.HttpStatusCode
import io.ktor.serialization.kotlinx.json.json
import io.ktor.server.application.Application
Expand All @@ -21,6 +23,7 @@ import io.ktor.server.routing.get
import io.ktor.server.routing.post
import io.ktor.server.routing.routing
import kotlinx.serialization.ExperimentalSerializationApi
import java.util.UUID
import org.koin.core.context.GlobalContext

@OptIn(ExperimentalSerializationApi::class)
Expand Down Expand Up @@ -81,19 +84,109 @@ fun Application.configureRouting() {
)

messageTemplate?.let { message ->
gitHubWebhookManager
.conversationsForRepository(response.repository.fullName)
.forEach { conversationId ->
wireAppSdk.getApplicationManager().sendMessage(
message = WireMessage.Text.create(
conversationId = conversationId,
text = message
)
)
}
wireAppSdk.sendGitHubMessage(
gitHubWebhookManager = gitHubWebhookManager,
event = event,
response = response,
text = message
)
}

return@post call.response.status(HttpStatusCode.OK)
}
}
}

private fun WireAppSdk.sendGitHubMessage(
gitHubWebhookManager: GitHubWebhookManager,
event: String,
response: GitHubResponse,
text: String
) {
gitHubWebhookManager
.conversationsForRepository(response.repository.fullName)
.forEach { conversationId ->
sendGitHubMessage(
gitHubWebhookManager = gitHubWebhookManager,
event = event,
response = response,
conversationId = conversationId,
text = text
)
}
}

private fun WireAppSdk.sendGitHubMessage(
gitHubWebhookManager: GitHubWebhookManager,
event: String,
response: GitHubResponse,
conversationId: QualifiedId,
text: String
) {
val workflowRunId = response.workflowRun?.id
val messageId = if (event == EVENT_WORKFLOW_RUN && workflowRunId != null) {
getApplicationManager().sendWorkflowRunMessage(
gitHubWebhookManager = gitHubWebhookManager,
repositoryFullName = response.repository.fullName,
workflowRunId = workflowRunId,
conversationId = conversationId,
text = text
)
} else {
getApplicationManager().sendTextMessage(
conversationId = conversationId,
text = text
)
}

if (event == EVENT_WORKFLOW_RUN && workflowRunId != null) {
gitHubWebhookManager.rememberWorkflowRunMessageId(
fullName = response.repository.fullName,
workflowRunId = workflowRunId,
conversationId = conversationId,
messageId = messageId
)
}
}

private fun WireApplicationManager.sendWorkflowRunMessage(
gitHubWebhookManager: GitHubWebhookManager,
repositoryFullName: String,
workflowRunId: Long,
conversationId: QualifiedId,
text: String
): UUID {
val replacingMessageId = gitHubWebhookManager.workflowRunMessageId(
fullName = repositoryFullName,
workflowRunId = workflowRunId,
conversationId = conversationId
)

val message = replacingMessageId
?.let {
WireMessage.TextEdited.create(
replacingMessageId = it,
conversationId = conversationId,
text = text
)
}
?: WireMessage.Text.create(
conversationId = conversationId,
text = text
)

return sendMessage(message = message)
}

private fun WireApplicationManager.sendTextMessage(
conversationId: QualifiedId,
text: String
): UUID =
sendMessage(
message = WireMessage.Text.create(
conversationId = conversationId,
text = text
)
)

private const val EVENT_WORKFLOW_RUN = "workflow_run"
2 changes: 0 additions & 2 deletions src/main/kotlin/com/wire/github/github/GitHubAppClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -335,12 +335,10 @@ class GitHubAppClient(

private fun repositoryEvents(): JsonArray =
buildJsonArray {
add(JsonPrimitive("check_suite"))
add(JsonPrimitive("issue_comment"))
add(JsonPrimitive("pull_request"))
add(JsonPrimitive("pull_request_review"))
add(JsonPrimitive("pull_request_review_comment"))
add(JsonPrimitive("status"))
add(JsonPrimitive("workflow_run"))
}

Expand Down
35 changes: 35 additions & 0 deletions src/main/kotlin/com/wire/github/github/GitHubWebhookManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,26 @@ class GitHubWebhookManager(
storage.set(lastActivityKey(fullName), clock.millis().toString())
}

fun workflowRunMessageId(
fullName: String,
workflowRunId: Long,
conversationId: QualifiedId
): UUID? =
storage
.get(workflowRunMessageIdKey(fullName, workflowRunId, conversationId))
?.let { runCatching { UUID.fromString(it) }.getOrNull() }

fun rememberWorkflowRunMessageId(
fullName: String,
workflowRunId: Long,
conversationId: QualifiedId,
messageId: UUID
) {
val key = workflowRunMessageIdKey(fullName, workflowRunId, conversationId)
storage.set(key, messageId.toString())
storage.sadd(workflowRunMessageIdsKey(fullName), key)
}

fun cleanupInactiveRepositories() {
val cutoff = clock.millis() - ENV_VAR_GITHUB_REPO_INACTIVITY_SECONDS * MILLIS_PER_SECOND
storage.smembers(KNOWN_REPOSITORIES_KEY).forEach { fullName ->
Expand All @@ -119,6 +139,10 @@ class GitHubWebhookManager(
storage.del(webhookIdKey(fullName))
storage.del(conversationsKey(fullName))
storage.del(lastActivityKey(fullName))
storage.smembers(workflowRunMessageIdsKey(fullName)).forEach { key ->
storage.del(key)
}
storage.del(workflowRunMessageIdsKey(fullName))
logger.info("Removed inactive GitHub webhook subscription for repository: $fullName")
}

Expand Down Expand Up @@ -149,6 +173,17 @@ class GitHubWebhookManager(
private fun lastActivityKey(fullName: String): String =
"$REPOSITORY_KEY_PREFIX:$fullName:last_activity"

private fun workflowRunMessageIdKey(
fullName: String,
workflowRunId: Long,
conversationId: QualifiedId
): String =
"$REPOSITORY_KEY_PREFIX:$fullName:workflow_run:$workflowRunId:" +
conversationId.toStorageKey()

private fun workflowRunMessageIdsKey(fullName: String): String =
"$REPOSITORY_KEY_PREFIX:$fullName:workflow_run_messages"

companion object {
const val GITHUB_WEBHOOK_PATH = "github/webhook"

Expand Down
32 changes: 0 additions & 32 deletions src/main/kotlin/com/wire/github/response/model/CheckSuite.kt

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,9 @@ data class GitHubResponse(
val sender: User,
val compare: String? = null,
val review: Review? = null,
@SerialName("check_suite")
val checkSuite: CheckSuite? = null,
@SerialName("workflow_run")
val workflowRun: WorkflowRun? = null,
val state: String? = null,
val context: String? = null,
val description: String? = null,
@SerialName("target_url")
val targetUrl: String? = null,
val sha: String? = null,
val repository: Repository,
val created: Boolean? = null,
val deleted: Boolean? = null
) {
val statusSuccessful: Boolean
get() = state == GITHUB_STATUS_SUCCESS

val statusFailed: Boolean
get() = state in GITHUB_STATUS_FAILURE_STATES
}

private const val GITHUB_STATUS_SUCCESS = "success"

private val GITHUB_STATUS_FAILURE_STATES = setOf(
"failure",
"error"
)
15 changes: 15 additions & 0 deletions src/main/kotlin/com/wire/github/response/model/WorkflowRun.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import kotlinx.serialization.Serializable

@Serializable
data class WorkflowRun(
val id: Long? = null,
val name: String? = null,
@SerialName("html_url")
val htmlUrl: String? = null,
Expand All @@ -20,9 +21,23 @@ data class WorkflowRun(

val failed: Boolean
get() = conclusion in WORKFLOW_RUN_FAILURE_CONCLUSIONS

val completed: Boolean
get() = status == WORKFLOW_RUN_STATUS_COMPLETED || conclusion != null

val inProgress: Boolean
get() = !completed

val cancelled: Boolean
get() = conclusion == WORKFLOW_RUN_CONCLUSION_CANCELLED

val neutral: Boolean
get() = completed && !successful && !failed && !cancelled
}

private const val WORKFLOW_RUN_CONCLUSION_SUCCESS = "success"
private const val WORKFLOW_RUN_CONCLUSION_CANCELLED = "cancelled"
private const val WORKFLOW_RUN_STATUS_COMPLETED = "completed"

private val WORKFLOW_RUN_FAILURE_CONCLUSIONS = setOf(
"failure",
Expand Down
12 changes: 1 addition & 11 deletions src/main/kotlin/com/wire/github/util/TemplateHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,9 @@ class TemplateHandler {
response: GitHubResponse
): String? =
when (event) {
EVENT_CHECK_SUITE ->
response.checkSuite
?.takeIf { it.successful || it.failed }
?.let { eventTemplatePath(event) }
EVENT_STATUS ->
response
.takeIf { it.statusSuccessful || it.statusFailed }
?.let { eventTemplatePath(event) }
EVENT_WORKFLOW_RUN ->
response.workflowRun
?.takeIf { it.successful || it.failed }
?.takeIf { it.id != null }
?.let { eventTemplatePath(event) }
else -> response.action?.let {
actionTemplatePath(
Expand Down Expand Up @@ -96,8 +88,6 @@ class TemplateHandler {
private companion object {
const val LANGUAGE_ENGLISH = "en"
const val TEMPLATE_DIRECTORY = "templates"
const val EVENT_CHECK_SUITE = "check_suite"
const val EVENT_STATUS = "status"
const val EVENT_WORKFLOW_RUN = "workflow_run"
}
}
12 changes: 0 additions & 12 deletions src/main/resources/templates/en/check_suite.template

This file was deleted.

12 changes: 0 additions & 12 deletions src/main/resources/templates/en/status.template

This file was deleted.

12 changes: 12 additions & 0 deletions src/main/resources/templates/en/workflow_run.template
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
{{#workflowRun.inProgress}}
⏳ **CI Running**{{#workflowRun.name}} {{workflowRun.name}}{{/workflowRun.name}}
{{/workflowRun.inProgress}}
{{#workflowRun.successful}}
✅ **CI Passed!**{{#workflowRun.name}} {{workflowRun.name}}{{/workflowRun.name}}
{{/workflowRun.successful}}
{{#workflowRun.failed}}
❌ **CI Failed!**{{#workflowRun.name}} {{workflowRun.name}}{{/workflowRun.name}}
{{/workflowRun.failed}}
{{#workflowRun.cancelled}}
⚪ **CI Cancelled**{{#workflowRun.name}} {{workflowRun.name}}{{/workflowRun.name}}
{{/workflowRun.cancelled}}
{{#workflowRun.neutral}}
⚪ **CI Completed**{{#workflowRun.name}} {{workflowRun.name}}{{/workflowRun.name}}{{#workflowRun.conclusion}} ({{workflowRun.conclusion}}){{/workflowRun.conclusion}}
{{/workflowRun.neutral}}

📦 **Repository:** {{repository.fullName}}
{{#workflowRun.status}}📋 **Status:** {{workflowRun.status}}
{{/workflowRun.status}}{{#workflowRun.conclusion}}🏁 **Conclusion:** {{workflowRun.conclusion}}
{{/workflowRun.conclusion}}
{{#workflowRun.headBranch}}🌿 **Branch:** {{workflowRun.headBranch}}
{{/workflowRun.headBranch}}{{#workflowRun.headSha}}🔖 **Commit:** `{{workflowRun.headSha}}`
{{/workflowRun.headSha}}{{#workflowRun.htmlUrl}}🔗 **Run:** {{workflowRun.htmlUrl}}
Expand Down
Loading