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

feat: add NumberSignal basic support #2502

Merged
merged 49 commits into from
Jul 26, 2024
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
3a11446
feat: add full stack NumberSignal
taefi Jun 3, 2024
9b9fdf8
fix type.ts
taefi Jun 3, 2024
cd5a67b
clean up
taefi Jun 3, 2024
cb49a6c
Revert change to package-lock.json
taefi Jun 5, 2024
4b90caf
refactor NumberSignal not to backed by EventQueue
taefi Jun 5, 2024
92cda91
Add javadoc and format
taefi Jun 5, 2024
eaf23a9
Add missing javadoc
taefi Jun 5, 2024
7f2d0d0
fix initial value and some refactoring
taefi Jun 6, 2024
82f73cf
simplify EventChannel internal and external signals
taefi Jun 7, 2024
cd0971a
simplify EventChannel
taefi Jun 7, 2024
c8c67fe
remove fluxConnectionActive signal and always connect
taefi Jun 7, 2024
6933668
remove dependency of NumberSignal on EventChannel
taefi Jun 7, 2024
4a6f1e0
register signal when subscribing to shared signal instance
taefi Jun 10, 2024
f8fb27d
add SignalHandler.ts so no generation is needed
taefi Jun 10, 2024
2e6b3c4
Merge branch 'main' into taefi/add-number-signal-basics
taefi Jun 10, 2024
4c337a0
fix import
taefi Jun 10, 2024
d5b4c73
reference preact/signals from core.ts instead of index.ts
taefi Jun 11, 2024
d9d316f
reference correct hilla-frontend version
taefi Jun 11, 2024
04b8596
make sure that signalsHandler endpoint is always registered
taefi Jun 11, 2024
86206b8
add javadoc
taefi Jun 11, 2024
fe7e549
add tests for NumberSignal
taefi Jun 11, 2024
fb07af3
add tests for SignalHandler
taefi Jun 11, 2024
7b6c619
fix some linting errors
taefi Jun 13, 2024
4ccdc81
add tests for SignalsRegistry
taefi Jun 13, 2024
283b7ec
fix some linting errors
taefi Jun 13, 2024
df1ee1d
check undefined to not mask zero values as false
taefi Jun 13, 2024
119b7e8
Merge branch 'main' into taefi/add-number-signal-basics
taefi Jul 18, 2024
ddc527b
refactor duplicate code
taefi Jul 18, 2024
ce42feb
define explicit types for signal events
taefi Jul 18, 2024
ad636a0
simplify full-stack signals and remove extension hacks
taefi Jul 19, 2024
d24e231
upgrade hilla-frontend dependency to latest alpha
taefi Jul 19, 2024
4368363
fix JSDoc default value notation
taefi Jul 19, 2024
153ed43
Apply suggestions from code review
taefi Jul 22, 2024
8e7d236
simplify generics definition and fix private field usages
taefi Jul 22, 2024
d08b4ee
apply suggestions from code review
taefi Jul 22, 2024
97e64ce
add test for SignalsHandler.ts
taefi Jul 22, 2024
520795c
Fix constructor access modifier
taefi Jul 22, 2024
32d566e
Add tests for NumberSignal
taefi Jul 22, 2024
e613540
Add tests for NumberSignalChannel
taefi Jul 23, 2024
d57b73c
Add tests for NumberSignalChannel
taefi Jul 23, 2024
63bb417
refactor message passing from string to json
taefi Jul 24, 2024
91663c6
add tests for StateEvent
taefi Jul 24, 2024
a93b080
Merge branch 'main' into taefi/add-number-signal-basics
taefi Jul 24, 2024
b602c0e
override equals and hashcode for NumberSignal and StateEvent
taefi Jul 25, 2024
e4dd869
test: fix event type
taefi Jul 26, 2024
dc2b2ea
Merge branch 'main' into taefi/add-number-signal-basics
taefi Jul 26, 2024
c7d00ed
test: change karma server port to avoid conflicts
taefi Jul 26, 2024
437425f
apply suggestions from code review
taefi Jul 26, 2024
fd70ae0
chore: fix prettier error in autogrid-columns.tsx
taefi Jul 26, 2024
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
3,152 changes: 777 additions & 2,375 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions packages/java/endpoint/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,11 @@
<artifactId>hamcrest-all</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.vaadin</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ public class EndpointController {
*/
public static final String ENDPOINT_MAPPER_FACTORY_BEAN_QUALIFIER = "endpointMapperFactory";

private static final String SIGNALS_HANDLER_BEAN_NAME = "signalsHandler";

private final ApplicationContext context;

EndpointRegistry endpointRegistry;
Expand Down Expand Up @@ -140,6 +142,15 @@ public void registerEndpoints(URL openApiResource) {
.registerEndpoint(endpointBean));
}

// make sure that signalsHandler endpoint is always registered:
if (endpointRegistry.get(SIGNALS_HANDLER_BEAN_NAME) == null) {
var signalHandlerBean = endpointBeans
.get(SIGNALS_HANDLER_BEAN_NAME);
if (signalHandlerBean != null) {
endpointRegistry.registerEndpoint(signalHandlerBean);
}
}

taefi marked this conversation as resolved.
Show resolved Hide resolved
if (!endpointRegistry.isEmpty()) {
HillaStats.reportHasEndpoint();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package com.vaadin.hilla.signals;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.DoubleNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

import com.fasterxml.jackson.databind.node.TextNode;
import com.vaadin.hilla.signals.core.StateEvent;
import jakarta.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Sinks;

import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.locks.ReentrantLock;

/**
* A signal that holds a number value.
*/
public class NumberSignal {

private static final Logger LOGGER = LoggerFactory
.getLogger(NumberSignal.class);

private final ReentrantLock lock = new ReentrantLock();

private final ObjectMapper mapper;

private final UUID id = UUID.randomUUID();

private Double value;

private final Set<Sinks.Many<ObjectNode>> subscribers = new HashSet<>();

/**
* Creates a new NumberSignal with the provided default value.
*
* @param defaultValue
* the default value
*/
public NumberSignal(@Nullable Double defaultValue) {
this.value = defaultValue;
this.mapper = new ObjectMapper();
}

/**
* Creates a new NumberSignal with the default value of 0.
*/
public NumberSignal() {
this(0.0);
}

/**
* Subscribes to the signal.
*
* @return a Flux of JSON events
*/
public Flux<ObjectNode> subscribe() {
Sinks.Many<ObjectNode> sink = Sinks.many().multicast()
taefi marked this conversation as resolved.
Show resolved Hide resolved
.onBackpressureBuffer();

return sink.asFlux().doOnSubscribe(ignore -> {
LOGGER.debug("New Flux subscription...");
lock.lock();
try {
var currentValue = createSnapshot();
sink.tryEmitNext(currentValue);
subscribers.add(sink);
} finally {
lock.unlock();
}
}).doFinally(ignore -> {
lock.lock();
try {
LOGGER.debug("Unsubscribing from NumberSignal...");
subscribers.remove(sink);
} finally {
lock.unlock();
}
});
}

/**
* Submits an event to the signal and notifies subscribers about the change
* of the signal value.
*
* @param event
* the event to submit
*/
public void submit(ObjectNode event) {
lock.lock();
try {
processEvent(event);
// Notify subscribers
subscribers.removeIf(sink -> {
var updatedValue = createSnapshot();
boolean failure = sink.tryEmitNext(updatedValue).isFailure();
if (failure) {
LOGGER.debug("Failed push");
}
return failure;
});
} finally {
lock.unlock();
}
}

/**
* Returns the signal UUID.
*
* @return the id
*/
public UUID getId() {
return this.id;
}

/**
* Returns the signal's current value.
*
* @return the value
*/
@Nullable
public Double getValue() {
return this.value;
}

private ObjectNode createSnapshot() {
var snapshot = new StateEvent<>(this.id, StateEvent.EventType.SNAPSHOT,
this.value);
return snapshot.toJson();
}

private void processEvent(ObjectNode event) {
try {
var stateEvent = new StateEvent<Double>(event);
if (isSetEvent(stateEvent)) {
this.value = stateEvent.getValue();
} else {
throw new UnsupportedOperationException(
"Unsupported event: " + event);
}
} catch (StateEvent.InvalidEventTypeException e) {
throw new UnsupportedOperationException(
"Unsupported JSON: " + event, e);
}
}

private boolean isSetEvent(StateEvent<?> event) {
return StateEvent.EventType.SET.equals(event.getEventType());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.vaadin.hilla.signals.config;

import com.vaadin.hilla.ConditionalOnFeatureFlag;
import com.vaadin.hilla.EndpointInvoker;
import com.vaadin.hilla.signals.handler.SignalsHandler;
import com.vaadin.hilla.signals.core.SignalsRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* Spring beans configuration for signals.
*/
@Configuration
public class SignalsConfiguration {

private SignalsRegistry signalsRegistry;
private SignalsHandler signalsHandler;
private final EndpointInvoker endpointInvoker;

public SignalsConfiguration(EndpointInvoker endpointInvoker) {
this.endpointInvoker = endpointInvoker;
}

/**
* Initializes the SignalsRegistry bean when the fullstackSignals feature
* flag is enabled.
*
* @return SignalsRegistry bean instance
*/
@ConditionalOnFeatureFlag("fullstackSignals")
@Bean
public SignalsRegistry signalsRegistry() {
if (signalsRegistry == null) {
signalsRegistry = new SignalsRegistry();
}
return signalsRegistry;
}

/**
* Initializes the SignalsHandler endpoint when the fullstackSignals feature
* flag is enabled.
*
* @return SignalsHandler endpoint instance
*/
@ConditionalOnFeatureFlag("fullstackSignals")
@Bean
public SignalsHandler signalsHandler() {
if (signalsHandler == null) {
signalsHandler = new SignalsHandler(signalsRegistry(),
endpointInvoker);
}
return signalsHandler;
}
}
Loading
Loading