From d2096f65f2cf9f71162dd18e91f85c899e2fe79d Mon Sep 17 00:00:00 2001 From: pranav-super Date: Mon, 4 Nov 2024 13:04:38 -0800 Subject: [PATCH 1/3] implement plan.events(...) procedural scheduling query --- .../constraints/NotImplementedPlan.java | 8 ++ .../timeline/collections/ExternalEvents.kt | 31 ++++++++ .../timeline/payloads/ExternalEvent.kt | 20 +++++ .../timeline/payloads/ExternalSource.kt | 14 ++++ .../procedural/timeline/plan/EventQuery.kt | 35 +++++++++ .../aerie/procedural/timeline/plan/Plan.kt | 6 ++ .../jpl/aerie/scheduler/goals/Procedure.java | 5 +- .../jpl/aerie/scheduler/model/Problem.java | 8 ++ .../scheduler/solver/PrioritySolver.java | 12 ++- .../plan/SchedulerToProcedurePlanAdapter.kt | 16 +++- scheduler-server/build.gradle | 1 + .../GraphQLMerlinDatabaseService.java | 74 ++++++++++++++++++- .../services/MerlinDatabaseService.java | 5 ++ scheduler-worker/build.gradle | 2 + .../services/SynchronousSchedulerAgent.java | 10 +++ .../services/MockMerlinDatabaseService.java | 10 +++ .../SchedulingDSLCompilationServiceTests.java | 9 +++ 17 files changed, 258 insertions(+), 8 deletions(-) create mode 100644 procedural/timeline/src/main/kotlin/gov/nasa/ammos/aerie/procedural/timeline/collections/ExternalEvents.kt create mode 100644 procedural/timeline/src/main/kotlin/gov/nasa/ammos/aerie/procedural/timeline/payloads/ExternalEvent.kt create mode 100644 procedural/timeline/src/main/kotlin/gov/nasa/ammos/aerie/procedural/timeline/payloads/ExternalSource.kt create mode 100644 procedural/timeline/src/main/kotlin/gov/nasa/ammos/aerie/procedural/timeline/plan/EventQuery.kt diff --git a/procedural/constraints/src/test/java/gov/nasa/ammos/aerie/procedural/constraints/NotImplementedPlan.java b/procedural/constraints/src/test/java/gov/nasa/ammos/aerie/procedural/constraints/NotImplementedPlan.java index 18fb2ee459..3f22e74646 100644 --- a/procedural/constraints/src/test/java/gov/nasa/ammos/aerie/procedural/constraints/NotImplementedPlan.java +++ b/procedural/constraints/src/test/java/gov/nasa/ammos/aerie/procedural/constraints/NotImplementedPlan.java @@ -2,8 +2,10 @@ import gov.nasa.ammos.aerie.procedural.timeline.Interval; import gov.nasa.ammos.aerie.procedural.timeline.collections.Directives; +import gov.nasa.ammos.aerie.procedural.timeline.collections.ExternalEvents; import gov.nasa.ammos.aerie.procedural.timeline.ops.SerialSegmentOps; import gov.nasa.ammos.aerie.procedural.timeline.payloads.Segment; +import gov.nasa.ammos.aerie.procedural.timeline.plan.EventQuery; import gov.nasa.ammos.aerie.procedural.timeline.plan.Plan; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; @@ -51,4 +53,10 @@ public > TL resource( { throw new NotImplementedError(); } + + @NotNull + @Override + public ExternalEvents events(@NotNull final EventQuery query) { + throw new NotImplementedError(); + } } diff --git a/procedural/timeline/src/main/kotlin/gov/nasa/ammos/aerie/procedural/timeline/collections/ExternalEvents.kt b/procedural/timeline/src/main/kotlin/gov/nasa/ammos/aerie/procedural/timeline/collections/ExternalEvents.kt new file mode 100644 index 0000000000..00ea957b43 --- /dev/null +++ b/procedural/timeline/src/main/kotlin/gov/nasa/ammos/aerie/procedural/timeline/collections/ExternalEvents.kt @@ -0,0 +1,31 @@ +package gov.nasa.ammos.aerie.procedural.timeline.collections + +import gov.nasa.ammos.aerie.procedural.timeline.Timeline +import gov.nasa.ammos.aerie.procedural.timeline.BaseTimeline +import gov.nasa.ammos.aerie.procedural.timeline.payloads.activities.Instance +import gov.nasa.ammos.aerie.procedural.timeline.ops.* +import gov.nasa.ammos.aerie.procedural.timeline.ops.coalesce.CoalesceNoOp +import gov.nasa.ammos.aerie.procedural.timeline.payloads.ExternalEvent +import gov.nasa.ammos.aerie.procedural.timeline.payloads.ExternalSource +import gov.nasa.ammos.aerie.procedural.timeline.util.preprocessList + +/** + * A timeline of external events. + */ +data class ExternalEvents(private val timeline: Timeline): + Timeline by timeline, + NonZeroDurationOps, + ParallelOps +{ + constructor(vararg events: ExternalEvent): this(events.asList()) + constructor(events: List): this(BaseTimeline(::ExternalEvents, preprocessList(events, null))) + + /** Filter by one or more types. */ + fun filterByType(vararg types: String) = filter { it.type in types } + + /** Filter by one or more event sources. */ + fun filterBySource(vararg sources: ExternalSource) = filter { it.source in sources } + + /** Filter by one or more derivation groups. */ + fun filterByDerivationGroup(vararg groups: String) = filter { it.source.derivationGroup in groups } +} diff --git a/procedural/timeline/src/main/kotlin/gov/nasa/ammos/aerie/procedural/timeline/payloads/ExternalEvent.kt b/procedural/timeline/src/main/kotlin/gov/nasa/ammos/aerie/procedural/timeline/payloads/ExternalEvent.kt new file mode 100644 index 0000000000..110728d770 --- /dev/null +++ b/procedural/timeline/src/main/kotlin/gov/nasa/ammos/aerie/procedural/timeline/payloads/ExternalEvent.kt @@ -0,0 +1,20 @@ +package gov.nasa.ammos.aerie.procedural.timeline.payloads + +import gov.nasa.ammos.aerie.procedural.timeline.Interval +import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue + +/** An external event instance. */ +data class ExternalEvent( + /** The string name of this event. */ + @JvmField + val key: String, + /** The type of the event. */ + @JvmField + val type: String, + /** The source this event comes from. */ + @JvmField + val source: ExternalSource, + override val interval: Interval, +): IntervalLike { + override fun withNewInterval(i: Interval) = ExternalEvent(key, type, source, i) +} diff --git a/procedural/timeline/src/main/kotlin/gov/nasa/ammos/aerie/procedural/timeline/payloads/ExternalSource.kt b/procedural/timeline/src/main/kotlin/gov/nasa/ammos/aerie/procedural/timeline/payloads/ExternalSource.kt new file mode 100644 index 0000000000..a28d1d904d --- /dev/null +++ b/procedural/timeline/src/main/kotlin/gov/nasa/ammos/aerie/procedural/timeline/payloads/ExternalSource.kt @@ -0,0 +1,14 @@ +package gov.nasa.ammos.aerie.procedural.timeline.payloads + +/** + * An external source instance. Used for querying purposes - see EventQuery.kt. + * The included fields represent the primary key used to identify External Sources. + */ +data class ExternalSource( + /** The string name of this source. */ + @JvmField + val key: String, + /** The derivation group that this source is a member of. */ + @JvmField + val derivationGroup: String, +) diff --git a/procedural/timeline/src/main/kotlin/gov/nasa/ammos/aerie/procedural/timeline/plan/EventQuery.kt b/procedural/timeline/src/main/kotlin/gov/nasa/ammos/aerie/procedural/timeline/plan/EventQuery.kt new file mode 100644 index 0000000000..8e83478780 --- /dev/null +++ b/procedural/timeline/src/main/kotlin/gov/nasa/ammos/aerie/procedural/timeline/plan/EventQuery.kt @@ -0,0 +1,35 @@ +package gov.nasa.ammos.aerie.procedural.timeline.plan + +import gov.nasa.ammos.aerie.procedural.timeline.payloads.ExternalSource + +/** Fields for filtering events as they are queried. */ +data class EventQuery( + /** + * A nullable list of derivation groups; the event must belong to one of them if present. + * + * If null, all derivation groups are allowed. + */ + val derivationGroups: List?, + + /** + * A nullable list of eventTypes; the event must belong to one of them if present. + * + * If null, all types are allowed. + */ + val eventTypes: List?, + + /** + * A nullable list of sources (described as a tuple of the source's (key, derivation group name)); the event must + * belong to one of them if present. + * + * If null, all sources are allowed. + */ + val sources: List?, +) { + constructor(derivationGroup: String?, eventType: String?, source: ExternalSource?): this( + derivationGroup?.let { listOf(it) }, + eventType?.let { listOf(it) }, + source?.let { listOf(it) } + ) + constructor(): this(null as String?, null, null) +} diff --git a/procedural/timeline/src/main/kotlin/gov/nasa/ammos/aerie/procedural/timeline/plan/Plan.kt b/procedural/timeline/src/main/kotlin/gov/nasa/ammos/aerie/procedural/timeline/plan/Plan.kt index dfdf77287c..4a739b2f11 100644 --- a/procedural/timeline/src/main/kotlin/gov/nasa/ammos/aerie/procedural/timeline/plan/Plan.kt +++ b/procedural/timeline/src/main/kotlin/gov/nasa/ammos/aerie/procedural/timeline/plan/Plan.kt @@ -7,6 +7,7 @@ import gov.nasa.ammos.aerie.procedural.timeline.payloads.activities.AnyDirective import gov.nasa.ammos.aerie.procedural.timeline.collections.Directives import gov.nasa.ammos.aerie.procedural.timeline.ops.SerialSegmentOps import gov.nasa.ammos.aerie.procedural.timeline.payloads.Segment +import gov.nasa.ammos.aerie.procedural.timeline.collections.ExternalEvents import java.time.Instant /** An interface for querying plan information and simulation results. */ @@ -41,4 +42,9 @@ interface Plan { * @param name string name of the resource */ fun > resource(name: String, deserializer: (List>) -> TL): TL + + /** Get external events associated with this plan. */ + fun events(query: EventQuery): ExternalEvents + /** Get all external events across all derivation groups associated with this plan. */ + fun events() = events(EventQuery()) } diff --git a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/Procedure.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/Procedure.java index 9495c91961..306aff1468 100644 --- a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/Procedure.java +++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/goals/Procedure.java @@ -1,5 +1,6 @@ package gov.nasa.jpl.aerie.scheduler.goals; +import gov.nasa.ammos.aerie.procedural.timeline.payloads.ExternalEvent; import gov.nasa.jpl.aerie.merlin.driver.MissionModel; import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; import gov.nasa.ammos.aerie.procedural.scheduling.ProcedureMapper; @@ -43,7 +44,8 @@ public void run( final MissionModel missionModel, final Function lookupActivityType, final SimulationFacade simulationFacade, - final DirectiveIdGenerator idGenerator + final DirectiveIdGenerator idGenerator, + Map> eventsByDerivationGroup ) { final ProcedureMapper procedureMapper; try { @@ -57,6 +59,7 @@ public void run( final var planAdapter = new SchedulerToProcedurePlanAdapter( plan, planHorizon, + eventsByDerivationGroup, problem.getDiscreteExternalProfiles(), problem.getRealExternalProfiles() ); diff --git a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/model/Problem.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/model/Problem.java index a2ffc9e7f7..ae70e94449 100644 --- a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/model/Problem.java +++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/model/Problem.java @@ -1,5 +1,6 @@ package gov.nasa.jpl.aerie.scheduler.model; +import gov.nasa.ammos.aerie.procedural.timeline.payloads.ExternalEvent; import gov.nasa.jpl.aerie.constraints.model.DiscreteProfile; import gov.nasa.jpl.aerie.constraints.model.LinearProfile; import gov.nasa.jpl.aerie.merlin.driver.MissionModel; @@ -47,6 +48,7 @@ public class Problem { private final Map realExternalProfiles = new HashMap<>(); private final Map discreteExternalProfiles = new HashMap<>(); + private Map> eventsByDerivationGroup = new HashMap<>(); /** * the initial seed plan to start scheduling from @@ -171,6 +173,10 @@ public void setExternalProfile(final Map realExternalProf this.discreteExternalProfiles.putAll(discreteExternalProfiles); } + public void setEventsByDerivationGroup(final Map> events) { + this.eventsByDerivationGroup = events; + } + public Map getRealExternalProfiles(){ return this.realExternalProfiles; } @@ -179,6 +185,8 @@ public Map getDiscreteExternalProfiles(){ return this.discreteExternalProfiles; } + public Map> getEventsByDerivationGroup() { return this.eventsByDerivationGroup; } + public void setGoals(List goals){ goalsOrderedByPriority.clear(); goalsOrderedByPriority.addAll(goals); diff --git a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/solver/PrioritySolver.java b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/solver/PrioritySolver.java index 209a82f1e4..a6629d3182 100644 --- a/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/solver/PrioritySolver.java +++ b/scheduler-driver/src/main/java/gov/nasa/jpl/aerie/scheduler/solver/PrioritySolver.java @@ -46,7 +46,6 @@ import static com.google.common.base.Preconditions.checkNotNull; import static gov.nasa.jpl.aerie.merlin.protocol.types.Duration.MICROSECOND; import static gov.nasa.jpl.aerie.merlin.protocol.types.Duration.ZERO; -import static gov.nasa.jpl.aerie.merlin.protocol.types.Duration.min; /** * prototype scheduling algorithm that schedules activities for a plan @@ -323,7 +322,16 @@ private void satisfyGoal(Goal goal) throws SchedulingInterruptedException{ satisfyOptionGoal(optionGoal); } else if (goal instanceof Procedure procedure) { if (!analysisOnly) { - procedure.run(problem, plan.getEvaluation(), plan, problem.getMissionModel(), this.problem::getActivityType, this.simulationFacade, this.idGenerator); + procedure.run( + problem, + plan.getEvaluation(), + plan, + problem.getMissionModel(), + this.problem::getActivityType, + this.simulationFacade, + this.idGenerator, + this.problem.getEventsByDerivationGroup() + ); } } else { satisfyGoalGeneral(goal); diff --git a/scheduler-driver/src/main/kotlin/gov/nasa/jpl/aerie/scheduler/plan/SchedulerToProcedurePlanAdapter.kt b/scheduler-driver/src/main/kotlin/gov/nasa/jpl/aerie/scheduler/plan/SchedulerToProcedurePlanAdapter.kt index 638ea9f979..c23b5cbdbc 100644 --- a/scheduler-driver/src/main/kotlin/gov/nasa/jpl/aerie/scheduler/plan/SchedulerToProcedurePlanAdapter.kt +++ b/scheduler-driver/src/main/kotlin/gov/nasa/jpl/aerie/scheduler/plan/SchedulerToProcedurePlanAdapter.kt @@ -2,11 +2,14 @@ package gov.nasa.jpl.aerie.scheduler.plan import gov.nasa.ammos.aerie.procedural.timeline.Interval import gov.nasa.ammos.aerie.procedural.timeline.collections.Directives +import gov.nasa.ammos.aerie.procedural.timeline.collections.ExternalEvents +import gov.nasa.ammos.aerie.procedural.timeline.payloads.ExternalEvent import gov.nasa.ammos.aerie.procedural.timeline.ops.SerialSegmentOps import gov.nasa.ammos.aerie.procedural.timeline.payloads.Segment import gov.nasa.ammos.aerie.procedural.timeline.payloads.activities.Directive import gov.nasa.ammos.aerie.procedural.timeline.payloads.activities.DirectiveStart import gov.nasa.ammos.aerie.procedural.timeline.payloads.activities.DirectiveStart.Anchor.AnchorPoint.Companion.anchorToStart +import gov.nasa.ammos.aerie.procedural.timeline.plan.EventQuery import gov.nasa.ammos.aerie.procedural.timeline.util.duration.minus import gov.nasa.ammos.aerie.procedural.timeline.util.duration.plus import gov.nasa.jpl.aerie.constraints.model.DiscreteProfile @@ -23,8 +26,9 @@ import gov.nasa.jpl.aerie.scheduler.model.Plan as SchedulerPlan data class SchedulerToProcedurePlanAdapter( private val schedulerPlan: SchedulerPlan, private val planningHorizon: PlanningHorizon, + private val eventsByDerivationGroup: Map>, private val discreteExternalResources: Map, - private val realExternalResources: Map, + private val realExternalResources: Map ): TimelinePlan, SchedulerPlan by schedulerPlan { override fun totalBounds() = Interval.between(Duration.ZERO, planningHorizon.aerieHorizonDuration) @@ -55,6 +59,16 @@ data class SchedulerToProcedurePlanAdapter( return Directives(result) } + override fun events(query: EventQuery): ExternalEvents { + var result = if (query.derivationGroups != null) query.derivationGroups!!.flatMap { + eventsByDerivationGroup[it] + ?: throw Error("derivation group either doesn't exist or isn't associated with plan: $it") + } + else eventsByDerivationGroup.values.flatten() + if (query.eventTypes != null) result = result.filter { it.type in query.eventTypes!! } + if (query.sources != null) result = result.filter { it.source in query.sources!! } + return ExternalEvents(result) + } override fun > resource( name: String, deserializer: (List>) -> TL diff --git a/scheduler-server/build.gradle b/scheduler-server/build.gradle index 88cc2434bf..07287b3df4 100644 --- a/scheduler-server/build.gradle +++ b/scheduler-server/build.gradle @@ -24,6 +24,7 @@ dependencies { implementation project(':permissions') implementation project(':constraints') implementation project(':scheduler-driver') + implementation project(':procedural:timeline') implementation project(':procedural:scheduling') implementation project(':type-utils') diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/GraphQLMerlinDatabaseService.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/GraphQLMerlinDatabaseService.java index 11ee8ef064..347b013262 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/GraphQLMerlinDatabaseService.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/GraphQLMerlinDatabaseService.java @@ -1,5 +1,8 @@ package gov.nasa.jpl.aerie.scheduler.server.services; +import gov.nasa.ammos.aerie.procedural.timeline.Interval; +import gov.nasa.ammos.aerie.procedural.timeline.payloads.ExternalEvent; +import gov.nasa.ammos.aerie.procedural.timeline.payloads.ExternalSource; import gov.nasa.jpl.aerie.constraints.model.DiscreteProfile; import gov.nasa.jpl.aerie.constraints.model.LinearProfile; import gov.nasa.jpl.aerie.json.BasicParsers; @@ -60,7 +63,9 @@ import java.nio.file.Path; import java.time.Instant; import java.time.LocalDateTime; +import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; import java.util.*; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; @@ -979,8 +984,7 @@ public Optional> getExternalDatasets(final PlanId planId) @Override public ExternalProfiles getExternalProfiles(final PlanId planId) - throws MerlinServiceException, IOException - { + throws MerlinServiceException, IOException { final Map realProfiles = new HashMap<>(); final Map discreteProfiles = new HashMap<>(); final var resourceTypes = new ArrayList(); @@ -1004,7 +1008,48 @@ public ExternalProfiles getExternalProfiles(final PlanId planId) } } return new ExternalProfiles(realProfiles, discreteProfiles, resourceTypes); -} + } + + @Override + public Map> getExternalEvents(final PlanId planId, final Instant horizonStart) + throws MerlinServiceException, IOException { + final var derivationGroupsRequest = """ + query DerivationGroupsForPlan { + plan_derivation_group(where: {plan_id: {_eq: %d}}) { + derivation_group_name + } + } + """.formatted(planId.id()); + final JsonObject derivationGroupsResponse = postRequest(derivationGroupsRequest).get(); + final var derivationGroups = Json.createArrayBuilder( + derivationGroupsResponse.getJsonObject("data").getJsonArray("plan_derivation_group") + .stream().map($ -> $.asJsonObject().getString("derivation_group_name")).toList() + ).build(); + + final var eventsRequest = """ + query DerivedEventsForPlan { + derived_events(where: {derivation_group_name: {_in: %s}}) { + source_key + event_type_name + event_key + duration + derivation_group_name + source_range + start_time + valid_at + } + }""".formatted(derivationGroups); + final JsonObject eventsResponse = postRequest(eventsRequest).get(); + + final var data = eventsResponse.getJsonObject("data").getJsonArray("derived_events"); + final var unorganized = parseExternalEvents(data, horizonStart); + final var result = new HashMap>(); + for (final var event: unorganized) { + final var list = result.computeIfAbsent(event.source.derivationGroup, $ -> new ArrayList<>()); + list.add(event); + } + return result; + } private Collection extractResourceTypes(final ProfileSet profileSet){ final var resourceTypes = new ArrayList(); @@ -1085,7 +1130,7 @@ private ProfileSet parseProfiles(JsonArray dataset){ return new ProfileSet(realProfiles, discreteProfiles); } - public ResourceProfile> parseProfile(JsonObject profile, JsonParser dynamicsParser){ + private ResourceProfile> parseProfile(JsonObject profile, JsonParser dynamicsParser){ // Profile segments are stored with their start offset relative to simulation start // We must convert these to durations describing how long each segment lasts final var type = chooseP(discreteValueSchemaTypeP, realValueSchemaTypeP).parse(profile.getJsonObject("type")).getSuccessOrThrow(); @@ -1130,6 +1175,27 @@ public ResourceProfile> parseProfile(JsonObject pr return ResourceProfile.of(type, segments); } + private List parseExternalEvents(final JsonArray eventsJson, final Instant horizonStart) { + final var result = new ArrayList(); + for (final var eventJson : eventsJson) { + final var e = eventJson.asJsonObject(); + final var start = new Duration( + horizonStart.until(ZonedDateTime.parse(e.getString("start_time")).toInstant(), ChronoUnit.MICROS) + ); + final var end = start.plus(Duration.fromString(e.getString("duration"))); + result.add(new ExternalEvent( + e.getString("event_key"), + e.getString("event_type_name"), + new ExternalSource( + e.getString("source_key"), + e.getString("derivation_group_name") + ), + Interval.between(start, end) + )); + } + return result; + } + private Map parseSimulatedActivities(JsonArray simulatedActivitiesArray, Instant simulationStart) throws InvalidJsonException { diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/MerlinDatabaseService.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/MerlinDatabaseService.java index 8856c101fc..18939f8d1f 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/MerlinDatabaseService.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/MerlinDatabaseService.java @@ -1,5 +1,6 @@ package gov.nasa.jpl.aerie.scheduler.server.services; +import gov.nasa.ammos.aerie.procedural.timeline.payloads.ExternalEvent; import gov.nasa.jpl.aerie.merlin.driver.SimulationResults; import gov.nasa.jpl.aerie.merlin.protocol.model.SchedulerModel; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; @@ -26,6 +27,7 @@ import java.io.IOException; import java.time.Instant; import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.Optional; @@ -100,6 +102,9 @@ void ensurePlanExists(final PlanId planId) ExternalProfiles getExternalProfiles(final PlanId planId) throws MerlinServiceException, IOException; + Map> getExternalEvents(final PlanId planId, final Instant horizonStart) + throws MerlinServiceException, IOException; + /** * Gets resource types associated to a plan, those coming from the mission model as well as those coming from external dataset resources * @param planId the plan id diff --git a/scheduler-worker/build.gradle b/scheduler-worker/build.gradle index b139bcbc25..2e3d69b2b2 100644 --- a/scheduler-worker/build.gradle +++ b/scheduler-worker/build.gradle @@ -113,6 +113,8 @@ dependencies { implementation project(':scheduler-server') implementation project(':parsing-utilities') implementation project(':constraints') + implementation project(':procedural:timeline') + implementation project(':procedural:scheduling') implementation 'io.javalin:javalin:5.6.3' implementation 'org.slf4j:slf4j-simple:2.0.7' diff --git a/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/services/SynchronousSchedulerAgent.java b/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/services/SynchronousSchedulerAgent.java index 5d046daeca..1347e2afac 100644 --- a/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/services/SynchronousSchedulerAgent.java +++ b/scheduler-worker/src/main/java/gov/nasa/jpl/aerie/scheduler/worker/services/SynchronousSchedulerAgent.java @@ -9,6 +9,7 @@ import java.net.URLClassLoader; import java.nio.charset.StandardCharsets; import java.nio.file.Path; +import java.time.Instant; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -20,6 +21,7 @@ import java.util.jar.JarFile; import java.util.stream.Collectors; +import gov.nasa.ammos.aerie.procedural.timeline.payloads.ExternalEvent; import gov.nasa.jpl.aerie.merlin.driver.MissionModel; import gov.nasa.jpl.aerie.merlin.driver.MissionModelLoader; import gov.nasa.jpl.aerie.merlin.driver.SimulationEngineConfiguration; @@ -147,12 +149,14 @@ public void schedule( schedulerMissionModel.schedulerModel() ); final var externalProfiles = loadExternalProfiles(planMetadata.planId()); + final var externalEventsByDerivationGroup = loadExternalEvents(planMetadata.planId(), planMetadata.horizon().getStartInstant()); final var initialSimulationResultsAndDatasetId = loadSimulationResults(planMetadata); //seed the problem with the initial plan contents final var loadedPlanComponents = loadInitialPlan(planMetadata, problem, initialSimulationResultsAndDatasetId.map(Pair::getKey)); problem.setInitialPlan(loadedPlanComponents.schedulerPlan(), initialSimulationResultsAndDatasetId.map(Pair::getKey)); problem.setExternalProfile(externalProfiles.realProfiles(), externalProfiles.discreteProfiles()); + problem.setEventsByDerivationGroup(externalEventsByDerivationGroup); //apply constraints/goals to the problem final var compiledGlobalSchedulingConditions = new ArrayList(); final var failedGlobalSchedulingConditions = new ArrayList>(); @@ -338,6 +342,12 @@ private ExternalProfiles loadExternalProfiles(final PlanId planId) return merlinDatabaseService.getExternalProfiles(planId); } + private Map> loadExternalEvents(final PlanId planId, final Instant horizonStart) + throws MerlinServiceException, IOException + { + return merlinDatabaseService.getExternalEvents(planId, horizonStart); + } + private Optional storeSimulationResults( SimulationData simulationData, PlanMetadata planMetadata, diff --git a/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/MockMerlinDatabaseService.java b/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/MockMerlinDatabaseService.java index 67ba1bbe02..f79c1acf1d 100644 --- a/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/MockMerlinDatabaseService.java +++ b/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/MockMerlinDatabaseService.java @@ -1,5 +1,6 @@ package gov.nasa.jpl.aerie.scheduler.worker.services; +import gov.nasa.ammos.aerie.procedural.timeline.payloads.ExternalEvent; import gov.nasa.jpl.aerie.merlin.driver.SimulationResults; import gov.nasa.jpl.aerie.merlin.protocol.model.SchedulerModel; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; @@ -18,11 +19,13 @@ import gov.nasa.jpl.aerie.scheduler.server.models.PlanMetadata; import gov.nasa.jpl.aerie.scheduler.server.models.ResourceType; import gov.nasa.jpl.aerie.scheduler.server.services.MerlinDatabaseService; +import gov.nasa.jpl.aerie.scheduler.server.services.MerlinServiceException; import gov.nasa.jpl.aerie.types.ActivityDirective; import gov.nasa.jpl.aerie.types.ActivityDirectiveId; import gov.nasa.jpl.aerie.types.MissionModelId; import org.apache.commons.lang3.tuple.Pair; +import java.io.IOException; import java.nio.file.Path; import java.time.Instant; import java.util.ArrayList; @@ -157,6 +160,13 @@ public ExternalProfiles getExternalProfiles(final PlanId planId) { return externalProfiles; } + @Override + public Map> getExternalEvents(final PlanId planId, final Instant horizonStart) + throws MerlinServiceException, IOException + { + return Map.of(); + } + @Override public Collection getResourceTypes(final PlanId planId) { diff --git a/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/SchedulingDSLCompilationServiceTests.java b/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/SchedulingDSLCompilationServiceTests.java index d2e0c2fc26..efd274cb7a 100644 --- a/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/SchedulingDSLCompilationServiceTests.java +++ b/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/SchedulingDSLCompilationServiceTests.java @@ -1,5 +1,6 @@ package gov.nasa.jpl.aerie.scheduler.worker.services; +import gov.nasa.ammos.aerie.procedural.timeline.payloads.ExternalEvent; import gov.nasa.jpl.aerie.constraints.tree.ActivitySpan; import gov.nasa.jpl.aerie.constraints.tree.DiscreteProfileFromDuration; import gov.nasa.jpl.aerie.constraints.tree.DiscreteResource; @@ -49,6 +50,7 @@ import org.junit.jupiter.api.TestInstance; import java.io.IOException; +import java.time.Instant; import java.util.Collection; import java.util.List; import java.util.Map; @@ -109,6 +111,13 @@ public ExternalProfiles getExternalProfiles(final PlanId planId) { return null; } + @Override + public Map> getExternalEvents(final PlanId planId, final Instant horizonStart) + throws MerlinServiceException, IOException + { + return Map.of(); + } + @Override public Collection getResourceTypes(final PlanId planId) { return null; From 17a66a80b01ece46fefc99d66be72f2a65344173 Mon Sep 17 00:00:00 2001 From: pranav-super Date: Mon, 4 Nov 2024 13:05:15 -0800 Subject: [PATCH 2/3] add external events scheduling e2e tests --- .../procedures/ExternalEventsSimpleGoal.java | 24 ++ .../ExternalEventsSourceQueryGoal.java | 39 +++ .../ExternalEventsTypeQueryGoal.java | 31 +++ .../scheduling/ExternalEventsTests.java | 234 ++++++++++++++++++ .../gov/nasa/jpl/aerie/e2e/utils/GQL.java | 70 ++++++ .../jpl/aerie/e2e/utils/HasuraRequests.java | 153 ++++++++++++ 6 files changed, 551 insertions(+) create mode 100644 e2e-tests/src/main/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/procedures/ExternalEventsSimpleGoal.java create mode 100644 e2e-tests/src/main/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/procedures/ExternalEventsSourceQueryGoal.java create mode 100644 e2e-tests/src/main/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/procedures/ExternalEventsTypeQueryGoal.java create mode 100644 e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/ExternalEventsTests.java diff --git a/e2e-tests/src/main/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/procedures/ExternalEventsSimpleGoal.java b/e2e-tests/src/main/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/procedures/ExternalEventsSimpleGoal.java new file mode 100644 index 0000000000..93b5e32aa6 --- /dev/null +++ b/e2e-tests/src/main/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/procedures/ExternalEventsSimpleGoal.java @@ -0,0 +1,24 @@ +package gov.nasa.jpl.aerie.e2e.procedural.scheduling.procedures; + +import gov.nasa.ammos.aerie.procedural.scheduling.annotations.SchedulingProcedure; +import gov.nasa.ammos.aerie.procedural.scheduling.Goal; +import gov.nasa.ammos.aerie.procedural.scheduling.plan.EditablePlan; +import gov.nasa.ammos.aerie.procedural.timeline.payloads.activities.DirectiveStart; +import gov.nasa.ammos.aerie.procedural.timeline.plan.EventQuery; +import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; + +@SchedulingProcedure +public record ExternalEventsSimpleGoal() implements Goal { + @Override + public void run(@NotNull final EditablePlan plan) { + EventQuery eventQuery = new EventQuery("TestGroup", null, null); + + for (final var e: plan.events(eventQuery)) { + plan.create("BiteBanana", new DirectiveStart.Absolute(e.getInterval().start), Map.of("biteSize", SerializedValue.of(1))); + } + plan.commit(); + } +} diff --git a/e2e-tests/src/main/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/procedures/ExternalEventsSourceQueryGoal.java b/e2e-tests/src/main/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/procedures/ExternalEventsSourceQueryGoal.java new file mode 100644 index 0000000000..e48739c2a7 --- /dev/null +++ b/e2e-tests/src/main/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/procedures/ExternalEventsSourceQueryGoal.java @@ -0,0 +1,39 @@ +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.EditablePlan; +import gov.nasa.ammos.aerie.procedural.timeline.payloads.ExternalSource; +import gov.nasa.ammos.aerie.procedural.timeline.payloads.activities.DirectiveStart; +import gov.nasa.ammos.aerie.procedural.timeline.plan.EventQuery; +import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Map; + +@SchedulingProcedure +public record ExternalEventsSourceQueryGoal() implements Goal { + @Override + public void run(@NotNull final EditablePlan plan) { + + // extract events belonging to the second source + EventQuery eventQuery = new EventQuery( + null, + null, + List.of(new ExternalSource("NewTest.json", "TestGroup_2")) + ); + + for (final var e: plan.events(eventQuery)) { + // filter events that we schedule off of by key + if (e.key.contains("01")) { + plan.create( + "BiteBanana", + // place the directive such that it is coincident with the event's start + new DirectiveStart.Absolute(e.getInterval().start), + Map.of("biteSize", SerializedValue.of(1))); + } + } + plan.commit(); + } +} diff --git a/e2e-tests/src/main/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/procedures/ExternalEventsTypeQueryGoal.java b/e2e-tests/src/main/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/procedures/ExternalEventsTypeQueryGoal.java new file mode 100644 index 0000000000..c6793d8f82 --- /dev/null +++ b/e2e-tests/src/main/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/procedures/ExternalEventsTypeQueryGoal.java @@ -0,0 +1,31 @@ +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.EditablePlan; +import gov.nasa.ammos.aerie.procedural.timeline.payloads.activities.DirectiveStart; +import gov.nasa.ammos.aerie.procedural.timeline.plan.EventQuery; +import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Map; + +@SchedulingProcedure +public record ExternalEventsTypeQueryGoal() implements Goal { + @Override + public void run(@NotNull final EditablePlan plan) { + + // demonstrate more complicated query functionality + EventQuery eventQuery = new EventQuery( + List.of("TestGroup", "TestGroup_2"), + List.of("TestType"), + null + ); + + for (final var e: plan.events(eventQuery)) { + plan.create("BiteBanana", new DirectiveStart.Absolute(e.getInterval().start), Map.of("biteSize", SerializedValue.of(1))); + } + plan.commit(); + } +} diff --git a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/ExternalEventsTests.java b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/ExternalEventsTests.java new file mode 100644 index 0000000000..c1928a9c54 --- /dev/null +++ b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/ExternalEventsTests.java @@ -0,0 +1,234 @@ +package gov.nasa.jpl.aerie.e2e.procedural.scheduling; + +import gov.nasa.jpl.aerie.e2e.types.GoalInvocationId; +import gov.nasa.jpl.aerie.e2e.types.Plan; +import gov.nasa.jpl.aerie.e2e.utils.GatewayRequests; +import gov.nasa.jpl.aerie.e2e.utils.HasuraRequests; +import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ExternalEventsTests extends ProceduralSchedulingSetup { + private GoalInvocationId procedureId; + private final static String SOURCE_TYPE = "TestType"; + private final static String EVENT_TYPE = "TestType"; + private final static String ADDITIONAL_EVENT_TYPE = EVENT_TYPE + "_2"; + private final static String DERIVATION_GROUP = "TestGroup"; + private final static String ADDITIONAL_DERIVATION_GROUP = DERIVATION_GROUP + "_2"; + + private final HasuraRequests.ExternalSource externalSource = new HasuraRequests.ExternalSource( + "Test.json", + SOURCE_TYPE, + DERIVATION_GROUP, + "2024-01-01T00:00:00Z", + "2023-01-01T00:00:00Z", + "2023-01-08T00:00:00Z", + "2024-10-01T00:00:00Z" + ); + private final List externalEvents = List.of( + new HasuraRequests.ExternalEvent( + "Event_01", + EVENT_TYPE, + externalSource.key(), + externalSource.derivation_group_name(), + "2023-01-01T01:00:00Z", + "01:00:00" + ), + new HasuraRequests.ExternalEvent( + "Event_02", + EVENT_TYPE, + externalSource.key(), + externalSource.derivation_group_name(), + "2023-01-01T03:00:00Z", + "01:00:00" + ), + new HasuraRequests.ExternalEvent( + "Event_03", + EVENT_TYPE, + externalSource.key(), + externalSource.derivation_group_name(), + "2023-01-01T05:00:00Z", + "01:00:00" + ) + ); + + private final HasuraRequests.ExternalSource additionalExternalSource = new HasuraRequests.ExternalSource( + "NewTest.json", + SOURCE_TYPE, + ADDITIONAL_DERIVATION_GROUP, + "2024-01-01T00:00:00Z", + "2023-01-01T00:00:00Z", + "2023-01-08T00:00:00Z", + "2024-10-01T00:00:00Z" + ); + + private final List additionalExternalEvents = List.of( + new HasuraRequests.ExternalEvent( + "Event_01", + EVENT_TYPE, + additionalExternalSource.key(), + additionalExternalSource.derivation_group_name(), + "2023-01-02T01:00:00Z", + "01:00:00" + ), + new HasuraRequests.ExternalEvent( + "Event_02", + ADDITIONAL_EVENT_TYPE, + additionalExternalSource.key(), + additionalExternalSource.derivation_group_name(), + "2023-01-02T03:00:00Z", + "01:00:00" + ), + new HasuraRequests.ExternalEvent( + "Event_03", + ADDITIONAL_EVENT_TYPE, + additionalExternalSource.key(), + additionalExternalSource.derivation_group_name(), + "2023-01-02T05:00:00Z", + "01:00:00" + ) + ); + + @BeforeEach + void localBeforeEach() throws IOException { + // Upload some External Events (and associated infrastructure) + hasura.insertExternalSourceType(SOURCE_TYPE); + hasura.insertExternalEventType(EVENT_TYPE); + hasura.insertDerivationGroup(DERIVATION_GROUP, SOURCE_TYPE); + hasura.insertExternalSource(externalSource); + hasura.insertExternalEvents(externalEvents); + hasura.insertPlanDerivationGroupAssociation(planId, DERIVATION_GROUP); + + // Upload additional External Events in a different derivation group and of a different type + hasura.insertExternalEventType(ADDITIONAL_EVENT_TYPE); + hasura.insertDerivationGroup(ADDITIONAL_DERIVATION_GROUP, SOURCE_TYPE); + hasura.insertExternalSource(additionalExternalSource); + hasura.insertExternalEvents(additionalExternalEvents); + hasura.insertPlanDerivationGroupAssociation(planId, ADDITIONAL_DERIVATION_GROUP); + } + + @AfterEach + void localAfterEach() throws IOException { + hasura.deleteSchedulingGoal(procedureId.goalId()); + + // External Event Related + hasura.deletePlanDerivationGroupAssociation(planId, DERIVATION_GROUP); + hasura.deletePlanDerivationGroupAssociation(planId, ADDITIONAL_DERIVATION_GROUP); + hasura.deleteExternalSource(externalSource); + hasura.deleteExternalSource(additionalExternalSource); + hasura.deleteDerivationGroup(DERIVATION_GROUP); + hasura.deleteDerivationGroup(ADDITIONAL_DERIVATION_GROUP); + hasura.deleteExternalSourceType(SOURCE_TYPE); + hasura.deleteExternalEventType(EVENT_TYPE); + hasura.deleteExternalEventType(ADDITIONAL_EVENT_TYPE); + } + + @Test + void testExternalEventSimple() throws IOException { + // first, run the goal + try (final var gateway = new GatewayRequests(playwright)) { + int procedureJarId = gateway.uploadJarFile("build/libs/ExternalEventsSimpleGoal.jar"); + // Add Scheduling Procedure + procedureId = hasura.createSchedulingSpecProcedure( + "Test Scheduling Procedure", + procedureJarId, + specId, + 0 + ); + } + hasura.awaitScheduling(specId); + final var plan = hasura.getPlan(planId); + final var activities = plan.activityDirectives(); + + // ensure the order lines up with the events' + activities.sort(Comparator.comparing(Plan.ActivityDirective::startOffset)); + + // compare arrays + assertEquals(externalEvents.size(), activities.size()); + for (int i = 0; i < activities.size(); i++) { + Instant activityStartTime = Duration.addToInstant( + Instant.parse(planStartTimestamp), + Duration.fromString(activities.get(i).startOffset()) + ); + assertEquals(externalEvents.get(i).start_time(), activityStartTime.toString()); + } + } + + @Test + void testExternalEventTypeQuery() throws IOException { + // first, run the goal + try (final var gateway = new GatewayRequests(playwright)) { + int procedureJarId = gateway.uploadJarFile("build/libs/ExternalEventsTypeQueryGoal.jar"); + // Add Scheduling Procedure + procedureId = hasura.createSchedulingSpecProcedure( + "Test Scheduling Procedure", + procedureJarId, + specId, + 0 + ); + } + hasura.awaitScheduling(specId); + final var plan = hasura.getPlan(planId); + final var activities = plan.activityDirectives(); + + // ensure the orderings line up + activities.sort(Comparator.comparing(Plan.ActivityDirective::startOffset)); + + // get the set of events we expect (anything in TestGroup or TestGroup_2, and of type TestType) + List expected = new ArrayList<>(); + expected.addAll(externalEvents); + expected.addAll( + additionalExternalEvents.stream() + .filter(e -> e.event_type_name().equals(EVENT_TYPE)) + .toList() + ); + + // explicitly ensure the orderings line up + expected.sort(Comparator.comparing(HasuraRequests.ExternalEvent::start_time)); + + // compare arrays + assertEquals(expected.size(), activities.size()); + for (int i = 0; i < activities.size(); i++) { + Instant activityStartTime = Duration.addToInstant( + Instant.parse(planStartTimestamp), + Duration.fromString(activities.get(i).startOffset()) + ); + assertEquals(activityStartTime.toString(), expected.get(i).start_time()); + } + } + + @Test + void testExternalEventSourceQuery() throws IOException { + // first, run the goal + try (final var gateway = new GatewayRequests(playwright)) { + int procedureJarId = gateway.uploadJarFile("build/libs/ExternalEventsSourceQueryGoal.jar"); + // Add Scheduling Procedure + procedureId = hasura.createSchedulingSpecProcedure( + "Test Scheduling Procedure", + procedureJarId, + specId, + 0 + ); + } + hasura.awaitScheduling(specId); + final var plan = hasura.getPlan(planId); + final var activities = plan.activityDirectives(); + + // only 1 activity this time + assertEquals(1, activities.size()); + Instant activityStartTime = Duration.addToInstant( + Instant.parse(planStartTimestamp), + Duration.fromString(activities.get(0).startOffset()) + ); + assertEquals(activityStartTime.toString(), additionalExternalEvents.get(0).start_time()); + } +} diff --git a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/utils/GQL.java b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/utils/GQL.java index 3de72613b4..8d07e3fa95 100644 --- a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/utils/GQL.java +++ b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/utils/GQL.java @@ -82,6 +82,38 @@ mutation CreateActivityDirective($activityDirectiveInsertInput: activity_directi id } }"""), + CREATE_EXTERNAL_EVENT_TYPE(""" + mutation CreateExternalEventType($eventType: external_event_type_insert_input!) { + createExternalEventType: insert_external_event_type_one(object: $eventType) { + name + } + }"""), + CREATE_EXTERNAL_EVENTS(""" + mutation InsertExternalEvents($objects: [external_event_insert_input!]!) { + insertExternalEvents: insert_external_event(objects: $objects) { + returning { + key + } + } + }"""), + CREATE_EXTERNAL_SOURCE(""" + mutation InsertExternalSource($object: external_source_insert_input!) { + insertExternalSource: insert_external_source_one(object: $object) { + key + } + }"""), + CREATE_EXTERNAL_SOURCE_TYPE(""" + mutation CreateExternalSourceType($sourceType: external_source_type_insert_input!) { + createExternalSourceType: insert_external_source_type_one(object: $sourceType) { + name + } + }"""), + CREATE_DERIVATION_GROUP(""" + mutation CreateDerivationGroup($derivationGroup: derivation_group_insert_input!) { + createDerivationGroup: insert_derivation_group_one(object: $derivationGroup) { + name + } + }"""), CREATE_MISSION_MODEL(""" mutation CreateMissionModel($model: mission_model_insert_input!) { insert_mission_model_one(object: $model) { @@ -95,6 +127,12 @@ mutation CreatePlan($plan: plan_insert_input!) { revision } }"""), + CREATE_PLAN_DERIVATION_GROUP(""" + mutation CreatePlanDerivationGroup($source: plan_derivation_group_insert_input!) { + planExternalSourceLink: insert_plan_derivation_group_one(object: $source) { + derivation_group_name + } + }"""), CREATE_SCHEDULING_SPEC_GOAL(""" mutation CreateSchedulingSpecGoal($spec_goal: scheduling_specification_goals_insert_input!) { insert_scheduling_specification_goals_one(object: $spec_goal) { @@ -140,12 +178,38 @@ mutation DeleteConstraint($id: Int!) { id } }"""), + DELETE_DERIVATION_GROUP(""" + mutation DeleteDerivationGroup($name: String!) { + deleteDerivationGroup: delete_derivation_group(where: { name: { _eq: $name } }) { + returning { + name + } + } + }"""), DELETE_EXTERNAL_DATASET(""" mutation deleteExtProfile($plan_id: Int!, $dataset_id: Int!) { delete_plan_dataset_by_pk(plan_id:$plan_id, dataset_id:$dataset_id) { dataset_id } }"""), + DELETE_EXTERNAL_EVENT_TYPE(""" + mutation DeleteExternalEventType($name: String!) { + deleteExternalEventType: delete_external_event_type_by_pk(name: $name) { + name + } + }"""), + DELETE_EXTERNAL_SOURCE(""" + mutation DeleteExternalSource($derivationGroupName: String!, $sourceKey: String!) { + deleteExternalSource: delete_external_source_by_pk(derivation_group_name: $derivationGroupName, key: $sourceKey) { + key + } + }"""), + DELETE_EXTERNAL_SOURCE_TYPE(""" + mutation DeleteExternalSourceType($name: String!) { + deleteExternalSourceType: delete_external_source_type_by_pk(name: $name) { + name + } + }"""), DELETE_MISSION_MODEL(""" mutation DeleteModel($id: Int!) { delete_mission_model_by_pk(id: $id) { @@ -174,6 +238,12 @@ mutation DeletePlan($id: Int!) { } } }"""), + DELETE_PLAN_DERIVATION_GROUP(""" + mutation DeletePlanExternalSource($derivationGroupName: String!, $planId: Int!) { + planDerivationGroupLink: delete_plan_derivation_group_by_pk(derivation_group_name: $derivationGroupName, plan_id: $planId) { + derivation_group_name + } + }"""), DELETE_SCHEDULING_GOAL(""" mutation DeleteSchedulingGoal($goalId: Int!) { delete_scheduling_specification_goals(where: {goal_id: {_eq: $goalId}}){ diff --git a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/utils/HasuraRequests.java b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/utils/HasuraRequests.java index ccb55dbe50..71b5dff661 100644 --- a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/utils/HasuraRequests.java +++ b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/utils/HasuraRequests.java @@ -10,6 +10,7 @@ import javax.json.Json; import javax.json.JsonArray; +import javax.json.JsonArrayBuilder; import javax.json.JsonValue; import javax.json.JsonObject; import java.io.IOException; @@ -98,6 +99,11 @@ private JsonObject makeRequest( } } + //region Records + public record ExternalEvent(String key, String event_type_name, String source_key, String derivation_group_name, String start_time, String duration) {} + public record ExternalSource(String key, String source_type_name, String derivation_group_name, String valid_at, String start_time, String end_time, String created_at){} + //endregion Records + //region Mission Model public int createMissionModel(int jarId, String name, String mission, String version) throws IOException, InterruptedException @@ -988,6 +994,153 @@ public void deleteExternalDataset(int planId, int datasetId) throws IOException } //endregion + // region External Events + public String insertExternalSourceType( + String name + ) throws IOException { + final var insertExternalSourceTypeBuilder = Json.createObjectBuilder() + .add("name", name) + .build(); + final var variables = Json.createObjectBuilder().add("sourceType", insertExternalSourceTypeBuilder).build(); + return makeRequest(GQL.CREATE_EXTERNAL_SOURCE_TYPE, variables) + .getJsonObject("createExternalSourceType") + .getString("name"); + } + public String insertExternalEventType( + String name + ) throws IOException { + final var insertExternalSourceTypeBuilder = Json.createObjectBuilder() + .add("name", name) + .build(); + final var variables = Json.createObjectBuilder().add("eventType", insertExternalSourceTypeBuilder).build(); + return makeRequest(GQL.CREATE_EXTERNAL_EVENT_TYPE, variables) + .getJsonObject("createExternalEventType") + .getString("name"); + } + public String insertDerivationGroup( + String name, + String sourceTypeName + ) throws IOException { + final var insertDerivationGroupBuilder = Json.createObjectBuilder() + .add("name", name) + .add("source_type_name", sourceTypeName) + .build(); + final var variables = Json.createObjectBuilder().add("derivationGroup", insertDerivationGroupBuilder).build(); + return makeRequest(GQL.CREATE_DERIVATION_GROUP, variables) + .getJsonObject("createDerivationGroup") + .getString("name"); + } + public String insertExternalSource( + ExternalSource externalSource + ) throws IOException { + final var insertExternalSourceBuilder = Json.createObjectBuilder() + .add("key", externalSource.key()) + .add("source_type_name", externalSource.source_type_name()) + .add("derivation_group_name", externalSource.derivation_group_name()) + .add("valid_at", externalSource.valid_at()) + .add("start_time", externalSource.start_time()) + .add("end_time", externalSource.end_time()) + .add("created_at", externalSource.created_at()) + .build(); + final var variables = Json.createObjectBuilder().add("object", insertExternalSourceBuilder).build(); + return makeRequest(GQL.CREATE_EXTERNAL_SOURCE, variables) + .getJsonObject("insertExternalSource") + .getString("key"); + } + public JsonArray insertExternalEvents( + List externalEvents + ) throws IOException { + JsonArrayBuilder formattedEvents = Json.createArrayBuilder(); + for (ExternalEvent e : externalEvents) { + formattedEvents.add( + Json.createObjectBuilder() + .add("key", e.key()) + .add("event_type_name", e.event_type_name()) + .add("source_key", e.source_key()) + .add("derivation_group_name", e.derivation_group_name()) + .add("start_time", e.start_time()) + .add("duration", e.duration()) + .build() + ); + } + final var variables = Json.createObjectBuilder() + .add("objects", formattedEvents.build()) + .build(); + return makeRequest(GQL.CREATE_EXTERNAL_EVENTS, variables) + .getJsonObject("insertExternalEvents") + .getJsonArray("returning"); + } + public String insertPlanDerivationGroupAssociation( + int planId, + String derivationGroupName + ) throws IOException { + final var insertPlanDerivationGroupBuilder = Json.createObjectBuilder() + .add("plan_id", planId) + .add("derivation_group_name", derivationGroupName) + .build(); + final var variables = Json.createObjectBuilder().add("source", insertPlanDerivationGroupBuilder).build(); + return makeRequest(GQL.CREATE_PLAN_DERIVATION_GROUP, variables) + .getJsonObject("planExternalSourceLink") + .getString("derivation_group_name"); + } + + public String deleteExternalSourceType( + String name + ) throws IOException { + final var variables = Json.createObjectBuilder() + .add("name", name) + .build(); + return makeRequest(GQL.DELETE_EXTERNAL_SOURCE_TYPE, variables) + .getJsonObject("deleteExternalSourceType") + .getString("name"); + } + public String deleteExternalEventType( + String name + ) throws IOException { + final var variables = Json.createObjectBuilder() + .add("name", name) + .build(); + return makeRequest(GQL.DELETE_EXTERNAL_EVENT_TYPE, variables) + .getJsonObject("deleteExternalEventType") + .getString("name"); + } + public JsonArray deleteDerivationGroup( + String name + ) throws IOException { + final var variables = Json.createObjectBuilder() + .add("name", name) + .build(); + return makeRequest(GQL.DELETE_DERIVATION_GROUP, variables) + .getJsonObject("deleteDerivationGroup") + .getJsonArray("returning"); + } + public String deleteExternalSource( + ExternalSource externalSource + ) throws IOException { + final var variables = Json.createObjectBuilder() + .add("sourceKey", externalSource.key()) + .add("derivationGroupName", externalSource.derivation_group_name()) + .build(); + // NOTE: this deletes external events as well, as deletions of sources cascade to their contained events. + return makeRequest(GQL.DELETE_EXTERNAL_SOURCE, variables) + .getJsonObject("deleteExternalSource") + .getString("key"); + } + public String deletePlanDerivationGroupAssociation( + int planId, + String derivationGroupName + ) throws IOException { + final var variables = Json.createObjectBuilder() + .add("planId", planId) + .add("derivationGroupName", derivationGroupName) + .build(); + return makeRequest(GQL.DELETE_PLAN_DERIVATION_GROUP, variables) + .getJsonObject("planDerivationGroupLink") + .getString("derivation_group_name"); + } + + // endregion + //region Constraints public List checkConstraints(int planID) throws IOException { final var variables = Json.createObjectBuilder() From b9055eb791494da728c4472da471b0d4b56be507 Mon Sep 17 00:00:00 2001 From: pranav-super Date: Mon, 4 Nov 2024 13:05:21 -0800 Subject: [PATCH 3/3] update README.md --- procedural/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/procedural/README.md b/procedural/README.md index a17cee1523..4cc8446d0b 100644 --- a/procedural/README.md +++ b/procedural/README.md @@ -4,4 +4,4 @@ This subproject holds the libraries provided to the users for procedural schedul ## Documentation -To generate the unified docs, run `./gradlew :procedural:dokkaHtmlMultiModule`. It will be available in `procedural/build/dokka/htmlMultiModule/index.html`. To view it locally, you'll need a static file server to avoid CORS problems. In IntelliJ, you can right-click on `index.html` and select `Open In -> Browser -> ...` and this will start a server for you. +To generate the unified docs, run `./gradlew dokkaHtmlMultiModule`. It will be available in `procedural/build/dokka/htmlMultiModule/index.html`. To view it locally, you'll need a static file server to avoid CORS problems. In IntelliJ, you can right-click on `index.html` and select `Open In -> Browser -> ...` and this will start a server for you.