diff --git a/e2e-tests/src/tests/scheduling-and-decomposition.test.ts b/e2e-tests/src/tests/scheduling-and-decomposition.test.ts new file mode 100644 index 0000000000..08339b2e28 --- /dev/null +++ b/e2e-tests/src/tests/scheduling-and-decomposition.test.ts @@ -0,0 +1,128 @@ +import { expect, test } from '@playwright/test'; +import req, {awaitScheduling} from '../utilities/requests.js'; + +test.describe('Scheduling and Decomposing Activities', () => { + const plan_start_timestamp = "2021-001T00:00:00.000"; + const plan_end_timestamp = "2021-090T00:00:00.000"; + let mission_model_id: number; + let plan_id: number; + let plan_revision: number; + let specification_id: number; + + test.beforeAll(async ({request}) => { + let rd = Math.random() * 100000; + let jar_id = await req.uploadJarFile(request); + // Add Mission Model + const model: MissionModelInsertInput = { + jar_id, + mission: 'aerie_e2e_tests', + name: 'Banananation (e2e tests)', + version: rd + "", + }; + mission_model_id = await req.createMissionModel(request, model); + await delay(1000); + + // Add Plan + const plan_input: CreatePlanInput = { + model_id: mission_model_id, + name: 'test_plan' + rd, + start_time: plan_start_timestamp, + duration: "24:00:00" + }; + plan_id = await req.createPlan(request, plan_input); + plan_revision = await req.getPlanRevision(request, plan_id); + + // Add Scheduling Spec + const schedulingSpecification: SchedulingSpecInsertInput = { + horizon_end: plan_end_timestamp, + horizon_start: plan_start_timestamp, + plan_id: plan_id, + plan_revision: plan_revision, + simulation_arguments: {}, + analysis_only: false + } + specification_id = await req.insertSchedulingSpecification(request, schedulingSpecification); + + // Add Goal + const schedulingGoal: SchedulingGoalInsertInput = + { + description: "Cardinality goal", + model_id: mission_model_id, + name: "my second scheduling goal!" + rd, + definition: + `export default function cardinalityGoalExample() { + return Goal.CardinalityGoal({ + activityTemplate: ActivityTemplates.parent({ label: "unlabeled"}), + specification: { duration: Temporal.Duration.from({ seconds: 10 }) }, + }); + }` + }; + let goal_id = await req.insertSchedulingGoal(request, schedulingGoal); + + // Assign Scheduling Spec Goal + const specGoal: SchedulingSpecGoalInsertInput = { + // @ts-ignore + goal_id: goal_id, + priority: 0, + specification_id: specification_id, + }; + await req.createSchedulingSpecGoal(request, specGoal); + }); + + test.afterAll(async ({request}) => { + // Deleting Plan and Model cascades the rest of the cleanup + await req.deleteMissionModel(request, mission_model_id); + await req.deletePlan(request, plan_id); + }); + + test('Scheduling Properly assigns Span Ids on Decomposing Activities', async ({request}) => { + // Run Scheduling + const schedulingResults = await awaitScheduling(request, specification_id); + const dataset_id = schedulingResults.datasetId; + + // Check Plan Activities + let plan = await req.getPlan(request, plan_id) + expect(plan.id).toEqual(plan_id); + expect(plan.activity_directives.length).toEqual(1); + + let parentActivity = plan.activity_directives.pop(); + expect(parentActivity.type).toEqual('parent'); + + const simulated_activities = (await req.getSimulationDatasetByDatasetId(request, dataset_id)).simulated_activities + expect(simulated_activities.length).toEqual(7) // 1 parent, 2 children, 4 grandchildren + + // Assert Parent Span + let parentSpans = simulated_activities.filter(a => a.type === "parent"); + expect(parentSpans.length).toEqual(1); + + let parentSpan = parentSpans.pop(); + expect(parentSpan.parent_id).toBeNull(); + expect(parentSpan.activity_directive.id).toEqual(parentActivity.id) + + // Assert Children Spans + let childrenSpans = simulated_activities.filter(a => a.type === "child"); + expect(childrenSpans.length).toEqual(2); + childrenSpans.forEach(a => { + expect(a.activity_directive).toBeNull(); + expect(a.parent_id).not.toBeNull(); + expect(a.parent_id).toEqual(parentSpan.id); + }); + let child1 = childrenSpans.pop(); + let child2 = childrenSpans.pop(); + + // Assert Grandchildren Spans + let grandchildrenSpans = simulated_activities.filter(a => a.type === "grandchild"); + expect(grandchildrenSpans.length).toEqual(4); + grandchildrenSpans.forEach(a => expect(a.activity_directive).toBeNull()); + + let gcFirstChild = grandchildrenSpans.filter(a => a.parent_id === child1.id); + expect(gcFirstChild.length).toEqual(2); + + let gcSecondChild = grandchildrenSpans.filter(a => a.parent_id === child2.id); + expect(gcSecondChild.length).toEqual(2); + }); +}); + +function delay(ms: number) { + return new Promise( resolve => setTimeout(resolve, ms) ); +} diff --git a/e2e-tests/src/types/simulation.d.ts b/e2e-tests/src/types/simulation.d.ts index f3f9ed6461..0843268feb 100644 --- a/e2e-tests/src/types/simulation.d.ts +++ b/e2e-tests/src/types/simulation.d.ts @@ -66,4 +66,7 @@ type SimulatedActivity = { duration: string | null; start_time: string; start_offset: string; + type: string; + parent_id: number | null; + id: number; } diff --git a/e2e-tests/src/utilities/gql.ts b/e2e-tests/src/utilities/gql.ts index ac8d4288fe..ace3e5c846 100644 --- a/e2e-tests/src/utilities/gql.ts +++ b/e2e-tests/src/utilities/gql.ts @@ -169,6 +169,9 @@ const gql = { duration start_time start_offset + parent_id + type: activity_type_name + id } } } diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/GraphQLMerlinService.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/GraphQLMerlinService.java index 95cedbad69..58efd0e1ed 100644 --- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/GraphQLMerlinService.java +++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/GraphQLMerlinService.java @@ -1654,9 +1654,8 @@ public HashMap postSpans(final DatasetId datasetId, response = postRequest(req, arguments).get(); final var returnedIds = response.getJsonObject("data").getJsonObject("insert_span").getJsonArray("returning"); final var simIdToPostgresId = new HashMap(ids.size()); - int i = 0; - for (final var id : ids) { - simIdToPostgresId.put(id, (long) returnedIds.get(i).asJsonObject().getInt("id")); + for (int i = 0; i< ids.size(); ++i) { + simIdToPostgresId.put(ids.get(i), (long) returnedIds.get(i).asJsonObject().getInt("id")); } return simIdToPostgresId; }