Skip to content

Commit

Permalink
Improvements to variables and conditional rendering of activity settings
Browse files Browse the repository at this point in the history
  • Loading branch information
tobias-weber committed Jan 10, 2025
1 parent 7c0f4f1 commit 6ec0ba5
Show file tree
Hide file tree
Showing 18 changed files with 153 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ public ActivityModel toModel( boolean includeState ) {
List<TypePreviewModel> inTypeModels = inTypePreview.stream().map(
inType -> inType.map( TypePreviewModel::of ).orElse( null ) ).toList();
String invalidReason = invalidStateReason == null ? null : invalidStateReason.toString();
return new ActivityModel( type, id, serializableSettings, config, rendering, this.state, inTypeModels, invalidReason );
return new ActivityModel( type, id, serializableSettings, config, rendering, this.state, inTypeModels, invalidReason, variables.getVariables() );

} else {
return new ActivityModel( type, id, serializableSettings, config, rendering );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ def my_function():

@IntSetting(key = "I1", displayName = "FIRST", defaultValue = 2, shortDescription = "This setting doesn't do anything.")
@StringSetting(key = "S1", displayName = "SECOND", shortDescription = "This setting doesn't do anything.")
@IntSetting(key = "X1", displayName = "X1", shortDescription = "Depends on I1 being 42 or 420", subPointer = "I1", subValues = { "42", "420" })
@StringSetting(key = "X2", displayName = "X2", shortDescription = "Depends on X1 being 3", subPointer = "I1", subValues = { "3" })
@StringSetting(key = "X3", displayName = "X3", shortDescription = "Depends on I1/doesNotExist being 7", subPointer = "I1/doesNotExist", subValues = { "7" })

@Group(key = "groupA", displayName = "Group A",
subgroups = { @Subgroup(key = "a", displayName = "Sub1") }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.polypheny.db.workflow.dag.settings.SettingDef;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
Expand Down Expand Up @@ -53,7 +54,15 @@

int position() default 100;

String subOf() default "";
/**
* See {@link SettingDef#getSubPointer()}
*/
String subPointer() default "";

/**
* See {@link SettingDef#getSubValues()}
*/
String[] subValues() default {};

// String-specific settings
boolean defaultValue() default false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.polypheny.db.workflow.dag.settings.SettingDef;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
Expand Down Expand Up @@ -48,7 +49,15 @@

int position() default 100;

String subOf() default "";
/**
* See {@link SettingDef#getSubPointer()}
*/
String subPointer() default "";

/**
* See {@link SettingDef#getSubValues()}
*/
String[] subValues() default {};

// Setting-specific properties

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.polypheny.db.catalog.logistic.DataModel;
import org.polypheny.db.workflow.dag.settings.SettingDef;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
Expand Down Expand Up @@ -54,7 +55,15 @@

int position() default 100;

String subOf() default "";
/**
* See {@link SettingDef#getSubPointer()}
*/
String subPointer() default "";

/**
* See {@link SettingDef#getSubValues()}
*/
String[] subValues() default {};

// Setting-specifics
String defaultNamespace() default "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.polypheny.db.workflow.dag.settings.SettingDef;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
Expand Down Expand Up @@ -48,7 +49,15 @@

int position() default 100;

String subOf() default "";
/**
* See {@link SettingDef#getSubPointer()}
*/
String subPointer() default "";

/**
* See {@link SettingDef#getSubValues()}
*/
String[] subValues() default {};

// Setting-specific properties

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.polypheny.db.workflow.dag.settings.SettingDef;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
Expand Down Expand Up @@ -53,7 +54,15 @@

int position() default 100; // manually impose order within subGroup (lower pos => further to the top)

String subOf() default ""; // determine the visibility of this setting, possibly using {@code SettingDef.SUB_SEP}.
/**
* See {@link SettingDef#getSubPointer()}
*/
String subPointer() default "";

/**
* See {@link SettingDef#getSubValues()}
*/
String[] subValues() default {};

// String-specific settings
String defaultValue() default "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ public class BoolSettingDef extends SettingDef {


public BoolSettingDef( BoolSetting a ) {
super( SettingType.BOOLEAN, a.key(), a.displayName(), a.shortDescription(), a.longDescription(), getDefaultValue( a.defaultValue() ), a.group(), a.subGroup(), a.position(), a.subOf() );
super( SettingType.BOOLEAN, a.key(), a.displayName(), a.shortDescription(), a.longDescription(), getDefaultValue( a.defaultValue() ),
a.group(), a.subGroup(), a.position(), a.subPointer(), a.subValues() );
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ public class DoubleSettingDef extends SettingDef {


public DoubleSettingDef( DoubleSetting a ) {
super( SettingType.DOUBLE, a.key(), a.displayName(), a.shortDescription(), a.longDescription(), getDefaultValue( a.defaultValue() ), a.group(), a.subGroup(), a.position(), a.subOf() );
super( SettingType.DOUBLE, a.key(), a.displayName(), a.shortDescription(), a.longDescription(), getDefaultValue( a.defaultValue() ),
a.group(), a.subGroup(), a.position(), a.subPointer(), a.subValues() );
minValue = a.min();
maxValue = a.max();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ public class EntitySettingDef extends SettingDef {


public EntitySettingDef( EntitySetting a ) {
super( SettingType.ENTITY, a.key(), a.displayName(), a.shortDescription(), a.longDescription(), getDefaultValue( a.defaultNamespace(), a.defaultName() ), a.group(), a.subGroup(), a.position(), a.subOf() );
super( SettingType.ENTITY, a.key(), a.displayName(), a.shortDescription(), a.longDescription(), getDefaultValue( a.defaultNamespace(), a.defaultName() ),
a.group(), a.subGroup(), a.position(), a.subPointer(), a.subValues() );
this.dataModel = a.dataModel();
this.mustExist = a.mustExist();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ public class IntSettingDef extends SettingDef {


public IntSettingDef( IntSetting a ) {
super( SettingType.INT, a.key(), a.displayName(), a.shortDescription(), a.longDescription(), getDefaultValue( a.defaultValue(), a.isList() ), a.group(), a.subGroup(), a.position(), a.subOf() );
super( SettingType.INT, a.key(), a.displayName(), a.shortDescription(), a.longDescription(), getDefaultValue( a.defaultValue(), a.isList() ),
a.group(), a.subGroup(), a.position(), a.subPointer(), a.subValues() );
this.isList = a.isList();
this.minValue = a.min();
this.maxValue = a.max();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.polypheny.db.workflow.dag.settings;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.lang.annotation.Annotation;
Expand All @@ -31,6 +32,7 @@
import java.util.stream.Collectors;
import lombok.Getter;
import lombok.Value;
import org.polypheny.db.catalog.exceptions.GenericRuntimeException;
import org.polypheny.db.util.Wrapper;
import org.polypheny.db.workflow.dag.activities.ActivityException.InvalidSettingException;
import org.polypheny.db.workflow.dag.annotations.ActivityDefinition;
Expand All @@ -43,7 +45,7 @@
@Getter
public abstract class SettingDef {

public static String SUB_SEP = ">"; // this separator is used to specify dependencies on values of other settings. For example "modeSelector>mode1" to specify that this setting is only active if modeSelector is equal to mode1
private static final ObjectMapper MAPPER = new ObjectMapper();

private final SettingType type;
private final String key;
Expand All @@ -57,21 +59,55 @@ public abstract class SettingDef {
private final String group;
private final String subgroup;
private final int position;
private final String subOf;

/**
* The subPointer is used to specify conditional rendering of this setting in the UI based on the value
* of another setting. An empty pointer disables conditional rendering for this setting.
* <p>
* SubPointer is a JsonPointer (<a href="https://datatracker.ietf.org/doc/html/rfc6901">RFC6901</a>) to a setting value.
* Note that "/" and "~" need to be escaped as ~1 and ~0 respectively.
* The leading "/" is optional in the constructor, as the first referenced object must always correspond to a setting name.
* Examples of valid pointers:
* <ul>
* <li>{@code "mySetting"}</li>
* <li>{@code "/mySetting"}</li>
* <li>{@code "mySetting/names"}</li>
* <li>{@code "mySetting/names/0"}</li>
* <li>{@code "my~1escaped~0setting~1example"}</li>
* </ul>
*/
private final String subPointer;

public SettingDef( SettingType type, String key, String displayName, String shortDescription, String longDescription, SettingValue defaultValue, String group, String subgroup, int position, String subOf ) {
assert !key.contains( SettingDef.SUB_SEP ) : "Setting key must not contain separator symbol '" + SUB_SEP + "': " + key;
/**
* SubValues is an array of values to compare the object at {@link #getSubPointer()} to.
* The setting is only rendered if at least one object is equal to a value of this array.
* The SettingDef constructor expects values specified as json. This implies that strings must be quoted!
*/
private final List<JsonNode> subValues;


public SettingDef(
SettingType type, String key, String displayName,
String shortDescription, String longDescription,
SettingValue defaultValue, String group, String subgroup,
int position, String subPointer, String[] subValues ) {
this.type = type;
this.key = key;
this.displayName = displayName;
this.displayName = displayName.isBlank() ? key : displayName;
this.shortDescription = shortDescription;
this.longDescription = longDescription.isEmpty() ? shortDescription : longDescription;
this.defaultValue = defaultValue;
this.group = group;
this.subgroup = subgroup;
this.position = position;
this.subOf = subOf;
this.subPointer = (subPointer.isEmpty() || subPointer.startsWith( "/" )) ? subPointer : "/" + subPointer;
this.subValues = Arrays.stream( subValues ).map( v -> {
try {
return MAPPER.readTree( v );
} catch ( JsonProcessingException e ) {
throw new GenericRuntimeException( "Invalid subValue for setting " + key + ": " + v, e );
}
} ).toList();
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ public class StringSettingDef extends SettingDef {


public StringSettingDef( StringSetting a ) {
super( SettingType.STRING, a.key(), a.displayName(), a.shortDescription(), a.longDescription(), getDefaultValue( a.defaultValue(), a.isList() ), a.group(), a.subGroup(), a.position(), a.subOf() );
super( SettingType.STRING, a.key(), a.displayName(), a.shortDescription(), a.longDescription(), getDefaultValue( a.defaultValue(), a.isList() ),
a.group(), a.subGroup(), a.position(), a.subPointer(), a.subValues() );
this.isList = a.isList();
this.minLength = a.minLength();
this.maxLength = a.maxLength();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@
public interface ReadableVariableStore {

/**
* A variable reference is given as an object with a VARIABLE_REF_FIELD key and the reference as a string value.
* A reference corresponds to a JsonPointer (https://datatracker.ietf.org/doc/html/rfc6901).
* A variable reference is given as an object with a {@link #VARIABLE_REF_FIELD} key and the reference as a string value.
* Additionally, a default value can be specified with {@link #VARIABLE_DEFAULT_FIELD}.
* <p>
* The reference corresponds to a JsonPointer (<a href="https://datatracker.ietf.org/doc/html/rfc6901">RFC6901</a>).
* Note that "/" and "~" in variable names need to be escaped as ~1 and ~0 respectively.
* The leading "/" is optional, as the first referenced object must always correspond to a variable name.
* Examples of valid references:
Expand All @@ -39,6 +41,7 @@ public interface ReadableVariableStore {
* </ul>
*/
String VARIABLE_REF_FIELD = "$ref";
String VARIABLE_DEFAULT_FIELD = "$default";

boolean contains( String key );

Expand All @@ -58,24 +61,27 @@ public interface ReadableVariableStore {
* Variable references are indicated by objects that contain a single field with name equal to the value of {@code VARIABLE_REF_FIELD}.
*
* @param node The node to be resolved recursively
* @param useDefaultIfMissing whether the default value specified by the variable reference is used in case the variable cannot be resolved.
* @return a JsonNode with any variable references replaced by their value stored in this store.
* @throws IllegalArgumentException if a variable reference cannot be resolved in the variableMap
* @throws IllegalArgumentException if a variable reference cannot be resolved in the variableMap and no default value can be used (useDefaultIfMissing is false or no default exists).
*/
JsonNode resolveVariables( JsonNode node );
JsonNode resolveVariables( JsonNode node, boolean useDefaultIfMissing );


/**
* Resolves variables in the given map by recursively replacing any variable references with their resolved values.
* Resolves variables in the given map by recursively replacing any variable references with their resolved values
* or the default value specified by the reference, if the variable cannot be found.
*
* @param nodes The nodes to be resolved
* @return an immutable map with JsonNodes that have any variable references replaced by their value stored in this store.
* @throws IllegalArgumentException if any {@link JsonNode} contains an unresolved variable reference.
* @throws IllegalArgumentException if any {@link JsonNode} contains an unresolved variable reference with no default value.
*/
Map<String, JsonNode> resolveVariables( Map<String, JsonNode> nodes );

/**
* Resolves variables in the given map by recursively replacing any variable references with their resolved values.
* If a variable cannot be resolved, inserts {@link Optional#empty()} for that variable.
* Default values are ignored.
*
* @param nodes The nodes to be resolved
* @return an immutable map with resolved variables wrapped in {@link Optional}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,10 @@ private void failIfReservedKey( String key ) {


// TODO: make sure to not change the existing JsonNode
public JsonNode resolveVariables( JsonNode node ) {
public JsonNode resolveVariables( JsonNode node, boolean useDefaultIfMissing ) {
if ( node.isObject() ) {
ObjectNode objectNode = (ObjectNode) node;
if ( objectNode.size() == 1 && objectNode.has( VARIABLE_REF_FIELD ) ) {
if ( objectNode.size() <= 2 && objectNode.has( VARIABLE_REF_FIELD ) ) {
String refString = objectNode.get( VARIABLE_REF_FIELD ).asText();
if ( refString.startsWith( "/" ) ) {
refString = refString.substring( 1 );
Expand All @@ -164,22 +164,26 @@ public JsonNode resolveVariables( JsonNode node ) {

// Replace the entire object with the value from the map, if it exists
if ( replacement == null ) {
throw new IllegalArgumentException( "Cannot resolve variable with name: " + variableRef );
if ( useDefaultIfMissing && objectNode.has( VARIABLE_DEFAULT_FIELD ) ) {
replacement = objectNode.get( VARIABLE_DEFAULT_FIELD );
} else {
throw new IllegalArgumentException( "Cannot resolve variable with name: " + variableRef );
}
}
return replacement;
} else {
// Recursively process child fields
Iterator<Entry<String, JsonNode>> fields = objectNode.fields();
while ( fields.hasNext() ) {
Map.Entry<String, JsonNode> field = fields.next();
objectNode.set( field.getKey(), resolveVariables( field.getValue() ) );
objectNode.set( field.getKey(), resolveVariables( field.getValue(), useDefaultIfMissing ) );
}
return objectNode;
}
} else if ( node.isArray() ) {
// Recursively process child fields
for ( int i = 0; i < node.size(); i++ ) {
((ArrayNode) node).set( i, resolveVariables( node.get( i ) ) );
((ArrayNode) node).set( i, resolveVariables( node.get( i ), useDefaultIfMissing ) );
}
return node;
} else {
Expand All @@ -192,7 +196,7 @@ public JsonNode resolveVariables( JsonNode node ) {
public Map<String, JsonNode> resolveVariables( Map<String, JsonNode> nodes ) {
Map<String, JsonNode> resolved = new HashMap<>();
for ( Entry<String, JsonNode> entry : nodes.entrySet() ) {
resolved.put( entry.getKey(), resolveVariables( entry.getValue() ) );
resolved.put( entry.getKey(), resolveVariables( entry.getValue(), true ) );
}
return Collections.unmodifiableMap( resolved );
}
Expand All @@ -203,7 +207,7 @@ public Map<String, Optional<JsonNode>> resolveAvailableVariables( Map<String, Js
Map<String, Optional<JsonNode>> resolved = new HashMap<>();
for ( Map.Entry<String, JsonNode> entry : nodes.entrySet() ) {
try {
resolved.put( entry.getKey(), Optional.of( resolveVariables( entry.getValue() ) ) );
resolved.put( entry.getKey(), Optional.of( resolveVariables( entry.getValue(), false ) ) );
} catch ( IllegalArgumentException e ) {
resolved.put( entry.getKey(), Optional.empty() );
}
Expand Down
Loading

0 comments on commit 6ec0ba5

Please sign in to comment.