fix(deploy): address final review — sensitiveKeys snapshot, dirty scrubbing, transition race, refetch invalidations
- Issue 1: add List<String> sensitiveKeys as 4th field to DeploymentConfigSnapshot; populate from agentConfig.getSensitiveKeys() in DeploymentExecutor; handleRestore hydrates from snap.sensitiveKeys directly; Deployment type in apps.ts gains sensitiveKeys field - Issue 2: after createApp succeeds, refetchQueries(['apps', envSlug]) before navigate so the new app is in cache before the router renders the deployed view (eliminates transient Save- disabled flash) - Issue 3: useDeploymentPageState useEffect now uses prevServerStateRef to detect local edits; background refetches only overwrite form when no local changes are present - Issue 5: handleRedeploy invalidates dirty-state + versions queries after createDeployment resolves; handleSave invalidates dirty-state after staged save - Issue 10: DirtyStateCalculator strips volatile agentConfig keys (version, updatedAt, updatedBy, environment, application) before JSON comparison via scrubAgentConfig(); adds versionBumpDoesNotMarkDirty test Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ package com.cameleer.server.core.runtime;
|
||||
|
||||
import com.cameleer.common.model.ApplicationConfig;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -15,6 +16,7 @@ import java.util.UUID;
|
||||
public record DeploymentConfigSnapshot(
|
||||
UUID jarVersionId,
|
||||
ApplicationConfig agentConfig,
|
||||
Map<String, Object> containerConfig
|
||||
Map<String, Object> containerConfig,
|
||||
List<String> sensitiveKeys
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -22,12 +23,23 @@ import java.util.UUID;
|
||||
*/
|
||||
public class DirtyStateCalculator {
|
||||
|
||||
private static final Set<String> AGENT_CONFIG_IGNORED_KEYS = Set.of(
|
||||
"version", "updatedAt", "updatedBy", "environment", "application"
|
||||
);
|
||||
|
||||
private final ObjectMapper mapper;
|
||||
|
||||
public DirtyStateCalculator(ObjectMapper mapper) {
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
private JsonNode scrubAgentConfig(JsonNode node) {
|
||||
if (!(node instanceof ObjectNode obj)) return node;
|
||||
ObjectNode copy = obj.deepCopy();
|
||||
for (String k : AGENT_CONFIG_IGNORED_KEYS) copy.remove(k);
|
||||
return copy;
|
||||
}
|
||||
|
||||
public DirtyStateResult compute(UUID desiredJarVersionId,
|
||||
ApplicationConfig desiredAgentConfig,
|
||||
Map<String, Object> desiredContainerConfig,
|
||||
@@ -44,8 +56,10 @@ public class DirtyStateCalculator {
|
||||
String.valueOf(desiredJarVersionId), String.valueOf(snapshot.jarVersionId())));
|
||||
}
|
||||
|
||||
compareJson("agentConfig", mapper.valueToTree(desiredAgentConfig),
|
||||
mapper.valueToTree(snapshot.agentConfig()), diffs);
|
||||
compareJson("agentConfig",
|
||||
scrubAgentConfig(mapper.valueToTree(desiredAgentConfig)),
|
||||
scrubAgentConfig(mapper.valueToTree(snapshot.agentConfig())),
|
||||
diffs);
|
||||
compareJson("containerConfig", mapper.valueToTree(desiredContainerConfig),
|
||||
mapper.valueToTree(snapshot.containerConfig()), diffs);
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ class DirtyStateCalculatorTest {
|
||||
Map<String, Object> container = Map.of("memoryLimitMb", 512);
|
||||
|
||||
UUID jarId = UUID.randomUUID();
|
||||
DeploymentConfigSnapshot snap = new DeploymentConfigSnapshot(jarId, cfg, container);
|
||||
DeploymentConfigSnapshot snap = new DeploymentConfigSnapshot(jarId, cfg, container, null);
|
||||
DirtyStateResult result = calc.compute(jarId, cfg, container, snap);
|
||||
|
||||
assertThat(result.dirty()).isFalse();
|
||||
@@ -53,7 +53,7 @@ class DirtyStateCalculatorTest {
|
||||
Map<String, Object> container = Map.of();
|
||||
UUID v1 = UUID.randomUUID();
|
||||
UUID v2 = UUID.randomUUID();
|
||||
DeploymentConfigSnapshot snap = new DeploymentConfigSnapshot(v1, cfg, container);
|
||||
DeploymentConfigSnapshot snap = new DeploymentConfigSnapshot(v1, cfg, container, null);
|
||||
|
||||
DirtyStateResult result = calc.compute(v2, cfg, container, snap);
|
||||
|
||||
@@ -71,7 +71,7 @@ class DirtyStateCalculatorTest {
|
||||
ApplicationConfig desiredCfg = new ApplicationConfig();
|
||||
desiredCfg.setSamplingRate(1.0);
|
||||
UUID jarId = UUID.randomUUID();
|
||||
DeploymentConfigSnapshot snap = new DeploymentConfigSnapshot(jarId, deployedCfg, Map.of());
|
||||
DeploymentConfigSnapshot snap = new DeploymentConfigSnapshot(jarId, deployedCfg, Map.of(), null);
|
||||
|
||||
DirtyStateResult result = calc.compute(jarId, desiredCfg, Map.of(), snap);
|
||||
|
||||
@@ -85,7 +85,7 @@ class DirtyStateCalculatorTest {
|
||||
DirtyStateCalculator calc = CALC;
|
||||
ApplicationConfig cfg = new ApplicationConfig();
|
||||
UUID jarId = UUID.randomUUID();
|
||||
DeploymentConfigSnapshot snap = new DeploymentConfigSnapshot(jarId, cfg, Map.of("memoryLimitMb", 512));
|
||||
DeploymentConfigSnapshot snap = new DeploymentConfigSnapshot(jarId, cfg, Map.of("memoryLimitMb", 512), null);
|
||||
|
||||
DirtyStateResult result = calc.compute(jarId, cfg, Map.of("memoryLimitMb", 1024), snap);
|
||||
|
||||
@@ -100,7 +100,7 @@ class DirtyStateCalculatorTest {
|
||||
ApplicationConfig desired = new ApplicationConfig();
|
||||
desired.setSamplingRate(1.0);
|
||||
UUID jarId = UUID.randomUUID();
|
||||
DeploymentConfigSnapshot snap = new DeploymentConfigSnapshot(jarId, null, Map.of());
|
||||
DeploymentConfigSnapshot snap = new DeploymentConfigSnapshot(jarId, null, Map.of(), null);
|
||||
|
||||
DirtyStateResult result = calc.compute(jarId, desired, Map.of(), snap);
|
||||
|
||||
@@ -118,7 +118,7 @@ class DirtyStateCalculatorTest {
|
||||
ApplicationConfig desired = new ApplicationConfig();
|
||||
desired.setTracedProcessors(Map.of("proc-1", "TRACE"));
|
||||
UUID jarId = UUID.randomUUID();
|
||||
DeploymentConfigSnapshot snap = new DeploymentConfigSnapshot(jarId, deployed, Map.of());
|
||||
DeploymentConfigSnapshot snap = new DeploymentConfigSnapshot(jarId, deployed, Map.of(), null);
|
||||
|
||||
DirtyStateResult result = calc.compute(jarId, desired, Map.of(), snap);
|
||||
|
||||
@@ -136,7 +136,7 @@ class DirtyStateCalculatorTest {
|
||||
ApplicationConfig desired = new ApplicationConfig();
|
||||
desired.setApplicationLogLevel("DEBUG");
|
||||
UUID jarId = UUID.randomUUID();
|
||||
DeploymentConfigSnapshot snap = new DeploymentConfigSnapshot(jarId, deployed, Map.of());
|
||||
DeploymentConfigSnapshot snap = new DeploymentConfigSnapshot(jarId, deployed, Map.of(), null);
|
||||
|
||||
DirtyStateResult result = calc.compute(jarId, desired, Map.of(), snap);
|
||||
|
||||
@@ -146,4 +146,19 @@ class DirtyStateCalculatorTest {
|
||||
assertThat(diff.staged()).isEqualTo("DEBUG");
|
||||
assertThat(diff.deployed()).isEqualTo("INFO");
|
||||
}
|
||||
|
||||
@Test
|
||||
void versionBumpDoesNotMarkDirty() {
|
||||
ApplicationConfig deployedCfg = new ApplicationConfig();
|
||||
deployedCfg.setSamplingRate(0.5);
|
||||
deployedCfg.setVersion(1);
|
||||
ApplicationConfig desiredCfg = new ApplicationConfig();
|
||||
desiredCfg.setSamplingRate(0.5);
|
||||
desiredCfg.setVersion(2); // bumped by save
|
||||
UUID jarId = UUID.randomUUID();
|
||||
DeploymentConfigSnapshot snap = new DeploymentConfigSnapshot(jarId, deployedCfg, Map.of(), null);
|
||||
|
||||
DirtyStateResult result = CALC.compute(jarId, desiredCfg, Map.of(), snap);
|
||||
assertThat(result.dirty()).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user