Skip to content

Commit 577a84c

Browse files
Pull request #705: Integration/maker checker workflow
Merge in WALTZ/waltz from integration/maker-checker-workflow to db-feature/waltz-7457-proposed-flow-workflow-enahancements * commit '8f17a98b884dbd7a9952a1506dd0b1eae7c34274': import set Revert "Pull request #689: use for update when users action on flows - so that no two actions get applied simultaneously" Revert "refactor" Revert "Pull request #695: CTCTOWALTZ-5244 sync of simultaneous flow actions"
2 parents 8783f44 + 8f17a98 commit 577a84c

5 files changed

Lines changed: 96 additions & 245 deletions

File tree

waltz-data/src/main/java/org/finos/waltz/data/entity_workflow/EntityWorkflowStateDao.java

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import org.finos.waltz.model.ImmutableEntityReference;
2626
import org.finos.waltz.model.entity_workflow.EntityWorkflowState;
2727
import org.finos.waltz.model.entity_workflow.ImmutableEntityWorkflowState;
28-
import org.finos.waltz.schema.Tables;
2928
import org.finos.waltz.schema.tables.records.EntityWorkflowStateRecord;
3029
import org.jooq.DSLContext;
3130
import org.jooq.Record;
@@ -95,26 +94,15 @@ public void createWorkflowState(Long workflowDefId,
9594
stateRecord.insert();
9695
}
9796

98-
public long updateStateTransactional(DSLContext dsl, Long workflowDefId, EntityReference ref, String user, String workflowState) {
97+
public long updateState(Long workflowDefId, EntityReference ref, String user, String workflowState) {
9998
return dsl
100-
.update(ENTITY_WORKFLOW_STATE)
101-
.set(ENTITY_WORKFLOW_STATE.STATE, workflowState)
102-
.set(ENTITY_WORKFLOW_STATE.LAST_UPDATED_AT, Timestamp.valueOf(nowUtc()))
103-
.set(ENTITY_WORKFLOW_STATE.LAST_UPDATED_BY, user)
104-
.where(ENTITY_WORKFLOW_STATE.WORKFLOW_ID.eq(workflowDefId)
105-
.and(ENTITY_WORKFLOW_STATE.ENTITY_ID.eq(ref.id()))
106-
.and(ENTITY_WORKFLOW_STATE.ENTITY_KIND.eq(ref.kind().name())))
107-
.execute();
108-
}
109-
110-
public EntityWorkflowState getProposedFlowWorkflowStateForUpdate(DSLContext tx, Long workflowId, EntityReference entityReference) {
111-
return tx
112-
.select(Tables.ENTITY_WORKFLOW_STATE.fields())
113-
.from(Tables.ENTITY_WORKFLOW_STATE)
114-
.where(Tables.ENTITY_WORKFLOW_STATE.WORKFLOW_ID.eq(workflowId))
115-
.and(Tables.ENTITY_WORKFLOW_STATE.ENTITY_KIND.eq(entityReference.kind().name()))
116-
.and(Tables.ENTITY_WORKFLOW_STATE.ENTITY_ID.eq(entityReference.id()))
117-
.forUpdate()
118-
.fetchOne(TO_DOMAIN_MAPPER);
99+
.update(ENTITY_WORKFLOW_STATE)
100+
.set(ENTITY_WORKFLOW_STATE.STATE, workflowState)
101+
.set(ENTITY_WORKFLOW_STATE.LAST_UPDATED_AT, Timestamp.valueOf(nowUtc()))
102+
.set(ENTITY_WORKFLOW_STATE.LAST_UPDATED_BY, user)
103+
.where(ENTITY_WORKFLOW_STATE.WORKFLOW_ID.eq(workflowDefId)
104+
.and(ENTITY_WORKFLOW_STATE.ENTITY_ID.eq(ref.id()))
105+
.and(ENTITY_WORKFLOW_STATE.ENTITY_KIND.eq(ref.kind().name())))
106+
.execute();
119107
}
120108
}

waltz-data/src/main/java/org/finos/waltz/data/entity_workflow/EntityWorkflowTransitionDao.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ public List<EntityWorkflowTransition> findForEntityReferenceAndWorkflowId(long w
8181
.fetch(TO_DOMAIN_MAPPER);
8282
}
8383

84-
public void createWorkflowTransitionTransactional(DSLContext dsl, Long entityWorkflowDefId, EntityReference ref, String username,
84+
public void createWorkflowTransition(Long entityWorkflowDefId, EntityReference ref, String username,
8585
String from, String to, String reason) {
8686
EntityWorkflowTransitionRecord transitionRecord = dsl.newRecord(ENTITY_WORKFLOW_TRANSITION);
8787
transitionRecord.setWorkflowId(entityWorkflowDefId);
@@ -105,9 +105,4 @@ public List<EntityWorkflowTransition> findForWorkflowId(long workflowId) {
105105
.orderBy(ENTITY_WORKFLOW_TRANSITION.LAST_UPDATED_AT.desc())
106106
.fetch(TO_DOMAIN_MAPPER);
107107
}
108-
109-
public void createWorkflowTransition(Long entityWorkflowDefId, EntityReference ref, String username,
110-
String from, String to, String reason) {
111-
createWorkflowTransitionTransactional(dsl, entityWorkflowDefId, ref, username, from, to, reason);
112-
}
113108
}

waltz-integration-test/src/test/java/org/finos/waltz/integration_test/inmem/service/ProposedFlowWorkflowServiceTest.java

Lines changed: 7 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -35,24 +35,9 @@
3535
import org.finos.waltz.model.entity_workflow.EntityWorkflowDefinition;
3636
import org.finos.waltz.model.logical_flow.ImmutableLogicalFlow;
3737
import org.finos.waltz.model.logical_flow.LogicalFlow;
38-
import org.finos.waltz.model.physical_flow.FlowAttributes;
39-
import org.finos.waltz.model.physical_flow.ImmutablePhysicalFlowCreateCommand;
40-
import org.finos.waltz.model.physical_flow.PhysicalFlowCreateCommandResponse;
41-
import org.finos.waltz.model.physical_flow.FrequencyKindValue;
42-
import org.finos.waltz.model.physical_flow.CriticalityValue;
43-
import org.finos.waltz.model.physical_flow.TransportKindValue;
44-
import org.finos.waltz.model.physical_flow.ImmutableFlowAttributes;
38+
import org.finos.waltz.model.physical_flow.*;
4539
import org.finos.waltz.model.physical_specification.PhysicalSpecification;
46-
import org.finos.waltz.model.proposed_flow.ProposedFlowResponse;
47-
import org.finos.waltz.model.proposed_flow.ProposedFlowCommand;
48-
import org.finos.waltz.model.proposed_flow.ImmutableProposedFlowCommand;
49-
import org.finos.waltz.model.proposed_flow.ProposedFlowCommandResponse;
50-
import org.finos.waltz.model.proposed_flow.ProposedFlowActionCommand;
51-
import org.finos.waltz.model.proposed_flow.ImmutableProposedFlowActionCommand;
52-
import org.finos.waltz.model.proposed_flow.FlowIdResponse;
53-
import org.finos.waltz.model.proposed_flow.ProposalType;
54-
import org.finos.waltz.model.proposed_flow.Reason;
55-
import org.finos.waltz.schema.tables.ProposedFlow;
40+
import org.finos.waltz.model.proposed_flow.*;
5641
import org.finos.waltz.schema.tables.records.InvolvementGroupRecord;
5742
import org.finos.waltz.service.changelog.ChangeLogService;
5843
import org.finos.waltz.service.data_flow.DataFlowService;
@@ -62,22 +47,15 @@
6247
import org.finos.waltz.service.proposed_flow_workflow.ProposedFlowWorkflowService;
6348
import org.finos.waltz.service.workflow_state_machine.exception.TransitionNotFoundException;
6449
import org.finos.waltz.service.workflow_state_machine.exception.TransitionPredicateFailedException;
65-
import org.finos.waltz.service.workflow_state_machine.proposed_flow.ProposedFlowWorkflowTransitionAction;
6650
import org.finos.waltz.test_common.helpers.*;
6751
import org.jooq.DSLContext;
6852
import org.junit.jupiter.api.BeforeEach;
6953
import org.junit.jupiter.api.Test;
70-
import org.slf4j.Logger;
71-
import org.slf4j.LoggerFactory;
7254
import org.springframework.beans.factory.annotation.Autowired;
7355

7456
import java.time.LocalDateTime;
75-
import java.util.ArrayList;
76-
import java.util.List;
7757
import java.util.Optional;
7858
import java.util.Set;
79-
import java.util.concurrent.CountDownLatch;
80-
import java.util.concurrent.TimeUnit;
8159

8260
import static org.finos.waltz.common.DateTimeUtilities.nowUtc;
8361
import static org.finos.waltz.data.proposed_flow.ProposedFlowDao.PROPOSE_FLOW_LIFECYCLE_WORKFLOW;
@@ -100,7 +78,6 @@
10078

10179
public class ProposedFlowWorkflowServiceTest extends BaseInMemoryIntegrationTest {
10280

103-
private static final Logger LOG = LoggerFactory.getLogger(ProposedFlowWorkflowServiceTest.class);
10481
private final String USER_NAME = "testUser";
10582

10683
@Autowired
@@ -176,7 +153,7 @@ public class ProposedFlowWorkflowServiceTest extends BaseInMemoryIntegrationTest
176153

177154
@BeforeEach
178155
public void setUp() {
179-
dsl.deleteFrom(ProposedFlow.PROPOSED_FLOW).execute();
156+
dsl.deleteFrom(org.finos.waltz.schema.tables.ProposedFlow.PROPOSED_FLOW).execute();
180157
dsl.deleteFrom(PHYSICAL_FLOW).execute();
181158
dsl.deleteFrom(PHYSICAL_SPECIFICATION).execute();
182159
dsl.deleteFrom(LOGICAL_FLOW).execute();
@@ -421,7 +398,7 @@ void twoStepApproval_shouldTransitionToFullyApproved_andCallOperation() throws F
421398
.specification(physicalSpecification)
422399
.flowAttributes(flowAttributes)
423400
.dataTypeIds(dataTypeIdSet)
424-
.proposalType(CREATE)
401+
.proposalType(ProposalType.CREATE)
425402
.build();
426403

427404
String userName = mkName(USER_NAME, "user1");
@@ -528,7 +505,7 @@ void proposedFlowAction_rejectionWithInvalidPermissions_throwsException() throws
528505
.specification(physicalSpecification)
529506
.flowAttributes(flowAttributes)
530507
.dataTypeIds(dataTypeIdSet)
531-
.proposalType(CREATE)
508+
.proposalType(ProposalType.CREATE)
532509
.build();
533510

534511
String userName = mkName(USER_NAME, "user1");
@@ -545,7 +522,7 @@ void proposedFlowAction_rejectionWithInvalidPermissions_throwsException() throws
545522
// 2. Act ----------------------------------------------------
546523
ProposedFlowResponse proposedFlowResponse = proposedFlowWorkflowService.proposedFlowAction(
547524
proposedFlowId,
548-
ProposedFlowWorkflowTransitionAction.REJECT,
525+
org.finos.waltz.service.workflow_state_machine.proposed_flow.ProposedFlowWorkflowTransitionAction.REJECT,
549526
userName,
550527
rejectCommand);
551528

@@ -573,7 +550,7 @@ public void testCannotApproveProposedFlowTwice() throws FlowCreationException, T
573550
.specification(physicalSpecification)
574551
.flowAttributes(flowAttributes)
575552
.dataTypeIds(dataTypeIdSet)
576-
.proposalType(CREATE)
553+
.proposalType(ProposalType.CREATE)
577554
.build();
578555

579556
String userName = mkName(USER_NAME, "user1");
@@ -673,100 +650,4 @@ public void validateProposedFlow_shouldPassIfFlowAttributeIsDifferent() {
673650
// 3. Assert: Validation should pass
674651
assertNull(validationResponse, "Validation should pass when a flow attribute (e.g., frequency) is different");
675652
}
676-
677-
@Test
678-
public void multipleUsersCannotActOnFlowAtTheSameTime() throws FlowCreationException, TransitionNotFoundException {
679-
Reason reason = proposedFlowWorkflowHelper.getReason();
680-
EntityReference owningEntity = proposedFlowWorkflowHelper.getOwningEntity();
681-
PhysicalSpecification physicalSpecification = proposedFlowWorkflowHelper.getPhysicalSpecification(owningEntity);
682-
FlowAttributes flowAttributes = proposedFlowWorkflowHelper.getFlowAttributes();
683-
Set<Long> dataTypeIdSet = proposedFlowWorkflowHelper.getDataTypeIdSet();
684-
685-
EntityReference sourceRef = appHelper.createNewApp(mkName(USER_NAME, "concurrent_src"), ouIds.a);
686-
EntityReference targetRef = appHelper.createNewApp(mkName(USER_NAME, "concurrent_tgt"), ouIds.a1);
687-
688-
ProposedFlowCommand command = ImmutableProposedFlowCommand.builder()
689-
.source(sourceRef)
690-
.target(targetRef)
691-
.reason(reason)
692-
.specification(physicalSpecification)
693-
.flowAttributes(flowAttributes)
694-
.dataTypeIds(dataTypeIdSet)
695-
.proposalType(CREATE)
696-
.build();
697-
698-
ProposedFlowCommandResponse proposeResponse = proposedFlowWorkflowService.proposeNewFlow(USER_NAME, command);
699-
Long proposedFlowId = proposeResponse.proposedFlowId();
700-
assertNotNull(proposedFlowId, "Proposed flow should be created");
701-
702-
String userA = mkName(USER_NAME, "concurrent_user_a");
703-
String userB = mkName(USER_NAME, "concurrent_user_b");
704-
Long personA = personHelper.createPerson(userA);
705-
Long personB = personHelper.createPerson(userB);
706-
707-
long involvementKind = involvementHelper.mkInvolvementKind("rel_abcde");
708-
involvementHelper.createInvolvement(personA, involvementKind, sourceRef);
709-
involvementHelper.createInvolvement(personB, involvementKind, targetRef);
710-
711-
InvolvementGroupRecord ig = permissionHelper.setupInvolvementGroup(involvementKind, USER_NAME);
712-
permissionHelper.setupPermissionGroupForProposedFlow(sourceRef, ig, USER_NAME, Operation.APPROVE);
713-
permissionHelper.setupPermissionGroupForProposedFlow(targetRef, ig, USER_NAME, Operation.APPROVE);
714-
715-
ProposedFlowActionCommand approveCommand = ImmutableProposedFlowActionCommand.builder()
716-
.comment("Approved by source approver")
717-
.build();
718-
719-
CountDownLatch startGate = new CountDownLatch(1);
720-
CountDownLatch doneGate = new CountDownLatch(2);
721-
722-
List<ProposedFlowResponse> responses = new ArrayList<>();
723-
List<Throwable> failures = new ArrayList<>();
724-
725-
Runnable approveTaskUserA = () -> mkRunnableTask(startGate, doneGate, proposedFlowId, responses, failures, approveCommand, userA);
726-
Runnable approveTaskUserB = () -> mkRunnableTask(startGate, doneGate, proposedFlowId, responses, failures, approveCommand, userB);
727-
728-
Thread thread1 = new Thread(approveTaskUserA, "approve-user-a");
729-
Thread thread2 = new Thread(approveTaskUserB, "approve-user-b");
730-
731-
thread1.start();
732-
thread2.start();
733-
734-
try {
735-
startGate.countDown();
736-
assertTrue(doneGate.await(10, TimeUnit.SECONDS), "Concurrent approve tasks did not finish in time");
737-
} catch (InterruptedException e) {
738-
LOG.error("Thread interrupted", e);
739-
Thread.currentThread().interrupt();
740-
}
741-
742-
assertTrue(failures.isEmpty(), "Unexpected thread failure occurred, check logs for DB/transaction errors");
743-
assertEquals(2, responses.size(), "Expected one response per user action");
744-
}
745-
746-
private void mkRunnableTask(CountDownLatch startGate,
747-
CountDownLatch doneGate,
748-
Long proposedFlowId,
749-
List<ProposedFlowResponse> responses,
750-
List<Throwable> failures,
751-
ProposedFlowActionCommand approveCommand,
752-
String user) {
753-
try {
754-
startGate.await(5, TimeUnit.SECONDS);
755-
ProposedFlowResponse response = proposedFlowWorkflowService.proposedFlowAction(
756-
proposedFlowId,
757-
APPROVE,
758-
user,
759-
approveCommand);
760-
synchronized (responses) {
761-
responses.add(response);
762-
}
763-
} catch (Exception e) {
764-
LOG.error("Concurrent approve failed for user {}", user, e);
765-
synchronized (failures) {
766-
failures.add(e);
767-
}
768-
} finally {
769-
doneGate.countDown();
770-
}
771-
}
772653
}

waltz-service/src/main/java/org/finos/waltz/service/entity_workflow/EntityWorkflowService.java

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
import org.finos.waltz.model.entity_workflow.EntityWorkflowState;
3434
import org.finos.waltz.model.entity_workflow.EntityWorkflowTransition;
3535
import org.finos.waltz.service.changelog.ChangeLogService;
36-
import org.jooq.DSLContext;
3736
import org.springframework.beans.factory.annotation.Autowired;
3837
import org.springframework.stereotype.Service;
3938

@@ -107,6 +106,22 @@ public void createEntityWorkflow(EntityReference ref, Long entityWorkflowDefinit
107106
changeLogService.write(changeLogList);
108107
}
109108

109+
public void updateStateTransition(String username, String reason, EntityWorkflowState workflowState,
110+
String currentState, String newState) {
111+
entityWorkflowStateDao.updateState(workflowState.workflowId(), workflowState.entityReference(),
112+
username, newState);
113+
entityWorkflowTransitionDao.createWorkflowTransition(workflowState.workflowId(), workflowState.entityReference(),
114+
username, currentState, newState, reason);
115+
116+
List<ChangeLog> changeLogList = Arrays.asList(
117+
mkChangeLog(workflowState.entityReference(), username, UPDATE,
118+
format("Entity Workflow State changed to %s", newState)),
119+
mkChangeLog(workflowState.entityReference(), username, UPDATE,
120+
format("Entity Workflow Transition saved with from: %s to: %s State", currentState, newState))
121+
);
122+
changeLogService.write(changeLogList);
123+
}
124+
110125
public void createEntityWorkflowResult(Long entityWorkflowDefinitionId, EntityReference workflowEntity, EntityReference resultEntity, String username) {
111126
entityWorkflowResultDao.create(entityWorkflowDefinitionId, workflowEntity, resultEntity, username);
112127
}
@@ -127,25 +142,4 @@ private ImmutableChangeLog mkChangeLog(EntityReference entityReference,
127142
.severity(Severity.INFORMATION)
128143
.build();
129144
}
130-
131-
public EntityWorkflowState getProposedFlowWorkflowStateForUpdate(DSLContext dsl, Long workflowId, EntityReference ref) {
132-
return entityWorkflowStateDao.getProposedFlowWorkflowStateForUpdate(dsl, workflowId, ref);
133-
}
134-
135-
public void updateStateTransitionTransactional(DSLContext dsl, String username, String reason, EntityWorkflowState workflowState,
136-
String currentState, String newState) {
137-
entityWorkflowStateDao.updateStateTransactional(dsl, workflowState.workflowId(), workflowState.entityReference(),
138-
username, newState);
139-
entityWorkflowTransitionDao.createWorkflowTransitionTransactional(dsl, workflowState.workflowId(), workflowState.entityReference(),
140-
username, currentState, newState, reason);
141-
142-
List<ChangeLog> changeLogList = Arrays.asList(
143-
mkChangeLog(workflowState.entityReference(), username, UPDATE,
144-
format("Entity Workflow State changed to %s", newState)),
145-
mkChangeLog(workflowState.entityReference(), username, UPDATE,
146-
format("Entity Workflow Transition saved with from: %s to: %s State", currentState, newState))
147-
);
148-
changeLogService.write(changeLogList);
149-
}
150-
151145
}

0 commit comments

Comments
 (0)