diff --git a/deployment/hasura/metadata/databases/tables/merlin/mission_model.yaml b/deployment/hasura/metadata/databases/tables/merlin/mission_model.yaml index fe689dc6bf..989c391a9e 100644 --- a/deployment/hasura/metadata/databases/tables/merlin/mission_model.yaml +++ b/deployment/hasura/metadata/databases/tables/merlin/mission_model.yaml @@ -119,18 +119,12 @@ delete_permissions: event_triggers: - definition: - enable_manual: false + enable_manual: true insert: columns: "*" update: columns: - - id - - revision - jar_id - - mission - - name - - version - - owner name: refreshActivityTypes retry_conf: interval_sec: 10 @@ -138,18 +132,12 @@ event_triggers: timeout_sec: 300 webhook: "{{AERIE_MERLIN_URL}}/refreshActivityTypes" - definition: - enable_manual: false + enable_manual: true insert: columns: "*" update: columns: - - id - - revision - - jar_id - - mission - - name - - version - - owner + - jar_id name: refreshModelParameters retry_conf: interval_sec: 10 @@ -157,7 +145,7 @@ event_triggers: timeout_sec: 300 webhook: "{{AERIE_MERLIN_URL}}/refreshModelParameters" - definition: - enable_manual: false + enable_manual: true insert: columns: "*" update: diff --git a/deployment/hasura/migrations/Aerie/7_pending_hasura_events/down.sql b/deployment/hasura/migrations/Aerie/7_pending_hasura_events/down.sql new file mode 100644 index 0000000000..47384ae291 --- /dev/null +++ b/deployment/hasura/migrations/Aerie/7_pending_hasura_events/down.sql @@ -0,0 +1,65 @@ +drop view hasura.refresh_resource_type_logs; +drop view hasura.refresh_model_parameter_logs; +drop view hasura.refresh_activity_type_logs; +drop function hasura.get_event_logs(_trigger_name text); + +create function hasura.get_event_logs(_trigger_name text) +returns table ( + model_id int, + model_name text, + model_version text, + triggering_user text, + delivered boolean, + success boolean, + tries int, + created_at timestamp, + next_retry_at timestamp, + status int, + error json, + error_message text, + error_type text +) +stable +security invoker +language plpgsql as $$ +begin + return query ( + select + (el.payload->'data'->'new'->>'id')::int as model_id, + el.payload->'data'->'new'->>'name' as model_name, + el.payload->'data'->'new'->>'version' as model_version, + el.payload->'session_variables'->>'x-hasura-user-id' as triggering_user, + el.delivered, + eil.status is not distinct from 200 as success, -- is not distinct from to catch `null` + el.tries, + el.created_at, + el.next_retry_at, + eil.status, + eil.response -> 'data'-> 'message' as error, + eil.response -> 'data'-> 'message'->>'message' as error_message, + eil.response -> 'data'-> 'message'->>'type' as error_type + from hdb_catalog.event_log el + join hdb_catalog.event_invocation_logs eil on el.id = eil.event_id + where trigger_name = _trigger_name); +end; +$$; +comment on function hasura.get_event_logs(_trigger_name text) is e'' + 'Get the logs for every run of a Hasura event with the specified trigger name.'; + +create view hasura.refresh_activity_type_logs as + select * from hasura.get_event_logs('refreshActivityTypes'); +comment on view hasura.refresh_activity_type_logs is e'' + 'View containing logs for every run of the Hasura event `refreshActivityTypes`.'; + +create view hasura.refresh_model_parameter_logs as + select * from hasura.get_event_logs('refreshModelParameters'); +comment on view hasura.refresh_model_parameter_logs is e'' + 'View containing logs for every run of the Hasura event `refreshModelParameters`.'; + +create view hasura.refresh_resource_type_logs as + select * from hasura.get_event_logs('refreshResourceTypes'); +comment on view hasura.refresh_resource_type_logs is e'' + 'View containing logs for every run of the Hasura event `refreshResourceTypes`.'; + + +call migrations.mark_migration_rolled_back('7') diff --git a/deployment/hasura/migrations/Aerie/7_pending_hasura_events/up.sql b/deployment/hasura/migrations/Aerie/7_pending_hasura_events/up.sql new file mode 100644 index 0000000000..427d5282ca --- /dev/null +++ b/deployment/hasura/migrations/Aerie/7_pending_hasura_events/up.sql @@ -0,0 +1,68 @@ +drop view hasura.refresh_resource_type_logs; +drop view hasura.refresh_model_parameter_logs; +drop view hasura.refresh_activity_type_logs; +drop function hasura.get_event_logs(_trigger_name text); + +create function hasura.get_event_logs(_trigger_name text) +returns table ( + model_id int, + model_name text, + model_version text, + triggering_user text, + pending boolean, + delivered boolean, + success boolean, + tries int, + created_at timestamp, + next_retry_at timestamp, + status int, + error json, + error_message text, + error_type text +) +stable +security invoker +language plpgsql as $$ +begin + return query ( + select + (el.payload->'data'->'new'->>'id')::int as model_id, + el.payload->'data'->'new'->>'name' as model_name, + el.payload->'data'->'new'->>'version' as model_version, + el.payload->'session_variables'->>'x-hasura-user-id' as triggering_user, + eil.id is null as pending, + el.delivered, + eil.status is not distinct from 200 as success, -- is not distinct from to catch `null` + el.tries, + el.created_at, + el.next_retry_at, + eil.status, + eil.response -> 'data'-> 'message' as error, + -- Javalin uses "title" as it's "message" field + coalesce(eil.response -> 'data'-> 'message'->> 'message', + eil.response -> 'data'-> 'message'->> 'title') as error_message, + eil.response -> 'data'-> 'message'->>'type' as error_type + from hdb_catalog.event_log el + left join hdb_catalog.event_invocation_logs eil on el.id = eil.event_id + where trigger_name = _trigger_name); +end; +$$; +comment on function hasura.get_event_logs(_trigger_name text) is e'' + 'Get the logs for every run of a Hasura event with the specified trigger name.'; + +create view hasura.refresh_activity_type_logs as + select * from hasura.get_event_logs('refreshActivityTypes'); +comment on view hasura.refresh_activity_type_logs is e'' + 'View containing logs for every run of the Hasura event `refreshActivityTypes`.'; + +create view hasura.refresh_model_parameter_logs as + select * from hasura.get_event_logs('refreshModelParameters'); +comment on view hasura.refresh_model_parameter_logs is e'' + 'View containing logs for every run of the Hasura event `refreshModelParameters`.'; + +create view hasura.refresh_resource_type_logs as + select * from hasura.get_event_logs('refreshResourceTypes'); +comment on view hasura.refresh_resource_type_logs is e'' + 'View containing logs for every run of the Hasura event `refreshResourceTypes`.'; + +call migrations.mark_migration_applied('7') diff --git a/deployment/postgres-init-db/sql/applied_migrations.sql b/deployment/postgres-init-db/sql/applied_migrations.sql index 5d2459096b..ce2b97f16e 100644 --- a/deployment/postgres-init-db/sql/applied_migrations.sql +++ b/deployment/postgres-init-db/sql/applied_migrations.sql @@ -9,3 +9,4 @@ call migrations.mark_migration_applied('3'); call migrations.mark_migration_applied('4'); call migrations.mark_migration_applied('5'); call migrations.mark_migration_applied('6'); +call migrations.mark_migration_applied('7'); diff --git a/deployment/postgres-init-db/sql/views/hasura/hasura_event_logs.sql b/deployment/postgres-init-db/sql/views/hasura/hasura_event_logs.sql index 224f4ce0e3..b402f8f5a1 100644 --- a/deployment/postgres-init-db/sql/views/hasura/hasura_event_logs.sql +++ b/deployment/postgres-init-db/sql/views/hasura/hasura_event_logs.sql @@ -4,6 +4,7 @@ returns table ( model_name text, model_version text, triggering_user text, + pending boolean, delivered boolean, success boolean, tries int, @@ -24,6 +25,7 @@ begin el.payload->'data'->'new'->>'name' as model_name, el.payload->'data'->'new'->>'version' as model_version, el.payload->'session_variables'->>'x-hasura-user-id' as triggering_user, + eil.id is null as pending, el.delivered, eil.status is not distinct from 200 as success, -- is not distinct from to catch `null` el.tries, @@ -31,10 +33,12 @@ begin el.next_retry_at, eil.status, eil.response -> 'data'-> 'message' as error, - eil.response -> 'data'-> 'message'->>'message' as error_message, + -- Javalin uses "title" as it's "message" field + coalesce(eil.response -> 'data'-> 'message'->> 'message', + eil.response -> 'data'-> 'message'->> 'title') as error_message, eil.response -> 'data'-> 'message'->>'type' as error_type from hdb_catalog.event_log el - join hdb_catalog.event_invocation_logs eil on el.id = eil.event_id + left join hdb_catalog.event_invocation_logs eil on el.id = eil.event_id where trigger_name = _trigger_name); end; $$; @@ -55,6 +59,3 @@ create view hasura.refresh_resource_type_logs as select * from hasura.get_event_logs('refreshResourceTypes'); comment on view hasura.refresh_resource_type_logs is e'' 'View containing logs for every run of the Hasura event `refreshResourceTypes`.'; - - - diff --git a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/MissionModelTests.java b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/MissionModelTests.java index 7b0776e285..bae7b43f10 100644 --- a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/MissionModelTests.java +++ b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/MissionModelTests.java @@ -21,6 +21,7 @@ import static gov.nasa.jpl.aerie.e2e.types.ValueSchema.*; import static java.util.Map.entry; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @TestInstance(TestInstance.Lifecycle.PER_CLASS) @@ -377,14 +378,17 @@ void hasuraEventLogsAreAccessible() throws IOException { // Check Activity Type Refresh Event Logs final var activityTypeRefreshLogs = modelLogs.refreshActivityTypesLogs(); assertEquals(1, activityTypeRefreshLogs.size()); - final var activityTypeLog = activityTypeRefreshLogs.get(0); + final var activityTypeLog = activityTypeRefreshLogs.getFirst(); assertEquals("Aerie Legacy", activityTypeLog.triggeringUser()); + assertFalse(activityTypeLog.pending()); assertTrue(activityTypeLog.delivered()); assertTrue(activityTypeLog.success()); assertEquals(1, activityTypeLog.tries()); - assertEquals(200, activityTypeLog.status()); + + assertTrue(activityTypeLog.status().isPresent()); + assertEquals(200, activityTypeLog.status().get()); assertTrue(activityTypeLog.error().isEmpty()); assertTrue(activityTypeLog.errorMessage().isEmpty()); @@ -393,14 +397,17 @@ void hasuraEventLogsAreAccessible() throws IOException { // Check Model Parameter Refresh Event Logs final var modelParamRefreshLogs = modelLogs.refreshModelParamsLogs(); assertEquals(1, modelParamRefreshLogs.size()); - final var modelParamLog = modelParamRefreshLogs.get(0); + final var modelParamLog = modelParamRefreshLogs.getFirst(); assertEquals("Aerie Legacy", modelParamLog.triggeringUser()); + assertFalse(modelParamLog.pending()); assertTrue(modelParamLog.delivered()); assertTrue(modelParamLog.success()); assertEquals(1, modelParamLog.tries()); - assertEquals(200, modelParamLog.status()); + + assertTrue(modelParamLog.status().isPresent()); + assertEquals(200, modelParamLog.status().get()); assertTrue(modelParamLog.error().isEmpty()); assertTrue(modelParamLog.errorMessage().isEmpty()); @@ -409,14 +416,17 @@ void hasuraEventLogsAreAccessible() throws IOException { // Check Resource Type Refresh Event Logs final var resourceTypeRefreshLogs = modelLogs.refreshResourceTypesLogs(); assertEquals(1, resourceTypeRefreshLogs.size()); - final var resourceTypeLog = resourceTypeRefreshLogs.get(0); + final var resourceTypeLog = resourceTypeRefreshLogs.getFirst(); assertEquals("Aerie Legacy", resourceTypeLog.triggeringUser()); + assertFalse(resourceTypeLog.pending()); assertTrue(resourceTypeLog.delivered()); assertTrue(resourceTypeLog.success()); assertEquals(1, resourceTypeLog.tries()); - assertEquals(200, resourceTypeLog.status()); + + assertTrue(resourceTypeLog.status().isPresent()); + assertEquals(200, resourceTypeLog.status().get()); assertTrue(resourceTypeLog.error().isEmpty()); assertTrue(resourceTypeLog.errorMessage().isEmpty()); diff --git a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/ModelEventLogs.java b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/ModelEventLogs.java index b342e0b153..546a6dadd2 100644 --- a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/ModelEventLogs.java +++ b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/ModelEventLogs.java @@ -14,17 +14,20 @@ public record ModelEventLogs( ) { public record EventLog( String triggeringUser, + boolean pending, boolean delivered, boolean success, int tries, String createdAt, - int status, + Optional status, Optional error, Optional errorMessage, Optional errorType ) { public static EventLog fromJSON(JsonObject json) { + final Optional status = json.isNull("status") ? + Optional.empty() : Optional.of(json.getInt("status")); final Optional error = json.isNull("error") ? Optional.empty() : Optional.of(json.getJsonObject("error")); final Optional errorMsg = json.isNull("error_message") ? @@ -34,11 +37,12 @@ public static EventLog fromJSON(JsonObject json) { return new EventLog( json.getString("triggering_user"), + json.getBoolean("pending"), json.getBoolean("delivered"), json.getBoolean("success"), json.getInt("tries"), json.getString("created_at"), - json.getInt("status"), + status, error, errorMsg, errorType); 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 ed7105974d..0ab5fcccba 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 @@ -273,6 +273,7 @@ query getModelLogs($modelId: Int!) { version refresh_activity_type_logs(order_by: {created_at: desc}) { triggering_user + pending delivered success tries @@ -284,6 +285,7 @@ query getModelLogs($modelId: Int!) { } refresh_model_parameter_logs(order_by: {created_at: desc}) { triggering_user + pending delivered success tries @@ -295,6 +297,7 @@ query getModelLogs($modelId: Int!) { } refresh_resource_type_logs(order_by: {created_at: desc}) { triggering_user + pending delivered success tries 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 d9f2666b64..aced50855d 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 @@ -163,17 +163,19 @@ public ModelEventLogs awaitModelEventLogs(int modelId, int timeout) throws IOExc final var variables = Json.createObjectBuilder().add("modelId", modelId).build(); for(int i = 0; i < timeout; ++i){ - final var logs = makeRequest(GQL.GET_MODEL_EVENT_LOGS, variables).getJsonObject("mission_model"); - if(logs.getJsonArray("refresh_activity_type_logs").isEmpty() - || logs.getJsonArray("refresh_model_parameter_logs").isEmpty() - || logs.getJsonArray("refresh_resource_type_logs").isEmpty()) { + final var logs = ModelEventLogs.fromJSON(makeRequest(GQL.GET_MODEL_EVENT_LOGS, variables) + .getJsonObject("mission_model")); + + if(logs.refreshActivityTypesLogs().getLast().pending() || + logs.refreshModelParamsLogs().getLast().pending() || + logs.refreshResourceTypesLogs().getLast().pending()) { try { Thread.sleep(1000); // 1s } catch (InterruptedException e) { throw new RuntimeException(e); } } else { - return ModelEventLogs.fromJSON(logs); + return logs; } } throw new TimeoutError("One or more mission model Hausra events did not return after " + timeout + " seconds"); diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/http/MerlinBindings.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/http/MerlinBindings.java index 1d45f90867..9f82696a64 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/http/MerlinBindings.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/http/MerlinBindings.java @@ -12,6 +12,7 @@ import gov.nasa.jpl.aerie.merlin.server.models.PlanId; import gov.nasa.jpl.aerie.merlin.server.services.GenerateConstraintsLibAction; import gov.nasa.jpl.aerie.merlin.server.services.GetSimulationResultsAction; +import gov.nasa.jpl.aerie.merlin.server.services.LocalMissionModelService; import gov.nasa.jpl.aerie.merlin.server.services.MissionModelService; import gov.nasa.jpl.aerie.merlin.server.services.PlanService; import gov.nasa.jpl.aerie.permissions.Action; @@ -126,6 +127,8 @@ private void postRefreshModelParameters(final Context ctx) { ctx.status(400).result(ResponseSerializers.serializeInvalidEntityException(ex).toString()); } catch (final MissionModelService.NoSuchMissionModelException ex) { ctx.status(404).result(ResponseSerializers.serializeNoSuchMissionModelException(ex).toString()); + } catch (final LocalMissionModelService.MissionModelLoadException ex) { + ctx.status(400).result(ResponseSerializers.serializeMissionModelLoadException(ex).toString()); } } @@ -140,6 +143,8 @@ private void postRefreshActivityTypes(final Context ctx) { ctx.status(400).result(ResponseSerializers.serializeInvalidEntityException(ex).toString()); } catch (final MissionModelService.NoSuchMissionModelException ex) { ctx.status(404).result(ResponseSerializers.serializeNoSuchMissionModelException(ex).toString()); + } catch (final LocalMissionModelService.MissionModelLoadException ex) { + ctx.status(400).result(ResponseSerializers.serializeMissionModelLoadException(ex).toString()); } } @@ -154,6 +159,8 @@ private void postRefreshResourceTypes(Context ctx) { ctx.status(400).result(ResponseSerializers.serializeInvalidEntityException(ex).toString()); } catch (final MissionModelService.NoSuchMissionModelException ex) { ctx.status(404).result(ResponseSerializers.serializeNoSuchMissionModelException(ex).toString()); + } catch (final LocalMissionModelService.MissionModelLoadException ex) { + ctx.status(400).result(ResponseSerializers.serializeMissionModelLoadException(ex).toString()); } } @@ -171,6 +178,8 @@ private void getResourceTypes(final Context ctx) { ctx.status(400).result(ResponseSerializers.serializeInvalidEntityException(ex).toString()); } catch (final MissionModelService.NoSuchMissionModelException ex) { ctx.status(404).result(ResponseSerializers.serializeNoSuchMissionModelException(ex).toString()); + } catch (final LocalMissionModelService.MissionModelLoadException ex) { + ctx.status(400).result(ResponseSerializers.serializeMissionModelLoadException(ex).toString()); } } @@ -286,6 +295,8 @@ private void validateActivityArguments(final Context ctx) { ctx.status(400).result(ResponseSerializers.serializeInvalidJsonException(ex).toString()); } catch (final InvalidEntityException ex) { ctx.status(400).result(ResponseSerializers.serializeInvalidEntityException(ex).toString()); + } catch (final LocalMissionModelService.MissionModelLoadException ex) { + ctx.status(400).result(ResponseSerializers.serializeMissionModelLoadException(ex).toString()); } } @@ -307,6 +318,8 @@ private void validateModelArguments(final Context ctx) { ctx.status(400).result(ResponseSerializers.serializeInvalidJsonException(ex).toString()); } catch (final InvalidEntityException ex) { ctx.status(400).result(ResponseSerializers.serializeInvalidEntityException(ex).toString()); + } catch (final LocalMissionModelService.MissionModelLoadException ex) { + ctx.status(400).result(ResponseSerializers.serializeMissionModelLoadException(ex).toString()); } } @@ -329,6 +342,8 @@ private void validatePlan(final Context ctx) { ctx.status(404).result(ResponseSerializers.serializeNoSuchPlanException(ex).toString()); } catch (final MissionModelService.NoSuchMissionModelException ex) { ctx.status(404).result(ResponseSerializers.serializeNoSuchMissionModelException(ex).toString()); + } catch (final LocalMissionModelService.MissionModelLoadException ex) { + ctx.status(400).result(ResponseSerializers.serializeMissionModelLoadException(ex).toString()); } } @@ -348,6 +363,8 @@ private void getModelEffectiveArguments(final Context ctx) { ctx.status(400).result(ResponseSerializers.serializeInvalidJsonException(ex).toString()); } catch (final InvalidEntityException ex) { ctx.status(400).result(ResponseSerializers.serializeInvalidEntityException(ex).toString()); + } catch (final LocalMissionModelService.MissionModelLoadException ex) { + ctx.status(400).result(ResponseSerializers.serializeMissionModelLoadException(ex).toString()); } } @@ -373,6 +390,8 @@ private void getActivityEffectiveArguments(final Context ctx) { ctx.status(400).result(ResponseSerializers.serializeInvalidJsonException(ex).toString()); } catch (final InvalidEntityException ex) { ctx.status(400).result(ResponseSerializers.serializeInvalidEntityException(ex).toString()); + } catch (final LocalMissionModelService.MissionModelLoadException ex) { + ctx.status(400).result(ResponseSerializers.serializeMissionModelLoadException(ex).toString()); } } @@ -391,6 +410,8 @@ private void getActivityEffectiveArgumentsBulk(final Context ctx) { ctx.status(400).result(ResponseSerializers.serializeInvalidJsonException(ex).toString()); } catch (final InvalidEntityException ex) { ctx.status(400).result(ResponseSerializers.serializeInvalidEntityException(ex).toString()); + } catch (final LocalMissionModelService.MissionModelLoadException ex) { + ctx.status(400).result(ResponseSerializers.serializeMissionModelLoadException(ex).toString()); } } diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/http/ResponseSerializers.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/http/ResponseSerializers.java index 2416129b0c..b2117e5107 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/http/ResponseSerializers.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/http/ResponseSerializers.java @@ -538,6 +538,7 @@ public static JsonValue serializeMissionModelLoadException( // TODO: Improve diagnostic information? return Json.createObjectBuilder() .add("message", ex.getMessage()) + .add("type", "Mission Model Load Failure") .build(); } diff --git a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/LocalMissionModelService.java b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/LocalMissionModelService.java index bd71ea6a4e..c1add5d963 100644 --- a/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/LocalMissionModelService.java +++ b/merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/LocalMissionModelService.java @@ -94,12 +94,10 @@ public Map getResourceSchemas(final String missionModelId) * @param missionModelId The ID of the mission model to load. * @return The set of all activity types in the named mission model, indexed by name. * @throws NoSuchMissionModelException If no mission model is known by the given ID. - * @throws MissionModelLoadException If the mission model cannot be loaded -- the JAR may be invalid, or the mission model - * it contains may not abide by the expected contract at load time. */ @Override public Map getActivityTypes(final String missionModelId) - throws NoSuchMissionModelException, MissionModelLoadException + throws NoSuchMissionModelException { try { return missionModelRepository.getActivityTypes(missionModelId);