Skip to content

Commit

Permalink
Add support for appending to attachments instead of overwriting the s…
Browse files Browse the repository at this point in the history
…tatus
  • Loading branch information
nickelsen committed Sep 30, 2024
1 parent 46d1485 commit 09810fb
Show file tree
Hide file tree
Showing 6 changed files with 294 additions and 89 deletions.
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ kotlin {
implementation(kotlin("test-annotations-common"))
implementation("io.kotest:kotest-framework-engine:$kotestVersion")
implementation("io.kotest:kotest-assertions-core:$kotestVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")
}
}
}
Expand Down
120 changes: 43 additions & 77 deletions src/commonMain/kotlin/com/monta/slack/notifier/SlackClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,22 @@ import com.monta.slack.notifier.model.JobStatus
import com.monta.slack.notifier.model.JobType
import com.monta.slack.notifier.model.SlackBlock
import com.monta.slack.notifier.model.SlackMessage
import com.monta.slack.notifier.util.JsonUtil
import com.monta.slack.notifier.service.Input
import com.monta.slack.notifier.util.buildTitle
import com.monta.slack.notifier.util.client
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.utils.io.charsets.*
import kotlinx.datetime.LocalDateTime
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

class SlackClient(
private val serviceName: String?,
private val serviceEmoji: String?,
private val slackToken: String,
private val slackChannelId: String,
private val input: Input,
private val slackHttpClient: SlackHttpClient,
) {

suspend fun create(
githubEvent: GithubEvent,
jobType: JobType,
jobStatus: JobStatus,
): String {
val response = makeSlackRequest(
val response = slackHttpClient.makeSlackRequest(
url = "https://slack.com/api/chat.postMessage",
message = generateMessageFromGithubEvent(
githubEvent = githubEvent,
Expand All @@ -45,9 +38,9 @@ class SlackClient(
jobType: JobType,
jobStatus: JobStatus,
): String {
val previousMessage = getSlackMessageById(messageId)
val previousMessage = slackHttpClient.getSlackMessageById(messageId)

val response = makeSlackRequest(
val response = slackHttpClient.makeSlackRequest(
url = "https://slack.com/api/chat.update",
message = generateMessageFromGithubEvent(
githubEvent = githubEvent,
Expand Down Expand Up @@ -99,7 +92,7 @@ class SlackClient(
),
SlackBlock.Text(
type = "mrkdwn",
text = " \n*Comitter:*\n${githubEvent.displayName}"
text = " \n*Committer:*\n${githubEvent.displayName}"
),
SlackBlock.Text(
type = "mrkdwn",
Expand All @@ -126,81 +119,54 @@ class SlackClient(
messageId: String? = null,
previousAttachments: List<SlackMessage.Attachment>? = null,
): SlackMessage {
val attachments = mutableMapOf<JobType, SlackMessage.Attachment>()
val attachments = if (input.appendAttachments) {
previousAttachments.orEmpty() + SlackMessage.Attachment(
color = jobStatus.color,
fields = listOf(
SlackMessage.Attachment.Field(
title = jobType.label + " ($LocalDateTime)",
short = false,
value = jobStatus.message
)
)
)
} else {
val attachments = mutableMapOf<JobType, SlackMessage.Attachment>()

previousAttachments?.forEach { previousAttachment ->
if (previousAttachment.jobType == null) {
return@forEach
previousAttachments?.forEach { previousAttachment ->
if (previousAttachment.jobType == null) {
return@forEach
}
attachments[previousAttachment.jobType] = previousAttachment
}
attachments[previousAttachment.jobType] = previousAttachment
}

attachments[jobType] = SlackMessage.Attachment(
color = jobStatus.color,
fields = listOf(
SlackMessage.Attachment.Field(
title = jobType.label,
short = false,
value = jobStatus.message
attachments[jobType] = SlackMessage.Attachment(
color = jobStatus.color,
fields = listOf(
SlackMessage.Attachment.Field(
title = jobType.label,
short = false,
value = jobStatus.message
)
)
)
)

attachments.values.toList()
}

return generateSlackMessageFromEvent(
githubEvent = githubEvent,
serviceName = serviceName,
serviceEmoji = serviceEmoji,
slackChannelId = slackChannelId,
serviceName = input.serviceName,
serviceEmoji = input.serviceEmoji,
slackChannelId = input.slackChannelId,
messageId = messageId,
attachments = attachments.values.toList()
attachments = attachments
)
}

private suspend fun getSlackMessageById(
messageId: String,
): MessageResponse? {
val response = client.get {
header("Authorization", "Bearer $slackToken")
url {
url("https://slack.com/api/conversations.history")
parameters.append("channel", slackChannelId)
parameters.append("oldest", messageId)
parameters.append("inclusive", "true")
parameters.append("limit", "1")
}
}

val bodyString = response.bodyAsText()

return if (response.status.value in 200..299) {
println("successfully got message bodyString=$bodyString")
JsonUtil.instance.decodeFromString(bodyString)
} else {
println("failed to get message $bodyString")
null
}
}

private suspend fun makeSlackRequest(url: String, message: SlackMessage): Response? {
val response = client.post(url) {
header("Authorization", "Bearer $slackToken")
contentType(ContentType.Application.Json.withParameter("charset", Charsets.UTF_8.name))
setBody(message)
}

val bodyString = response.bodyAsText()

return if (response.status.value in 200..299) {
println("successfully posted message bodyString=$bodyString")
JsonUtil.instance.decodeFromString(bodyString)
} else {
println("failed to post message $bodyString")
null
}
}

@Serializable
private data class Response(
data class Response(
@SerialName("ok")
val ok: Boolean, // true
@SerialName("channel")
Expand All @@ -210,7 +176,7 @@ class SlackClient(
)

@Serializable
private data class MessageResponse(
data class MessageResponse(
@SerialName("ok")
val ok: Boolean, // true
@SerialName("messages")
Expand Down
67 changes: 67 additions & 0 deletions src/commonMain/kotlin/com/monta/slack/notifier/SlackHttpClient.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.monta.slack.notifier

import com.monta.slack.notifier.SlackClient.MessageResponse
import com.monta.slack.notifier.SlackClient.Response
import com.monta.slack.notifier.model.SlackMessage
import com.monta.slack.notifier.service.Input
import com.monta.slack.notifier.util.JsonUtil
import com.monta.slack.notifier.util.client
import io.ktor.client.HttpClient
import io.ktor.client.request.get
import io.ktor.client.request.header
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import io.ktor.client.request.url
import io.ktor.client.statement.bodyAsText
import io.ktor.http.*
import io.ktor.utils.io.charsets.*

open class SlackHttpClient(
private val input: Input,
) {

open suspend fun getSlackMessageById(
messageId: String,
): MessageResponse? {
val response = client.get {
header("Authorization", "Bearer ${input.slackToken}")
url {
url("https://slack.com/api/conversations.history")
parameters.append("channel", input.slackChannelId)
parameters.append("oldest", messageId)
parameters.append("inclusive", "true")
parameters.append("limit", "1")
}
}

val bodyString = response.bodyAsText()

return if (response.status.value in 200..299) {
println("successfully got message bodyString=$bodyString")
JsonUtil.instance.decodeFromString(bodyString)
} else {
println("failed to get message $bodyString")
null
}
}

open suspend fun makeSlackRequest(url: String, message: SlackMessage): Response? {
val response = client.post(url) {
header("Authorization", "Bearer ${input.slackToken}")
contentType(ContentType.Application.Json.withParameter("charset", Charsets.UTF_8.name))
setBody(message)
}

val bodyString = response.bodyAsText()

return if (response.status.value in 200..299) {
println("successfully posted message bodyString=$bodyString")
JsonUtil.instance.decodeFromString(bodyString)
} else {
println("failed to post message $bodyString")
null
}
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ package com.monta.slack.notifier.command
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.options.required
import com.monta.slack.notifier.SlackHttpClient
import com.monta.slack.notifier.model.GithubEvent
import com.monta.slack.notifier.model.JobStatus
import com.monta.slack.notifier.model.JobType
import com.monta.slack.notifier.model.serializers.BaseGithubContext
import com.monta.slack.notifier.service.Input
import com.monta.slack.notifier.service.PublishSlackService
import com.monta.slack.notifier.util.client
import com.monta.slack.notifier.util.populateEventFromJson
import com.monta.slack.notifier.util.readStringFromFile
import kotlinx.coroutines.runBlocking
Expand Down Expand Up @@ -73,14 +76,24 @@ class PublishSlackCommand : CliktCommand() {
envvar = "SLACK_MESSAGE_ID"
)

private val appendStatusUpdates: String? by option(
help = "Append status updates or change to latest status",
envvar = "APPEND_STATUS_UPDATES"
)

override fun run() {
runBlocking {
val githubEvent = getGithubEvent()
PublishSlackService(
val input = Input(
serviceName = serviceName.valueOrNull(),
serviceEmoji = serviceEmoji.valueOrNull(),
slackToken = slackToken,
slackChannelId = slackChannelId
slackChannelId = slackChannelId,
appendAttachments = appendStatusUpdates.toBoolean()
)
PublishSlackService(
input = input,
slackHttpClient = SlackHttpClient(input)
).publish(
githubEvent = githubEvent,
jobType = JobType.fromString(jobType),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
package com.monta.slack.notifier.service

import com.monta.slack.notifier.SlackClient
import com.monta.slack.notifier.SlackHttpClient
import com.monta.slack.notifier.model.GithubEvent
import com.monta.slack.notifier.model.JobStatus
import com.monta.slack.notifier.model.JobType
import com.monta.slack.notifier.util.writeToOutput

data class Input(
val serviceName: String?,
val serviceEmoji: String?,
val slackToken: String,
val slackChannelId: String,
val appendAttachments: Boolean = false,
)

class PublishSlackService(
serviceName: String?,
serviceEmoji: String?,
slackToken: String,
slackChannelId: String,
input: Input,
slackHttpClient: SlackHttpClient,
) {

private val slackClient = SlackClient(
serviceName = serviceName,
serviceEmoji = serviceEmoji,
slackToken = slackToken,
slackChannelId = slackChannelId
input = input,
slackHttpClient = slackHttpClient
)

suspend fun publish(
Expand All @@ -37,7 +41,7 @@ class PublishSlackService(
messageId = slackMessageId,
githubEvent = githubEvent,
jobType = jobType,
jobStatus = jobStatus
jobStatus = jobStatus,
)
}

Expand Down
Loading

0 comments on commit 09810fb

Please sign in to comment.