Skip to content
Draft
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
37 changes: 31 additions & 6 deletions src/main/kotlin/com/procurify/flagcounter/FlagCounter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ class FlagCounter(
* TODO Cleanup this method
*/
private fun postFlagDetails(flagDetails: List<FlagDetail>) {

// Post Total Count Update
totalMessager.postMessage(formatTotalCountMessage(flagDetails.size)).fold(
totalMessager.postMessage(formatTotalCountMessage(flagDetails)).fold(
{ errorMessager.postOrThrow("Failed to post total count message") },
{
val teamFlagMap = groupFlagsByOwner(flagDetails, teamMessagers.keys)
Expand All @@ -46,7 +47,7 @@ class FlagCounter(
errorMessager.postOrThrow(
"Failed to send updates for ${
failedTeamMessages.joinToString(separator = "\n") { it.id }
}}"
}"
)
}
}
Expand Down Expand Up @@ -76,11 +77,16 @@ class FlagCounter(
this.postMessage(message).mapLeft { throw Exception(message) }

/**
* Formats a [totalFlagCount] into a message to be sent by the messager
* Formats a message about the total flag count in the system based on the [teamFlags]
*/
private fun formatTotalCountMessage(totalFlagCount: Int): String {
private fun formatTotalCountMessage(teamFlags: List<FlagDetail>): String {

Choose a reason for hiding this comment

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

minor: If we follow the principle of least knowledge here and pass flagSize and medianAgeInDays as arguments, this function may be easier to test as we then do not need to mock/instantiate the teamFlags object to pass in.

val totalFlagCount = teamFlags.size
val comparisonMessage = FlagEquivalentMessageGenerator.getNearestNumberMessage(totalFlagCount)
return "There are currently $totalFlagCount flags in the system!\n$comparisonMessage"
return """
There are currently $totalFlagCount flags in the system!
$comparisonMessage
The median age of the flags in the system is ${teamFlags.getMedianAgeInDays()} days
""".trimIndent()
}

/**
Expand All @@ -90,13 +96,32 @@ class FlagCounter(
private fun formatTeamMessage(teamFlags: List<FlagDetail>): String {
// TODO Parameterize link url based on project/environment configuration
return """Hey ${teamFlags.firstOrNull()?.owner?.name ?: "team"}!
|Launch Darkly thinks ${removableFlagCount(teamFlags)} of your ${teamFlags.size} flags could be ready for removal.
|LaunchDarkly thinks ${removableFlagCount(teamFlags)} of your ${teamFlags.size} flags could be ready for removal.
|The median age of the flags you maintain is ${teamFlags.getMedianAgeInDays()} days
|Take a look ${flagReader.flagListUrl}""".trimMargin()
}

private fun removableFlagCount(flags: List<FlagDetail>): Int {
return flags.filter { it.status == Status.REMOVABLE }.size
}

companion object {

private const val MILLISECONDS_IN_A_DAY = 1000 * 60 * 60 * 24
fun List<FlagDetail>.getMedianAgeInDays(): Long = this
.map { it.creationDate }
.sorted()
.let { creationDates ->
(System.currentTimeMillis() - creationDates.getMedian()) / MILLISECONDS_IN_A_DAY
}

private fun List<Long>.getMedian() =
if (this.size % 2 == 0) {
(this[this.size / 2] + this[this.size / 2 - 1]) / 2
} else {
this[this.size / 2]
}
}
}

/**
Expand Down
3 changes: 2 additions & 1 deletion src/main/kotlin/com/procurify/flagcounter/FlagReader.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ interface FlagReader {
data class FlagDetail(
val key: String,
val owner: Owner,
val status: Status
val status: Status,
val creationDate: Long
)

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ class LaunchDarklyFlagReader(
Status.REMOVABLE
} else {
Status.ACTIVE
}
},
creationDate = flag.creationDate
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ data class LDFlagDetail(
@JsonProperty("_maintainer")
val maintainer: LDFlagMaintainer,
@JsonProperty("_links")
val links: LDFlagLinks
val links: LDFlagLinks,
val creationDate: Long
)

/**
Expand Down
12 changes: 7 additions & 5 deletions src/test/kotlin/com/procurify/flagcounter/FlagCounterTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ class FlagCounterTest {

@Test
fun `ensure that grouping by email groups flags for all provided email addresses`() {
val creationDate = System.currentTimeMillis()

val alpha = TeamIdentifier("alpha@test.com")
val beta = TeamIdentifier("beta@test.com")

val alphaMaintainer = Owner("", alpha.id)

val alphaDetail1 = FlagDetail("ABC", alphaMaintainer, Status.ACTIVE)
val alphaDetail2 = FlagDetail("DEF", alphaMaintainer, Status.ACTIVE)
val alphaDetail1 = FlagDetail("ABC", alphaMaintainer, Status.ACTIVE, creationDate)
val alphaDetail2 = FlagDetail("DEF", alphaMaintainer, Status.ACTIVE, creationDate)

val teamsMap = mapOf(
alpha to mockk<Messager>(),
Expand All @@ -25,9 +27,9 @@ class FlagCounterTest {
val flagResponse = listOf(
alphaDetail1,
alphaDetail2,
FlagDetail("GHI", Owner("", "third@test.com"), Status.ACTIVE),
FlagDetail("JKL", Owner("", "third@test.com"), Status.ACTIVE),
FlagDetail("MNO", Owner("", "fourth@test.com"), Status.ACTIVE),
FlagDetail("GHI", Owner("", "third@test.com"), Status.ACTIVE, creationDate),
FlagDetail("JKL", Owner("", "third@test.com"), Status.ACTIVE, creationDate),
FlagDetail("MNO", Owner("", "fourth@test.com"), Status.ACTIVE, creationDate),
)

val flagCounter = FlagCounter(
Expand Down
5 changes: 4 additions & 1 deletion src/test/kotlin/com/procurify/flagcounter/TestApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@ class TestApplication {
fun `Test Implementation End to End`() {
val slackMessager = SlackMessager(System.getenv("SLACK_URL"))
val launchDarklyFlagReader = LaunchDarklyFlagReader(System.getenv("LAUNCHDARKLY_KEY"))
val teamMessagers = EnvironmentTeamParser.parseJsonIntoTeamsMap(System.getenv("TEAMS_MAP"))
.mapKeys { TeamIdentifier(it.key) }
.mapValues { SlackMessager(it.value) }

FlagCounter(
totalMessager = slackMessager,
errorMessager = mockk(),
flagReader = launchDarklyFlagReader,
teamMessagers = mapOf()
teamMessagers = teamMessagers
).fetchFlagsAndPostMessages()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ class LaunchDarklyFlagReaderTest {

val maintainer = LDFlagMaintainer("", "")

val creationDate = System.currentTimeMillis()

val status = LDStatus.LAUNCHED

val referenceAlpha = LDFlagReference("/path/alpha")
Expand All @@ -28,9 +30,9 @@ class LaunchDarklyFlagReaderTest {
val flagResponse = LDFlagResponse(
totalCount = 3,
items = listOf(
LDFlagDetail(keyAlpha, maintainer, LDFlagLinks(referenceAlpha)),
LDFlagDetail(keyBeta, maintainer, LDFlagLinks(referenceBeta)),
LDFlagDetail(keyGamma, maintainer, LDFlagLinks(referenceGamma))
LDFlagDetail(keyAlpha, maintainer, LDFlagLinks(referenceAlpha), creationDate),
LDFlagDetail(keyBeta, maintainer, LDFlagLinks(referenceBeta), creationDate),
LDFlagDetail(keyGamma, maintainer, LDFlagLinks(referenceGamma), creationDate)
)
)

Expand All @@ -47,8 +49,8 @@ class LaunchDarklyFlagReaderTest {
val expectedOwner = Owner(maintainer.firstName, maintainer.email)
val expectedStatus = Status.REMOVABLE
val expectedZipped = listOf(
FlagDetail(keyAlpha, expectedOwner, expectedStatus),
FlagDetail(keyBeta, expectedOwner, expectedStatus),
FlagDetail(keyAlpha, expectedOwner, expectedStatus, creationDate),
FlagDetail(keyBeta, expectedOwner, expectedStatus, creationDate),
)

assertEquals(expectedZipped, actualZipped)
Expand All @@ -61,13 +63,15 @@ class LaunchDarklyFlagReaderTest {

val maintainer = LDFlagMaintainer(null, "")

val creationDate = System.currentTimeMillis()

val status = LDStatus.LAUNCHED

val referenceAlpha = LDFlagReference("/path/alpha")
val flagResponse = LDFlagResponse(
totalCount = 1,
items = listOf(
LDFlagDetail(keyAlpha, maintainer, LDFlagLinks(referenceAlpha)),
LDFlagDetail(keyAlpha, maintainer, LDFlagLinks(referenceAlpha), creationDate),
)
)

Expand All @@ -82,7 +86,7 @@ class LaunchDarklyFlagReaderTest {
val expectedOwner = Owner(maintainer.firstName, maintainer.email)
val expectedStatus = Status.REMOVABLE
val expectedZipped = listOf(
FlagDetail(keyAlpha, expectedOwner, expectedStatus),
FlagDetail(keyAlpha, expectedOwner, expectedStatus, creationDate),
)

assertEquals(expectedZipped, actualZipped)
Expand Down