Skip to content

Commit 9c925d6

Browse files
authored
Merge pull request #1610 from NASA-AMMOS/feat/scheduler-activity-deletion
Procedural Scheduling activity deletion
2 parents 1388d24 + 81f23aa commit 9c925d6

File tree

42 files changed

+1478
-389
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1478
-389
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package gov.nasa.jpl.aerie.e2e.procedural.scheduling.procedures;
2+
3+
import gov.nasa.ammos.aerie.procedural.scheduling.Goal;
4+
import gov.nasa.ammos.aerie.procedural.scheduling.annotations.SchedulingProcedure;
5+
import gov.nasa.ammos.aerie.procedural.scheduling.plan.DeletedAnchorStrategy;
6+
import gov.nasa.ammos.aerie.procedural.scheduling.plan.EditablePlan;
7+
import gov.nasa.ammos.aerie.procedural.timeline.payloads.activities.DirectiveStart;
8+
import gov.nasa.jpl.aerie.merlin.protocol.types.Duration;
9+
import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue;
10+
import gov.nasa.jpl.aerie.types.ActivityDirectiveId;
11+
import org.jetbrains.annotations.NotNull;
12+
13+
import java.util.Map;
14+
15+
/**
16+
* Creates three activities in a chain of anchors, then deletes one.
17+
* If `whichToDelete` is negative, this leaves all three activities.
18+
* If `rollback` is true, this will roll the edit back before finishing.
19+
*/
20+
@SchedulingProcedure
21+
public record ActivityDeletionGoal(int whichToDelete, DeletedAnchorStrategy anchorStrategy, boolean rollback) implements Goal {
22+
@Override
23+
public void run(@NotNull final EditablePlan plan) {
24+
final var ids = new ActivityDirectiveId[3];
25+
26+
ids[0] = plan.create(
27+
"BiteBanana",
28+
new DirectiveStart.Absolute(Duration.HOUR),
29+
Map.of("biteSize", SerializedValue.of(0))
30+
);
31+
ids[1] = plan.create(
32+
"BiteBanana",
33+
new DirectiveStart.Anchor(ids[0], Duration.HOUR, DirectiveStart.Anchor.AnchorPoint.End),
34+
Map.of("biteSize", SerializedValue.of(1))
35+
);
36+
ids[2] = plan.create(
37+
"BiteBanana",
38+
new DirectiveStart.Anchor(ids[1], Duration.HOUR, DirectiveStart.Anchor.AnchorPoint.Start),
39+
Map.of("biteSize", SerializedValue.of(2))
40+
);
41+
42+
plan.commit();
43+
44+
if (whichToDelete >= 0) {
45+
plan.delete(ids[whichToDelete], anchorStrategy);
46+
}
47+
48+
if (rollback) plan.rollback();
49+
else plan.commit();
50+
}
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package gov.nasa.jpl.aerie.e2e.procedural.scheduling.procedures;
2+
3+
import gov.nasa.ammos.aerie.procedural.scheduling.Goal;
4+
import gov.nasa.ammos.aerie.procedural.scheduling.annotations.SchedulingProcedure;
5+
import gov.nasa.ammos.aerie.procedural.scheduling.plan.DeletedAnchorStrategy;
6+
import gov.nasa.ammos.aerie.procedural.scheduling.plan.EditablePlan;
7+
import gov.nasa.ammos.aerie.procedural.timeline.payloads.activities.DirectiveStart;
8+
import gov.nasa.jpl.aerie.merlin.protocol.types.Duration;
9+
import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue;
10+
import gov.nasa.jpl.aerie.types.ActivityDirectiveId;
11+
import org.jetbrains.annotations.NotNull;
12+
13+
import java.util.Map;
14+
15+
/**
16+
* Deletes all Bite Bananas with extreme prejudice. Used to test that updated
17+
* anchors are saved in the database properly.
18+
*/
19+
@SchedulingProcedure
20+
public record DeleteBiteBananasGoal(DeletedAnchorStrategy anchorStrategy) implements Goal {
21+
@Override
22+
public void run(@NotNull final EditablePlan plan) {
23+
plan.directives("BiteBanana").forEach($ -> plan.delete($, anchorStrategy));
24+
plan.commit();
25+
}
26+
}

e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/BasicTests.java

+8
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,14 @@ void executeEDSLAndProcedure() throws IOException {
134134
final var args = Json.createObjectBuilder().add("quantity", 4).build();
135135
hasura.updateSchedulingSpecGoalArguments(procedureId.invocationId(), args);
136136

137+
final String recurrenceGoalDefinition =
138+
"""
139+
export default function myGoal() {
140+
return Goal.ActivityRecurrenceGoal({
141+
activityTemplate: ActivityTemplates.PeelBanana({peelDirection: 'fromStem'}),
142+
interval: Temporal.Duration.from({hours:1})
143+
})}""";
144+
137145
hasura.createSchedulingSpecGoal(
138146
"Recurrence Scheduling Test Goal",
139147
recurrenceGoalDefinition,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
package gov.nasa.jpl.aerie.e2e.procedural.scheduling;
2+
3+
import gov.nasa.jpl.aerie.e2e.types.GoalInvocationId;
4+
import gov.nasa.jpl.aerie.e2e.utils.GatewayRequests;
5+
import org.junit.jupiter.api.AfterEach;
6+
import org.junit.jupiter.api.BeforeEach;
7+
import org.junit.jupiter.api.Test;
8+
import org.opentest4j.AssertionFailedError;
9+
10+
import javax.json.Json;
11+
import javax.json.JsonValue;
12+
import java.io.IOException;
13+
import java.util.Objects;
14+
15+
import static org.junit.jupiter.api.Assertions.assertEquals;
16+
import static org.junit.jupiter.api.Assertions.assertThrows;
17+
import static org.junit.jupiter.api.Assertions.assertTrue;
18+
19+
public class DatabaseDeletionTests extends ProceduralSchedulingSetup {
20+
private GoalInvocationId procedureId;
21+
22+
@BeforeEach
23+
void localBeforeEach() throws IOException {
24+
try (final var gateway = new GatewayRequests(playwright)) {
25+
int procedureJarId = gateway.uploadJarFile("build/libs/DeleteBiteBananasGoal.jar");
26+
// Add Scheduling Procedure
27+
procedureId = hasura.createSchedulingSpecProcedure(
28+
"Test Scheduling Procedure",
29+
procedureJarId,
30+
specId,
31+
0
32+
);
33+
}
34+
}
35+
36+
@AfterEach
37+
void localAfterEach() throws IOException {
38+
hasura.deleteSchedulingGoal(procedureId.goalId());
39+
}
40+
41+
@Test
42+
void deletesDirectiveAlreadyInDatabase() throws IOException {
43+
final var args = Json
44+
.createObjectBuilder()
45+
.add("anchorStrategy", "PreserveTree")
46+
.build();
47+
hasura.updateSchedulingSpecGoalArguments(procedureId.invocationId(), args);
48+
49+
hasura.insertActivityDirective(
50+
planId,
51+
"BiteBanana",
52+
"1h",
53+
JsonValue.EMPTY_JSON_OBJECT
54+
);
55+
hasura.updatePlanRevisionSchedulingSpec(planId);
56+
57+
var plan = hasura.getPlan(planId);
58+
assertEquals(1, plan.activityDirectives().size());
59+
60+
hasura.awaitScheduling(specId);
61+
62+
plan = hasura.getPlan(planId);
63+
assertEquals(0, plan.activityDirectives().size());
64+
}
65+
66+
@Test
67+
void deletesDirectiveInDatabaseWithAnchor() throws IOException {
68+
final var args = Json
69+
.createObjectBuilder()
70+
.add("anchorStrategy", "PreserveTree")
71+
.build();
72+
hasura.updateSchedulingSpecGoalArguments(procedureId.invocationId(), args);
73+
74+
final var bite = hasura.insertActivityDirective(
75+
planId,
76+
"BiteBanana",
77+
"1h",
78+
JsonValue.EMPTY_JSON_OBJECT
79+
);
80+
81+
final var grow = hasura.insertActivityDirective(
82+
planId,
83+
"GrowBanana",
84+
"1h",
85+
JsonValue.EMPTY_JSON_OBJECT,
86+
Json.createObjectBuilder().add("anchor_id", bite)
87+
);
88+
hasura.updatePlanRevisionSchedulingSpec(planId);
89+
90+
var plan = hasura.getPlan(planId);
91+
var activities = plan.activityDirectives();
92+
assertEquals(2, activities.size());
93+
assertTrue(activities.stream().anyMatch(
94+
it -> Objects.equals(it.type(), "GrowBanana")
95+
&& Objects.equals(it.anchorId(), bite)
96+
&& Objects.equals(it.startOffset(), "01:00:00")
97+
));
98+
99+
hasura.awaitScheduling(specId);
100+
101+
plan = hasura.getPlan(planId);
102+
103+
activities = plan.activityDirectives();
104+
assertEquals(1, activities.size());
105+
106+
assertTrue(activities.stream().anyMatch(
107+
it -> Objects.equals(it.type(), "GrowBanana")
108+
&& Objects.equals(it.anchorId(), null)
109+
&& Objects.equals(it.startOffset(), "02:00:00")
110+
));
111+
}
112+
113+
@Test
114+
void deletesDirectiveInDatabaseInMiddleOfChain() throws IOException {
115+
116+
// Creates 5 activities, deletes "Bite".
117+
// grow1 <- bite
118+
// bite <- grow (id not assigned to a variable)
119+
// bite <- grow2
120+
// grow2 <- grow3
121+
122+
// Bite has two children, a grandchild, and a parent.
123+
124+
final var args = Json
125+
.createObjectBuilder()
126+
.add("anchorStrategy", "PreserveTree")
127+
.build();
128+
hasura.updateSchedulingSpecGoalArguments(procedureId.invocationId(), args);
129+
130+
final var grow1 = hasura.insertActivityDirective(
131+
planId,
132+
"GrowBanana",
133+
"1h",
134+
JsonValue.EMPTY_JSON_OBJECT
135+
);
136+
137+
final var bite = hasura.insertActivityDirective(
138+
planId,
139+
"BiteBanana",
140+
"1h",
141+
JsonValue.EMPTY_JSON_OBJECT,
142+
Json.createObjectBuilder().add("anchor_id", grow1)
143+
);
144+
145+
int grow2 = -1;
146+
for (int i = 0; i < 2; i++) {
147+
grow2 = hasura.insertActivityDirective(
148+
planId,
149+
"GrowBanana",
150+
i + "h",
151+
JsonValue.EMPTY_JSON_OBJECT,
152+
Json.createObjectBuilder().add("anchor_id", bite)
153+
);
154+
}
155+
156+
final var grow3 = hasura.insertActivityDirective(
157+
planId,
158+
"GrowBanana",
159+
"0h",
160+
JsonValue.EMPTY_JSON_OBJECT,
161+
Json.createObjectBuilder().add("anchor_id", grow2)
162+
);
163+
hasura.updatePlanRevisionSchedulingSpec(planId);
164+
165+
var plan = hasura.getPlan(planId);
166+
var activities = plan.activityDirectives();
167+
assertEquals(5, activities.size());
168+
169+
hasura.awaitScheduling(specId);
170+
171+
plan = hasura.getPlan(planId);
172+
173+
activities = plan.activityDirectives();
174+
assertEquals(4, activities.size());
175+
176+
assertTrue(activities.stream().anyMatch(
177+
it -> Objects.equals(it.type(), "GrowBanana")
178+
&& Objects.equals(it.id(), grow1)
179+
&& Objects.equals(it.anchorId(), null)
180+
));
181+
final int finalGrow2 = grow2;
182+
assertTrue(activities.stream().anyMatch(
183+
it -> Objects.equals(it.type(), "GrowBanana")
184+
&& Objects.equals(it.id(), finalGrow2)
185+
&& Objects.equals(it.anchorId(), grow1)
186+
&& Objects.equals(it.startOffset(), "02:00:00")
187+
));
188+
assertTrue(activities.stream().anyMatch(
189+
it -> Objects.equals(it.type(), "GrowBanana")
190+
&& Objects.equals(it.anchorId(), grow1)
191+
&& Objects.equals(it.startOffset(), "01:00:00")
192+
));
193+
assertTrue(activities.stream().anyMatch(
194+
it -> Objects.equals(it.type(), "GrowBanana")
195+
&& Objects.equals(it.id(), grow3)
196+
&& Objects.equals(it.anchorId(), finalGrow2)
197+
&& Objects.equals(it.startOffset(), "00:00:00")
198+
));
199+
}
200+
201+
@Test
202+
void deleteCascadeInDatabase() throws IOException {
203+
final var args = Json
204+
.createObjectBuilder()
205+
.add("anchorStrategy", "Cascade")
206+
.build();
207+
hasura.updateSchedulingSpecGoalArguments(procedureId.invocationId(), args);
208+
209+
final var bite = hasura.insertActivityDirective(
210+
planId,
211+
"BiteBanana",
212+
"1h",
213+
JsonValue.EMPTY_JSON_OBJECT
214+
);
215+
216+
final var grow = hasura.insertActivityDirective(
217+
planId,
218+
"GrowBanana",
219+
"1h",
220+
JsonValue.EMPTY_JSON_OBJECT,
221+
Json.createObjectBuilder().add("anchor_id", bite)
222+
);
223+
hasura.updatePlanRevisionSchedulingSpec(planId);
224+
225+
226+
var plan = hasura.getPlan(planId);
227+
assertEquals(2, plan.activityDirectives().size());
228+
229+
hasura.awaitScheduling(specId);
230+
231+
plan = hasura.getPlan(planId);
232+
233+
assertEquals(0, plan.activityDirectives().size());
234+
}
235+
236+
@Test
237+
void deleteErrorInDatabase() throws IOException {
238+
final var args = Json
239+
.createObjectBuilder()
240+
.add("anchorStrategy", "Error")
241+
.build();
242+
hasura.updateSchedulingSpecGoalArguments(procedureId.invocationId(), args);
243+
244+
final var bite = hasura.insertActivityDirective(
245+
planId,
246+
"BiteBanana",
247+
"1h",
248+
JsonValue.EMPTY_JSON_OBJECT
249+
);
250+
251+
final var grow = hasura.insertActivityDirective(
252+
planId,
253+
"GrowBanana",
254+
"1h",
255+
JsonValue.EMPTY_JSON_OBJECT,
256+
Json.createObjectBuilder().add("anchor_id", bite)
257+
);
258+
hasura.updatePlanRevisionSchedulingSpec(planId);
259+
260+
261+
var plan = hasura.getPlan(planId);
262+
assertEquals(2, plan.activityDirectives().size());
263+
264+
assertThrows(AssertionFailedError.class, () -> hasura.awaitScheduling(specId));
265+
266+
plan = hasura.getPlan(planId);
267+
268+
assertEquals(2, plan.activityDirectives().size());
269+
}
270+
}

0 commit comments

Comments
 (0)