Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support appending to message attachments instead of replacing #74

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
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
111 changes: 39 additions & 72 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,24 @@ 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.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 appendAttachments: Boolean,
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 +40,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 +94,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 +121,53 @@ class SlackClient(
messageId: String? = null,
previousAttachments: List<SlackMessage.Attachment>? = null,
): SlackMessage {
val attachments = mutableMapOf<JobType, SlackMessage.Attachment>()
val attachments = if (appendAttachments) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the primary change of the PR, adding support for appending attachments instead of replacing them, depending on the input property.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would break the updating of status in the notifier?

image

Specifically where it goes from In Progress to Success or am I wrong?

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,
messageId = messageId,
attachments = attachments.values.toList()
attachments = attachments
)
}

private suspend fun getSlackMessageById(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These functions are moved to SlackHttpClient.

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 +177,7 @@ class SlackClient(
)

@Serializable
private data class MessageResponse(
data class MessageResponse(
@SerialName("ok")
val ok: Boolean, // true
@SerialName("messages")
Expand Down
66 changes: 66 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,66 @@
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.util.JsonUtil
import com.monta.slack.notifier.util.client
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.ContentType
import io.ktor.http.contentType
import io.ktor.utils.io.charsets.Charsets
import io.ktor.utils.io.charsets.name

open class SlackHttpClient(
private val slackToken: String,
private val slackChannelId: String,
) {

open 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
}
}

open 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
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ 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
Expand Down Expand Up @@ -73,14 +74,20 @@ 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(
serviceName = serviceName.valueOrNull(),
serviceEmoji = serviceEmoji.valueOrNull(),
slackToken = slackToken,
slackChannelId = slackChannelId
slackChannelId = slackChannelId,
appendAttachments = appendStatusUpdates.toBoolean(),
slackHttpClient = SlackHttpClient(slackToken, slackChannelId)
).publish(
githubEvent = githubEvent,
jobType = JobType.fromString(jobType),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
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

class PublishSlackService(
serviceName: String?,
serviceEmoji: String?,
slackToken: String,
slackChannelId: String,
private val serviceName: String?,
private val serviceEmoji: String?,
private val slackChannelId: String,
private val appendAttachments: Boolean = false,
private val slackHttpClient: SlackHttpClient,
) {

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

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

Expand Down
Loading