takeWhile with subscribing to new Single each time - rx-java2

I have a data source fun getData(page : Int) : Single<List<Data>>.
I'd like to create a behaviour where I could subscribe to that source each time with different parameters each time until conditionCheck() returns true.
I imagine something like this:
getData(page)
.doOnNext { page++ }
.doOnNext { /* manipulate data */ }
.takeWhile { conditionCheck() }
.subscribe({
print "Completed"
})

Here is a different way to think about it (pseudocode-ish):
initialPage = Observable.just(1);
futurePages = PublishSubject.create();
allPossiblePages = Observable.concat(initialPage, futurePages);
allPossibleData = allPossiblePages
.map(page -> getData(page))
.do(page -> futurePages.onNext(/* manipulate page data */));
dataYouWant = allPossibleData
.filter(data -> conditionCheck(data))
.takeFirst()
.subscribe( /* data should be here */);
Maybe this isn't the best way to do this, someone will speak up if that's the case. I'm not a big fan of the line .do(page -> futurePages.onNext ..), but something like this might work.
I put together an example that runs using rxjs (easiest to prototype/demo) here: https://plnkr.co/edit/uXE53ygo8REzpbMJdvsD

Related

crafting the body for request does not work concurrently

I would like to send simultaneous requests through gatlings for some duration
below is the snippet of my code where I am crafting the requests.
JSON file contents function which is used for crafting the json. its been used in the main request
the TestDevice_dev.csv has list of devices till 30 after 30 I will reuse it.
TestDevice1
TestDevice2
TestDevice3
.
.
.
val dFeeder = csv("TestDevice_dev.csv").circular
val trip_dte_tunnel_1 = scenario("TripSimulation")
.feed(dFeeder)
.exec(session => {
val key = conf.getString("config.env.sign_key")
var bodyTrip = CannedRequests.jsonFileContents("${deviceID}")
//deviceId comes from the feeder
session.set("trip_sign", SignatureGeneration.getSignature(key, bodyTrip))
session.set("tripBody",bodyTrip)
})
.exec(http("trip")
.post(trip_url)
.headers(trip_Headers_withsign)
.body(StringBody("${tripBody}")).asJSON.check(status.is(201)))
.exec(flushSessionCookies)
the scenario is started as below
val scn_trip = scenario("trip simulation")
.repeat{1} {
exec(DataExchange.trip_dte_tunnel_1)
}
setUp(scn_trip.inject(constantUsersPerSec(5) during (5 seconds))) ```
it runs fine if there is 1 user for 5 seconds but not simulatenous users.
the json request which is crafted looks like the below
"events":[
{
"deviceDetailsDataModel":{
"deviceId":"<deviceID>"
},
"eventDateTime":"<timeStamp>",
"tripInfoDataModel":{
"ignitionStatus":"ON",
"ignitionONTime":"<onTimeStamp>"
}
},
{
"deviceDetailsDataModel":{
"deviceId":"<deviceID>"
},
"eventDateTime":"<timeStamp>",
"tripInfoDataModel":{
"ignitionStatus":"ON",
"ignitionONTime":"<onTimeStamp>"
}
},
{
"deviceDetailsDataModel":{
"deviceId":"<deviceID>"
},
"eventDateTime":"<timeStamp>",
"tripInfoDataModel":{
"ignitionOFFTime":"<onTimeStamp>",
"ignitionStatus":"OFF"
}
}
]
}`
`def jsonFileContents(deviceId: String): String= {
val fileName = "trip-data.json"
var stringBuilder=""
var timeStamp1:Long = ZonedDateTime.now(ZoneId.of("America/Chicago")).toInstant().toEpochMilli().toLong - 10000.toLong
for (line <- (Source fromFile fileName).getLines) {
if (line.contains("eventDateTime")) {
var lineReplace=line.replaceAll("<timeStamp>", timeStamp1.toString())
stringBuilder=stringBuilder+lineReplace
timeStamp1 = timeStamp1+1000.toLong
}
else if (line.contains("onTimeStamp")) {
var lineReplace1=line.replaceAll("<onTimeStamp>", timeStamp1.toString)
stringBuilder=stringBuilder+lineReplace1
}
else if (line.contains("deviceID")){
var lineReplace2=line.replace("<deviceID>", deviceId)
stringBuilder=stringBuilder+lineReplace2
}
else {
stringBuilder =stringBuilder+line
}
}
stringBuilder
}
`
Best guess: your feeder contains one single entry and you're using the default queue strategy. Either add more entries in your feeder file to match the number of users, or use a different strategy.
This really is explained in the documentation, including the tutorials. I recommend you take some time to read the documentation before rushing into the code, you'll save lots of time in the end.
You don't need to do your own parameter substitution of values in the json file - Gatling supports passing en ELFileBody as the body where you can have a json file with gatling EL expressions like ${deviceId}.

How do I update a MongoDB document with new value using reactors Mono? (Kotlin)

So the context is that I require to update a value in a single document, I have a Mono, the parameter Object contains values such as username (to find the correct user by unique username) and an amount value.
The problem is that this value (due to other components of my application) is the value by which I need to increase/decrease the users balance, as opposed to passing a new balance. I intend to do this using two Monos where one finds the user, then this is combined to the other Mono with the inbound request, where I can then perform a simple sum (i.e balance + changeRequest.amount) then return this to the document database.
override fun increaseBalance(changeRequest: Mono<ChangeBalanceRequestResource>): Mono<ChangeBalanceResponse> {
val changeAmount: Mono<Decimal128> = changeRequest.map { it.transactionAmount }
val user: Mono<User> = changeRequest.flatMap { rxUserRepository.findByUsername(it.username)
val newBalace = user.map {
val r = changeAmount.block()
it.balance = sumBalance(it.balance!!, r!!)
rxUserRepository.save(it)
}
.flatMap { it }
.map { it.balance!! }
return Mono.just(ChangeBalanceResponse("success", newBalace.block()!!))
}
Obviously I'm trying to achieve this in a non-blocking fashion. I'm also open to using only a single Mono if that's possible/optimal. I also appreciate I've truly butchered the example and used .block as a placeholder to illustrate what I'm trying to achieve.
P.S this is my first post, so any tips on how to express my problem clearer would be useful.
Here's how I would do this in Java (Using Double instead of Decimal128):
public Mono<ChangeBalanceResponse> increaseBalance(Mono<ChangeBalanceRequestResource> changeRequest) {
Mono<Double> changeAmount = changeRequest.map(a -> a.transactionAmount());
Mono<User> user = changeRequest.map(a -> a.username()).flatMap(RxUserRepository::findByUsername);
return Mono.zip(changeAmount,user).flatMap(t2 -> {
Double changeAmount = t2.getT1();
User user = t2.getT2();
//assumes User is chained
return rxUserRepository.save(user.balance(sumBalance(changeAmount,user.balance())));
}).map(res -> new ChangeBalanceResponse("success",res.newBalance()))
}

Combination of two Mono with condition

I want to combine result form two Mono based on some condition. Both Mono are results of WebClient calls:
The first one is a single call expecting fast response.
The second one is a combination of several calls with a slow response.
The idea to "cancel" the second Mono if result from the first one satisfies some condition to save time and avoid unnecessary network calls. If the first's Mono result is not enough zip it with the second Mono.
A Kotlin code sample to explain my idea:
fun getResult(): Mono<Result> {
val trivialResultMono: Mono<Result> = webClient.getResult()
val nonTrivialResultMono: Mono<Result> = webClient
.getResult()
.flatMap { webClient.getResult1(it) }
.flatMap { webClient.getResult2(it) }
.flatMap { webClient.getResult2(it) }
//here I need to check if trivial result satisfies some condition,
//for example trivialResult.size > 5 if it's true I just return
//trivialResultMono from getResult() function,
//it it's false something like this:
return Mono.zip(trivialResultMono, nonTrivialResultMono) { trivialResult, nonTrivialResult ->
trivialResult + nonTrivialResult
}
}
UPDATE:
To be more clear let's say that trivialResult comes in 1 second, nonTrivialResult in 2 seconds. I want to get my final result in 1 second in case of trivialResult.size > 5 and in 2 seconds otherwise.
Using just Mono.zip(trivialResultMono, nonTrivialResultMono) I will always get my final result in 2 seconds.
Using filter + switchIfEmpty it will take 1 second if trivialResult.size > 5 and 3 seconds otherwise. Please correct me if I wrong.
You could filter your trivialResultMono and apply switchIfEmpty operator
return trivialResultMono
.filter(trivialResult -> trivialResult.size > 5)
.switchIfEmpty(Mono.zip(...))
Update for merge approach:
Mono<Result> zipResultMono = Mono.zip...
return Flux.merge(
trivialResultMono.map(trivialResult -> Tuples.of(1, trivialResult)),
zipResultMono.map(zipResult -> Tuples.of(2, zipResult)))
.filter(tuple ->
(tuple.getT1().equals(1) && tuple.getT2().size > 5) ||
tuple.getT1().equals(2))
.next()
.map(Tuple2::getT2);
You could skip converting to the Tuple2 if zipResult always has size more then 5
You can achieve this with flatMap and map:
trivial.flatMap(trivialResult -> {
if (trivialResult.size > 5) {
return Mono.just(trivialResult);
} else {
return nonTrivial.map(nonTrivialResult -> trivialResult + nonTrivialResult);
}
});

How can you create a timer that works on a List in Rx?

I want to look for an entire list of items to be found before I complete and if that entire list isn't found, then an exception (a Timeout or custom one) is to be thrown. Like the built in Observable.timer() but instead of the test passing once the first item is emitted, I want it to require all of the items in a list to be found.
Here is an example. Let's say I have some test function that emits Observable<FoundNumber>. It looks like this:
var emittedList: List<String?> = listOf(null, "202", "302", "400")
data class FoundNumber(val numberId: String?)
fun scanNumbers(): Observable<FoundNumber> = Observable
.intervalRange(0,
emittedList.size.toLong(),
0,
1,
TimeUnit.SECONDS).map { index ->
FoundNumber(emittedList[index.toInt()]) }
That function will then be called to get numbers that will be compared to a list of expected numbers. It doesn't matter if there are additional numbers coming from scanForNumbers that aren't in the "target" list. They will just be ignored. Something like this:
val expectedNumbers = listOf("202", "302","999")
scanForNumbers(expectedNumbers)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe { value -> Log.d(TAG, "Was returned a $value") }
So, the expected numbers (202, 302, and 999) don't exactly match with the numbers that will be emitted (202, 302, and 400). So, a timeout SHOULD occur, but with the built in version of Observable.timer(), it will not time out since at least one item was observed.
Here is kind of what I'd like to have. Anyone know how to code this up in RxJava/RxKotlin?
fun scanForNumbers(targets: List<String>): Observable<FoundNumber> {
val accumulator: Pair<Set<Any>, FoundNumber?> = targets.toSet() to null
return scanNumbers()
.SPECIAL_TIMEOUT_FOR_LIST(5, TimeUnit.SECONDS, List)
.scan(accumulator) { acc, next ->
val (set, previous) = acc
val stringSet:MutableSet<String> = hashSetOf()
set.forEach { stringSet.add(it.toString()) }
val item = if (next.numberId in stringSet) {
next
} else null
(set - next) to item // return set and nullable item
}
.filter { Log.d(TAG, "Filtering on ${it.second}")
it.second != null } // item not null
.take(targets.size.toLong()) // limit to the number of items
.map { it.second } // unwrap the item from the pair
.map { FoundController(it.numberId) } // wrap in your class
}
How do you code, hopefully using RxJava/Kotlin, a means to timeout on a list as mentioned?
I think I get it now, you want the timeout to begin counting from the moment you subscribe, not after you observe items.
If this is what you need, then the takeUntil operator could help you:
return scanNumbers()
.takeUntil(Observable.timer(5, TimeUnit.SECONDS))
.scan(accumulator) { acc, next -> ...
In this case, the timer will begin counting as soon as you subscribe. If the main observable completes before then great, if not, then the timer will complete the main observable anyways.
But takeUntil by itself will not throw an error, it will just complete. If you need it to end with an error, then you could use the following combination:
return scanNumbers()
.takeUntil(
Observable
.error<Void>(new TimeoutError("timeout!"))
.delay(5, TimeUnit.SECONDS, true))
.scan(accumulator) { acc, next -> ...

Search data in Observable

I'd like to have a search input, that display the result on keypress.
At the moment, this is what I have :
mylist: Observable<MyData[]>;
term = new FormControl();
ngOnInit() {
this.mylist = this.term.valueChanges
.debounceTime(400)
.distinctUntilChanged()
.switchMap(term => this.searchData(term));
}
searchData(valueToSearch:string){
if(valueToSearch == ''){
this.channels = MyData.find();
}
return MyData.find({'title':new RegExp(valueToSearch)});
}
It works quite well, but I have trouble to initialize "mylist", and I think my method isn't performant at all.
Basically, I want when my component is initialize, that:
this.mylist = MyData.find();
And on keypress, I want my search to be done on this.mylist, to avoid doing too much request.
Is it possible ?
I hope I'm clear.
Thanks by advance guys.
You must subscribe to the mapped data. Modify to the below code
this.term.valueChanges
.debounceTime(400)
.distinctUntilChanged()
.switchMap(term => this.searchData(term))
.subscribe((result) => {
this.mylist = result
});;
#Julia is correct , modify your searchData() function as below
searchData(valueToSearch:string):Observable<any> {
if(valueToSearch == ''){
this.channels = MyData.find();
}
return <Observable<any>>MyData.find({'title':new RegExp(valueToSearch)});
}