From 4ef280abbe57562d5f56b5f66df3c9913d51a3d8 Mon Sep 17 00:00:00 2001 From: Steven Noto Date: Fri, 28 Oct 2022 23:21:07 -0500 Subject: [PATCH] Add option to split channel stats by week or month. --- README.md | 4 +- .../simplyautomatic/slack/status/Interval.kt | 6 +++ .../slack/status/MessageStats.kt | 2 +- .../slack/status/SlackStatusApplication.kt | 51 ++++++++++++++++--- 4 files changed, 54 insertions(+), 9 deletions(-) create mode 100644 src/main/kotlin/com/simplyautomatic/slack/status/Interval.kt diff --git a/README.md b/README.md index 5616afb..a580038 100644 --- a/README.md +++ b/README.md @@ -39,9 +39,9 @@ Specify `--text='some text' --emoji='emoji-name'` when setting status, and optio java -jar slack-status.jar --set-status --text='Lunch break' --emoji='sandwich' --expires='1pm' -Specify `--channel-id='Slack channel ID' --start='date/time' --end='date/time'` when getting stats: +Specify `--channel-id='Slack channel ID' --start='date/time' --end='date/time'` when getting stats, and optionally `--split-by='week|month'`: - java -jar slack-status.jar --get-channel-stats --channel-id='CXYZ123' --start='January 1, 2022 0:00' --end='January 31, 2022 23:59' + java -jar slack-status.jar --get-channel-stats --channel-id='CXYZ123' --start='January 1, 2022 0:00' --end='January 31, 2022 23:59' --split-by='week' [Natty](https://github.com/joestelmach/natty) is used for natural language date parsing, so expressions like "tomorrow morning" or "wednesday at 5pm" will be recognized for date/time arguments. diff --git a/src/main/kotlin/com/simplyautomatic/slack/status/Interval.kt b/src/main/kotlin/com/simplyautomatic/slack/status/Interval.kt new file mode 100644 index 0000000..97c468f --- /dev/null +++ b/src/main/kotlin/com/simplyautomatic/slack/status/Interval.kt @@ -0,0 +1,6 @@ +package com.simplyautomatic.slack.status + +enum class Interval { + WEEK, + MONTH +} \ No newline at end of file diff --git a/src/main/kotlin/com/simplyautomatic/slack/status/MessageStats.kt b/src/main/kotlin/com/simplyautomatic/slack/status/MessageStats.kt index 62d7e75..1b09cb3 100644 --- a/src/main/kotlin/com/simplyautomatic/slack/status/MessageStats.kt +++ b/src/main/kotlin/com/simplyautomatic/slack/status/MessageStats.kt @@ -1,6 +1,6 @@ package com.simplyautomatic.slack.status -data class MessageStats(val periodName: String) { +data class MessageStats(var periodName: String = "") { var numMessages = 0 var numThreads = 0 val threadsStats = mutableListOf() diff --git a/src/main/kotlin/com/simplyautomatic/slack/status/SlackStatusApplication.kt b/src/main/kotlin/com/simplyautomatic/slack/status/SlackStatusApplication.kt index 7a6ff6a..c4851e4 100644 --- a/src/main/kotlin/com/simplyautomatic/slack/status/SlackStatusApplication.kt +++ b/src/main/kotlin/com/simplyautomatic/slack/status/SlackStatusApplication.kt @@ -14,6 +14,10 @@ import org.springframework.boot.ApplicationRunner import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication import java.text.SimpleDateFormat +import java.time.DayOfWeek +import java.time.ZoneId +import java.time.format.DateTimeFormatter +import java.time.temporal.TemporalAdjusters import java.util.* import kotlin.system.exitProcess @@ -70,12 +74,17 @@ class SlackStatusApplication : ApplicationRunner { val end = args?.getOptionValues("end")?.first() val startDate = Parser().parse(start ?: "").flatMap { it.dates }.firstOrNull() val endDate = Parser().parse(end ?: "").flatMap { it.dates }.firstOrNull() + val splitBy = args?.getOptionValues("split-by")?.first() ?: "" + val splitByInterval = Interval.values().firstOrNull { it.name == splitBy.uppercase() } if (channelId == null || startDate == null || endDate == null) { throw Exception("Channel and start/end dates must be provided to get stats.") } + if (splitBy.isNotBlank() && splitByInterval == null) { + throw Exception("Channel stats can only be split-by 'week' or 'month', not '$splitBy'.") + } - val channelStats = getChannelStats(slackApiToken, channelId, startDate, endDate) - printChannelStats(listOf(channelStats)) + val channelStats = getChannelStats(slackApiToken, channelId, startDate, endDate, splitByInterval) + printChannelStats(channelStats) } } } catch (e: Exception) { @@ -86,6 +95,21 @@ class SlackStatusApplication : ApplicationRunner { exitProcess(0) } + fun getPrettyIntervalNameFromSlackTimestamp(timestamp: String, interval: Interval): String { + val date = Date(timestamp.substringBefore(".").toLong() * 1000) + return when (interval) { + Interval.WEEK -> { + val startOfWeek = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate() + .with(TemporalAdjusters.previousOrSame(DayOfWeek.SUNDAY)) + startOfWeek.format(DateTimeFormatter.ofPattern("'Week of 'YYYY/MM/dd")) + } + + Interval.MONTH -> { + SimpleDateFormat("'Month of 'YYYY/MM").format(date) + } + } + } + fun getMode(args: List?): Mode { if (args == null || args.contains("usage") || args.contains("help")) return Mode.USAGE if (args.contains("clear-status")) return Mode.CLEAR_STATUS @@ -100,7 +124,7 @@ class SlackStatusApplication : ApplicationRunner { |Set your Slack API user token via environment variable `SLACK_API_TOKEN` or as `--token=`. |Specify `--get-status`, `--set-status`, `--clear-status`, `--get-channel-stats`, or `--help` for mode. |Specify `--text='some text' --emoji='emoji-name'` when setting status, and optionally `--expires='date/time'` - |Specify `--channel-id='Slack channel ID' --start='date/time' --end='date/time'` when getting stats.""".trimMargin()) + |Specify `--channel-id='Slack channel ID' --start='date/time' --end='date/time'` when getting stats, and optionally `--split-by='week|month'`.""".trimMargin()) } fun getSlackClient(apiToken: String?): MethodsClient { @@ -144,7 +168,7 @@ class SlackStatusApplication : ApplicationRunner { } } - fun getChannelStats(apiToken: String?, channelId: String, startDate: Date, endDate: Date): MessageStats { + fun getChannelStats(apiToken: String?, channelId: String, startDate: Date, endDate: Date, interval: Interval?): List { val client = getSlackClient(apiToken) val request = ConversationsHistoryRequest.builder() @@ -154,13 +178,25 @@ class SlackStatusApplication : ApplicationRunner { .limit(100) .build() - val messageStats = MessageStats("$startDate to $endDate") + var messageStats = MessageStats(if (interval == null) "$startDate to $endDate" else "") + val messagesStatsList = mutableListOf(messageStats) + do { val response = client.conversationsHistory(request) if (!response.isOk) throw Exception("Slack API returned error ${response.error ?: response.warning}") request.cursor = response.responseMetadata?.nextCursor // Use cursor to track pagination response.messages.forEach { + // Split by interval if specified + if (interval != null) { + val messagePeriodName = getPrettyIntervalNameFromSlackTimestamp(it.ts, interval) + if (messageStats.periodName.isEmpty()) messageStats.periodName = messagePeriodName + if (messageStats.periodName != messagePeriodName) { + messageStats = MessageStats(messagePeriodName) + messagesStatsList += messageStats + } + } + messageStats.numMessages++ if (!it.threadTs.isNullOrBlank() && it.subtype != "thread_broadcast") { messageStats.numThreads++ @@ -168,7 +204,10 @@ class SlackStatusApplication : ApplicationRunner { } } } while (request.cursor?.isNotEmpty() == true) - return messageStats + + messagesStatsList.sortBy { it.periodName } + + return messagesStatsList } fun printChannelStats(stats: List) {