diff --git a/pom.xml b/pom.xml
index 1ca25ba3f7..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
@@ -363,6 +337,14 @@
test
+
+ org.junit
+ junit-bom
+ ${junit.version}
+ pom
+ import
+
+
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..4563d31507
--- /dev/null
+++ b/waltz-data/src/main/java/org/finos/waltz/data/survey/SurveyInstanceActionQueueDao.java
@@ -0,0 +1,148 @@
+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;
+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 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
+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(surveyInstanceActionParams)
+ .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();
+ };
+
+
+ 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
+ public 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..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
@@ -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,14 @@ public int markApproved(long instanceId, String userName) {
.execute();
}
+ public int reopenSurvey(Optional tx,
+ long instanceId,
+ LocalDate dueDate,
+ LocalDate approvalDueDate) {
- public int reopenSurvey(long instanceId) {
- return dsl
+ DSLContext dslContext = tx.orElse(dsl);
+
+ return dslContext
.update(si)
.set(si.STATUS, SurveyInstanceStatus.IN_PROGRESS.name())
.set(si.APPROVED_AT, (Timestamp) null)
@@ -409,6 +415,8 @@ public int reopenSurvey(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-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-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-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..232d4e600e
--- /dev/null
+++ b/waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/SurveyInstanceActionQueueTest.java
@@ -0,0 +1,439 @@
+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;
+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;
+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;
+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.Optional;
+import java.util.Set;
+
+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;
+
+ @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;
+
+ @Autowired
+ private ChangeLogService changeLogService;
+
+ @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, 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());
+ Assertions.assertEquals(SurveyInstanceStatus.APPROVED, postApproval.status(), "Action should be marked 'approved'");
+
+ 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(SurveyInstanceActionParams reason) {
+ try {
+ return JacksonUtilities.getJsonMapper().writeValueAsString(reason);
+ } catch (JsonProcessingException e) {
+ return null;
+ }
+ }
+
+
+ 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, 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-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/SurveyInstanceActionParams.java b/waltz-model/src/main/java/org/finos/waltz/model/survey/SurveyInstanceActionParams.java
new file mode 100644
index 0000000000..6c2e92a2a5
--- /dev/null
+++ b/waltz-model/src/main/java/org/finos/waltz/model/survey/SurveyInstanceActionParams.java
@@ -0,0 +1,19 @@
+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.time.LocalDate;
+import java.util.Optional;
+
+@Value.Immutable
+@JsonSerialize(as = ImmutableSurveyInstanceActionParams.class)
+@JsonDeserialize(as = ImmutableSurveyInstanceActionParams.class)
+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/SurveyInstanceActionQueueItem.java b/waltz-model/src/main/java/org/finos/waltz/model/survey/SurveyInstanceActionQueueItem.java
new file mode 100644
index 0000000000..d5c670751c
--- /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;
+import java.util.Optional;
+
+@Value.Immutable
+public abstract class SurveyInstanceActionQueueItem implements IdProvider {
+
+ public abstract SurveyInstanceAction action();
+ public abstract Long surveyInstanceId();
+ public abstract Optional 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-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-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/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/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/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..4f9ed0343c
--- /dev/null
+++ b/waltz-service/src/main/java/org/finos/waltz/service/survey/SurveyInstanceActionQueueService.java
@@ -0,0 +1,202 @@
+package org.finos.waltz.service.survey;
+
+import org.finos.waltz.common.Checks;
+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;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDate;
+import java.util.List;
+import java.util.Optional;
+
+import static java.lang.String.format;
+import static org.finos.waltz.model.EntityReference.mkRef;
+
+@Service
+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
+ public 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;
+ }
+
+ /**
+ * 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();
+
+ 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("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,
+ actionId,
+ 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("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(),
+ instance.status().name());
+
+ LOG.info(msg);
+ surveyInstanceActionQueueDao.updateActionStatus(
+ tx,
+ actionId,
+ SurveyInstanceActionStatus.PRECONDITION_FAILURE,
+ msg);
+
+ ChangeLog changeLog = mkChangelogForAction(tx, action, msg);
+ changeLogService.write(Optional.of(tx), changeLog);
+
+ } else {
+
+ String username = action.submittedBy();
+
+ 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 {
+
+ // 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 applied queued action: %s to survey: %d. New status is: %s",
+ action.action().name(),
+ action.surveyInstanceId(),
+ surveyInstanceStatus.name());
+
+ LOG.info(msg);
+ surveyInstanceActionQueueDao.updateActionStatus(
+ tx,
+ actionId,
+ SurveyInstanceActionStatus.SUCCESS,
+ null);
+
+ ChangeLog changeLog = mkChangelogForAction(tx, action, msg);
+ changeLogService.write(Optional.of(tx), changeLog);
+ });
+
+
+ } catch (Exception e) {
+
+ String msg = format("Failed to apply queued action: %s to survey: %d. Error when updating: %s",
+ action.action().name(),
+ action.surveyInstanceId(),
+ e.getMessage());
+
+ LOG.error(msg);
+ surveyInstanceActionQueueDao.updateActionStatus(
+ tx,
+ actionId,
+ SurveyInstanceActionStatus.EXECUTION_FAILURE,
+ msg);
+
+ ChangeLog changeLog = mkChangelogForAction(tx, action, msg);
+ changeLogService.write(Optional.of(tx), changeLog);
+ }
+ }
+
+ });
+
+ });
+ }
+
+
+ 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();
+ }
+
+ public SurveyInstanceActionQueueItem getById(long id) {
+ return surveyInstanceActionQueueDao.getById(id);
+ }
+
+}
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..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
@@ -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,
+ /**
+ * 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
+ * @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,31 @@ 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, instanceId, versionedInstanceId);
+ nbupdates = surveyInstanceDao.reopenSurvey(
+ tx,
+ instanceId,
+ command.newDueDate().orElse(surveyInstance.dueDate()),
+ command.newApprovalDueDate().orElse(surveyInstance.approvalDueDate()));
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 +346,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 +359,7 @@ protected int removeUnnecessaryResponses(long instanceId) {
}
if (!toRemove.isEmpty()) {
- return surveyQuestionResponseDao.deletePreviousResponse(toRemove);
+ return surveyQuestionResponseDao.deletePreviousResponse(tx, toRemove);
} else {
return 0;
}
@@ -529,17 +546,26 @@ 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()
@@ -598,6 +624,7 @@ public int copyResponses(long sourceSurveyId, CopySurveyResponsesCommand copyCom
.forEach(trgInstanceId -> {
updateStatus(
+ Optional.empty(),
username,
trgInstanceId,
ImmutableSurveyInstanceStatusChangeCommand.builder()
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();
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);
}
}
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