Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement debounce configuration #70

Merged
merged 1 commit into from
Sep 6, 2024
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.inngest.springbootdemo.testfunctions;

import com.inngest.FunctionContext;
import com.inngest.InngestFunction;
import com.inngest.InngestFunctionConfigBuilder;
import com.inngest.Step;
import org.jetbrains.annotations.NotNull;

import java.time.Duration;

public class DebouncedFunction extends InngestFunction {

@NotNull
@Override
public InngestFunctionConfigBuilder config(InngestFunctionConfigBuilder builder) {
return builder
.id("DebouncedFunction")
.name("Debounced Function")
.triggerEvent("test/debounced_2_second")
.debounce(Duration.ofSeconds(2));
}

private static int answer = 42;

@Override
public Integer execute(FunctionContext ctx, Step step) {
return step.run("result", () -> answer++, Integer.class);
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.inngest.springbootdemo;

import com.inngest.Inngest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
import org.springframework.beans.factory.annotation.Autowired;

import static org.junit.jupiter.api.Assertions.assertEquals;

@IntegrationTest
@Execution(ExecutionMode.CONCURRENT)
class DebouncedFunctionIntegrationTest {
@Autowired
private DevServerComponent devServer;

@Autowired
private Inngest client;

@Test
void testDebouncedFunctionExecutesTrailingEdge() throws Exception {
String firstEvent = InngestFunctionTestHelpers.sendEvent(client, "test/debounced_2_second").getIds()[0];
String secondEvent = InngestFunctionTestHelpers.sendEvent(client, "test/debounced_2_second").getIds()[0];

Thread.sleep(4000);

// With debouncing, the first event is skipped in favor of the second one because they were both sent within
// the debounce period of 2 seconds
assertEquals(0, devServer.runsByEvent(firstEvent).data.length);
RunEntry<Object> secondRun = devServer.runsByEvent(secondEvent).first();

assertEquals("Completed", secondRun.getStatus());
assertEquals(42, secondRun.getOutput());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ protected HashMap<String, InngestFunction> functions() {
addInngestFunction(functions, new InvokeFailureFunction());
addInngestFunction(functions, new TryCatchRunFunction());
addInngestFunction(functions, new ThrottledFunction());
addInngestFunction(functions, new DebouncedFunction());

return functions;
}
Expand Down
2 changes: 2 additions & 0 deletions inngest/src/main/kotlin/com/inngest/Function.kt
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ internal class InternalFunctionConfig
@Json(serializeNull = false)
val throttle: Throttle? = null,
@Json(serializeNull = false)
val debounce: Debounce? = null,
@Json(serializeNull = false)
val batchEvents: BatchEvents? = null,
val steps: Map<String, StepConfig>,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class InngestFunctionConfigBuilder {
private var concurrency: MutableList<Concurrency>? = null
private var retries = 3
private var throttle: Throttle? = null
private var debounce: Debounce? = null
private var batchEvents: BatchEvents? = null

/**
Expand Down Expand Up @@ -147,6 +148,30 @@ class InngestFunctionConfigBuilder {
burst: Int? = null,
): InngestFunctionConfigBuilder = apply { this.throttle = Throttle(limit, period, key, burst) }

/**
* Debounce delays functions for the `period` specified. If an event is sent,
* the function will not run until at least `period` has elapsed.
*
* If any new events are received that match the same debounce `key`, the
* function is rescheduled for another `period` delay, and the triggering
* event is replaced with the latest event received.
*
* See the [Debounce documentation](https://innge.st/debounce) for more
* information.
*
* @param period The period of time to delay after receiving the last trigger to run the function.
* @param key An optional key to use for debouncing.
* @param timeout The maximum time that a debounce can be extended before running.
* If events are continually received within the given period, a function
* will always run after the given timeout period.
*/
@JvmOverloads
fun debounce(
period: Duration,
key: String? = null,
timeout: Duration? = null,
): InngestFunctionConfigBuilder = apply { this.debounce = Debounce(period, key, timeout) }

private fun buildSteps(serveUrl: String): Map<String, StepConfig> {
val scheme = serveUrl.split("://")[0]
return mapOf(
Expand Down Expand Up @@ -179,6 +204,7 @@ class InngestFunctionConfigBuilder {
triggers,
concurrency,
throttle,
debounce,
batchEvents,
steps = buildSteps(serverUrl),
)
Expand Down Expand Up @@ -248,6 +274,18 @@ internal data class Throttle
val burst: Int? = null,
)

internal data class Debounce
@JvmOverloads
constructor(
@KlaxonDuration
val period: Duration,
@Json(serializeNull = false)
val key: String? = null,
@Json(serializeNull = false)
@KlaxonDuration
val timeout: Duration? = null,
)

internal data class BatchEvents
@JvmOverloads
constructor(
Expand Down
Loading