Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Procedural Scheduling activity deletion #1610

Merged
merged 22 commits into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package gov.nasa.jpl.aerie.e2e.procedural.scheduling.procedures;

import gov.nasa.ammos.aerie.procedural.scheduling.Goal;
import gov.nasa.ammos.aerie.procedural.scheduling.annotations.SchedulingProcedure;
import gov.nasa.ammos.aerie.procedural.scheduling.plan.DeletedAnchorStrategy;
import gov.nasa.ammos.aerie.procedural.scheduling.plan.EditablePlan;
import gov.nasa.ammos.aerie.procedural.timeline.payloads.activities.DirectiveStart;
import gov.nasa.jpl.aerie.merlin.protocol.types.Duration;
import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue;
import gov.nasa.jpl.aerie.types.ActivityDirectiveId;
import org.jetbrains.annotations.NotNull;

import java.util.Map;

/**
* Creates three activities in a chain of anchors, then deletes one.
* If `whichToDelete` is negative, this leaves all three activities.
* If `rollback` is true, this will roll the edit back before finishing.
*/
@SchedulingProcedure
public record ActivityDeletionGoal(int whichToDelete, DeletedAnchorStrategy anchorStrategy, boolean rollback) implements Goal {
@Override
public void run(@NotNull final EditablePlan plan) {
final var ids = new ActivityDirectiveId[3];

ids[0] = plan.create(
"BiteBanana",
new DirectiveStart.Absolute(Duration.HOUR),
Map.of("biteSize", SerializedValue.of(0))
);
ids[1] = plan.create(
"BiteBanana",
new DirectiveStart.Anchor(ids[0], Duration.HOUR, DirectiveStart.Anchor.AnchorPoint.End),
Map.of("biteSize", SerializedValue.of(1))
);
ids[2] = plan.create(
"BiteBanana",
new DirectiveStart.Anchor(ids[1], Duration.HOUR, DirectiveStart.Anchor.AnchorPoint.Start),
Map.of("biteSize", SerializedValue.of(2))
);

plan.commit();

if (whichToDelete >= 0) {
plan.delete(ids[whichToDelete], anchorStrategy);
}
JoelCourtney marked this conversation as resolved.
Show resolved Hide resolved

if (rollback) plan.rollback();
else plan.commit();
}
}
JoelCourtney marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package gov.nasa.jpl.aerie.e2e.procedural.scheduling.procedures;

import gov.nasa.ammos.aerie.procedural.scheduling.Goal;
import gov.nasa.ammos.aerie.procedural.scheduling.annotations.SchedulingProcedure;
import gov.nasa.ammos.aerie.procedural.scheduling.plan.DeletedAnchorStrategy;
import gov.nasa.ammos.aerie.procedural.scheduling.plan.EditablePlan;
import gov.nasa.ammos.aerie.procedural.timeline.payloads.activities.DirectiveStart;
import gov.nasa.jpl.aerie.merlin.protocol.types.Duration;
import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue;
import gov.nasa.jpl.aerie.types.ActivityDirectiveId;
import org.jetbrains.annotations.NotNull;

import java.util.Map;

/**
* Deletes all Bite Bananas with extreme prejudice. Used to test that updated
* anchors are saved in the database properly.
*/
@SchedulingProcedure
public record DeleteBiteBananasGoal(DeletedAnchorStrategy anchorStrategy) implements Goal {
@Override
public void run(@NotNull final EditablePlan plan) {
plan.directives("BiteBanana").forEach($ -> plan.delete($, anchorStrategy));
plan.commit();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,14 @@ void executeEDSLAndProcedure() throws IOException {
final var args = Json.createObjectBuilder().add("quantity", 4).build();
hasura.updateSchedulingSpecGoalArguments(procedureId.invocationId(), args);

final String recurrenceGoalDefinition =
"""
export default function myGoal() {
return Goal.ActivityRecurrenceGoal({
activityTemplate: ActivityTemplates.PeelBanana({peelDirection: 'fromStem'}),
interval: Temporal.Duration.from({hours:1})
})}""";

hasura.createSchedulingSpecGoal(
"Recurrence Scheduling Test Goal",
recurrenceGoalDefinition,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
package gov.nasa.jpl.aerie.e2e.procedural.scheduling;

import gov.nasa.jpl.aerie.e2e.types.GoalInvocationId;
import gov.nasa.jpl.aerie.e2e.utils.GatewayRequests;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.opentest4j.AssertionFailedError;

import javax.json.Json;
import javax.json.JsonValue;
import java.io.IOException;
import java.util.Objects;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class DatabaseDeletionTests extends ProceduralSchedulingSetup {
private GoalInvocationId procedureId;

@BeforeEach
void localBeforeEach() throws IOException {
try (final var gateway = new GatewayRequests(playwright)) {
int procedureJarId = gateway.uploadJarFile("build/libs/DeleteBiteBananasGoal.jar");
// Add Scheduling Procedure
procedureId = hasura.createSchedulingSpecProcedure(
"Test Scheduling Procedure",
procedureJarId,
specId,
0
);
}
}

@AfterEach
void localAfterEach() throws IOException {
hasura.deleteSchedulingGoal(procedureId.goalId());
}

@Test
void deletesDirectiveAlreadyInDatabase() throws IOException {
final var args = Json
.createObjectBuilder()
.add("anchorStrategy", "PreserveTree")
.build();
hasura.updateSchedulingSpecGoalArguments(procedureId.invocationId(), args);

hasura.insertActivityDirective(
planId,
"BiteBanana",
"1h",
JsonValue.EMPTY_JSON_OBJECT
);
hasura.updatePlanRevisionSchedulingSpec(planId);

var plan = hasura.getPlan(planId);
assertEquals(1, plan.activityDirectives().size());

hasura.awaitScheduling(specId);

plan = hasura.getPlan(planId);
assertEquals(0, plan.activityDirectives().size());
}

@Test
void deletesDirectiveInDatabaseWithAnchor() throws IOException {
final var args = Json
.createObjectBuilder()
.add("anchorStrategy", "PreserveTree")
.build();
hasura.updateSchedulingSpecGoalArguments(procedureId.invocationId(), args);

final var bite = hasura.insertActivityDirective(
planId,
"BiteBanana",
"1h",
JsonValue.EMPTY_JSON_OBJECT
);

final var grow = hasura.insertActivityDirective(
planId,
"GrowBanana",
"1h",
JsonValue.EMPTY_JSON_OBJECT,
Json.createObjectBuilder().add("anchor_id", bite)
);
hasura.updatePlanRevisionSchedulingSpec(planId);

var plan = hasura.getPlan(planId);
var activities = plan.activityDirectives();
assertEquals(2, activities.size());
assertTrue(activities.stream().anyMatch(
it -> Objects.equals(it.type(), "GrowBanana")
&& Objects.equals(it.anchorId(), bite)
&& Objects.equals(it.startOffset(), "01:00:00")
));

hasura.awaitScheduling(specId);

plan = hasura.getPlan(planId);

activities = plan.activityDirectives();
assertEquals(1, activities.size());

assertTrue(activities.stream().anyMatch(
it -> Objects.equals(it.type(), "GrowBanana")
&& Objects.equals(it.anchorId(), null)
&& Objects.equals(it.startOffset(), "02:00:00")
));
}

@Test
void deletesDirectiveInDatabaseInMiddleOfChain() throws IOException {

// Creates 5 activities, deletes "Bite".
// grow1 <- bite
// bite <- grow (id not assigned to a variable)
// bite <- grow2
// grow2 <- grow3

// Bite has two children, a grandchild, and a parent.

final var args = Json
.createObjectBuilder()
.add("anchorStrategy", "PreserveTree")
.build();
hasura.updateSchedulingSpecGoalArguments(procedureId.invocationId(), args);

final var grow1 = hasura.insertActivityDirective(
planId,
"GrowBanana",
"1h",
JsonValue.EMPTY_JSON_OBJECT
);

final var bite = hasura.insertActivityDirective(
planId,
"BiteBanana",
"1h",
JsonValue.EMPTY_JSON_OBJECT,
Json.createObjectBuilder().add("anchor_id", grow1)
);

int grow2 = -1;
for (int i = 0; i < 2; i++) {
grow2 = hasura.insertActivityDirective(
planId,
"GrowBanana",
i + "h",
JsonValue.EMPTY_JSON_OBJECT,
Json.createObjectBuilder().add("anchor_id", bite)
);
}

final var grow3 = hasura.insertActivityDirective(
planId,
"GrowBanana",
"0h",
JsonValue.EMPTY_JSON_OBJECT,
Json.createObjectBuilder().add("anchor_id", grow2)
);
hasura.updatePlanRevisionSchedulingSpec(planId);

var plan = hasura.getPlan(planId);
var activities = plan.activityDirectives();
assertEquals(5, activities.size());

hasura.awaitScheduling(specId);

plan = hasura.getPlan(planId);

activities = plan.activityDirectives();
assertEquals(4, activities.size());

assertTrue(activities.stream().anyMatch(
it -> Objects.equals(it.type(), "GrowBanana")
&& Objects.equals(it.id(), grow1)
&& Objects.equals(it.anchorId(), null)
));
final int finalGrow2 = grow2;
assertTrue(activities.stream().anyMatch(
it -> Objects.equals(it.type(), "GrowBanana")
&& Objects.equals(it.id(), finalGrow2)
&& Objects.equals(it.anchorId(), grow1)
&& Objects.equals(it.startOffset(), "02:00:00")
));
assertTrue(activities.stream().anyMatch(
it -> Objects.equals(it.type(), "GrowBanana")
&& Objects.equals(it.anchorId(), grow1)
&& Objects.equals(it.startOffset(), "01:00:00")
));
assertTrue(activities.stream().anyMatch(
it -> Objects.equals(it.type(), "GrowBanana")
&& Objects.equals(it.id(), grow3)
&& Objects.equals(it.anchorId(), finalGrow2)
&& Objects.equals(it.startOffset(), "00:00:00")
));
}

@Test
void deleteCascadeInDatabase() throws IOException {
final var args = Json
.createObjectBuilder()
.add("anchorStrategy", "Cascade")
.build();
hasura.updateSchedulingSpecGoalArguments(procedureId.invocationId(), args);

final var bite = hasura.insertActivityDirective(
planId,
"BiteBanana",
"1h",
JsonValue.EMPTY_JSON_OBJECT
);

final var grow = hasura.insertActivityDirective(
planId,
"GrowBanana",
"1h",
JsonValue.EMPTY_JSON_OBJECT,
Json.createObjectBuilder().add("anchor_id", bite)
);
hasura.updatePlanRevisionSchedulingSpec(planId);


var plan = hasura.getPlan(planId);
assertEquals(2, plan.activityDirectives().size());

hasura.awaitScheduling(specId);

plan = hasura.getPlan(planId);

assertEquals(0, plan.activityDirectives().size());
}

@Test
void deleteErrorInDatabase() throws IOException {
final var args = Json
.createObjectBuilder()
.add("anchorStrategy", "Error")
.build();
hasura.updateSchedulingSpecGoalArguments(procedureId.invocationId(), args);

final var bite = hasura.insertActivityDirective(
planId,
"BiteBanana",
"1h",
JsonValue.EMPTY_JSON_OBJECT
);

final var grow = hasura.insertActivityDirective(
planId,
"GrowBanana",
"1h",
JsonValue.EMPTY_JSON_OBJECT,
Json.createObjectBuilder().add("anchor_id", bite)
);
hasura.updatePlanRevisionSchedulingSpec(planId);


var plan = hasura.getPlan(planId);
assertEquals(2, plan.activityDirectives().size());

assertThrows(AssertionFailedError.class, () -> hasura.awaitScheduling(specId));

plan = hasura.getPlan(planId);

assertEquals(2, plan.activityDirectives().size());
}
}
Loading
Loading