시나리오 수정

This commit is contained in:
현성필
2025-02-02 15:38:43 +09:00
parent 53d33fd4d3
commit 42d542418a
6 changed files with 453 additions and 3 deletions
@@ -264,7 +264,7 @@ const DiagramArea = forwardRef(({onSave, onFlowChange, isExecuting}, ref) => {
position,
data: {
...apiData,
id: `api-${Date.now()}` // Ensure unique API ID
id: apiData.id,
}
};
@@ -0,0 +1,59 @@
package com.eactive.testmaster.client.dto;
public class ReactFlowEdge {
private String id;
private String source;
private String target;
private String sourceHandle;
private String targetHandle;
private String type = "smoothstep";
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getSource() {
return source;
}
public void setSource(String source) {
this.source = source;
}
public String getTarget() {
return target;
}
public void setTarget(String target) {
this.target = target;
}
public String getSourceHandle() {
return sourceHandle;
}
public void setSourceHandle(String sourceHandle) {
this.sourceHandle = sourceHandle;
}
public String getTargetHandle() {
return targetHandle;
}
public void setTargetHandle(String targetHandle) {
this.targetHandle = targetHandle;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
@@ -0,0 +1,25 @@
package com.eactive.testmaster.client.dto;
import java.util.ArrayList;
import java.util.List;
public class ReactFlowGraph {
private List<ReactFlowNode> nodes = new ArrayList<>();
private List<ReactFlowEdge> edges = new ArrayList<>();
public List<ReactFlowNode> getNodes() {
return nodes;
}
public void setNodes(List<ReactFlowNode> nodes) {
this.nodes = nodes;
}
public List<ReactFlowEdge> getEdges() {
return edges;
}
public void setEdges(List<ReactFlowEdge> edges) {
this.edges = edges;
}
}
@@ -0,0 +1,135 @@
package com.eactive.testmaster.client.dto;
import java.util.Map;
public class ReactFlowNode {
private String id;
private String type;
private Position position;
private NodeData data;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Position getPosition() {
return position;
}
public void setPosition(Position position) {
this.position = position;
}
public NodeData getData() {
return data;
}
public void setData(NodeData data) {
this.data = data;
}
public static class Position {
private double x;
private double y;
public Position() {
}
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
}
public static class NodeData {
private String id;
private String name;
private String method;
private String path;
private String condition;
private Long duration;
private Map<String, Object> additionalData;
public NodeData() {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getCondition() {
return condition;
}
public void setCondition(String condition) {
this.condition = condition;
}
public Long getDuration() {
return duration;
}
public void setDuration(Long duration) {
this.duration = duration;
}
public Map<String, Object> getAdditionalData() {
return additionalData;
}
public void setAdditionalData(Map<String, Object> additionalData) {
this.additionalData = additionalData;
}
}
}
@@ -29,6 +29,7 @@ import org.springframework.stereotype.Service;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import org.thymeleaf.util.StringUtils;
@Service
//@Scope("session")
@@ -49,7 +50,7 @@ public class LoadTestService extends TextWebSocketHandler {
private final RingBuffer<ApiRequestEvent> ringBuffer;
public LoadTestService(SimpMessagingTemplate template, LoadTestMonitorMap loadTestMonitorMap,ApiClientFactory apiClientFactory, ApiScenarioMapper apiScenarioMapper, ApiRequestMapper apiRequestMapper) {
public LoadTestService(SimpMessagingTemplate template, LoadTestMonitorMap loadTestMonitorMap, ApiClientFactory apiClientFactory, ApiScenarioMapper apiScenarioMapper, ApiRequestMapper apiRequestMapper) {
this.template = template;
this.loadTestMonitorMap = loadTestMonitorMap;
this.apiScenarioMapper = apiScenarioMapper;
@@ -130,7 +131,15 @@ public class LoadTestService extends TextWebSocketHandler {
taskScheduler.setPoolSize(numberOfUsers + 1); // Add extra space for cancellation tasks
taskScheduler.initialize();
ApiScenario apiScenario = apiScenarioMapper.map(loadTestDTO.getScenarioId());
ScenarioNode scenario = LoadTestGraphBuilder.getTopNode(apiScenario.getScenario());
ScenarioNode scenario = null;
if (apiScenario != null) {
if (!StringUtils.isEmpty(apiScenario.getScenario()) && apiScenario.getScenario().contains("nodes")) {
scenario = ReactFlowGraphBuilder.convertToScenarioNode(apiScenario.getScenario());
} else {
scenario = LoadTestGraphBuilder.getTopNode(apiScenario.getScenario());
}
}
traverseDFS(scenario);
List<ScheduledFuture<?>> scheduledFutures = new ArrayList<>();
@@ -0,0 +1,222 @@
package com.eactive.testmaster.loadtest.service;
import com.eactive.testmaster.client.dto.ReactFlowGraph;
import com.eactive.testmaster.client.dto.internal.ScenarioNode;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.HashMap;
import java.util.Map;
public class ReactFlowGraphBuilder {
private static final ObjectMapper objectMapper = new ObjectMapper();
static {
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
public static ReactFlowGraph loadData(String data) throws JsonProcessingException {
return objectMapper.readValue(data, new TypeReference<ReactFlowGraph>() {
});
}
public static ScenarioNode convertToScenarioNode(String data) {
try {
return convertToScenarioNode(loadData(data));
} catch (JsonProcessingException e) {
return new ScenarioNode();
}
}
public static ScenarioNode convertToScenarioNode(ReactFlowGraph flowGraph) {
Map<String, ScenarioNode> nodeMap = new HashMap<>();
// First pass: Create all nodes
flowGraph.getNodes().forEach(flowNode -> {
ScenarioNode scenarioNode = new ScenarioNode();
scenarioNode.setId(flowNode.getId());
scenarioNode.setName(flowNode.getData().getName());
scenarioNode.setClassName(getScenarioNodeClass(flowNode.getType()));
// Convert node-specific data
Map<String, String> data = new HashMap<>();
switch (flowNode.getType()) {
case "apiNode":
data.put("id", flowNode.getData().getId());
data.put("method", flowNode.getData().getMethod());
data.put("path", flowNode.getData().getPath());
break;
case "conditionNode":
data.put("condition", flowNode.getData().getCondition());
break;
case "delayNode":
data.put("duration", String.valueOf(flowNode.getData().getDuration()));
break;
}
scenarioNode.setData(data);
nodeMap.put(flowNode.getId(), scenarioNode);
});
// Second pass: Connect nodes
flowGraph.getEdges().forEach(edge -> {
ScenarioNode sourceNode = nodeMap.get(edge.getSource());
if (sourceNode != null) {
sourceNode.addNode(nodeMap.get(edge.getTarget()));
}
});
// Find and return the start node
return nodeMap.values().stream()
.filter(node -> "start".equals(node.getClassName()))
.findFirst()
.orElse(null);
}
private static String getScenarioNodeClass(String nodeType) {
switch (nodeType) {
case "startNode":
return "start";
case "apiNode":
return "api";
case "conditionNode":
return "condition";
case "delayNode":
return "delay";
default:
return "default";
}
}
public static String testData = "{\n"
+ " \"nodes\": [\n"
+ " {\n"
+ " \"id\": \"start\",\n"
+ " \"type\": \"startNode\",\n"
+ " \"position\": {\n"
+ " \"x\": 100,\n"
+ " \"y\": 50\n"
+ " },\n"
+ " \"data\": {\n"
+ " \"id\": \"start\"\n"
+ " },\n"
+ " \"measured\": {\n"
+ " \"width\": 100,\n"
+ " \"height\": 56\n"
+ " },\n"
+ " \"className\": null\n"
+ " },\n"
+ " {\n"
+ " \"id\": \"node-1738370266933-9tienpcqk\",\n"
+ " \"type\": \"apiNode\",\n"
+ " \"position\": {\n"
+ " \"x\": 360,\n"
+ " \"y\": 195\n"
+ " },\n"
+ " \"data\": {\n"
+ " \"id\": \"c240c9da-17a4-4dc7-bc93-a29b54aa324e\",\n"
+ " \"name\": \"api1\",\n"
+ " \"path\": \"api1?test={{test}}\",\n"
+ " \"method\": \"POST\",\n"
+ " \"type\": \"HTTP\",\n"
+ " \"server\": 53,\n"
+ " \"headers\": [\n"
+ " {\n"
+ " \"enabled\": true,\n"
+ " \"key\": \"hhaa\",\n"
+ " \"value\": \"{{test}}\"\n"
+ " }\n"
+ " ],\n"
+ " \"queryParams\": [\n"
+ " {\n"
+ " \"enabled\": true,\n"
+ " \"key\": \"test\",\n"
+ " \"value\": \"{{test}}\"\n"
+ " }\n"
+ " ],\n"
+ " \"variables\": [],\n"
+ " \"requestBody\": \"{{test}}\",\n"
+ " \"preRequestScript\": \"console.log(11);\",\n"
+ " \"postRequestScript\": \"console.log(22);\",\n"
+ " \"layout\": null,\n"
+ " \"collectionId\": \"d4088731-685e-41ea-b557-61d7ac3d4ab3\"\n"
+ " },\n"
+ " \"measured\": {\n"
+ " \"width\": 200,\n"
+ " \"height\": 106\n"
+ " }\n"
+ " },\n"
+ " {\n"
+ " \"id\": \"node-1738370270698-hj4f76knw\",\n"
+ " \"type\": \"apiNode\",\n"
+ " \"position\": {\n"
+ " \"x\": 720,\n"
+ " \"y\": 315\n"
+ " },\n"
+ " \"data\": {\n"
+ " \"id\": \"c240c9da-17a4-4dc7-bc93-a29b54aa324e\",\n"
+ " \"name\": \"api1\",\n"
+ " \"path\": \"api1?test={{test}}\",\n"
+ " \"method\": \"POST\",\n"
+ " \"type\": \"HTTP\",\n"
+ " \"server\": 53,\n"
+ " \"headers\": [\n"
+ " {\n"
+ " \"enabled\": true,\n"
+ " \"key\": \"hhaa\",\n"
+ " \"value\": \"{{test}}\"\n"
+ " }\n"
+ " ],\n"
+ " \"queryParams\": [\n"
+ " {\n"
+ " \"enabled\": true,\n"
+ " \"key\": \"test\",\n"
+ " \"value\": \"{{test}}\"\n"
+ " }\n"
+ " ],\n"
+ " \"variables\": [],\n"
+ " \"requestBody\": \"{{test}}\",\n"
+ " \"preRequestScript\": \"console.log(11);\",\n"
+ " \"postRequestScript\": \"console.log(22);\",\n"
+ " \"layout\": null,\n"
+ " \"collectionId\": \"d4088731-685e-41ea-b557-61d7ac3d4ab3\"\n"
+ " },\n"
+ " \"measured\": {\n"
+ " \"width\": 200,\n"
+ " \"height\": 106\n"
+ " }\n"
+ " }\n"
+ " ],\n"
+ " \"edges\": [\n"
+ " {\n"
+ " \"type\": \"smoothstep\",\n"
+ " \"source\": \"start\",\n"
+ " \"sourceHandle\": null,\n"
+ " \"target\": \"node-1738370266933-9tienpcqk\",\n"
+ " \"targetHandle\": null,\n"
+ " \"id\": \"edge-1738370269180-dqspjlvej\"\n"
+ " },\n"
+ " {\n"
+ " \"type\": \"smoothstep\",\n"
+ " \"source\": \"node-1738370266933-9tienpcqk\",\n"
+ " \"sourceHandle\": null,\n"
+ " \"target\": \"node-1738370270698-hj4f76knw\",\n"
+ " \"targetHandle\": null,\n"
+ " \"id\": \"edge-1738370272761-db6f2xim6\"\n"
+ " }\n"
+ " ],\n"
+ " \"viewport\": {\n"
+ " \"x\": 0,\n"
+ " \"y\": 0,\n"
+ " \"zoom\": 1\n"
+ " }\n"
+ "}";
public static void main(String[] args) throws JsonProcessingException {
ScenarioNode node = ReactFlowGraphBuilder.convertToScenarioNode(testData);
System.out.println(node);
}
}