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