Skip to content

Commit

Permalink
fetch bearer token via the application for each request.
Browse files Browse the repository at this point in the history
  • Loading branch information
lwluc committed Nov 3, 2023
1 parent b216444 commit a15a6e2
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 34 deletions.
21 changes: 3 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ and convert it to [XES](https://xes-standard.org/) for Predictive Process Monito
* [🚫 Limitations](#-limitations)
* [🚀🔜 Coming Soon... 🌟🎉👀](#-coming-soon-)
* [🚀 Getting Started](#-getting-started)
* [Obtaining a Bearer Token from Camunda Optimize](#obtaining-a-bearer-token-from-camunda-optimize)
* [Configuring `application.yaml`](#configuring-applicationyaml)

# ✨Features
Expand Down Expand Up @@ -44,27 +43,13 @@ in Camunda Optimize, make sure it contains only a single process definition.

# 🚀 Getting Started

## Obtaining a Bearer Token from Camunda Optimize

To acquire a Bearer Token from Camunda Optimize, you can use the following curl command:

```shell
curl --request POST \
--url https://login.cloud.camunda.io/oauth/token \
--header 'Content-Type: application/json' \
--data '{
"client_id": "<client_id>",
"client_secret": "<client_secret>",
"audience
```
## Configuring `application.yaml`

To set up your configuration in `application.yaml`, follow these steps:

1. Add the Bearer Token you obtained earlier.
2. Include the Optimize base URL.
3. Specify the Raw Data Report ID.
1. Include the Optimize base URL.
2. Specify the Raw Data Report ID.
3. Add the Client ID and Secret.
4. Specify the filename for the resulting XML (optionally, along with its path).

Once you've completed these configurations, you'll be prepared to retrieve the data and convert it to XES format 🎉
Expand Down
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,21 @@ package de.envite.greenbpm.optimzetoxes.optimizeexport.adapter.out.optimize
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpHeaders
import org.springframework.web.reactive.function.client.WebClient


@Configuration
@EnableConfigurationProperties(OptimizeClientProperties::class)
private class OptimizeClient {

// TODO: Get token by e.g. using Spring Boot OAuth Client
// But keep in mind it should work with Optimize in a C7 set-up as well
@Bean
fun configureWebClient(optimizeClientProperties: OptimizeClientProperties): WebClient {
return WebClient.builder()
@Bean("optimize-client")
fun configureOptimizeWebClient(optimizeClientProperties: OptimizeClientProperties): WebClient =
WebClient.builder()
.baseUrl(optimizeClientProperties.baseUrl!!)
.defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer ${optimizeClientProperties.bearerToken}")
.build()
}
@Bean("login-client")
fun configureLoginWebClient(optimizeClientProperties: OptimizeClientProperties): WebClient =
WebClient.builder()
.baseUrl(optimizeClientProperties.tokenBaseUrl!!)
.build()
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import org.springframework.context.annotation.Configuration
@ConfigurationProperties(prefix = "optimize")
internal data class OptimizeClientProperties(
var baseUrl: String? = null,
var bearerToken: String? = null,
var reportId: String? = null,
)
var clientId: String? = null,
var clientSecret: String? = null,
var tokenBaseUrl: String? = null,
)
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,48 @@ import de.envite.greenbpm.optimzetoxes.Logging
import de.envite.greenbpm.optimzetoxes.log
import de.envite.greenbpm.optimzetoxes.optimizeexport.domain.model.OptimizeData
import de.envite.greenbpm.optimzetoxes.optimizeexport.usecase.out.RawDataQuery
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatusCode
import org.springframework.stereotype.Service
import org.springframework.web.reactive.function.BodyInserters
import org.springframework.web.reactive.function.client.WebClient
import reactor.core.publisher.Mono

@Service
internal class OptimizeRawDataQueryService(
private val webClient: WebClient,
@Qualifier("optimize-client")
private val webClientOptimize: WebClient,
@Qualifier("login-client")
private val webClientLogin: WebClient,
private val optimizeClientProperties: OptimizeClientProperties
): RawDataQuery, Logging {

@Throws(DataQueryException::class)
private fun queryToken(): TokenResponse =
webClientLogin
.post()
.body(BodyInserters.fromValue(TokenRequest(optimizeClientProperties.clientId!!, optimizeClientProperties.clientSecret!!)))
.retrieve()
.onStatus(HttpStatusCode::isError) { _ ->
Mono.error { throw DataQueryException("Could not fetch Bearer Token from optimize") }
}
.bodyToMono(TokenResponse::class.java)
.blockOptional()
.orElseThrow { DataQueryException("Could not parse Bearer Token from optimize") }

@Throws(DataQueryException::class)
override fun queryData(): OptimizeData {
val token = queryToken()

val uri = "/api/public/export/report/${optimizeClientProperties.reportId}/result/json?paginationTimeout=60&limit=2"

log().info("Fetch Camunda Optimize Raw Data Export from URI {}'", uri)

return webClient
return webClientOptimize
.get()
.uri(uri)
.header(HttpHeaders.AUTHORIZATION, "Bearer ${token.access_token}")
.retrieve()
.onStatus(HttpStatusCode::isError) { _ ->
Mono.error { throw DataQueryException("Could not fetch data from optimize") }
Expand All @@ -32,5 +54,15 @@ internal class OptimizeRawDataQueryService(
.blockOptional()
.orElseThrow { DataQueryException("Could not parse data from optimize") }
}
}

private class TokenResponse (
var access_token: String? = null
)

}
private data class TokenRequest(
val client_id: String,
val client_secret: String,
val audience: String = "optimize.camunda.io",
val grant_type: String = "client_credentials"
)
4 changes: 3 additions & 1 deletion src/main/resources/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ spring:

optimize:
baseUrl: <optimize-base-url>
bearerToken: <token>
reportId: <report-id>
client-id: <client_id>
client-secret: <client_secret>
token-base-url: https://login.cloud.camunda.io/oauth/token

xes-mapping:
filename: <xes-result-filename-or-path>.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,23 @@ class OptimizeRawDataQueryServiceTest {
}

private lateinit var classUnderTest: OptimizeRawDataQueryService
private val optimizeClientProperties: OptimizeClientProperties = OptimizeClientProperties().apply { reportId = "1" }
private val optimizeClientProperties: OptimizeClientProperties = OptimizeClientProperties().apply {
reportId = "1"
clientId = "7"
clientSecret = "secret"
}

@BeforeEach
fun setUpClassUnderTest() {
val webclient = WebClient.builder()
.baseUrl("http://localhost:${mockWebServer.port}")
.build()
classUnderTest = OptimizeRawDataQueryService(webclient, optimizeClientProperties)
classUnderTest = OptimizeRawDataQueryService(webclient, webclient, optimizeClientProperties)
}

@Test
fun should_query_data() {
enqueueToMock(mockWebServer, "/optimize-response/optimize-sample-token-response.json")
enqueueToMock(mockWebServer, "/optimize-response/optimize-sample-export-response.json")

val expectedProcessDefinitionKey = "customer_onboarding_en"
Expand All @@ -67,8 +72,19 @@ class OptimizeRawDataQueryServiceTest {
// TODO: Test more fields
}

@Test
fun should_throw_on_missing_token() {
mockWebServer.enqueue(MockResponse().setResponseCode(404))

val exception = shouldThrow<DataQueryException> {
classUnderTest.queryData()
}
exception.message shouldContain "Could not fetch Bearer Token from optimize"
}

@Test
fun should_throw_on_404() {
enqueueToMock(mockWebServer, "/optimize-response/optimize-sample-token-response.json")
mockWebServer.enqueue(MockResponse().setResponseCode(404))

val exception = shouldThrow<DataQueryException> {
Expand All @@ -79,6 +95,7 @@ class OptimizeRawDataQueryServiceTest {

@Test
fun should_throw_on_500() {
enqueueToMock(mockWebServer, "/optimize-response/optimize-sample-token-response.json")
mockWebServer.enqueue(MockResponse().setResponseCode(500))

val exception = shouldThrow<DataQueryException> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IlFVVXdPVFpDUTBVM01qZEVRME0wTkRFelJrUkJORFk0T0RZeE1FRTBSa1pFUlVWRVF6bERNZyJ9.eyJodHRwczovL2NhbXVuZGEuY29tL29yZ0lkIjoiZjFhMDgyZTgtNjcyOC00NDJkLTlkZjAtMzlmZmNjMzlmNWVlIiwiaXNzIjoiaHR0cHM6Ly93ZWJsb2dpbi5jbG91ZC5jYW11bmRhLmlvLyIsInN1YiI6Im13bzkwdDJyMzE2MDd6MzZCTkg2OXRXRktCWDU1ajFXQGNsaWVudHMiLCJhdWQiOiJvcHRpbWl6ZS5jYW11bmRhLmlvIiwiaWF0IjoxNjk5MDA0NTg4LCJleHAiOjE2OTkwOTA5ODgsImF6cCI6Im13bzkwdDJyMzE2MDd6MzZCTkg2OXRXRktCWDU1ajFXIiwic2NvcGUiOiI1YTM1YmY0ZC1lNWI0LTQyMDItYmFkMi0xMTA1MjNiOTBhNTAiLCJndHkiOiJjbGllbnQtY3JlZGVudGlhbHMifQ.W7waSsKU120ZcnbBqi_VNMJjlmrJ2m9LnbQ4pK6pPddkDGtRnyoE7qYqpp0u_iN1kW5bmDwru9WA6ulVNjxxG_QqR0veVQAdW01dq4kBBsbth8a_f2zKL4FV2_qGwV7HFE0JK4tgf_nLTIbu4JYbMfmu3s7U7BDC4n-43K1gdzM3UZw3Cy4mxRwTy-e09Z7WDrijmzAWVfeCf2O6nsFVw8RPi1EdGdN7u-5tPb7EK5NHn4WCMj7yhEV06R1DNu7QF2m1Ww905xxbYBRtJD4Tpv1Gr6zLd82rmXdVVJ3RK7QygfsCZiQfhQY-CLpQwBTnlSviIpNei4S-0vZ8NwsgXQ",
"scope": "5a35bf4d-e5b4-4202-bad2-110523b90a50",
"expires_in": 70133,
"token_type": "Bearer"
}

0 comments on commit a15a6e2

Please sign in to comment.