Skip to content

Commit

Permalink
feat: Recording endpoint + test
Browse files Browse the repository at this point in the history
  • Loading branch information
JanCizmar committed Jul 12, 2023
1 parent 85b25b5 commit 909481a
Show file tree
Hide file tree
Showing 23 changed files with 330 additions and 126 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package io.tolgee.api.v2.controllers

import io.sentry.Sentry
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.tags.Tag
import io.tolgee.component.reporting.BusinessEventPublisher
import io.tolgee.dtos.request.BusinessEventReportRequest
import io.tolgee.service.organization.OrganizationRoleService
import io.tolgee.service.security.SecurityService
import io.tolgee.util.Logging
import io.tolgee.util.logger
import org.springframework.web.bind.annotation.CrossOrigin
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@RestController
@CrossOrigin(origins = ["*"])
@RequestMapping(value = ["/v2/business-events"])
@Tag(name = "Reports business events from frontend app")
class BusinessEventController(
private val businessEventPublisher: BusinessEventPublisher,
private val securityService: SecurityService,
private val organizationRoleService: OrganizationRoleService,
) : Logging {
@PostMapping("/report")
@Operation(summary = "Reports business event")
fun report(@RequestBody eventData: BusinessEventReportRequest) {
try {
eventData.projectId?.let { securityService.checkAnyProjectPermission(it) }
eventData.organizationId?.let { organizationRoleService.checkUserCanView(it) }
businessEventPublisher.publish(eventData)
} catch (e: Throwable) {
logger.error("Error storing event", e)
Sentry.captureException(e)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ class V2ImportController(
private val projectHolder: ProjectHolder,
private val languageService: LanguageService,
private val namespaceService: NamespaceService,

) {
@PostMapping("", consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
@AccessWithAnyProjectPermission()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ class V2ExportController(
.toList()
.map { language -> language.tag }
.toSet()

val exported = exportService.export(projectHolder.project.id, params)
checkExportNotEmpty(exported)
return getExportResponse(params, exported)
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package io.tolgee.configuration

import io.tolgee.activity.ActivityFilter
import io.tolgee.component.UtmReportingFilter
import io.tolgee.component.VersionFilter
import io.tolgee.configuration.tolgee.TolgeeProperties
import io.tolgee.security.DisabledAuthenticationFilter
Expand Down Expand Up @@ -35,7 +34,6 @@ class WebSecurityConfig @Autowired constructor(
private val rateLimitsFilterFactory: RateLimitsFilterFactory,
private val patAuthFilter: PatAuthFilter,
private val versionFilter: VersionFilter,
private val utmReportingFilter: UtmReportingFilter,
) : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http
Expand All @@ -56,7 +54,6 @@ class WebSecurityConfig @Autowired constructor(
.addFilterAfter(disabledAuthenticationFilter, ApiKeyAuthFilter::class.java)
.addFilterAfter(projectPermissionFilter, JwtTokenFilter::class.java)
.addFilterAfter(activityFilter, ProjectPermissionFilter::class.java)
.addFilterAfter(utmReportingFilter, ProjectPermissionFilter::class.java)
.addFilterAfter(
rateLimitsFilterFactory.create(RateLimitLifeCyclePoint.AFTER_AUTHORIZATION),
ProjectPermissionFilter::class.java
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,38 @@
package io.tolgee.activity

import com.posthog.java.PostHog
import io.tolgee.ProjectAuthControllerTest
import io.tolgee.development.testDataBuilder.data.BaseTestData
import io.tolgee.fixtures.AuthorizedRequestFactory
import io.tolgee.fixtures.andAssertThatJson
import io.tolgee.fixtures.andIsOk
import io.tolgee.fixtures.andPrettyPrint
import io.tolgee.fixtures.isValidId
import io.tolgee.fixtures.node
import io.tolgee.fixtures.waitForNotThrowing
import io.tolgee.testing.annotations.ProjectJWTAuthTestMethod
import io.tolgee.testing.assert
import net.javacrumbs.jsonunit.assertj.JsonAssert
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.mockito.kotlin.any
import org.mockito.kotlin.argThat
import org.mockito.kotlin.eq
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.http.HttpHeaders
import java.math.BigDecimal

class ActivityLogTest : ProjectAuthControllerTest("/v2/projects/") {

private lateinit var testData: BaseTestData

@MockBean
@Autowired
lateinit var postHog: PostHog

@BeforeEach
fun setup() {
testData = BaseTestData()
Expand All @@ -36,7 +52,8 @@ class ActivityLogTest : ProjectAuthControllerTest("/v2/projects/") {
fun `it stores and returns translation set activity`() {
performProjectAuthPut(
"translations",
mapOf("key" to "key", "translations" to mapOf(testData.englishLanguage.tag to "Test"))
mapOf("key" to "key", "translations" to mapOf(testData.englishLanguage.tag to "Test")),

).andIsOk

performProjectAuthGet("activity").andIsOk.andPrettyPrint.andAssertThatJson {
Expand All @@ -62,6 +79,31 @@ class ActivityLogTest : ProjectAuthControllerTest("/v2/projects/") {
}
}

@Test
@ProjectJWTAuthTestMethod
fun `it publishes business event to external monitor`() {
performPut(
"/v2/projects/${project.id}/translations",
mapOf("key" to "key", "translations" to mapOf(testData.englishLanguage.tag to "Test")),
HttpHeaders().also {
it["Authorization"] = listOf(AuthorizedRequestFactory.getBearerTokenString(generateJwtToken(userAccount!!.id)))
it["X-Tolgee-Utm"] = "eyJ1dG1faGVsbG8iOiJoZWxsbyJ9"
}
).andIsOk

var params: Map<String, Any?>? = null
waitForNotThrowing(timeout = 10000) {
verify(postHog, times(1)).capture(
any(), eq("SET_TRANSLATIONS"),
argThat {
params = this
true
}
)
}
params!!["utm_hello"].assert.isEqualTo("hello")
}

private fun JsonAssert.isValidTranslationModifications() {
node("auto") {
node("old").isEqualTo(null)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package io.tolgee.api.v2.controllers

import com.posthog.java.PostHog
import io.tolgee.ProjectAuthControllerTest
import io.tolgee.development.testDataBuilder.data.BaseTestData
import io.tolgee.fixtures.AuthorizedRequestFactory
import io.tolgee.fixtures.andIsOk
import io.tolgee.fixtures.waitForNotThrowing
import io.tolgee.testing.annotations.ProjectJWTAuthTestMethod
import io.tolgee.testing.assert
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.mockito.kotlin.any
import org.mockito.kotlin.argThat
import org.mockito.kotlin.eq
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.http.HttpHeaders

class BusinessEventControllerTest : ProjectAuthControllerTest("/v2/projects/") {
private lateinit var testData: BaseTestData

@MockBean
@Autowired
lateinit var postHog: PostHog

@BeforeEach
fun setup() {
testData = BaseTestData()
testDataService.saveTestData(testData.root)
projectSupplier = { testData.projectBuilder.self }
userAccount = testData.user
}

@Test
@ProjectJWTAuthTestMethod
fun `it publishes business event to external monitor`() {
performPost(
"/v2/business-events/report",
mapOf(
"eventName" to "TEST_EVENT",
"organizationId" to testData.userAccountBuilder.defaultOrganizationBuilder.self.id,
"projectId" to testData.projectBuilder.self.id,
"data" to mapOf("test" to "test")
),
HttpHeaders().also {
it["Authorization"] = listOf(AuthorizedRequestFactory.getBearerTokenString(generateJwtToken(userAccount!!.id)))
it["X-Tolgee-Utm"] = "eyJ1dG1faGVsbG8iOiJoZWxsbyJ9"
}
).andIsOk

var params: Map<String, Any?>? = null
waitForNotThrowing(timeout = 10000) {
verify(postHog, times(1)).capture(
any(), eq("TEST_EVENT"),
argThat {
params = this
true
}
)
}
params!!["utm_hello"].assert.isEqualTo("hello")
params!!["organizationId"].assert.isNotNull
params!!["organizationName"].assert.isEqualTo("test_username")
params!!["utm_hello"].assert.isEqualTo("hello")
params!!["test"].assert.isEqualTo("test")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,9 @@ import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.mock.mockito.MockBean
import kotlin.properties.Delegates

@SpringBootTest(properties = ["tolgee.post-hog.api-key=mock"])
@AutoConfigureMockMvc
class PublicControllerTest :
AbstractControllerTest() {
Expand Down
29 changes: 29 additions & 0 deletions backend/data/src/main/kotlin/io/tolgee/activity/ActivityFilter.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package io.tolgee.activity

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import io.sentry.Sentry
import org.springframework.stereotype.Component
import org.springframework.web.filter.OncePerRequestFilter
import org.springframework.web.method.HandlerMethod
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
import java.nio.charset.StandardCharsets
import java.util.*
import javax.servlet.FilterChain
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
Expand All @@ -26,11 +30,36 @@ class ActivityFilter(
activityHolder.activity = activityAnnotation.activity
}

assignUtmDataHolder(request)

filterChain.doFilter(request, response)
}

private fun getActivityAnnotation(request: HttpServletRequest): RequestActivity? {
val handlerMethod = (requestMappingHandlerMapping.getHandler(request)?.handler as HandlerMethod?)
return handlerMethod?.getMethodAnnotation(RequestActivity::class.java)
}

fun assignUtmDataHolder(request: HttpServletRequest) {
try {
val headerValue = request.getHeader(UTM_HEADER_NAME) ?: return
val parsed = parseUtmValues(headerValue) ?: return
activityHolder.utmData = parsed
} catch (e: Exception) {
Sentry.captureException(e)
logger.error(e)
}
}

fun parseUtmValues(headerValue: String?): Map<String, Any?>? {
val base64Decoded = Base64.getDecoder().decode(headerValue)
val utmParamsJson = String(base64Decoded, StandardCharsets.UTF_8)
val utmParams = mutableMapOf<String, String>()
return jacksonObjectMapper().readValue(utmParamsJson, utmParams::class.java)
.filterKeys { it.startsWith("utm_") }
}

companion object {
const val UTM_HEADER_NAME = "X-Tolgee-Utm"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,28 @@ import io.tolgee.events.OnProjectActivityEvent
import io.tolgee.model.EntityWithId
import io.tolgee.model.activity.ActivityModifiedEntity
import io.tolgee.model.activity.ActivityRevision
import org.slf4j.LoggerFactory
import io.tolgee.util.Logging
import org.springframework.context.ApplicationContext
import javax.annotation.PreDestroy
import kotlin.reflect.KClass

open class ActivityHolder(
private val applicationContext: ApplicationContext
) {
) : Logging {
open var activity: ActivityType? = null

open var meta: MutableMap<String, Any?> = mutableMapOf()

open var activityRevision: ActivityRevision? = null

private val logger = LoggerFactory.getLogger(this::class.java)

open var modifiedCollections: MutableMap<Pair<EntityWithId, String>, List<Any?>?> = mutableMapOf()

open var transactionRollbackOnly = false

open var organizationId: Long? = null

open var utmData: UtmData = null

@PreDestroy
open fun preDestroy() {
if (!transactionRollbackOnly) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
package io.tolgee.activity

import org.springframework.stereotype.Component
import org.springframework.web.context.annotation.RequestScope

@Component
@RequestScope
class UtmDataHolder {
var data: Map<String, Any?>? = null
}
typealias UtmData = Map<String, Any?>?
Loading

0 comments on commit 909481a

Please sign in to comment.