From 0c261d6ce89c0875edcbc51b5f050a3ed12606f1 Mon Sep 17 00:00:00 2001 From: woodjes Date: Mon, 20 Nov 2023 14:21:31 +0000 Subject: [PATCH 1/9] Add job to scheduledJobService to run and action any pending entries #CTCTOWALTZ-2680 #6860 --- .../waltz/data/changelog/ChangeLogDao.java | 5 +- .../survey/SurveyInstanceActionQueueDao.java | 126 ++++++++++++++++ .../waltz/data/survey/SurveyInstanceDao.java | 31 ++-- .../survey/SurveyQuestionResponseDao.java | 20 ++- .../jobs/harness/SurveyInstanceHarness.java | 14 +- .../tools/survey/SurveyTemplateMigrate.java | 3 +- .../survey/SurveyInstanceActionQueueItem.java | 26 ++++ .../survey/SurveyInstanceActionStatus.java | 11 ++ .../service/changelog/ChangeLogService.java | 8 +- .../end_user_app/EndUserAppService.java | 3 +- .../scheduled_job/ScheduledJobService.java | 10 +- .../SurveyInstanceActionQueueService.java | 136 ++++++++++++++++++ .../service/survey/SurveyInstanceService.java | 37 +++-- .../endpoints/api/SurveyInstanceEndpoint.java | 3 + 14 files changed, 384 insertions(+), 49 deletions(-) create mode 100644 waltz-data/src/main/java/org/finos/waltz/data/survey/SurveyInstanceActionQueueDao.java create mode 100644 waltz-model/src/main/java/org/finos/waltz/model/survey/SurveyInstanceActionQueueItem.java create mode 100644 waltz-model/src/main/java/org/finos/waltz/model/survey/SurveyInstanceActionStatus.java create mode 100644 waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceActionQueueService.java diff --git a/waltz-data/src/main/java/org/finos/waltz/data/changelog/ChangeLogDao.java b/waltz-data/src/main/java/org/finos/waltz/data/changelog/ChangeLogDao.java index ea14a422b3..93b71224cd 100644 --- a/waltz-data/src/main/java/org/finos/waltz/data/changelog/ChangeLogDao.java +++ b/waltz-data/src/main/java/org/finos/waltz/data/changelog/ChangeLogDao.java @@ -304,10 +304,11 @@ public List> getContributionScoresForUsers(List userIds) { } - public int write(ChangeLog changeLog) { + public int write(Optional tx, ChangeLog changeLog) { checkNotNull(changeLog, "changeLog must not be null"); + DSLContext dslContext = tx.orElse(dsl); - return dsl + return dslContext .insertInto(CHANGE_LOG) .set(CHANGE_LOG.MESSAGE, changeLog.message()) .set(CHANGE_LOG.PARENT_ID, changeLog.parentReference().id()) diff --git a/waltz-data/src/main/java/org/finos/waltz/data/survey/SurveyInstanceActionQueueDao.java b/waltz-data/src/main/java/org/finos/waltz/data/survey/SurveyInstanceActionQueueDao.java new file mode 100644 index 0000000000..bd884b2811 --- /dev/null +++ b/waltz-data/src/main/java/org/finos/waltz/data/survey/SurveyInstanceActionQueueDao.java @@ -0,0 +1,126 @@ +package org.finos.waltz.data.survey; + +import org.finos.waltz.common.DateTimeUtilities; +import org.finos.waltz.model.survey.ImmutableSurveyInstanceActionQueueItem; +import org.finos.waltz.model.survey.SurveyInstanceAction; +import org.finos.waltz.model.survey.SurveyInstanceActionQueueItem; +import org.finos.waltz.model.survey.SurveyInstanceActionStatus; +import org.finos.waltz.model.survey.SurveyInstanceStatus; +import org.finos.waltz.schema.tables.records.SurveyInstanceActionQueueRecord; +import org.jooq.Condition; +import org.jooq.DSLContext; +import org.jooq.Record; +import org.jooq.Record1; +import org.jooq.RecordMapper; +import org.jooq.SelectConditionStep; +import org.jooq.impl.DSL; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Repository; + +import java.sql.Timestamp; +import java.util.List; + +import static java.lang.String.format; +import static java.util.Optional.ofNullable; +import static org.finos.waltz.schema.Tables.SURVEY_INSTANCE_ACTION_QUEUE; + +@Repository +public class SurveyInstanceActionQueueDao { + + private final DSLContext dsl; + + public static final RecordMapper TO_DOMAIN_MAPPER = r -> { + SurveyInstanceActionQueueRecord record = r.into(SURVEY_INSTANCE_ACTION_QUEUE); + return ImmutableSurveyInstanceActionQueueItem.builder() + .id(record.getId()) + .action(SurveyInstanceAction.valueOf(record.getAction())) + .surveyInstanceId(record.getSurveyInstanceId()) + .actionParams(record.getActionParams()) + .initialState(SurveyInstanceStatus.valueOf(record.getInitialState())) + .submittedAt(DateTimeUtilities.toLocalDateTime(record.getSubmittedAt())) + .submittedBy(record.getSubmittedBy()) + .actionedAt(ofNullable(record.getActionedAt()).map(Timestamp::toLocalDateTime).orElse(null)) + .status(SurveyInstanceActionStatus.valueOf(record.getStatus())) + .message(record.getMessage()) + .provenance(record.getProvenance()) + .build(); + }; + + + @Autowired + SurveyInstanceActionQueueDao(DSLContext dsl) { + this.dsl = dsl; + } + + + public List findPendingActions() { + Condition isPending = SURVEY_INSTANCE_ACTION_QUEUE.STATUS.eq(SurveyInstanceActionStatus.PENDING.name()); + return mkSelectByCondition(dsl, isPending) + .orderBy(SURVEY_INSTANCE_ACTION_QUEUE.SUBMITTED_AT) + .fetch(TO_DOMAIN_MAPPER); + } + + + public SurveyInstanceActionQueueItem getById(Long id) { + Condition idCondition = SURVEY_INSTANCE_ACTION_QUEUE.ID.eq(id); + return mkSelectByCondition(dsl, idCondition) + .fetchOne(TO_DOMAIN_MAPPER); + } + + + private SelectConditionStep mkSelectByCondition(DSLContext dslContext, Condition condition) { + return dslContext + .select(SURVEY_INSTANCE_ACTION_QUEUE.fields()) + .from(SURVEY_INSTANCE_ACTION_QUEUE) + .where(condition); + } + + + public void updateActionStatus(DSLContext tx, Long actionId, SurveyInstanceActionStatus instanceActionStatus, String msg) { + int updated = tx + .update(SURVEY_INSTANCE_ACTION_QUEUE) + .set(SURVEY_INSTANCE_ACTION_QUEUE.ACTIONED_AT, DateTimeUtilities.nowUtcTimestamp()) + .set(SURVEY_INSTANCE_ACTION_QUEUE.STATUS, instanceActionStatus.name()) + .set(SURVEY_INSTANCE_ACTION_QUEUE.MESSAGE, msg) + .where(SURVEY_INSTANCE_ACTION_QUEUE.ID.eq(actionId) + .and(SURVEY_INSTANCE_ACTION_QUEUE.STATUS.eq(SurveyInstanceActionStatus.IN_PROGRESS.name()))) + .execute(); + + if (updated != 1) { + String messageString = "Unable to update action queue item with id: %d as %d records were updated. " + + "Reverting all action changes, this action will be attempted again in future as will be rolled back to 'PENDING'"; + + throw new IllegalStateException(format( + messageString, + actionId, + updated)); + } + } + + + public void markActionInProgress(DSLContext tx, Long actionId) { + + SelectConditionStep> inProgressAction = DSL + .select(SURVEY_INSTANCE_ACTION_QUEUE.ID) + .from(SURVEY_INSTANCE_ACTION_QUEUE) + .where(SURVEY_INSTANCE_ACTION_QUEUE.STATUS.eq(SurveyInstanceActionStatus.IN_PROGRESS.name())); + + int updated = tx + .update(SURVEY_INSTANCE_ACTION_QUEUE) + .set(SURVEY_INSTANCE_ACTION_QUEUE.STATUS, SurveyInstanceActionStatus.IN_PROGRESS.name()) + .where(SURVEY_INSTANCE_ACTION_QUEUE.ID.eq(actionId) + .and(SURVEY_INSTANCE_ACTION_QUEUE.STATUS.eq(SurveyInstanceActionStatus.PENDING.name())) + .and(DSL.notExists(inProgressAction))) + .execute(); + + if (updated != 1) { + + String messageString = "Unable to mark action %d as 'IN_PROGRESS', either the action id was not found, the action is no longer pending or there is another action currently marked 'IN_PROGRESS'"; + + throw new IllegalStateException(format( + messageString, + actionId, + updated)); + } + } +} diff --git a/waltz-data/src/main/java/org/finos/waltz/data/survey/SurveyInstanceDao.java b/waltz-data/src/main/java/org/finos/waltz/data/survey/SurveyInstanceDao.java index 66f41c9d76..06cb473b40 100644 --- a/waltz-data/src/main/java/org/finos/waltz/data/survey/SurveyInstanceDao.java +++ b/waltz-data/src/main/java/org/finos/waltz/data/survey/SurveyInstanceDao.java @@ -274,10 +274,12 @@ public long create(SurveyInstanceCreateCommand command) { } - public long createPreviousVersion(SurveyInstance currentInstance) { + public long createPreviousVersion(Optional tx, SurveyInstance currentInstance) { checkNotNull(currentInstance, "currentInstance cannot be null"); - SurveyInstanceRecord record = dsl.newRecord(si); + DSLContext dslContext = tx.orElse(dsl); + + SurveyInstanceRecord record = dslContext.newRecord(si); record.setSurveyRunId(currentInstance.surveyRunId()); record.setEntityKind(currentInstance.surveyEntity().kind().name()); record.setEntityId(currentInstance.surveyEntity().id()); @@ -313,10 +315,10 @@ public int deleteForSurveyRun(long surveyRunId) { } - public int updateStatus(long instanceId, SurveyInstanceStatus newStatus) { + public int updateStatus(Optional tx, long instanceId, SurveyInstanceStatus newStatus) { checkNotNull(newStatus, "newStatus cannot be null"); - - return dsl + DSLContext dslContext = tx.orElse(dsl); + return dslContext .update(si) .set(si.STATUS, newStatus.name()) .where(si.STATUS.notEqual(newStatus.name()) @@ -367,11 +369,10 @@ public int updateOwningRoleForSurveyRun(long surveyRunId, String role) { .execute(); } - - public int markSubmitted(long instanceId, String userName) { + public int markSubmitted(Optional tx, long instanceId, String userName) { checkNotNull(userName, "userName cannot be null"); - - return dsl + DSLContext dslContext = tx.orElse(dsl); + return dslContext .update(si) .set(si.STATUS, SurveyInstanceStatus.COMPLETED.name()) .set(si.SUBMITTED_AT, Timestamp.valueOf(nowUtc())) @@ -385,10 +386,10 @@ public int markSubmitted(long instanceId, String userName) { } - public int markApproved(long instanceId, String userName) { + public int markApproved(Optional tx, long instanceId, String userName) { checkNotNull(userName, "userName cannot be null"); - - return dsl + DSLContext dslContext = tx.orElse(dsl); + return dslContext .update(si) .set(si.APPROVED_AT, Timestamp.valueOf(nowUtc())) .set(si.APPROVED_BY, userName) @@ -399,9 +400,9 @@ public int markApproved(long instanceId, String userName) { .execute(); } - - public int reopenSurvey(long instanceId) { - return dsl + public int reopenSurvey(Optional tx, long instanceId) { + DSLContext dslContext = tx.orElse(dsl); + return dslContext .update(si) .set(si.STATUS, SurveyInstanceStatus.IN_PROGRESS.name()) .set(si.APPROVED_AT, (Timestamp) null) diff --git a/waltz-data/src/main/java/org/finos/waltz/data/survey/SurveyQuestionResponseDao.java b/waltz-data/src/main/java/org/finos/waltz/data/survey/SurveyQuestionResponseDao.java index 1885eb020e..bd572a0d38 100644 --- a/waltz-data/src/main/java/org/finos/waltz/data/survey/SurveyQuestionResponseDao.java +++ b/waltz-data/src/main/java/org/finos/waltz/data/survey/SurveyQuestionResponseDao.java @@ -184,8 +184,11 @@ public List findForInstance(long surveyInstanceI } - public int deletePreviousResponse(List previousResponses) { + public int deletePreviousResponse(Optional tx, List previousResponses) { checkNotNull(previousResponses, "responses cannot be null"); + + DSLContext dslContext = tx.orElse(dsl); + if (!previousResponses.isEmpty()) { Set instanceIds = map( previousResponses, @@ -198,13 +201,13 @@ public int deletePreviousResponse(List previousR previousResponses, qr -> qr.questionResponse().questionId()); - int rmSingleCount = dsl + int rmSingleCount = dslContext .deleteFrom(Tables.SURVEY_QUESTION_RESPONSE) .where(SURVEY_QUESTION_RESPONSE.SURVEY_INSTANCE_ID.eq(instanceId)) .and(SURVEY_QUESTION_RESPONSE.QUESTION_ID.in(previousResponseIds)) .execute(); - int rmListCount = dsl + int rmListCount = dslContext .deleteFrom(SURVEY_QUESTION_LIST_RESPONSE) .where(SURVEY_QUESTION_LIST_RESPONSE.SURVEY_INSTANCE_ID.eq(instanceId)) .and(SURVEY_QUESTION_LIST_RESPONSE.QUESTION_ID.in(previousResponseIds)) @@ -328,8 +331,11 @@ private void saveEntityListResponse(DSLContext txDsl, } - public void cloneResponses(long sourceSurveyInstanceId, long targetSurveyInstanceId) { - List responseRecords = dsl + public void cloneResponses(Optional tx, long sourceSurveyInstanceId, long targetSurveyInstanceId) { + + DSLContext dslContext = tx.orElse(dsl); + + List responseRecords = dslContext .select(SURVEY_QUESTION_RESPONSE.fields()) .select(entityNameField) .from(SURVEY_QUESTION_RESPONSE) @@ -343,7 +349,7 @@ public void cloneResponses(long sourceSurveyInstanceId, long targetSurveyInstanc }) .collect(toList()); - List listResponseRecords = dsl + List listResponseRecords = dslContext .select(SURVEY_QUESTION_LIST_RESPONSE.fields()) .from(SURVEY_QUESTION_LIST_RESPONSE) .where(SURVEY_QUESTION_LIST_RESPONSE.SURVEY_INSTANCE_ID.eq(sourceSurveyInstanceId)) @@ -356,7 +362,7 @@ public void cloneResponses(long sourceSurveyInstanceId, long targetSurveyInstanc }) .collect(toList()); - dsl.transaction(configuration -> { + dslContext.transaction(configuration -> { DSLContext txDsl = DSL.using(configuration); txDsl.batchInsert(responseRecords) diff --git a/waltz-jobs/src/main/java/org/finos/waltz/jobs/harness/SurveyInstanceHarness.java b/waltz-jobs/src/main/java/org/finos/waltz/jobs/harness/SurveyInstanceHarness.java index 3575674d85..ff9e4248a2 100644 --- a/waltz-jobs/src/main/java/org/finos/waltz/jobs/harness/SurveyInstanceHarness.java +++ b/waltz-jobs/src/main/java/org/finos/waltz/jobs/harness/SurveyInstanceHarness.java @@ -1,11 +1,7 @@ package org.finos.waltz.jobs.harness; -import org.finos.waltz.data.survey.SurveyInstanceDao; -import org.finos.waltz.data.survey.SurveyQuestionResponseDao; -import org.finos.waltz.model.attestation.SyncRecipientsResponse; -import org.finos.waltz.model.survey.SurveyInstanceFormDetails; import org.finos.waltz.service.DIConfiguration; -import org.finos.waltz.service.survey.SurveyInstanceEvaluator; +import org.finos.waltz.service.survey.SurveyInstanceActionQueueService; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class SurveyInstanceHarness { @@ -13,12 +9,10 @@ public class SurveyInstanceHarness { public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(DIConfiguration.class); - SurveyInstanceDao dao = ctx.getBean(SurveyInstanceDao.class); + SurveyInstanceActionQueueService svc = ctx.getBean(SurveyInstanceActionQueueService.class); - SyncRecipientsResponse reassignRecipientsCounts = dao.getReassignRecipientsCounts(); + svc.performActions(); - dao.reassignRecipients(); - - System.out.println("-------------"); + System.out.println("------------- Done!"); } } diff --git a/waltz-jobs/src/main/java/org/finos/waltz/jobs/tools/survey/SurveyTemplateMigrate.java b/waltz-jobs/src/main/java/org/finos/waltz/jobs/tools/survey/SurveyTemplateMigrate.java index b221d6972f..df4843a14e 100644 --- a/waltz-jobs/src/main/java/org/finos/waltz/jobs/tools/survey/SurveyTemplateMigrate.java +++ b/waltz-jobs/src/main/java/org/finos/waltz/jobs/tools/survey/SurveyTemplateMigrate.java @@ -18,6 +18,7 @@ import java.time.LocalDate; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -69,7 +70,7 @@ public static void main(String[] args) { LOG.info("Added recipients to new survey instance [id: {}]", newSiId); // withdraw the old one - siDao.updateStatus(si.id().get(), WITHDRAWN); + siDao.updateStatus(Optional.empty(), si.id().get(), WITHDRAWN); LOG.info("Old survey instance [id: {}] withdrawn", si.id().get()); } diff --git a/waltz-model/src/main/java/org/finos/waltz/model/survey/SurveyInstanceActionQueueItem.java b/waltz-model/src/main/java/org/finos/waltz/model/survey/SurveyInstanceActionQueueItem.java new file mode 100644 index 0000000000..973cb49994 --- /dev/null +++ b/waltz-model/src/main/java/org/finos/waltz/model/survey/SurveyInstanceActionQueueItem.java @@ -0,0 +1,26 @@ +package org.finos.waltz.model.survey; + +import org.finos.waltz.model.IdProvider; +import org.finos.waltz.model.Nullable; +import org.immutables.value.Value; + +import java.time.LocalDateTime; + +@Value.Immutable +public abstract class SurveyInstanceActionQueueItem implements IdProvider { + + public abstract SurveyInstanceAction action(); + public abstract Long surveyInstanceId(); + @Nullable + public abstract String actionParams(); + public abstract SurveyInstanceStatus initialState(); + public abstract LocalDateTime submittedAt(); + public abstract String submittedBy(); + @Nullable + public abstract LocalDateTime actionedAt(); + public abstract SurveyInstanceActionStatus status(); + @Nullable + public abstract String message(); + public abstract String provenance(); + +} diff --git a/waltz-model/src/main/java/org/finos/waltz/model/survey/SurveyInstanceActionStatus.java b/waltz-model/src/main/java/org/finos/waltz/model/survey/SurveyInstanceActionStatus.java new file mode 100644 index 0000000000..40f7d778de --- /dev/null +++ b/waltz-model/src/main/java/org/finos/waltz/model/survey/SurveyInstanceActionStatus.java @@ -0,0 +1,11 @@ +package org.finos.waltz.model.survey; + +public enum SurveyInstanceActionStatus { + + PENDING, + IN_PROGRESS, + PRECONDITION_FAILURE, + EXECUTION_FAILURE, + SUCCESS + +} diff --git a/waltz-service/src/main/java/org/finos/waltz/service/changelog/ChangeLogService.java b/waltz-service/src/main/java/org/finos/waltz/service/changelog/ChangeLogService.java index 43183c5d3e..b8d05c33a6 100644 --- a/waltz-service/src/main/java/org/finos/waltz/service/changelog/ChangeLogService.java +++ b/waltz-service/src/main/java/org/finos/waltz/service/changelog/ChangeLogService.java @@ -41,6 +41,7 @@ import org.finos.waltz.model.physical_flow.PhysicalFlow; import org.finos.waltz.model.physical_specification.PhysicalSpecification; import org.finos.waltz.model.tally.DateTally; +import org.jooq.DSLContext; import org.jooq.lambda.tuple.Tuple2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -152,7 +153,12 @@ public List findByUser(String userName, public int write(ChangeLog changeLog) { - return changeLogDao.write(changeLog); + return changeLogDao.write(Optional.empty(), changeLog); + } + + + public int write(Optional tx, ChangeLog changeLog) { + return changeLogDao.write(tx, changeLog); } diff --git a/waltz-service/src/main/java/org/finos/waltz/service/end_user_app/EndUserAppService.java b/waltz-service/src/main/java/org/finos/waltz/service/end_user_app/EndUserAppService.java index 40688ca702..b86c6cb647 100644 --- a/waltz-service/src/main/java/org/finos/waltz/service/end_user_app/EndUserAppService.java +++ b/waltz-service/src/main/java/org/finos/waltz/service/end_user_app/EndUserAppService.java @@ -49,6 +49,7 @@ import java.util.Collection; import java.util.List; +import java.util.Optional; import static java.lang.String.format; import static org.finos.waltz.common.Checks.checkNotNull; @@ -124,7 +125,7 @@ public AppRegistrationResponse promoteToApplication(Long id, ChangeLogComment co migrateEudaInvolvements(id, appRegistrationResponse); - changeLogDao.write(mkChangeLog(appRegistrationResponse, comment, username)); + changeLogDao.write(Optional.empty(), mkChangeLog(appRegistrationResponse, comment, username)); return appRegistrationResponse; } diff --git a/waltz-service/src/main/java/org/finos/waltz/service/scheduled_job/ScheduledJobService.java b/waltz-service/src/main/java/org/finos/waltz/service/scheduled_job/ScheduledJobService.java index b69feaf749..6caa2f571a 100644 --- a/waltz-service/src/main/java/org/finos/waltz/service/scheduled_job/ScheduledJobService.java +++ b/waltz-service/src/main/java/org/finos/waltz/service/scheduled_job/ScheduledJobService.java @@ -32,6 +32,7 @@ import org.finos.waltz.service.logical_flow.LogicalFlowService; import org.finos.waltz.service.physical_specification_data_type.PhysicalSpecDataTypeService; import org.finos.waltz.service.report_grid.ReportGridFilterViewService; +import org.finos.waltz.service.survey.SurveyInstanceActionQueueService; import org.finos.waltz.service.survey.SurveyInstanceService; import org.finos.waltz.service.usage_info.DataTypeUsageService; import org.slf4j.Logger; @@ -64,7 +65,7 @@ public class ScheduledJobService { private final ReportGridFilterViewService reportGridFilterViewService; private final CostService costService; - + private final SurveyInstanceActionQueueService surveyInstanceActionQueueService; private final ComplexityService complexityService; @@ -79,8 +80,10 @@ public ScheduledJobService(AttestationRunService attestationRunService, PhysicalSpecDataTypeService physicalSpecDataTypeService, ReportGridFilterViewService reportGridFilterViewService, ScheduledJobDao scheduledJobDao, + SurveyInstanceActionQueueService surveyInstanceActionQueueService, SurveyInstanceService surveyInstanceService) { + checkNotNull(attestationRunService, "attestationRunService cannot be null"); checkNotNull(complexityService, "complexityService cannot be null"); checkNotNull(costService, "costService cannot be null"); @@ -90,6 +93,7 @@ public ScheduledJobService(AttestationRunService attestationRunService, checkNotNull(physicalSpecDataTypeService, "physicalSpecDataTypeService cannot be null"); checkNotNull(reportGridFilterViewService, "reportGridFilterViewService cannot be null"); checkNotNull(scheduledJobDao, "scheduledJobDao cannot be null"); + checkNotNull(surveyInstanceActionQueueService, "surveyInstanceActionQueueService cannot be null"); checkNotNull(surveyInstanceService, "surveyInstanceService cannot be null"); this.attestationRunService = attestationRunService; @@ -102,6 +106,7 @@ public ScheduledJobService(AttestationRunService attestationRunService, this.physicalSpecDataTypeService = physicalSpecDataTypeService; this.reportGridFilterViewService = reportGridFilterViewService; this.scheduledJobDao = scheduledJobDao; + this.surveyInstanceActionQueueService = surveyInstanceActionQueueService; this.surveyInstanceService = surveyInstanceService; } @@ -158,6 +163,9 @@ public void run() { runIfNeeded(JobKey.COMPLEXITY_REBUILD_MEASURABLE, (jk) -> complexityService.populateMeasurableComplexities()); + + surveyInstanceActionQueueService.performActions(); + } diff --git a/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceActionQueueService.java b/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceActionQueueService.java new file mode 100644 index 0000000000..4147864e11 --- /dev/null +++ b/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceActionQueueService.java @@ -0,0 +1,136 @@ +package org.finos.waltz.service.survey; + +import org.finos.waltz.common.Checks; +import org.finos.waltz.data.survey.SurveyInstanceActionQueueDao; +import org.finos.waltz.model.survey.ImmutableSurveyInstanceStatusChangeCommand; +import org.finos.waltz.model.survey.SurveyInstance; +import org.finos.waltz.model.survey.SurveyInstanceActionQueueItem; +import org.finos.waltz.model.survey.SurveyInstanceActionStatus; +import org.finos.waltz.model.survey.SurveyInstanceStatus; +import org.jooq.DSLContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +import static java.lang.String.format; + +@Service +public class SurveyInstanceActionQueueService { + + private static final Logger LOG = LoggerFactory.getLogger(SurveyInstanceActionQueueService.class); + private final SurveyInstanceActionQueueDao surveyInstanceActionQueueDao; + private final SurveyInstanceService surveyInstanceService; + private final DSLContext dslContext; + + @Autowired + SurveyInstanceActionQueueService(SurveyInstanceActionQueueDao surveyInstanceActionQueueDao, + SurveyInstanceService surveyInstanceService, + DSLContext dslContext) { + + Checks.checkNotNull(surveyInstanceActionQueueDao, "surveyInstanceActionQueueDao cannot be null"); + Checks.checkNotNull(surveyInstanceService, "surveyInstanceService cannot be null"); + Checks.checkNotNull(dslContext, "dslContext cannot be null"); + + this.surveyInstanceActionQueueDao = surveyInstanceActionQueueDao; + this.surveyInstanceService = surveyInstanceService; + this.dslContext = dslContext; + } + + public void performActions() { + + List pendingActions = surveyInstanceActionQueueDao.findPendingActions(); + + pendingActions + .forEach(action -> { + + dslContext.transaction(ctx -> { + + DSLContext tx = ctx.dsl(); + + Long actionId = action.id().get(); + + SurveyInstance instance = surveyInstanceService.getById(action.surveyInstanceId()); + + surveyInstanceActionQueueDao.markActionInProgress(tx, actionId); + + if (instance == null) { + + String msg = format("Could not find survey instance with id: %d to apply action: %s", action.surveyInstanceId(), action.action().name()); + LOG.info(msg); + surveyInstanceActionQueueDao.updateActionStatus( + tx, + actionId, + SurveyInstanceActionStatus.PRECONDITION_FAILURE, + msg); + + } else if (instance.status() != action.initialState()) { + + String msg = format("Initial state of survey instance with id: %d is not as expected: %s and is actually %s, will not apply action: %s", + action.surveyInstanceId(), + action.initialState().name(), + instance.status().name(), + action.action().name()); + LOG.info(msg); + surveyInstanceActionQueueDao.updateActionStatus( + tx, + actionId, + SurveyInstanceActionStatus.PRECONDITION_FAILURE, + msg); + + } else { + + String username = action.submittedBy(); + + ImmutableSurveyInstanceStatusChangeCommand updateCmd = ImmutableSurveyInstanceStatusChangeCommand + .builder() + .action(action.action()) + .reason(Optional.ofNullable(action.actionParams())) + .build(); + + try { + + SurveyInstanceStatus surveyInstanceStatus = surveyInstanceService.updateStatus( + Optional.of(tx), + username, + action.surveyInstanceId(), + updateCmd); + + String msg = format("Successfully updated survey instance id: %d with action: %s, new status is: %s", + action.surveyInstanceId(), + action.action().name(), + surveyInstanceStatus.name()); + + LOG.info(msg); + surveyInstanceActionQueueDao.updateActionStatus( + tx, + actionId, + SurveyInstanceActionStatus.SUCCESS, + null); + + } catch (Exception e) { + + String msg = format("Error when updating survey instance id: %d with action: %s. %s", + action.surveyInstanceId(), + action.action().name(), + e.getMessage()); + + LOG.error(msg); + surveyInstanceActionQueueDao.updateActionStatus( + tx, + actionId, + SurveyInstanceActionStatus.EXECUTION_FAILURE, + msg); + } + } + + }); + + }); + + } + +} diff --git a/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceService.java b/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceService.java index 11c67b96b2..febceca6c9 100644 --- a/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceService.java +++ b/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceService.java @@ -56,6 +56,7 @@ import org.finos.waltz.model.utils.IdUtilities; import org.finos.waltz.service.changelog.ChangeLogService; import org.finos.waltz.service.user.UserRoleService; +import org.jooq.DSLContext; import org.jooq.Record1; import org.jooq.Select; import org.springframework.beans.factory.annotation.Autowired; @@ -66,6 +67,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import static java.lang.String.format; @@ -75,6 +77,7 @@ import static org.finos.waltz.common.Checks.fail; import static org.finos.waltz.common.CollectionUtilities.find; import static org.finos.waltz.common.CollectionUtilities.isEmpty; +import static org.finos.waltz.common.SetUtilities.map; import static org.finos.waltz.common.StringUtilities.isEmpty; import static org.finos.waltz.common.StringUtilities.joinUsing; import static org.finos.waltz.model.survey.SurveyInstanceStateMachineFactory.simple; @@ -260,7 +263,16 @@ public Person checkPersonIsRecipient(String userName, long instanceId) { } - public SurveyInstanceStatus updateStatus(String userName, + /** + * Uupdates a survey instance using a StatusChangeCommand. If a DSLContext is provided it will use this otherwise will use the DSLContext within the DAO. + * @param tx optional DSLContext to use as part of a transaction + * @param userName the user to submit the status change + * @param instanceId the id of the survey being updated + * @param command the action details + * @return the updated survey instance + */ + public SurveyInstanceStatus updateStatus(Optional tx, + String userName, long instanceId, SurveyInstanceStatusChangeCommand command) { @@ -283,7 +295,7 @@ public SurveyInstanceStatus updateStatus(String userName, SurveyInstanceFormDetails formDetails = instanceViewService.getFormDetailsById(instanceId); if (! formDetails.missingMandatoryQuestionIds().isEmpty()) { Map questionsById = indexByOptionalId(formDetails.activeQuestions()); - Set missingMandatoryQuestions = SetUtilities.map(formDetails.missingMandatoryQuestionIds(), questionsById::get); + Set missingMandatoryQuestions = map(formDetails.missingMandatoryQuestionIds(), questionsById::get); fail("Some questions are missing, namely: %s", joinUsing( missingMandatoryQuestions, SurveyQuestion::questionText, @@ -296,26 +308,27 @@ public SurveyInstanceStatus updateStatus(String userName, switch (command.action()) { case APPROVING: checkTrue(newStatus.equals(SurveyInstanceStatus.APPROVED), "The resolved new status for APPROVING should be 'APPROVED', resolved to: " + newStatus); - nbupdates = surveyInstanceDao.markApproved(instanceId, userName); + nbupdates = surveyInstanceDao.markApproved(tx, instanceId, userName); break; case SUBMITTING: checkTrue(newStatus.equals(SurveyInstanceStatus.COMPLETED), "The resolved new status for SUBMITTING should be 'COMPLETED', resolved to: " + newStatus); - removeUnnecessaryResponses(instanceId); - nbupdates = surveyInstanceDao.markSubmitted(instanceId, userName); + removeUnnecessaryResponses(tx, instanceId); + nbupdates = surveyInstanceDao.markSubmitted(tx, instanceId, userName); break; case REOPENING: checkTrue(newStatus.equals(SurveyInstanceStatus.IN_PROGRESS), "The resolved new status for REOPENING should be 'IN_PROGRESS', resolved to: " + newStatus); // if survey is being sent back, store current responses as a version - long versionedInstanceId = surveyInstanceDao.createPreviousVersion(surveyInstance); - surveyQuestionResponseDao.cloneResponses(surveyInstance.id().get(), versionedInstanceId); - nbupdates = surveyInstanceDao.reopenSurvey(instanceId); + long versionedInstanceId = surveyInstanceDao.createPreviousVersion(tx, surveyInstance); + surveyQuestionResponseDao.cloneResponses(tx, surveyInstance.id().get(), versionedInstanceId); + nbupdates = surveyInstanceDao.reopenSurvey(tx, instanceId); break; default: - nbupdates = surveyInstanceDao.updateStatus(instanceId, newStatus); + nbupdates = surveyInstanceDao.updateStatus(tx, instanceId, newStatus); } if (nbupdates > 0) { changeLogService.write( + tx, ImmutableChangeLog.builder() .operation(Operation.UPDATE) .userId(userName) @@ -329,7 +342,7 @@ public SurveyInstanceStatus updateStatus(String userName, } - protected int removeUnnecessaryResponses(long instanceId) { + protected int removeUnnecessaryResponses(Optional tx, long instanceId) { List availableQuestions = surveyQuestionService.findForSurveyInstance(instanceId); List questionResponses = surveyQuestionResponseDao.findForInstance(instanceId); Set availableQuestionIds = IdUtilities.toIds(availableQuestions); @@ -342,7 +355,7 @@ protected int removeUnnecessaryResponses(long instanceId) { } if (!toRemove.isEmpty()) { - return surveyQuestionResponseDao.deletePreviousResponse(toRemove); + return surveyQuestionResponseDao.deletePreviousResponse(tx, toRemove); } else { return 0; } @@ -529,6 +542,7 @@ public List findPossibleActionsForInstance(String userName public SurveyInstancePermissions getPermissions(String userName, Long instanceId) { + Person person = personDao.getActiveByUserEmail(userName); SurveyInstance instance = surveyInstanceDao.getById(instanceId); SurveyRun run = surveyRunDao.getById(instance.surveyRunId()); @@ -598,6 +612,7 @@ public int copyResponses(long sourceSurveyId, CopySurveyResponsesCommand copyCom .forEach(trgInstanceId -> { updateStatus( + Optional.empty(), username, trgInstanceId, ImmutableSurveyInstanceStatusChangeCommand.builder() diff --git a/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/SurveyInstanceEndpoint.java b/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/SurveyInstanceEndpoint.java index d11ef8ecad..1431a43735 100644 --- a/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/SurveyInstanceEndpoint.java +++ b/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/SurveyInstanceEndpoint.java @@ -34,6 +34,7 @@ import org.springframework.stereotype.Service; import java.time.LocalDate; +import java.util.Optional; import static org.finos.waltz.common.Checks.checkNotNull; import static org.finos.waltz.model.HierarchyQueryScope.EXACT; @@ -161,6 +162,7 @@ public void register() { // set status to in progress surveyInstanceService.updateStatus( + Optional.empty(), userName, instanceId, ImmutableSurveyInstanceStatusChangeCommand.builder() @@ -191,6 +193,7 @@ public void register() { SurveyInstanceStatusChangeCommand command = readBody(req, SurveyInstanceStatusChangeCommand.class); return surveyInstanceService.updateStatus( + Optional.empty(), getUsername(req), getId(req), command From 9c54c91ab659406ab43fb649cf150226ce2c0f5c Mon Sep 17 00:00:00 2001 From: woodjes Date: Mon, 20 Nov 2023 14:51:17 +0000 Subject: [PATCH 2/9] Add job to scheduledJobService to run and action any pending entries #CTCTOWALTZ-2680 #6860 --- .../SurveyInstanceActionQueueService.java | 44 ++++++++++++------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceActionQueueService.java b/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceActionQueueService.java index 4147864e11..b4a44b9c87 100644 --- a/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceActionQueueService.java +++ b/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceActionQueueService.java @@ -40,6 +40,11 @@ public class SurveyInstanceActionQueueService { this.dslContext = dslContext; } + /** + * Looks for any 'PENDING' actions in the survey_instance_action_queue and attempts to run them in order of submission time to the queue. + * In case of error or a precondition failure a message is saved to the action in the table. + * A transaction is created for each action and all changes will be rolled back if an error occurs during runtime. + */ public void performActions() { List pendingActions = surveyInstanceActionQueueDao.findPendingActions(); @@ -93,23 +98,30 @@ public void performActions() { try { - SurveyInstanceStatus surveyInstanceStatus = surveyInstanceService.updateStatus( - Optional.of(tx), - username, - action.surveyInstanceId(), - updateCmd); - - String msg = format("Successfully updated survey instance id: %d with action: %s, new status is: %s", - action.surveyInstanceId(), - action.action().name(), - surveyInstanceStatus.name()); + // We need a new transaction here so that any changes get rolled back; we do not fail the overall + // action transaction as that resets the action to 'PENDING' and would lose the error message + + tx.transaction(actionCtx -> { + DSLContext actionTx = actionCtx.dsl(); + SurveyInstanceStatus surveyInstanceStatus = surveyInstanceService.updateStatus( + Optional.of(actionTx), + username, + action.surveyInstanceId(), + updateCmd); + + String msg = format("Successfully updated survey instance id: %d with action: %s, new status is: %s", + action.surveyInstanceId(), + action.action().name(), + surveyInstanceStatus.name()); + + LOG.info(msg); + surveyInstanceActionQueueDao.updateActionStatus( + tx, + actionId, + SurveyInstanceActionStatus.SUCCESS, + null); + }); - LOG.info(msg); - surveyInstanceActionQueueDao.updateActionStatus( - tx, - actionId, - SurveyInstanceActionStatus.SUCCESS, - null); } catch (Exception e) { From 4d79350035bc2b796874739f0156022aa9c90898 Mon Sep 17 00:00:00 2001 From: woodjes Date: Tue, 21 Nov 2023 12:07:09 +0000 Subject: [PATCH 3/9] Add junit bom dependency to keep junit dependency versions aligned (jupiter tests failing to start) #CTCTOWALTZ-2680 #6860 --- pom.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pom.xml b/pom.xml index 1ca25ba3f7..ee2f3c63e6 100644 --- a/pom.xml +++ b/pom.xml @@ -363,6 +363,14 @@ test + + org.junit + junit-bom + ${junit.version} + pom + import + + From bcbef55383838636530d8148a65aec889303cd9b Mon Sep 17 00:00:00 2001 From: woodjes Date: Tue, 21 Nov 2023 16:52:02 +0000 Subject: [PATCH 4/9] Integration tests for survey instance action queue #CTCTOWALTZ-2680 #6860 --- .../survey/SurveyInstanceActionQueueDao.java | 2 +- .../SurveyInstanceActionQueueTest.java | 390 ++++++++++++++++++ .../SurveyInstanceActionQueueService.java | 8 + .../helpers/ActionQueueHelper.java | 46 +++ .../helpers/SurveyTemplateHelper.java | 14 + 5 files changed, 459 insertions(+), 1 deletion(-) create mode 100644 waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/SurveyInstanceActionQueueTest.java create mode 100644 waltz-test-common/src/main/java/org/finos/waltz/test_common/helpers/ActionQueueHelper.java diff --git a/waltz-data/src/main/java/org/finos/waltz/data/survey/SurveyInstanceActionQueueDao.java b/waltz-data/src/main/java/org/finos/waltz/data/survey/SurveyInstanceActionQueueDao.java index bd884b2811..72bf6702be 100644 --- a/waltz-data/src/main/java/org/finos/waltz/data/survey/SurveyInstanceActionQueueDao.java +++ b/waltz-data/src/main/java/org/finos/waltz/data/survey/SurveyInstanceActionQueueDao.java @@ -61,7 +61,7 @@ public List findPendingActions() { } - public SurveyInstanceActionQueueItem getById(Long id) { + public SurveyInstanceActionQueueItem getById(long id) { Condition idCondition = SURVEY_INSTANCE_ACTION_QUEUE.ID.eq(id); return mkSelectByCondition(dsl, idCondition) .fetchOne(TO_DOMAIN_MAPPER); diff --git a/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/SurveyInstanceActionQueueTest.java b/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/SurveyInstanceActionQueueTest.java new file mode 100644 index 0000000000..2513ef3221 --- /dev/null +++ b/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/SurveyInstanceActionQueueTest.java @@ -0,0 +1,390 @@ +package org.finos.waltz.integration_test.inmem.service; + +import org.finos.waltz.common.DateTimeUtilities; +import org.finos.waltz.common.exception.InsufficientPrivelegeException; +import org.finos.waltz.integration_test.inmem.BaseInMemoryIntegrationTest; +import org.finos.waltz.model.EntityKind; +import org.finos.waltz.model.EntityReference; +import org.finos.waltz.model.IdSelectionOptions; +import org.finos.waltz.model.ReleaseLifecycleStatus; +import org.finos.waltz.model.survey.ImmutableInstancesAndRecipientsCreateCommand; +import org.finos.waltz.model.survey.ImmutableSurveyQuestionResponse; +import org.finos.waltz.model.survey.ImmutableSurveyRunCreateCommand; +import org.finos.waltz.model.survey.InstancesAndRecipientsCreateCommand; +import org.finos.waltz.model.survey.SurveyInstance; +import org.finos.waltz.model.survey.SurveyInstanceAction; +import org.finos.waltz.model.survey.SurveyInstanceActionQueueItem; +import org.finos.waltz.model.survey.SurveyInstanceActionStatus; +import org.finos.waltz.model.survey.SurveyInstanceStatus; +import org.finos.waltz.model.survey.SurveyIssuanceKind; +import org.finos.waltz.model.survey.SurveyQuestionResponse; +import org.finos.waltz.service.survey.SurveyInstanceActionQueueService; +import org.finos.waltz.service.survey.SurveyInstanceService; +import org.finos.waltz.service.survey.SurveyRunService; +import org.finos.waltz.test_common.helpers.ActionQueueHelper; +import org.finos.waltz.test_common.helpers.AppHelper; +import org.finos.waltz.test_common.helpers.InvolvementHelper; +import org.finos.waltz.test_common.helpers.PersonHelper; +import org.finos.waltz.test_common.helpers.SurveyTemplateHelper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Set; + +import static java.lang.String.format; +import static java.util.Collections.emptySet; +import static org.finos.waltz.common.CollectionUtilities.find; +import static org.finos.waltz.common.DateTimeUtilities.nowUtcTimestamp; +import static org.finos.waltz.common.DateTimeUtilities.toLocalDate; +import static org.finos.waltz.test_common.helpers.NameHelper.mkName; + +@Service +public class SurveyInstanceActionQueueTest extends BaseInMemoryIntegrationTest { + + @Autowired + private AppHelper appHelper; + + @Autowired + private SurveyTemplateHelper templateHelper; + + @Autowired + private SurveyRunService runService; + + @Autowired + private SurveyInstanceService instanceService; + + @Autowired + private SurveyInstanceActionQueueService actionQueueService; + + @Autowired + private ActionQueueHelper actionQueueHelper; + + @Autowired + private PersonHelper personHelper; + + @Autowired + private InvolvementHelper involvementHelper; + + @Test + public void processActionsCanSubmitSurveys() throws InsufficientPrivelegeException { + String username = mkName("submitSurvey"); + SurveyInstance instance = setupSurvey("submitSurvey", username); + actionQueueHelper.addActionToQueue(instance.id().get(), SurveyInstanceAction.SUBMITTING, null, instance.status(), username); + List pendingActions = actionQueueService.findPendingActions(); + + Assertions.assertEquals(1, pendingActions.size(), "One action should exist in the queue before processing"); + + actionQueueService.performActions(); + + List pendingActionsPostProcess = actionQueueService.findPendingActions(); + Assertions.assertEquals(0, pendingActionsPostProcess.size(), "No actions should exist in the queue after processing"); + + SurveyInstance instancePostAction = instanceService.getById(instance.id().get()); + Assertions.assertEquals(SurveyInstanceStatus.COMPLETED, instancePostAction.status(), "After submitting action the survey should be marked as completed"); + } + + @Test + public void processActionsCanHandleMultipleActions() throws InsufficientPrivelegeException { + String username = mkName("multipleActions"); + SurveyInstance instance = setupSurvey("multipleActions", username); + actionQueueHelper.addActionToQueue(instance.id().get(), SurveyInstanceAction.SUBMITTING, null, instance.status(), username); + actionQueueHelper.addActionToQueue(instance.id().get(), SurveyInstanceAction.APPROVING, "Reason given", SurveyInstanceStatus.COMPLETED, username); + actionQueueHelper.addActionToQueue(instance.id().get(), SurveyInstanceAction.REOPENING, null, SurveyInstanceStatus.APPROVED, username); + List pendingActions = actionQueueService.findPendingActions(); + + Assertions.assertEquals(3, pendingActions.size(), "Three actions should exist in the queue before processing"); + + actionQueueService.performActions(); + + List pendingActionsPostProcess = actionQueueService.findPendingActions(); + Assertions.assertEquals(0, pendingActionsPostProcess.size(), "No actions should exist in the queue after processing"); + + SurveyInstance instancePostAction = instanceService.getById(instance.id().get()); + Assertions.assertEquals(SurveyInstanceStatus.IN_PROGRESS, instancePostAction.status(), "Survey instance should be 'in progress' after submission, approval and reopen"); + } + + @Test + public void processActionsWillOrderBySubmittedTimeAndOnlyActionFirstIfDuplicates() throws InsufficientPrivelegeException { + String username = mkName("multipleActions"); + SurveyInstance instance = setupSurvey("multipleActions", username); + Long actionId = actionQueueHelper.addActionToQueue(instance.id().get(), SurveyInstanceAction.SUBMITTING, null, instance.status(), username); + Long action2Id = actionQueueHelper.addActionToQueue(instance.id().get(), SurveyInstanceAction.SUBMITTING, null, instance.status(), username); + Long action3Id = actionQueueHelper.addActionToQueue(instance.id().get(), SurveyInstanceAction.SUBMITTING, null, instance.status(), username); + Long action4Id = actionQueueHelper.addActionToQueue(instance.id().get(), SurveyInstanceAction.SUBMITTING, null, instance.status(), username); + Long action5Id = actionQueueHelper.addActionToQueue(instance.id().get(), SurveyInstanceAction.SUBMITTING, null, instance.status(), username); + List pendingActions = actionQueueService.findPendingActions(); + + Assertions.assertEquals(5, pendingActions.size(), "Two actions should exist in the queue before processing"); + + actionQueueService.performActions(); + + List pendingActionsPostProcess = actionQueueService.findPendingActions(); + Assertions.assertEquals(0, pendingActionsPostProcess.size(), "No actions should exist in the queue after processing"); + + SurveyInstance instancePostAction = instanceService.getById(instance.id().get()); + Assertions.assertEquals(SurveyInstanceStatus.COMPLETED, instancePostAction.status(), "Survey instance should be 'completed' after submission"); + + SurveyInstanceActionQueueItem action = actionQueueService.getById(actionId); + SurveyInstanceActionQueueItem action2 = actionQueueService.getById(action2Id); + SurveyInstanceActionQueueItem action3 = actionQueueService.getById(action3Id); + SurveyInstanceActionQueueItem action4 = actionQueueService.getById(action4Id); + SurveyInstanceActionQueueItem action5 = actionQueueService.getById(action5Id); + Assertions.assertEquals(SurveyInstanceActionStatus.SUCCESS, action.status(), "First action to be submitted should be successful"); + Assertions.assertEquals(SurveyInstanceActionStatus.PRECONDITION_FAILURE, action2.status(), "Duplicate actions should be ignored as survey initial status changed"); + Assertions.assertEquals(SurveyInstanceActionStatus.PRECONDITION_FAILURE, action3.status(), "Duplicate actions should be ignored as survey initial status changed"); + Assertions.assertEquals(SurveyInstanceActionStatus.PRECONDITION_FAILURE, action4.status(), "Duplicate actions should be ignored as survey initial status changed"); + Assertions.assertEquals(SurveyInstanceActionStatus.PRECONDITION_FAILURE, action5.status(), "Duplicate actions should be ignored as survey initial status changed"); + } + + @Test + public void processActionsCannotSubmitIfMandatoryQuestionsOutstanding() throws InsufficientPrivelegeException { + String username = mkName("mandatoryQuestion"); + SurveyInstance instance = setupSurvey("mandatoryQuestion", username, false); + Long actionId = actionQueueHelper.addActionToQueue(instance.id().get(), SurveyInstanceAction.SUBMITTING, null, instance.status(), username); + List pendingActions = actionQueueService.findPendingActions(); + + Assertions.assertEquals(1, pendingActions.size(), "One action should exist in the queue before processing"); + + actionQueueService.performActions(); + + List pendingActionsPostProcess = actionQueueService.findPendingActions(); + Assertions.assertEquals(0, pendingActionsPostProcess.size(), "No actions should exist in the queue after processing"); + + SurveyInstance instancePostAction = instanceService.getById(instance.id().get()); + Assertions.assertEquals(SurveyInstanceStatus.NOT_STARTED, instancePostAction.status(), "After submitting action the status should not have changed as mandatory questions outstanding"); + + SurveyInstanceActionQueueItem action = actionQueueService.getById(actionId); + Assertions.assertEquals(SurveyInstanceActionStatus.EXECUTION_FAILURE, action.status()); + Assertions.assertNotNull(action.message()); + } + + @Test + public void processActionsCannotSubmitIfNotPermitted() throws InsufficientPrivelegeException { + String username = mkName("notPermitted"); + SurveyInstance instance = setupSurvey("notPermitted", username); + Long actionId = actionQueueHelper.addActionToQueue(instance.id().get(), SurveyInstanceAction.SUBMITTING, null, instance.status(), "unauthorisedUser"); + List pendingActions = actionQueueService.findPendingActions(); + + Assertions.assertEquals(1, pendingActions.size(), "One action should exist in the queue before processing"); + + actionQueueService.performActions(); + + List pendingActionsPostProcess = actionQueueService.findPendingActions(); + Assertions.assertEquals(0, pendingActionsPostProcess.size(), "No actions should exist in the queue after processing"); + + SurveyInstance instancePostAction = instanceService.getById(instance.id().get()); + Assertions.assertEquals(SurveyInstanceStatus.NOT_STARTED, instancePostAction.status(), "After submitting action the status should not have changed as mandatory questions outstanding"); + + SurveyInstanceActionQueueItem action = actionQueueService.getById(actionId); + Assertions.assertEquals(SurveyInstanceActionStatus.EXECUTION_FAILURE, action.status()); + Assertions.assertNotNull(action.message()); + } + + @Test + public void processActionsCannotSubmitSurveyInstanceStatusIncorrect() throws InsufficientPrivelegeException { + String username = mkName("surveyStatusMismatch"); + SurveyInstance instance = setupSurvey("surveyStatusMismatch", username); + Long actionId = actionQueueHelper.addActionToQueue(instance.id().get(), SurveyInstanceAction.SUBMITTING, null, SurveyInstanceStatus.IN_PROGRESS, username); + List pendingActions = actionQueueService.findPendingActions(); + + Assertions.assertEquals(1, pendingActions.size(), "One action should exist in the queue before processing"); + + actionQueueService.performActions(); + + List pendingActionsPostProcess = actionQueueService.findPendingActions(); + Assertions.assertEquals(0, pendingActionsPostProcess.size(), "No actions should exist in the queue after processing"); + + SurveyInstance instancePostAction = instanceService.getById(instance.id().get()); + Assertions.assertEquals(SurveyInstanceStatus.NOT_STARTED, instancePostAction.status(), "After submitting action the status should not have changed as mandatory questions outstanding"); + + SurveyInstanceActionQueueItem action = actionQueueService.getById(actionId); + Assertions.assertEquals(SurveyInstanceActionStatus.PRECONDITION_FAILURE, action.status()); + Assertions.assertNotNull(action.message()); + } + + @Test + public void processActionsFailureOfOneActionDoesNotStopOthersCompleting() throws InsufficientPrivelegeException { + String username = mkName("multipleSurveys"); + String username2 = mkName("multipleSurveys"); + SurveyInstance instance = setupSurvey("multipleSurveys", username); + SurveyInstance instance2 = setupSurvey("multipleSurveys", username2); + Long actionId = actionQueueHelper.addActionToQueue(instance.id().get(), SurveyInstanceAction.SUBMITTING, null, SurveyInstanceStatus.COMPLETED, username); + Long actionId2 = actionQueueHelper.addActionToQueue(instance2.id().get(), SurveyInstanceAction.SUBMITTING, null, instance2.status(), username2); + + List pendingActions = actionQueueService.findPendingActions(); + Assertions.assertEquals(2, pendingActions.size(), "Two action should exist in the queue before processing"); + + actionQueueService.performActions(); + + List pendingActionsPostProcess = actionQueueService.findPendingActions(); + Assertions.assertEquals(0, pendingActionsPostProcess.size(), "No actions should exist in the queue after processing"); + + SurveyInstance instancePostAction = instanceService.getById(instance.id().get()); + Assertions.assertEquals(SurveyInstanceStatus.NOT_STARTED, instancePostAction.status(), "Survey should not have been completed, should remain 'not started'"); + + SurveyInstance instance2PostAction = instanceService.getById(instance2.id().get()); + Assertions.assertEquals(SurveyInstanceStatus.COMPLETED, instance2PostAction.status(), "Survey should be completed"); + + SurveyInstanceActionQueueItem action = actionQueueService.getById(actionId); + Assertions.assertEquals(SurveyInstanceActionStatus.PRECONDITION_FAILURE, action.status()); + Assertions.assertNotNull(action.message()); + + SurveyInstanceActionQueueItem action2 = actionQueueService.getById(actionId2); + Assertions.assertEquals(SurveyInstanceActionStatus.SUCCESS, action2.status()); + } + + @Test + public void processActionsShouldInformWhenTransitionIsNotAllowed() throws InsufficientPrivelegeException { + String username = mkName("multipleSurveys"); + SurveyInstance instance = setupSurvey("multipleSurveys", username); + Long actionId = actionQueueHelper.addActionToQueue(instance.id().get(), SurveyInstanceAction.APPROVING, null, SurveyInstanceStatus.NOT_STARTED, username); + + List pendingActions = actionQueueService.findPendingActions(); + Assertions.assertEquals(1, pendingActions.size(), "Two action should exist in the queue before processing"); + + actionQueueService.performActions(); + + List pendingActionsPostProcess = actionQueueService.findPendingActions(); + Assertions.assertEquals(0, pendingActionsPostProcess.size(), "No actions should exist in the queue after processing"); + + SurveyInstance instancePostAction = instanceService.getById(instance.id().get()); + Assertions.assertEquals(SurveyInstanceStatus.NOT_STARTED, instancePostAction.status(), "Action should not have been completed, should remain 'not started'"); + + SurveyInstanceActionQueueItem action = actionQueueService.getById(actionId); + Assertions.assertEquals(SurveyInstanceActionStatus.EXECUTION_FAILURE, action.status()); + Assertions.assertNotNull(action.message()); + } + + @Test + public void processActionsCanDoAllActions() throws InsufficientPrivelegeException { + String username = mkName("rejected"); + SurveyInstance instance = setupSurvey("rejected", username); + + // Rejection + actionQueueHelper.addActionToQueue(instance.id().get(), SurveyInstanceAction.SUBMITTING, null, SurveyInstanceStatus.NOT_STARTED, username); + Long rejectedId = actionQueueHelper.addActionToQueue(instance.id().get(), SurveyInstanceAction.REJECTING, null, SurveyInstanceStatus.COMPLETED, username); + + actionQueueService.performActions(); + + SurveyInstance postRejection = instanceService.getById(instance.id().get()); + Assertions.assertEquals(SurveyInstanceStatus.REJECTED, postRejection.status(), "Survey should be marked 'rejected'"); + + SurveyInstanceActionQueueItem rejected = actionQueueService.getById(rejectedId); + Assertions.assertEquals(SurveyInstanceActionStatus.SUCCESS, rejected.status()); + + // Withdraw + Long withdrawalId = actionQueueHelper.addActionToQueue(instance.id().get(), SurveyInstanceAction.WITHDRAWING, null, SurveyInstanceStatus.REJECTED, username); + actionQueueService.performActions(); + + SurveyInstance postWithdrawal = instanceService.getById(instance.id().get()); + Assertions.assertEquals(SurveyInstanceStatus.WITHDRAWN, postWithdrawal.status(), "Survey should be marked 'withdrawn'"); + + SurveyInstanceActionQueueItem withdrawal = actionQueueService.getById(withdrawalId); + Assertions.assertEquals(SurveyInstanceActionStatus.SUCCESS, withdrawal.status()); + + // Reopen + Long reopenId = actionQueueHelper.addActionToQueue(instance.id().get(), SurveyInstanceAction.REOPENING, null, SurveyInstanceStatus.WITHDRAWN, username); + actionQueueService.performActions(); + + SurveyInstance postReopen = instanceService.getById(instance.id().get()); + Assertions.assertEquals(SurveyInstanceStatus.IN_PROGRESS, postReopen.status(), "Survey should be marked 'in progress'"); + + SurveyInstanceActionQueueItem reopened = actionQueueService.getById(reopenId); + Assertions.assertEquals(SurveyInstanceActionStatus.SUCCESS, reopened.status()); + + // Submit + Long submitId = actionQueueHelper.addActionToQueue(instance.id().get(), SurveyInstanceAction.SUBMITTING, null, SurveyInstanceStatus.IN_PROGRESS, username); + actionQueueService.performActions(); + + SurveyInstance postSubmit = instanceService.getById(instance.id().get()); + Assertions.assertEquals(SurveyInstanceStatus.COMPLETED, postSubmit.status(), "Action should be marked 'completed'"); + + SurveyInstanceActionQueueItem submit = actionQueueService.getById(submitId); + Assertions.assertEquals(SurveyInstanceActionStatus.SUCCESS, submit.status()); + + // Approve + Long approvalId = actionQueueHelper.addActionToQueue(instance.id().get(), SurveyInstanceAction.APPROVING, "reason", SurveyInstanceStatus.COMPLETED, username); + actionQueueService.performActions(); + + SurveyInstance postApproval = instanceService.getById(instance.id().get()); + Assertions.assertEquals(SurveyInstanceStatus.APPROVED, postApproval.status(), "Action should be marked 'approved'"); + + SurveyInstanceActionQueueItem approval = actionQueueService.getById(approvalId); + Assertions.assertEquals(SurveyInstanceActionStatus.SUCCESS, approval.status()); + } + + + private SurveyInstance setupSurvey(String nameStem, String username) throws InsufficientPrivelegeException { + return setupSurvey(nameStem, username, true); + } + + private SurveyInstance setupSurvey(String nameStem, String username, boolean populateResponses) throws InsufficientPrivelegeException { + + EntityReference a1 = appHelper.createNewApp(mkName(nameStem), ouIds.a); + Long personId = personHelper.createPerson(username); + + long templateId = templateHelper.createTemplate(username, mkName(nameStem)); + long qId = templateHelper.addMandatoryQuestion(templateId); + long q2Id = templateHelper.addQuestion(templateId); + + templateHelper.updateStatus(username, templateId, ReleaseLifecycleStatus.ACTIVE); + + long invKindId = involvementHelper.mkInvolvementKind(mkName("invKind")); + involvementHelper.createInvolvement(personId, invKindId, a1); + + ImmutableSurveyRunCreateCommand runCmd = mkRunCommand(templateId, EntityReference.mkRef(EntityKind.ORG_UNIT, ouIds.a), invKindId); + Long runId = runService.createSurveyRun(username, runCmd).id().get(); + + InstancesAndRecipientsCreateCommand createCmd = mkInstancesRecipCreateCmd(runId); + runService.createSurveyInstancesAndRecipients(createCmd); + + Set instances = instanceService.findForSurveyRun(runId); + + SurveyInstance sourceSurvey = find(instances, d -> d.surveyEntity().id() == a1.id()).get(); + + if (populateResponses) { + instanceService.saveResponse(username, sourceSurvey.id().get(), mkResponse(qId)); + instanceService.saveResponse(username, sourceSurvey.id().get(), mkResponse(q2Id)); + } + + return sourceSurvey; + } + + + private SurveyQuestionResponse mkResponse(Long qId) { + return ImmutableSurveyQuestionResponse.builder() + .questionId(qId) + .stringResponse("yes") + .comment("comment") + .build(); + } + + + private InstancesAndRecipientsCreateCommand mkInstancesRecipCreateCmd(Long runId) { + return ImmutableInstancesAndRecipientsCreateCommand.builder() + .surveyRunId(runId) + .dueDate(toLocalDate(nowUtcTimestamp())) + .approvalDueDate(toLocalDate(nowUtcTimestamp())) + .excludedRecipients(emptySet()) + .build(); + } + + + private ImmutableSurveyRunCreateCommand mkRunCommand(Long templateId, EntityReference selectorRef, Long invKindId) { + return ImmutableSurveyRunCreateCommand.builder() + .issuanceKind(SurveyIssuanceKind.GROUP) + .name(mkName("test run")) + .description("run desc") + .selectionOptions(IdSelectionOptions.mkOpts(selectorRef)) + .surveyTemplateId(templateId) + .addInvolvementKindIds(invKindId) + .addOwnerInvKindIds(invKindId) + .dueDate(DateTimeUtilities.today().plusMonths(1)) + .approvalDueDate(DateTimeUtilities.today().plusMonths(1)) + .contactEmail("someone@somewhere.com") + .build(); + } +} diff --git a/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceActionQueueService.java b/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceActionQueueService.java index b4a44b9c87..ed83dbdc90 100644 --- a/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceActionQueueService.java +++ b/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceActionQueueService.java @@ -142,7 +142,15 @@ public void performActions() { }); }); + } + + + public List findPendingActions() { + return surveyInstanceActionQueueDao.findPendingActions(); + } + public SurveyInstanceActionQueueItem getById(long id) { + return surveyInstanceActionQueueDao.getById(id); } } diff --git a/waltz-test-common/src/main/java/org/finos/waltz/test_common/helpers/ActionQueueHelper.java b/waltz-test-common/src/main/java/org/finos/waltz/test_common/helpers/ActionQueueHelper.java new file mode 100644 index 0000000000..d666b03199 --- /dev/null +++ b/waltz-test-common/src/main/java/org/finos/waltz/test_common/helpers/ActionQueueHelper.java @@ -0,0 +1,46 @@ +package org.finos.waltz.test_common.helpers; + +import org.finos.waltz.common.DateTimeUtilities; +import org.finos.waltz.model.EntityKind; +import org.finos.waltz.model.EntityReference; +import org.finos.waltz.model.Operation; +import org.finos.waltz.model.involvement.EntityInvolvementChangeCommand; +import org.finos.waltz.model.involvement.ImmutableEntityInvolvementChangeCommand; +import org.finos.waltz.model.involvement_kind.ImmutableInvolvementKindCreateCommand; +import org.finos.waltz.model.involvement_kind.InvolvementKindCreateCommand; +import org.finos.waltz.model.survey.SurveyInstanceAction; +import org.finos.waltz.model.survey.SurveyInstanceActionStatus; +import org.finos.waltz.model.survey.SurveyInstanceStatus; +import org.finos.waltz.schema.tables.records.SurveyInstanceActionQueueRecord; +import org.finos.waltz.service.involvement.InvolvementService; +import org.finos.waltz.service.involvement_kind.InvolvementKindService; +import org.jooq.DSLContext; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import static org.finos.waltz.model.EntityReference.mkRef; +import static org.finos.waltz.schema.Tables.SURVEY_INSTANCE_ACTION_QUEUE; + +@Service +public class ActionQueueHelper { + + @Autowired + private DSLContext dsl; + + @Autowired + public ActionQueueHelper() { + } + + public Long addActionToQueue(Long surveyInstanceId, SurveyInstanceAction action, String actionParams, SurveyInstanceStatus initialState, String submitter) { + SurveyInstanceActionQueueRecord r = dsl.newRecord(SURVEY_INSTANCE_ACTION_QUEUE); + r.setSurveyInstanceId(surveyInstanceId); + r.setAction(action.name()); + r.setActionParams(actionParams); + r.setInitialState(initialState.name()); + r.setStatus(SurveyInstanceActionStatus.PENDING.name()); + r.setSubmittedBy(submitter); + r.setProvenance("ActionQueueHelper"); + r.insert(); + return r.getId(); + } +} diff --git a/waltz-test-common/src/main/java/org/finos/waltz/test_common/helpers/SurveyTemplateHelper.java b/waltz-test-common/src/main/java/org/finos/waltz/test_common/helpers/SurveyTemplateHelper.java index abb76f7e32..88b6a35876 100644 --- a/waltz-test-common/src/main/java/org/finos/waltz/test_common/helpers/SurveyTemplateHelper.java +++ b/waltz-test-common/src/main/java/org/finos/waltz/test_common/helpers/SurveyTemplateHelper.java @@ -62,6 +62,20 @@ public long addQuestion(long templateId) { return surveyQuestionService.create(question); } + public long addMandatoryQuestion(long templateId) { + + SurveyQuestion question = ImmutableSurveyQuestion + .builder() + .fieldType(SurveyQuestionFieldType.TEXT) + .sectionName("section") + .questionText("question") + .surveyTemplateId(templateId) + .isMandatory(true) + .build(); + + return surveyQuestionService.create(question); + } + public void deleteAllSurveyTemplate() { dsl.deleteFrom(SURVEY_QUESTION_RESPONSE).execute(); dsl.deleteFrom(SURVEY_QUESTION).execute(); From c3299bfcc7638fc3ee55ea73d5e3faed376d20ff Mon Sep 17 00:00:00 2001 From: woodjes Date: Tue, 21 Nov 2023 17:53:03 +0000 Subject: [PATCH 5/9] Integration tests for survey instance action queue #CTCTOWALTZ-2680 #6860 --- .../survey/SurveyInstanceActionQueueDao.java | 24 ++++++++- .../SurveyInstanceActionQueueTest.java | 54 +++++++++++++++++-- .../survey/SurveyInstanceActionParams.java | 16 ++++++ .../survey/SurveyInstanceActionQueueItem.java | 4 +- .../SurveyInstanceActionQueueService.java | 5 +- 5 files changed, 96 insertions(+), 7 deletions(-) create mode 100644 waltz-model/src/main/java/org/finos/waltz/model/survey/SurveyInstanceActionParams.java diff --git a/waltz-data/src/main/java/org/finos/waltz/data/survey/SurveyInstanceActionQueueDao.java b/waltz-data/src/main/java/org/finos/waltz/data/survey/SurveyInstanceActionQueueDao.java index 72bf6702be..6095204e34 100644 --- a/waltz-data/src/main/java/org/finos/waltz/data/survey/SurveyInstanceActionQueueDao.java +++ b/waltz-data/src/main/java/org/finos/waltz/data/survey/SurveyInstanceActionQueueDao.java @@ -1,8 +1,11 @@ package org.finos.waltz.data.survey; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import org.finos.waltz.common.DateTimeUtilities; import org.finos.waltz.model.survey.ImmutableSurveyInstanceActionQueueItem; import org.finos.waltz.model.survey.SurveyInstanceAction; +import org.finos.waltz.model.survey.SurveyInstanceActionParams; import org.finos.waltz.model.survey.SurveyInstanceActionQueueItem; import org.finos.waltz.model.survey.SurveyInstanceActionStatus; import org.finos.waltz.model.survey.SurveyInstanceStatus; @@ -19,9 +22,11 @@ import java.sql.Timestamp; import java.util.List; +import java.util.Optional; import static java.lang.String.format; import static java.util.Optional.ofNullable; +import static org.finos.waltz.common.JacksonUtilities.getJsonMapper; import static org.finos.waltz.schema.Tables.SURVEY_INSTANCE_ACTION_QUEUE; @Repository @@ -30,12 +35,16 @@ public class SurveyInstanceActionQueueDao { private final DSLContext dsl; public static final RecordMapper TO_DOMAIN_MAPPER = r -> { + SurveyInstanceActionQueueRecord record = r.into(SURVEY_INSTANCE_ACTION_QUEUE); + + Optional surveyInstanceActionParams = readParams(getJsonMapper(), record.getActionParams()); + return ImmutableSurveyInstanceActionQueueItem.builder() .id(record.getId()) .action(SurveyInstanceAction.valueOf(record.getAction())) .surveyInstanceId(record.getSurveyInstanceId()) - .actionParams(record.getActionParams()) + .actionParams(surveyInstanceActionParams) .initialState(SurveyInstanceStatus.valueOf(record.getInitialState())) .submittedAt(DateTimeUtilities.toLocalDateTime(record.getSubmittedAt())) .submittedBy(record.getSubmittedBy()) @@ -47,6 +56,19 @@ public class SurveyInstanceActionQueueDao { }; + private static Optional readParams(ObjectMapper jsonMapper, String actionParams) { + if(actionParams == null) { + return Optional.empty(); + } else { + try { + return Optional.ofNullable(jsonMapper.readValue(actionParams, SurveyInstanceActionParams.class)); + } catch (JsonProcessingException e) { + return Optional.empty(); + } + } + } + + @Autowired SurveyInstanceActionQueueDao(DSLContext dsl) { this.dsl = dsl; diff --git a/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/SurveyInstanceActionQueueTest.java b/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/SurveyInstanceActionQueueTest.java index 2513ef3221..a082066ad8 100644 --- a/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/SurveyInstanceActionQueueTest.java +++ b/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/SurveyInstanceActionQueueTest.java @@ -1,13 +1,17 @@ package org.finos.waltz.integration_test.inmem.service; +import com.fasterxml.jackson.core.JsonProcessingException; import org.finos.waltz.common.DateTimeUtilities; +import org.finos.waltz.common.JacksonUtilities; import org.finos.waltz.common.exception.InsufficientPrivelegeException; import org.finos.waltz.integration_test.inmem.BaseInMemoryIntegrationTest; import org.finos.waltz.model.EntityKind; import org.finos.waltz.model.EntityReference; import org.finos.waltz.model.IdSelectionOptions; import org.finos.waltz.model.ReleaseLifecycleStatus; +import org.finos.waltz.model.changelog.ChangeLog; import org.finos.waltz.model.survey.ImmutableInstancesAndRecipientsCreateCommand; +import org.finos.waltz.model.survey.ImmutableSurveyInstanceActionParams; import org.finos.waltz.model.survey.ImmutableSurveyQuestionResponse; import org.finos.waltz.model.survey.ImmutableSurveyRunCreateCommand; import org.finos.waltz.model.survey.InstancesAndRecipientsCreateCommand; @@ -18,6 +22,7 @@ import org.finos.waltz.model.survey.SurveyInstanceStatus; import org.finos.waltz.model.survey.SurveyIssuanceKind; import org.finos.waltz.model.survey.SurveyQuestionResponse; +import org.finos.waltz.service.changelog.ChangeLogService; import org.finos.waltz.service.survey.SurveyInstanceActionQueueService; import org.finos.waltz.service.survey.SurveyInstanceService; import org.finos.waltz.service.survey.SurveyRunService; @@ -32,18 +37,22 @@ import org.springframework.stereotype.Service; import java.util.List; +import java.util.Optional; import java.util.Set; -import static java.lang.String.format; import static java.util.Collections.emptySet; +import static org.finos.waltz.common.CollectionUtilities.any; import static org.finos.waltz.common.CollectionUtilities.find; import static org.finos.waltz.common.DateTimeUtilities.nowUtcTimestamp; import static org.finos.waltz.common.DateTimeUtilities.toLocalDate; +import static org.finos.waltz.model.EntityReference.mkRef; import static org.finos.waltz.test_common.helpers.NameHelper.mkName; @Service public class SurveyInstanceActionQueueTest extends BaseInMemoryIntegrationTest { + public static final String REASON_TEXT = "test reason to capture"; + @Autowired private AppHelper appHelper; @@ -68,6 +77,9 @@ public class SurveyInstanceActionQueueTest extends BaseInMemoryIntegrationTest { @Autowired private InvolvementHelper involvementHelper; + @Autowired + private ChangeLogService changeLogService; + @Test public void processActionsCanSubmitSurveys() throws InsufficientPrivelegeException { String username = mkName("submitSurvey"); @@ -306,7 +318,32 @@ public void processActionsCanDoAllActions() throws InsufficientPrivelegeExceptio Assertions.assertEquals(SurveyInstanceActionStatus.SUCCESS, submit.status()); // Approve - Long approvalId = actionQueueHelper.addActionToQueue(instance.id().get(), SurveyInstanceAction.APPROVING, "reason", SurveyInstanceStatus.COMPLETED, username); + Long approvalId = actionQueueHelper.addActionToQueue(instance.id().get(), SurveyInstanceAction.APPROVING, null, SurveyInstanceStatus.COMPLETED, username); + actionQueueService.performActions(); + + SurveyInstance postApproval = instanceService.getById(instance.id().get()); + Assertions.assertEquals(SurveyInstanceStatus.APPROVED, postApproval.status(), "Action should be marked 'approved'"); + + SurveyInstanceActionQueueItem approval = actionQueueService.getById(approvalId); + Assertions.assertEquals(SurveyInstanceActionStatus.SUCCESS, approval.status()); + } + + + @Test + public void processActionsAbleToParseReasonFromAction() throws InsufficientPrivelegeException { + String username = mkName("reason"); + SurveyInstance instance = setupSurvey("reason", username); + + actionQueueHelper.addActionToQueue(instance.id().get(), SurveyInstanceAction.SUBMITTING, null, SurveyInstanceStatus.NOT_STARTED, username); + + ImmutableSurveyInstanceActionParams reasonParams = ImmutableSurveyInstanceActionParams + .builder() + .reason(REASON_TEXT) + .build(); + + String reason = mkReasonString(reasonParams); + + Long approvalId = actionQueueHelper.addActionToQueue(instance.id().get(), SurveyInstanceAction.APPROVING, reason, SurveyInstanceStatus.COMPLETED, username); actionQueueService.performActions(); SurveyInstance postApproval = instanceService.getById(instance.id().get()); @@ -314,6 +351,17 @@ public void processActionsCanDoAllActions() throws InsufficientPrivelegeExceptio SurveyInstanceActionQueueItem approval = actionQueueService.getById(approvalId); Assertions.assertEquals(SurveyInstanceActionStatus.SUCCESS, approval.status()); + + List changeLogs = changeLogService.findByParentReference(mkRef(EntityKind.SURVEY_INSTANCE, postApproval.id().get()), Optional.empty(), Optional.empty()); + Assertions.assertTrue(any(changeLogs, d -> d.message().contains(REASON_TEXT)), "Reason should be captured in the change log"); + } + + private String mkReasonString(ImmutableSurveyInstanceActionParams reason) { + try { + return JacksonUtilities.getJsonMapper().writeValueAsString(reason); + } catch (JsonProcessingException e) { + return null; + } } @@ -335,7 +383,7 @@ private SurveyInstance setupSurvey(String nameStem, String username, boolean pop long invKindId = involvementHelper.mkInvolvementKind(mkName("invKind")); involvementHelper.createInvolvement(personId, invKindId, a1); - ImmutableSurveyRunCreateCommand runCmd = mkRunCommand(templateId, EntityReference.mkRef(EntityKind.ORG_UNIT, ouIds.a), invKindId); + ImmutableSurveyRunCreateCommand runCmd = mkRunCommand(templateId, mkRef(EntityKind.ORG_UNIT, ouIds.a), invKindId); Long runId = runService.createSurveyRun(username, runCmd).id().get(); InstancesAndRecipientsCreateCommand createCmd = mkInstancesRecipCreateCmd(runId); diff --git a/waltz-model/src/main/java/org/finos/waltz/model/survey/SurveyInstanceActionParams.java b/waltz-model/src/main/java/org/finos/waltz/model/survey/SurveyInstanceActionParams.java new file mode 100644 index 0000000000..5812b442b2 --- /dev/null +++ b/waltz-model/src/main/java/org/finos/waltz/model/survey/SurveyInstanceActionParams.java @@ -0,0 +1,16 @@ +package org.finos.waltz.model.survey; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.immutables.value.Value; + +import java.util.Optional; + +@Value.Immutable +@JsonSerialize(as = ImmutableSurveyInstanceActionParams.class) +@JsonDeserialize(as = ImmutableSurveyInstanceActionParams.class) +public abstract class SurveyInstanceActionParams { + + public abstract Optional reason(); + +} diff --git a/waltz-model/src/main/java/org/finos/waltz/model/survey/SurveyInstanceActionQueueItem.java b/waltz-model/src/main/java/org/finos/waltz/model/survey/SurveyInstanceActionQueueItem.java index 973cb49994..d5c670751c 100644 --- a/waltz-model/src/main/java/org/finos/waltz/model/survey/SurveyInstanceActionQueueItem.java +++ b/waltz-model/src/main/java/org/finos/waltz/model/survey/SurveyInstanceActionQueueItem.java @@ -5,14 +5,14 @@ import org.immutables.value.Value; import java.time.LocalDateTime; +import java.util.Optional; @Value.Immutable public abstract class SurveyInstanceActionQueueItem implements IdProvider { public abstract SurveyInstanceAction action(); public abstract Long surveyInstanceId(); - @Nullable - public abstract String actionParams(); + public abstract Optional actionParams(); public abstract SurveyInstanceStatus initialState(); public abstract LocalDateTime submittedAt(); public abstract String submittedBy(); diff --git a/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceActionQueueService.java b/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceActionQueueService.java index ed83dbdc90..0debf08036 100644 --- a/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceActionQueueService.java +++ b/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceActionQueueService.java @@ -4,6 +4,7 @@ import org.finos.waltz.data.survey.SurveyInstanceActionQueueDao; import org.finos.waltz.model.survey.ImmutableSurveyInstanceStatusChangeCommand; import org.finos.waltz.model.survey.SurveyInstance; +import org.finos.waltz.model.survey.SurveyInstanceActionParams; import org.finos.waltz.model.survey.SurveyInstanceActionQueueItem; import org.finos.waltz.model.survey.SurveyInstanceActionStatus; import org.finos.waltz.model.survey.SurveyInstanceStatus; @@ -26,6 +27,7 @@ public class SurveyInstanceActionQueueService { private final SurveyInstanceService surveyInstanceService; private final DSLContext dslContext; + @Autowired SurveyInstanceActionQueueService(SurveyInstanceActionQueueDao surveyInstanceActionQueueDao, SurveyInstanceService surveyInstanceService, @@ -90,10 +92,11 @@ public void performActions() { String username = action.submittedBy(); + Optional reason = action.actionParams().map(SurveyInstanceActionParams::reason).orElse(Optional.empty()); ImmutableSurveyInstanceStatusChangeCommand updateCmd = ImmutableSurveyInstanceStatusChangeCommand .builder() .action(action.action()) - .reason(Optional.ofNullable(action.actionParams())) + .reason(reason) .build(); try { From 8107b3ae4d3fb4a0669da187bc6342772fd8f7c7 Mon Sep 17 00:00:00 2001 From: woodjes Date: Wed, 22 Nov 2023 13:21:54 +0000 Subject: [PATCH 6/9] Added more survey instance params to the action queue params - Supports updating due date and approval due date #CTCTOWALTZ-2680 #6860 --- .../waltz/data/survey/SurveyInstanceDao.java | 9 ++++++- .../SurveyInstanceActionQueueTest.java | 3 ++- .../survey/SurveyInstanceActionParams.java | 3 +++ .../SurveyInstanceStatusChangeCommand.java | 4 +++- .../SurveyInstanceActionQueueService.java | 9 ++++++- .../service/survey/SurveyInstanceService.java | 24 ++++++++++++++----- 6 files changed, 42 insertions(+), 10 deletions(-) diff --git a/waltz-data/src/main/java/org/finos/waltz/data/survey/SurveyInstanceDao.java b/waltz-data/src/main/java/org/finos/waltz/data/survey/SurveyInstanceDao.java index 06cb473b40..0a40283114 100644 --- a/waltz-data/src/main/java/org/finos/waltz/data/survey/SurveyInstanceDao.java +++ b/waltz-data/src/main/java/org/finos/waltz/data/survey/SurveyInstanceDao.java @@ -400,8 +400,13 @@ public int markApproved(Optional tx, long instanceId, String userNam .execute(); } - public int reopenSurvey(Optional tx, long instanceId) { + public int reopenSurvey(Optional tx, + long instanceId, + LocalDate dueDate, + LocalDate approvalDueDate) { + DSLContext dslContext = tx.orElse(dsl); + return dslContext .update(si) .set(si.STATUS, SurveyInstanceStatus.IN_PROGRESS.name()) @@ -410,6 +415,8 @@ public int reopenSurvey(Optional tx, long instanceId) { .set(si.SUBMITTED_AT, (Timestamp) null) .set(si.SUBMITTED_BY, (String) null) .set(si.ISSUED_ON, toSqlDate(nowUtcTimestamp())) //update the issued on to the current date + .set(si.DUE_DATE, toSqlDate(dueDate)) + .set(si.APPROVAL_DUE_DATE, toSqlDate(approvalDueDate)) .where(si.ID.eq(instanceId) .and(si.ORIGINAL_INSTANCE_ID.isNull()) .and(si.STATUS.in( diff --git a/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/SurveyInstanceActionQueueTest.java b/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/SurveyInstanceActionQueueTest.java index a082066ad8..232d4e600e 100644 --- a/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/SurveyInstanceActionQueueTest.java +++ b/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/SurveyInstanceActionQueueTest.java @@ -17,6 +17,7 @@ import org.finos.waltz.model.survey.InstancesAndRecipientsCreateCommand; import org.finos.waltz.model.survey.SurveyInstance; import org.finos.waltz.model.survey.SurveyInstanceAction; +import org.finos.waltz.model.survey.SurveyInstanceActionParams; import org.finos.waltz.model.survey.SurveyInstanceActionQueueItem; import org.finos.waltz.model.survey.SurveyInstanceActionStatus; import org.finos.waltz.model.survey.SurveyInstanceStatus; @@ -356,7 +357,7 @@ public void processActionsAbleToParseReasonFromAction() throws InsufficientPrive Assertions.assertTrue(any(changeLogs, d -> d.message().contains(REASON_TEXT)), "Reason should be captured in the change log"); } - private String mkReasonString(ImmutableSurveyInstanceActionParams reason) { + private String mkReasonString(SurveyInstanceActionParams reason) { try { return JacksonUtilities.getJsonMapper().writeValueAsString(reason); } catch (JsonProcessingException e) { diff --git a/waltz-model/src/main/java/org/finos/waltz/model/survey/SurveyInstanceActionParams.java b/waltz-model/src/main/java/org/finos/waltz/model/survey/SurveyInstanceActionParams.java index 5812b442b2..6c2e92a2a5 100644 --- a/waltz-model/src/main/java/org/finos/waltz/model/survey/SurveyInstanceActionParams.java +++ b/waltz-model/src/main/java/org/finos/waltz/model/survey/SurveyInstanceActionParams.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; import org.immutables.value.Value; +import java.time.LocalDate; import java.util.Optional; @Value.Immutable @@ -12,5 +13,7 @@ public abstract class SurveyInstanceActionParams { public abstract Optional reason(); + public abstract Optional newDueDate(); + public abstract Optional newApprovalDueDate(); } diff --git a/waltz-model/src/main/java/org/finos/waltz/model/survey/SurveyInstanceStatusChangeCommand.java b/waltz-model/src/main/java/org/finos/waltz/model/survey/SurveyInstanceStatusChangeCommand.java index 8407cd8f0e..12ee8fb35f 100644 --- a/waltz-model/src/main/java/org/finos/waltz/model/survey/SurveyInstanceStatusChangeCommand.java +++ b/waltz-model/src/main/java/org/finos/waltz/model/survey/SurveyInstanceStatusChangeCommand.java @@ -24,6 +24,7 @@ import org.finos.waltz.model.command.Command; import org.immutables.value.Value; +import java.time.LocalDate; import java.util.Optional; @Value.Immutable @@ -32,7 +33,8 @@ public abstract class SurveyInstanceStatusChangeCommand implements Command { public abstract SurveyInstanceAction action(); - public abstract Optional reason(); + public abstract Optional newDueDate(); + public abstract Optional newApprovalDueDate(); } diff --git a/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceActionQueueService.java b/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceActionQueueService.java index 0debf08036..41fc573373 100644 --- a/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceActionQueueService.java +++ b/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceActionQueueService.java @@ -1,6 +1,7 @@ package org.finos.waltz.service.survey; import org.finos.waltz.common.Checks; +import org.finos.waltz.common.CollectionUtilities; import org.finos.waltz.data.survey.SurveyInstanceActionQueueDao; import org.finos.waltz.model.survey.ImmutableSurveyInstanceStatusChangeCommand; import org.finos.waltz.model.survey.SurveyInstance; @@ -14,6 +15,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.time.LocalDate; import java.util.List; import java.util.Optional; @@ -92,11 +94,16 @@ public void performActions() { String username = action.submittedBy(); - Optional reason = action.actionParams().map(SurveyInstanceActionParams::reason).orElse(Optional.empty()); + Optional reason = action.actionParams().flatMap(SurveyInstanceActionParams::reason); + Optional dueDate = action.actionParams().flatMap(SurveyInstanceActionParams::newDueDate); + Optional approvalDueDate = action.actionParams().flatMap(SurveyInstanceActionParams::newApprovalDueDate); + ImmutableSurveyInstanceStatusChangeCommand updateCmd = ImmutableSurveyInstanceStatusChangeCommand .builder() .action(action.action()) .reason(reason) + .newDueDate(dueDate) + .newApprovalDueDate(approvalDueDate) .build(); try { diff --git a/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceService.java b/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceService.java index febceca6c9..3d99f54bf2 100644 --- a/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceService.java +++ b/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceService.java @@ -264,7 +264,7 @@ public Person checkPersonIsRecipient(String userName, long instanceId) { /** - * Uupdates a survey instance using a StatusChangeCommand. If a DSLContext is provided it will use this otherwise will use the DSLContext within the DAO. + * Updates a survey instance using a StatusChangeCommand. If a DSLContext is provided it will use this otherwise will use the DSLContext within the DAO. * @param tx optional DSLContext to use as part of a transaction * @param userName the user to submit the status change * @param instanceId the id of the survey being updated @@ -319,8 +319,12 @@ public SurveyInstanceStatus updateStatus(Optional tx, checkTrue(newStatus.equals(SurveyInstanceStatus.IN_PROGRESS), "The resolved new status for REOPENING should be 'IN_PROGRESS', resolved to: " + newStatus); // if survey is being sent back, store current responses as a version long versionedInstanceId = surveyInstanceDao.createPreviousVersion(tx, surveyInstance); - surveyQuestionResponseDao.cloneResponses(tx, surveyInstance.id().get(), versionedInstanceId); - nbupdates = surveyInstanceDao.reopenSurvey(tx, instanceId); + surveyQuestionResponseDao.cloneResponses(tx, instanceId, versionedInstanceId); + nbupdates = surveyInstanceDao.reopenSurvey( + tx, + instanceId, + command.newDueDate().orElse(surveyInstance.dueDate()), + command.newApprovalDueDate().orElse(surveyInstance.approvalDueDate())); break; default: nbupdates = surveyInstanceDao.updateStatus(tx, instanceId, newStatus); @@ -544,16 +548,24 @@ public List findPossibleActionsForInstance(String userName public SurveyInstancePermissions getPermissions(String userName, Long instanceId) { Person person = personDao.getActiveByUserEmail(userName); + SurveyInstance instance = surveyInstanceDao.getById(instanceId); SurveyRun run = surveyRunDao.getById(instance.surveyRunId()); boolean isAdmin = userRoleService.hasRole(userName, SystemRole.SURVEY_ADMIN); - boolean isParticipant = surveyInstanceRecipientDao.isPersonInstanceRecipient(person.id().get(), instanceId); - boolean isOwner = person.id() + + boolean isParticipant = person != null && person.id() + .map(pid -> surveyInstanceRecipientDao.isPersonInstanceRecipient(pid, instanceId)) + .orElse(false); + + boolean isOwner = person != null && person.id() .map(pid -> surveyInstanceOwnerDao.isPersonInstanceOwner(pid, instanceId) || Objects.equals(run.ownerId(), pid)) .orElse(false); - boolean hasOwningRole = userRoleService.hasRole(person.email(), instance.owningRole()); + + boolean hasOwningRole = userRoleService.hasRole(userName, instance.owningRole()); + boolean isLatest = instance.originalInstanceId() == null; + boolean editableStatus = instance.status() == SurveyInstanceStatus.NOT_STARTED || instance.status() == SurveyInstanceStatus.IN_PROGRESS; return ImmutableSurveyInstancePermissions.builder() From 52800ac5aef002ace11d307bf4aa4045e6b114cd Mon Sep 17 00:00:00 2001 From: woodjes Date: Wed, 22 Nov 2023 14:08:50 +0000 Subject: [PATCH 7/9] Adding change logs to SurveyActionQueueService - Used to inform if an action has been applied via scheduled queue, useful as prints the reason if the action failed. #CTCTOWALTZ-2680 #6860 --- .../SurveyInstanceActionQueueService.java | 54 +++++++++++++++---- 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceActionQueueService.java b/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceActionQueueService.java index 41fc573373..aa28a09fc7 100644 --- a/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceActionQueueService.java +++ b/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceActionQueueService.java @@ -1,14 +1,19 @@ package org.finos.waltz.service.survey; import org.finos.waltz.common.Checks; -import org.finos.waltz.common.CollectionUtilities; import org.finos.waltz.data.survey.SurveyInstanceActionQueueDao; +import org.finos.waltz.model.EntityKind; +import org.finos.waltz.model.Operation; +import org.finos.waltz.model.Severity; +import org.finos.waltz.model.changelog.ChangeLog; +import org.finos.waltz.model.changelog.ImmutableChangeLog; import org.finos.waltz.model.survey.ImmutableSurveyInstanceStatusChangeCommand; import org.finos.waltz.model.survey.SurveyInstance; import org.finos.waltz.model.survey.SurveyInstanceActionParams; import org.finos.waltz.model.survey.SurveyInstanceActionQueueItem; import org.finos.waltz.model.survey.SurveyInstanceActionStatus; import org.finos.waltz.model.survey.SurveyInstanceStatus; +import org.finos.waltz.service.changelog.ChangeLogService; import org.jooq.DSLContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -20,6 +25,7 @@ import java.util.Optional; import static java.lang.String.format; +import static org.finos.waltz.model.EntityReference.mkRef; @Service public class SurveyInstanceActionQueueService { @@ -27,20 +33,25 @@ public class SurveyInstanceActionQueueService { private static final Logger LOG = LoggerFactory.getLogger(SurveyInstanceActionQueueService.class); private final SurveyInstanceActionQueueDao surveyInstanceActionQueueDao; private final SurveyInstanceService surveyInstanceService; + + private final ChangeLogService changeLogService; private final DSLContext dslContext; @Autowired SurveyInstanceActionQueueService(SurveyInstanceActionQueueDao surveyInstanceActionQueueDao, SurveyInstanceService surveyInstanceService, + ChangeLogService changeLogService, DSLContext dslContext) { Checks.checkNotNull(surveyInstanceActionQueueDao, "surveyInstanceActionQueueDao cannot be null"); Checks.checkNotNull(surveyInstanceService, "surveyInstanceService cannot be null"); + Checks.checkNotNull(changeLogService, "changeLogService cannot be null"); Checks.checkNotNull(dslContext, "dslContext cannot be null"); this.surveyInstanceActionQueueDao = surveyInstanceActionQueueDao; this.surveyInstanceService = surveyInstanceService; + this.changeLogService = changeLogService; this.dslContext = dslContext; } @@ -68,7 +79,8 @@ public void performActions() { if (instance == null) { - String msg = format("Could not find survey instance with id: %d to apply action: %s", action.surveyInstanceId(), action.action().name()); + String msg = format("Failed to apply queued action: %s. Could not find survey instance with id: %d", action.action().name(), action.surveyInstanceId()); + LOG.info(msg); surveyInstanceActionQueueDao.updateActionStatus( tx, @@ -76,13 +88,17 @@ public void performActions() { SurveyInstanceActionStatus.PRECONDITION_FAILURE, msg); + ChangeLog changeLog = mkChangelogForAction(tx, action, msg); + changeLogService.write(Optional.of(tx), changeLog); + } else if (instance.status() != action.initialState()) { - String msg = format("Initial state of survey instance with id: %d is not as expected: %s and is actually %s, will not apply action: %s", + String msg = format("Failed to apply queued action: %s. Initial state of survey instance with id: %d is not as expected: %s and is actually %s", + action.action().name(), action.surveyInstanceId(), action.initialState().name(), - instance.status().name(), - action.action().name()); + instance.status().name()); + LOG.info(msg); surveyInstanceActionQueueDao.updateActionStatus( tx, @@ -90,6 +106,9 @@ public void performActions() { SurveyInstanceActionStatus.PRECONDITION_FAILURE, msg); + ChangeLog changeLog = mkChangelogForAction(tx, action, msg); + changeLogService.write(Optional.of(tx), changeLog); + } else { String username = action.submittedBy(); @@ -119,8 +138,7 @@ public void performActions() { action.surveyInstanceId(), updateCmd); - String msg = format("Successfully updated survey instance id: %d with action: %s, new status is: %s", - action.surveyInstanceId(), + String msg = format("Successfully applied queued action: %s. New status is: %s", action.action().name(), surveyInstanceStatus.name()); @@ -130,14 +148,17 @@ public void performActions() { actionId, SurveyInstanceActionStatus.SUCCESS, null); + + ChangeLog changeLog = mkChangelogForAction(tx, action, msg); + changeLogService.write(Optional.of(tx), changeLog); }); } catch (Exception e) { - String msg = format("Error when updating survey instance id: %d with action: %s. %s", - action.surveyInstanceId(), + String msg = format("Failed to apply queued action: %s. Error when updating survey instance id: %d, %s", action.action().name(), + action.surveyInstanceId(), e.getMessage()); LOG.error(msg); @@ -146,6 +167,9 @@ public void performActions() { actionId, SurveyInstanceActionStatus.EXECUTION_FAILURE, msg); + + ChangeLog changeLog = mkChangelogForAction(tx, action, msg); + changeLogService.write(Optional.of(tx), changeLog); } } @@ -155,6 +179,18 @@ public void performActions() { } + private ChangeLog mkChangelogForAction(DSLContext tx, SurveyInstanceActionQueueItem action, String msg) { + return ImmutableChangeLog + .builder() + .message(msg) + .userId(action.submittedBy()) + .parentReference(mkRef(EntityKind.SURVEY_INSTANCE, action.surveyInstanceId())) + .operation(Operation.UPDATE) + .severity(Severity.INFORMATION) + .build(); + } + + public List findPendingActions() { return surveyInstanceActionQueueDao.findPendingActions(); } From ecea9c5f7e5491645fb9918edd29b8e028219972 Mon Sep 17 00:00:00 2001 From: woodjes Date: Wed, 22 Nov 2023 15:22:22 +0000 Subject: [PATCH 8/9] Adding change logs to SurveyActionQueueService - Used to inform if an action has been applied via scheduled queue, useful as prints the reason if the action failed. #CTCTOWALTZ-2680 #6860 --- pom.xml | 26 ------------------- .../service/DataTypeDecoratorServiceTest.java | 18 ++++++------- .../data_type/DataTypeDecoratorService.java | 4 +-- .../SurveyInstanceActionQueueService.java | 7 ++--- .../LogicalFlowDecoratorStatsEndpoint.java | 26 +++++++++++-------- 5 files changed, 30 insertions(+), 51 deletions(-) diff --git a/pom.xml b/pom.xml index ee2f3c63e6..59cc948cb3 100644 --- a/pom.xml +++ b/pom.xml @@ -310,24 +310,6 @@ - - org.junit.jupiter - junit-jupiter-api - ${junit.version} - - - org.junit.jupiter - junit-jupiter-engine - ${junit.version} - test - - - - org.junit.platform - junit-platform-launcher - 1.7.2 - test - org.mockito @@ -342,14 +324,6 @@ test - - - org.junit.vintage - junit-vintage-engine - ${junit.version} - test - - com.tngtech.archunit archunit diff --git a/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/DataTypeDecoratorServiceTest.java b/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/DataTypeDecoratorServiceTest.java index e484299d1d..233ea03e05 100644 --- a/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/DataTypeDecoratorServiceTest.java +++ b/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/DataTypeDecoratorServiceTest.java @@ -74,14 +74,14 @@ public class DataTypeDecoratorServiceTest extends BaseInMemoryIntegrationTest { @Test public void findByFlowIds() { - Collection lfDecs = dtdSvc.findByFlowIds(emptyList(), EntityKind.LOGICAL_DATA_FLOW); - Collection psDecs = dtdSvc.findByFlowIds(emptyList(), EntityKind.PHYSICAL_SPECIFICATION); + Set lfDecs = dtdSvc.findByFlowIds(emptyList(), EntityKind.LOGICAL_DATA_FLOW); + Set psDecs = dtdSvc.findByFlowIds(emptyList(), EntityKind.PHYSICAL_SPECIFICATION); - assertEquals(emptyList(), lfDecs, "If empty id list provided returns empty list"); - assertEquals(emptyList(), psDecs, "If empty id list provided returns empty list"); + assertEquals(emptySet(), lfDecs, "If empty id list provided returns empty set"); + assertEquals(emptySet(), psDecs, "If empty id list provided returns empty set"); Collection invalidId = dtdSvc.findByFlowIds(asList(-1L), EntityKind.LOGICAL_DATA_FLOW); - assertEquals(emptyList(), invalidId, "If flow id doesn't exist returns empty list"); + assertEquals(emptySet(), invalidId, "If flow id doesn't exist returns empty set"); assertThrows(IllegalArgumentException.class, () -> dtdSvc.findByFlowIds(asList(-1L), EntityKind.APPLICATION), @@ -92,8 +92,8 @@ public void findByFlowIds() { LogicalFlow flow = lfHelper.createLogicalFlow(a, b); - Collection withNoDecorators = dtdSvc.findByFlowIds(asList(flow.entityReference().id()), EntityKind.LOGICAL_DATA_FLOW); - assertEquals(emptyList(), withNoDecorators, + Set withNoDecorators = dtdSvc.findByFlowIds(asList(flow.entityReference().id()), EntityKind.LOGICAL_DATA_FLOW); + assertEquals(emptySet(), withNoDecorators, "flow has no decorators"); Long dtId = dataTypeHelper.createDataType("findByFlowIds"); @@ -101,7 +101,7 @@ public void findByFlowIds() { dtdSvc.updateDecorators(username, flow.entityReference(), asSet(dtId), emptySet()); - Collection flowDecorators = dtdSvc.findByFlowIds(asList(flow.entityReference().id()), EntityKind.LOGICAL_DATA_FLOW); + Set flowDecorators = dtdSvc.findByFlowIds(asList(flow.entityReference().id()), EntityKind.LOGICAL_DATA_FLOW); assertEquals(1, flowDecorators.size(), "Flow with one datatype associated returns a set with one decorator"); assertEquals(dtId, Long.valueOf(first(flowDecorators).dataTypeId()), "Returns the correct datatype id on the decorator"); @@ -110,7 +110,7 @@ public void findByFlowIds() { Long dtId3 = dataTypeHelper.createDataType("findByFlowIds3"); dtdSvc.updateDecorators(username, flow.entityReference(), asSet(dtId2, dtId3), emptySet()); - Collection multipleDecorators = dtdSvc.findByFlowIds(asList(flow.entityReference().id()), EntityKind.LOGICAL_DATA_FLOW); + Set multipleDecorators = dtdSvc.findByFlowIds(asList(flow.entityReference().id()), EntityKind.LOGICAL_DATA_FLOW); assertEquals(3, multipleDecorators.size()); assertEquals(asSet(dtId, dtId2, dtId3), map(multipleDecorators, DataTypeDecorator::dataTypeId), "Returns all decorators for the flow"); diff --git a/waltz-service/src/main/java/org/finos/waltz/service/data_type/DataTypeDecoratorService.java b/waltz-service/src/main/java/org/finos/waltz/service/data_type/DataTypeDecoratorService.java index 99e6627db1..581bb56927 100644 --- a/waltz-service/src/main/java/org/finos/waltz/service/data_type/DataTypeDecoratorService.java +++ b/waltz-service/src/main/java/org/finos/waltz/service/data_type/DataTypeDecoratorService.java @@ -296,9 +296,9 @@ private Collection getSelectorForLogicalFlow(DataTypeDecorato } - public Collection findByFlowIds(Collection ids, EntityKind entityKind) { + public Set findByFlowIds(Collection ids, EntityKind entityKind) { if (isEmpty(ids)) { - return Collections.emptyList(); + return Collections.emptySet(); } return dataTypeDecoratorDaoSelectorFactory .getDao(entityKind) diff --git a/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceActionQueueService.java b/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceActionQueueService.java index aa28a09fc7..38c0861a53 100644 --- a/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceActionQueueService.java +++ b/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceActionQueueService.java @@ -93,7 +93,7 @@ public void performActions() { } else if (instance.status() != action.initialState()) { - String msg = format("Failed to apply queued action: %s. Initial state of survey instance with id: %d is not as expected: %s and is actually %s", + String msg = format("Failed to apply queued action: %s to survey: %d. Initial state of survey is not as expected: %s and is actually %s", action.action().name(), action.surveyInstanceId(), action.initialState().name(), @@ -138,8 +138,9 @@ public void performActions() { action.surveyInstanceId(), updateCmd); - String msg = format("Successfully applied queued action: %s. New status is: %s", + String msg = format("Successfully applied queued action: %s to survey: %d. New status is: %s", action.action().name(), + action.surveyInstanceId(), surveyInstanceStatus.name()); LOG.info(msg); @@ -156,7 +157,7 @@ public void performActions() { } catch (Exception e) { - String msg = format("Failed to apply queued action: %s. Error when updating survey instance id: %d, %s", + String msg = format("Failed to apply queued action: %s to survey: %d. Error when updating: %s", action.action().name(), action.surveyInstanceId(), e.getMessage()); diff --git a/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/LogicalFlowDecoratorStatsEndpoint.java b/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/LogicalFlowDecoratorStatsEndpoint.java index 098ac44706..a20ed723bd 100644 --- a/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/LogicalFlowDecoratorStatsEndpoint.java +++ b/waltz-web/src/main/java/org/finos/waltz/web/endpoints/api/LogicalFlowDecoratorStatsEndpoint.java @@ -18,17 +18,17 @@ package org.finos.waltz.web.endpoints.api; -import org.finos.waltz.service.data_flow_decorator.LogicalFlowDecoratorService; -import org.finos.waltz.service.data_type.DataTypeDecoratorService; -import org.finos.waltz.service.user.UserRoleService; -import org.finos.waltz.web.ListRoute; -import org.finos.waltz.web.endpoints.Endpoint; import org.finos.waltz.model.IdSelectionOptions; import org.finos.waltz.model.data_flow_decorator.DecoratorRatingSummary; import org.finos.waltz.model.data_flow_decorator.LogicalFlowDecoratorStat; import org.finos.waltz.model.data_flow_decorator.UpdateDataFlowDecoratorsAction; import org.finos.waltz.model.datatype.DataTypeDecorator; import org.finos.waltz.model.user.SystemRole; +import org.finos.waltz.service.data_flow_decorator.LogicalFlowDecoratorService; +import org.finos.waltz.service.data_type.DataTypeDecoratorService; +import org.finos.waltz.service.user.UserRoleService; +import org.finos.waltz.web.ListRoute; +import org.finos.waltz.web.endpoints.Endpoint; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -38,15 +38,19 @@ import java.io.IOException; import java.util.Arrays; -import java.util.Collection; import java.util.List; +import java.util.Set; -import static org.finos.waltz.web.WebUtilities.*; -import static org.finos.waltz.web.endpoints.EndpointUtilities.getForList; -import static org.finos.waltz.web.endpoints.EndpointUtilities.postForList; import static org.finos.waltz.common.Checks.checkNotNull; import static org.finos.waltz.common.CollectionUtilities.map; import static org.finos.waltz.model.EntityKind.LOGICAL_DATA_FLOW; +import static org.finos.waltz.web.WebUtilities.getUsername; +import static org.finos.waltz.web.WebUtilities.mkPath; +import static org.finos.waltz.web.WebUtilities.readBody; +import static org.finos.waltz.web.WebUtilities.readIdSelectionOptionsFromBody; +import static org.finos.waltz.web.WebUtilities.requireRole; +import static org.finos.waltz.web.endpoints.EndpointUtilities.getForList; +import static org.finos.waltz.web.endpoints.EndpointUtilities.postForList; @Service @@ -118,14 +122,14 @@ public void register() { } - private Collection updateDecoratorsBatchRoute(Request request, Response response) throws IOException { + private Set updateDecoratorsBatchRoute(Request request, Response response) throws IOException { requireRole(userRoleService, request, SystemRole.BULK_FLOW_EDITOR); String user = getUsername(request); List actions = Arrays.asList(readBody(request, UpdateDataFlowDecoratorsAction[].class)); logicalFlowDecoratorService.addDecoratorsBatch(actions, user); - return dataTypeDecoratorService.findByFlowIds(map(actions, a -> a.flowId()), LOGICAL_DATA_FLOW); + return dataTypeDecoratorService.findByFlowIds(map(actions, UpdateDataFlowDecoratorsAction::flowId), LOGICAL_DATA_FLOW); } } From e4e55f2adeb4f83c2a947c7140d50e151844be7a Mon Sep 17 00:00:00 2001 From: woodjes Date: Fri, 24 Nov 2023 09:28:46 +0000 Subject: [PATCH 9/9] as you --- .../waltz/data/survey/SurveyInstanceActionQueueDao.java | 2 +- waltz-ng/package-lock.json | 2 +- .../AggregateOverlayDiagramService.java | 5 +++-- .../service/survey/SurveyInstanceActionQueueService.java | 3 +-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/waltz-data/src/main/java/org/finos/waltz/data/survey/SurveyInstanceActionQueueDao.java b/waltz-data/src/main/java/org/finos/waltz/data/survey/SurveyInstanceActionQueueDao.java index 6095204e34..4563d31507 100644 --- a/waltz-data/src/main/java/org/finos/waltz/data/survey/SurveyInstanceActionQueueDao.java +++ b/waltz-data/src/main/java/org/finos/waltz/data/survey/SurveyInstanceActionQueueDao.java @@ -70,7 +70,7 @@ private static Optional readParams(ObjectMapper json @Autowired - SurveyInstanceActionQueueDao(DSLContext dsl) { + public SurveyInstanceActionQueueDao(DSLContext dsl) { this.dsl = dsl; } diff --git a/waltz-ng/package-lock.json b/waltz-ng/package-lock.json index 612ff044ea..2423092528 100644 --- a/waltz-ng/package-lock.json +++ b/waltz-ng/package-lock.json @@ -1,6 +1,6 @@ { "name": "waltz-ng", - "version": "1.55.0", + "version": "1.56.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/waltz-service/src/main/java/org/finos/waltz/service/aggregate_overlay_diagram/AggregateOverlayDiagramService.java b/waltz-service/src/main/java/org/finos/waltz/service/aggregate_overlay_diagram/AggregateOverlayDiagramService.java index 5dd43bc64b..dc703063df 100644 --- a/waltz-service/src/main/java/org/finos/waltz/service/aggregate_overlay_diagram/AggregateOverlayDiagramService.java +++ b/waltz-service/src/main/java/org/finos/waltz/service/aggregate_overlay_diagram/AggregateOverlayDiagramService.java @@ -29,9 +29,9 @@ import org.finos.waltz.model.aggregate_overlay_diagram.AggregateOverlayDiagramPreset; import org.finos.waltz.model.aggregate_overlay_diagram.BackingEntity; import org.finos.waltz.model.aggregate_overlay_diagram.ImmutableAggregateOverlayDiagramInfo; -import org.finos.waltz.model.aggregate_overlay_diagram.OverlayDiagramSaveCommand; import org.finos.waltz.model.aggregate_overlay_diagram.OverlayDiagramKind; import org.finos.waltz.model.aggregate_overlay_diagram.OverlayDiagramPresetCreateCommand; +import org.finos.waltz.model.aggregate_overlay_diagram.OverlayDiagramSaveCommand; import org.finos.waltz.model.aggregate_overlay_diagram.overlay.AggregatedEntitiesWidgetData; import org.finos.waltz.model.aggregate_overlay_diagram.overlay.AggregatedEntitiesWidgetDatum; import org.finos.waltz.model.aggregate_overlay_diagram.overlay.ApplicationChangeWidgetData; @@ -111,7 +111,8 @@ public class AggregateOverlayDiagramService { public AggregateOverlayDiagramService(AggregateOverlayDiagramDao aggregateOverlayDiagramDao, AppCountWidgetDao appCountWidgetDao, TargetAppCostWidgetDao targetAppCostWidgetDao, - RatingCostWidgetDao ratingCostWidgetDao, AssessmentRatingWidgetDao appAssessmentWidgetDao, + RatingCostWidgetDao ratingCostWidgetDao, + AssessmentRatingWidgetDao appAssessmentWidgetDao, BackingEntityWidgetDao backingEntityWidgetDao, AppCostWidgetDao appCostWidgetDao, AggregatedEntitiesWidgetDao aggregatedEntitiesWidgetDao, diff --git a/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceActionQueueService.java b/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceActionQueueService.java index 38c0861a53..4f9ed0343c 100644 --- a/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceActionQueueService.java +++ b/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceActionQueueService.java @@ -33,13 +33,12 @@ public class SurveyInstanceActionQueueService { private static final Logger LOG = LoggerFactory.getLogger(SurveyInstanceActionQueueService.class); private final SurveyInstanceActionQueueDao surveyInstanceActionQueueDao; private final SurveyInstanceService surveyInstanceService; - private final ChangeLogService changeLogService; private final DSLContext dslContext; @Autowired - SurveyInstanceActionQueueService(SurveyInstanceActionQueueDao surveyInstanceActionQueueDao, + public SurveyInstanceActionQueueService(SurveyInstanceActionQueueDao surveyInstanceActionQueueDao, SurveyInstanceService surveyInstanceService, ChangeLogService changeLogService, DSLContext dslContext) {