Skip to content
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
17 changes: 17 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,18 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-picocli</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-openapi</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-math3</artifactId>
Expand Down Expand Up @@ -213,6 +225,11 @@
<artifactId>quarkus-junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
Comment thread
stalep marked this conversation as resolved.
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
Expand Down
6 changes: 5 additions & 1 deletion src/main/java/io/hyperfoil/tools/h5m/api/Folder.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package io.hyperfoil.tools.h5m.api;

import jakarta.validation.constraints.NotEmpty;
import org.eclipse.microprofile.openapi.annotations.media.Schema;

public record Folder(Long id, @NotEmpty String name) {
@Schema(description = "A folder containing uploaded data")
public record Folder(
@Schema(description = "Unique folder ID") Long id,
@Schema(description = "Folder name") @NotEmpty String name) {
}
12 changes: 11 additions & 1 deletion src/main/java/io/hyperfoil/tools/h5m/api/Node.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,15 @@

import java.util.List;

public record Node(Long id, String name, String fqdn, NodeType type, NodeGroup group, String operation, List<Node> sources) {
import org.eclipse.microprofile.openapi.annotations.media.Schema;

@Schema(description = "A transformation node in the DAG pipeline")
public record Node(
@Schema(description = "Unique node ID") Long id,
@Schema(description = "Node name") String name,
@Schema(description = "Fully qualified domain name") String fqdn,
@Schema(description = "Node type") NodeType type,
@Schema(description = "Parent node group") NodeGroup group,
@Schema(description = "Node operation (jq filter, JS function, etc.)") String operation,
@Schema(description = "Source dependency nodes") List<Node> sources) {
}
9 changes: 8 additions & 1 deletion src/main/java/io/hyperfoil/tools/h5m/api/NodeGroup.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,12 @@

import java.util.List;

public record NodeGroup(Long id, String name, Node root, List<Node> sources) {
import org.eclipse.microprofile.openapi.annotations.media.Schema;

@Schema(description = "A group of nodes forming a transformation pipeline")
public record NodeGroup(
@Schema(description = "Unique group ID") Long id,
@Schema(description = "Group name") String name,
@Schema(description = "Root input node") Node root,
@Schema(description = "Top-level transformation nodes") List<Node> sources) {
}
3 changes: 3 additions & 0 deletions src/main/java/io/hyperfoil/tools/h5m/api/NodeType.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package io.hyperfoil.tools.h5m.api;

import org.eclipse.microprofile.openapi.annotations.media.Schema;

@Schema(description = "The type of transformation a node performs")
public enum NodeType {
FINGERPRINT("fp"),
FIXED_THRESHOLD("ft"),
Expand Down
10 changes: 7 additions & 3 deletions src/main/java/io/hyperfoil/tools/h5m/api/Value.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package io.hyperfoil.tools.h5m.api;

import com.fasterxml.jackson.databind.JsonNode;
import org.eclipse.microprofile.openapi.annotations.media.Schema;

import java.util.List;

public record Value(Long id, JsonNode data, Node node, Folder folder) {
@Schema(description = "A computed value produced by a node")
public record Value(
@Schema(description = "Unique value ID") Long id,
@Schema(description = "The JSON data payload") JsonNode data,
@Schema(description = "The node that produced this value") Node node,
@Schema(description = "The folder this value belongs to") Folder folder) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import io.hyperfoil.tools.h5m.api.Node;
import io.hyperfoil.tools.h5m.api.NodeType;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;

import java.util.List;

Expand All @@ -22,7 +20,7 @@ public interface NodeServiceInterface {
* @param operation The operation associated with the node.
* @return The ID of the created node.
*/
Long create(@NotEmpty String name, @NotNull Long groupId, @NotNull NodeType type, String operation);
Long create(String name, Long groupId, NodeType type, String operation);

/**
* Creates a new node with sources and configuration.
Expand All @@ -35,22 +33,14 @@ public interface NodeServiceInterface {
* @return The ID of the created node.
* @throws JsonProcessingException If there is an error processing the configuration JSON.
*/
Long create(@NotEmpty String name, @NotNull Long groupId, @NotNull NodeType type, List<Long> sources, Object configuration) throws JsonProcessingException;
Long createConfigured(String name, Long groupId, NodeType type, List<Long> sources, Object configuration) throws JsonProcessingException;

/**
* Deletes a node by its mode ID.
* Deletes a node by its ID.
*
* @param modeId The mode ID to delete.
* @param nodeId The ID of the node to delete.
*/
void delete(Long modeId);

/**
* Finds nodes by their fully qualified domain name (FQDN).
*
* @param name The FQDN of the node.
* @return A list of matching nodes.
*/
List<Node> findNodeByFqdn(String name);
void delete(Long nodeId);

/**
* Finds nodes by their fully qualified domain name (FQDN) within a specific group.
Expand All @@ -61,4 +51,13 @@ public interface NodeServiceInterface {
*/
List<Node> findNodeByFqdn(String name, Long groupId);

/**
* Finds nodes by their fully qualified domain name (FQDN).
* Used by CLI commands. Not exposed as a REST endpoint.
*
* @param fqdn The FQDN of the node.
* @return A list of matching nodes.
*/
List<Node> findNodeByFqdn(String fqdn);

}
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,8 @@ public Integer call() throws Exception {
}
}

Long fingerprintId = nodeService.create("_fp-" + name, foundGroup.id(), NodeType.FINGERPRINT, fingerprintNodes, null);
nodeService.create(name, foundGroup.id(), NodeType.FIXED_THRESHOLD, List.of(fingerprintId, groupByNode.id(), rangeNode.id()), new FixedThresholdConfig(min, max, minInclusive, maxInclusive, fingerprintFilter));
Long fingerprintId = nodeService.createConfigured("_fp-" + name, foundGroup.id(), NodeType.FINGERPRINT, fingerprintNodes, null);
nodeService.createConfigured(name, foundGroup.id(), NodeType.FIXED_THRESHOLD, List.of(fingerprintId, groupByNode.id(), rangeNode.id()), new FixedThresholdConfig(min, max, minInclusive, maxInclusive, fingerprintFilter));

return 0;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,9 @@ public Integer call() throws Exception {
}
}

Long fingerprintId = nodeService.create("_fp-" + name, foundGroup.id(), NodeType.FINGERPRINT, fingerprintNodes, null);
Long fingerprintId = nodeService.createConfigured("_fp-" + name, foundGroup.id(), NodeType.FINGERPRINT, fingerprintNodes, null);
List<Long> sources = domainNode == null ? List.of(fingerprintId, groupByNode.id(), rangeNode.id()) : List.of(fingerprintId, groupByNode.id(), rangeNode.id(), domainNode.id());
nodeService.create(name, foundGroup.id(), NodeType.RELATIVE_DIFFERENCE, sources, new RelativeDifferenceConfig(filter, threshold, window, minPrevious, fingerprintFilter));
nodeService.createConfigured(name, foundGroup.id(), NodeType.RELATIVE_DIFFERENCE, sources, new RelativeDifferenceConfig(filter, threshold, window, minPrevious, fingerprintFilter));

return 0;
}
Expand Down
75 changes: 75 additions & 0 deletions src/main/java/io/hyperfoil/tools/h5m/rest/FolderResource.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package io.hyperfoil.tools.h5m.rest;

import com.fasterxml.jackson.databind.JsonNode;
import io.hyperfoil.tools.h5m.api.Folder;
import io.hyperfoil.tools.h5m.api.svc.FolderServiceInterface;
import io.hyperfoil.tools.yaup.json.Json;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;

import java.util.Map;

@Path("/api/folder")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Folder", description = "Manage folders for uploaded data")
public class FolderResource {

@Inject
FolderServiceInterface folderService;

@GET
@Path("{name}")
@Operation(description = "Retrieve a folder by its name")
public Folder byName(@PathParam("name") String name) {
return folderService.byName(name);
}

@GET
@Operation(description = "Get the upload count for all folders")
public Map<String, Integer> getFolderUploadCount() {
return folderService.getFolderUploadCount();
}

@POST
@Path("{name}")
@Operation(description = "Create a new folder")
public long create(@PathParam("name") String name) {
return folderService.create(name);
}

@DELETE
@Path("{name}")
@Operation(description = "Delete a folder by its name")
public long delete(@PathParam("name") String name) {
return folderService.delete(name);
}
Comment thread
stalep marked this conversation as resolved.

@POST
@Path("{name}/upload")
@Operation(description = "Upload JSON data to a folder")
public void upload(
@PathParam("name") String name,
@QueryParam("path") @Parameter(description = "Path within the folder") String path,
JsonNode data) {
folderService.upload(name, path, data);
}

@POST
@Path("{name}/recalculate")
@Operation(description = "Recalculate all values in a folder")
public void recalculate(@PathParam("name") String name) {
folderService.recalculate(name);
}

@GET
@Path("{name}/structure")
@Operation(description = "Get the structural representation of a folder")
public Json structure(@PathParam("name") String name) {
return folderService.structure(name);
}
}
33 changes: 33 additions & 0 deletions src/main/java/io/hyperfoil/tools/h5m/rest/NodeGroupResource.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.hyperfoil.tools.h5m.rest;

import io.hyperfoil.tools.h5m.api.NodeGroup;
import io.hyperfoil.tools.h5m.api.svc.NodeGroupServiceInterface;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;

@Path("/api/group")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "NodeGroup", description = "Manage node groups (transformation pipelines)")
public class NodeGroupResource {

@Inject
NodeGroupServiceInterface nodeGroupService;

@GET
@Path("{name}")
@Operation(description = "Retrieve a node group by its name")
public NodeGroup byName(@PathParam("name") String groupName) {
return nodeGroupService.byName(groupName);
}

@DELETE
@Path("{id}")
@Operation(description = "Delete a node group by its ID")
public void delete(@PathParam("id") Long groupId) {
nodeGroupService.delete(groupId);
}
Comment thread
stalep marked this conversation as resolved.
}
70 changes: 70 additions & 0 deletions src/main/java/io/hyperfoil/tools/h5m/rest/NodeResource.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package io.hyperfoil.tools.h5m.rest;

import com.fasterxml.jackson.core.JsonProcessingException;
import io.hyperfoil.tools.h5m.api.Node;
import io.hyperfoil.tools.h5m.api.NodeType;
import io.hyperfoil.tools.h5m.api.svc.NodeServiceInterface;
import jakarta.inject.Inject;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;

import java.util.List;

@Path("/api/node")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "Node", description = "Manage transformation nodes in the DAG pipeline")
public class NodeResource {

@Inject
NodeServiceInterface nodeService;

@POST
@Operation(description = "Create a new node with an operation")
public Long create(
@QueryParam("name") @NotEmpty String name,
@QueryParam("groupId") @NotNull Long groupId,
@QueryParam("type") @NotNull NodeType type,
@QueryParam("operation") String operation) {
return nodeService.create(name, groupId, type, operation);
}

@POST
@Path("configured")
@Operation(description = "Create a new node with sources and configuration")
public Long createConfigured(
@QueryParam("name") @NotEmpty String name,
@QueryParam("groupId") @NotNull Long groupId,
@QueryParam("type") @NotNull NodeType type,
@QueryParam("sources") @NotNull @NotEmpty List<Long> sources,
Object configuration) {
try {
return nodeService.createConfigured(name, groupId, type, sources, configuration);
} catch (JsonProcessingException e) {
throw new BadRequestException("Invalid node configuration payload", e);
} catch (IllegalArgumentException e) {
throw new BadRequestException("Invalid node configuration request: " + e.getMessage(), e);
}
}

@DELETE
@Path("{id}")
@Operation(description = "Delete a node by its ID")
public void delete(@PathParam("id") Long nodeId) {
nodeService.delete(nodeId);
}

@GET
@Path("find")
@Operation(description = "Find nodes by FQDN within a specific group")
public List<Node> findNodeByFqdn(
@QueryParam("name") @Parameter(description = "FQDN of the node") String name,
@QueryParam("groupId") @Parameter(description = "Group ID to search within") Long groupId) {
return nodeService.findNodeByFqdn(name, groupId);
}
}
Loading
Loading