Skip to content

Commit 95c29f4

Browse files
committed
Closes #2378 - Add DeleteHistoryEvent for Task
1 parent 1abf68e commit 95c29f4

File tree

7 files changed

+221
-7
lines changed

7 files changed

+221
-7
lines changed

common/taskana-common/src/main/java/pro/taskana/common/internal/util/ObjectAttributeChangeDetector.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ public static <T> String determineChangesInAttributes(T oldObject, T newObject)
4040

4141
// this has to be checked after we deal with List data types, because
4242
// we want to allow different implementations of the List interface to work as well.
43-
if (!oldObject.getClass().equals(newObject.getClass())) {
43+
if (!oldObject.getClass().equals(newObject.getClass())
44+
&& !oldObject.getClass().isAssignableFrom(newObject.getClass())) {
4445
throw new SystemException(
4546
String.format(
4647
"The classes differ between the oldObject(%s) and newObject(%s). "

history/taskana-simplehistory-provider/pom.xml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,12 @@
9191
<version>${version.oracle}</version>
9292
<scope>test</scope>
9393
</dependency>
94-
94+
<dependency>
95+
<groupId>pro.taskana</groupId>
96+
<artifactId>taskana-test-api</artifactId>
97+
<version>${project.version}</version>
98+
<scope>test</scope>
99+
</dependency>
95100
</dependencies>
96101

97102
</project>
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package acceptance.events.task;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import java.util.List;
6+
import java.util.stream.IntStream;
7+
import org.json.JSONArray;
8+
import org.json.JSONObject;
9+
import org.junit.jupiter.api.BeforeAll;
10+
import org.junit.jupiter.api.Test;
11+
import org.junit.jupiter.api.extension.ExtendWith;
12+
import pro.taskana.classification.api.ClassificationService;
13+
import pro.taskana.classification.api.models.ClassificationSummary;
14+
import pro.taskana.common.api.TaskanaEngine;
15+
import pro.taskana.common.test.security.JaasExtension;
16+
import pro.taskana.common.test.security.WithAccessId;
17+
import pro.taskana.simplehistory.impl.SimpleHistoryServiceImpl;
18+
import pro.taskana.spi.history.api.TaskanaHistory;
19+
import pro.taskana.spi.history.api.events.task.TaskHistoryEvent;
20+
import pro.taskana.spi.history.api.events.task.TaskHistoryEventType;
21+
import pro.taskana.task.api.TaskService;
22+
import pro.taskana.task.api.TaskState;
23+
import pro.taskana.task.api.models.Task;
24+
import pro.taskana.testapi.DefaultTestEntities;
25+
import pro.taskana.testapi.TaskanaInject;
26+
import pro.taskana.testapi.TaskanaIntegrationTest;
27+
import pro.taskana.testapi.WithServiceProvider;
28+
import pro.taskana.testapi.builder.TaskBuilder;
29+
import pro.taskana.workbasket.api.WorkbasketService;
30+
import pro.taskana.workbasket.api.models.WorkbasketSummary;
31+
32+
@WithServiceProvider(
33+
serviceProviderInterface = TaskanaHistory.class,
34+
serviceProviders = SimpleHistoryServiceImpl.class)
35+
@TaskanaIntegrationTest
36+
@ExtendWith(JaasExtension.class)
37+
class CreateHistoryEventOnTaskDeletionAccTest {
38+
@TaskanaInject TaskanaEngine taskanaEngine;
39+
@TaskanaInject TaskService taskService;
40+
@TaskanaInject WorkbasketService workbasketService;
41+
@TaskanaInject ClassificationService classificationService;
42+
ClassificationSummary defaultClassificationSummary;
43+
WorkbasketSummary defaultWorkbasketSummary;
44+
Task task1;
45+
Task task2;
46+
Task task3;
47+
Task task4;
48+
private SimpleHistoryServiceImpl historyService = new SimpleHistoryServiceImpl();
49+
50+
@WithAccessId(user = "admin")
51+
@BeforeAll
52+
void setUp() throws Exception {
53+
historyService.initialize(taskanaEngine);
54+
55+
defaultClassificationSummary =
56+
DefaultTestEntities.defaultTestClassification()
57+
.buildAndStoreAsSummary(classificationService);
58+
defaultWorkbasketSummary =
59+
DefaultTestEntities.defaultTestWorkbasket().buildAndStoreAsSummary(workbasketService);
60+
61+
task1 = createTask().buildAndStore(taskService);
62+
task2 = createTask().state(TaskState.COMPLETED).buildAndStore(taskService);
63+
task3 = createTask().state(TaskState.COMPLETED).buildAndStore(taskService);
64+
task4 = createTask().state(TaskState.COMPLETED).buildAndStore(taskService);
65+
}
66+
67+
@WithAccessId(user = "admin")
68+
@Test
69+
void should_CreateDeleteHistoryEvent_When_TaskIsDeleted() throws Exception {
70+
historyService.deleteHistoryEventsByTaskIds(List.of(task4.getId()));
71+
72+
taskService.deleteTask(task4.getId());
73+
74+
List<TaskHistoryEvent> events =
75+
historyService.createTaskHistoryQuery().taskIdIn(task4.getId()).list();
76+
assertThat(events).hasSize(1);
77+
assertDeleteHistoryEvent(
78+
events.get(0).getId(), TaskState.COMPLETED.toString(), "DELETED", "admin");
79+
}
80+
81+
@WithAccessId(user = "admin")
82+
@Test
83+
void should_CreateDeleteHistoryEvent_When_TaskIsForceDeleted() throws Exception {
84+
historyService.deleteHistoryEventsByTaskIds(List.of(task1.getId()));
85+
86+
taskService.forceDeleteTask(task1.getId());
87+
88+
List<TaskHistoryEvent> events =
89+
historyService.createTaskHistoryQuery().taskIdIn(task1.getId()).list();
90+
assertThat(events).hasSize(1);
91+
assertDeleteHistoryEvent(events.get(0).getId(), TaskState.READY.toString(), "DELETED", "admin");
92+
}
93+
94+
@WithAccessId(user = "admin")
95+
@Test
96+
void should_CreateDeleteHistoryEvents_When_MultipleTasksAreDeleted() throws Exception {
97+
List<String> taskIds = List.of(task2.getId(), task3.getId());
98+
historyService.deleteHistoryEventsByTaskIds(taskIds);
99+
100+
taskService.deleteTasks(taskIds);
101+
102+
List<TaskHistoryEvent> events =
103+
historyService.createTaskHistoryQuery().taskIdIn(taskIds.toArray(new String[0])).list();
104+
assertThat(events)
105+
.extracting(TaskHistoryEvent::getTaskId)
106+
.containsExactlyInAnyOrderElementsOf(taskIds);
107+
for (TaskHistoryEvent event : events) {
108+
assertDeleteHistoryEvent(event.getId(), TaskState.COMPLETED.toString(), "DELETED", "admin");
109+
}
110+
}
111+
112+
private void assertDeleteHistoryEvent(
113+
String eventId, String expectedOldTaskState, String expectedNewTaskState, String expectedUser)
114+
throws Exception {
115+
TaskHistoryEvent event = historyService.getTaskHistoryEvent(eventId);
116+
assertThat(event.getDetails()).isNotNull();
117+
JSONArray changes = new JSONObject(event.getDetails()).getJSONArray("changes");
118+
assertThat(changes.length()).isPositive();
119+
120+
JSONObject stateChange =
121+
IntStream.range(0, changes.length())
122+
.mapToObj(changes::getJSONObject)
123+
.filter(obj -> "state".equals(obj.optString("fieldName")))
124+
.findFirst()
125+
.orElse(null);
126+
String oldState = stateChange.get("oldValue").toString();
127+
String newState = stateChange.get("newValue").toString();
128+
assertThat(oldState).isEqualTo(expectedOldTaskState);
129+
assertThat(newState).isEqualTo(expectedNewTaskState);
130+
131+
assertThat(event.getOldValue()).isEqualTo(expectedOldTaskState);
132+
assertThat(event.getNewValue()).isEqualTo(expectedNewTaskState);
133+
assertThat(event.getUserId()).isEqualTo(expectedUser);
134+
assertThat(event.getEventType()).isEqualTo(TaskHistoryEventType.DELETED.getName());
135+
}
136+
137+
private TaskBuilder createTask() {
138+
return TaskBuilder.newTask()
139+
.classificationSummary(defaultClassificationSummary)
140+
.workbasketSummary(defaultWorkbasketSummary)
141+
.primaryObjRef(DefaultTestEntities.defaultTestObjectReference().build());
142+
}
143+
}

history/taskana-simplehistory-provider/src/test/java/acceptance/events/task/DeleteHistoryEventsOnTaskDeletionAccTest.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import pro.taskana.simplehistory.impl.TaskHistoryQueryImpl;
1616
import pro.taskana.simplehistory.impl.task.TaskHistoryQueryMapper;
1717
import pro.taskana.spi.history.api.events.task.TaskHistoryEvent;
18+
import pro.taskana.spi.history.api.events.task.TaskHistoryEventType;
1819
import pro.taskana.task.api.exceptions.TaskNotFoundException;
1920

2021
@ExtendWith(JaasExtension.class)
@@ -47,7 +48,8 @@ void should_DeleteHistoryEvents_When_TaskIsDeletedWithHistoryDeletionEnabled() t
4748
listEvents =
4849
taskHistoryQueryMapper.queryHistoryEvents(
4950
(TaskHistoryQueryImpl) historyService.createTaskHistoryQuery().taskIdIn(taskid));
50-
assertThat(listEvents).isEmpty();
51+
assertThat(listEvents).hasSize(1);
52+
assertThat(listEvents.get(0).getEventType()).isEqualTo(TaskHistoryEventType.DELETED.getName());
5153
}
5254

5355
@Test
@@ -87,7 +89,9 @@ void should_DeleteHistoryEvents_When_TasksAreDeletedWithHistoryDeletionEnabled()
8789
taskHistoryQueryMapper.queryHistoryEvents(
8890
(TaskHistoryQueryImpl)
8991
historyService.createTaskHistoryQuery().taskIdIn(taskId_1, taskId_2));
90-
assertThat(listEvents).isEmpty();
92+
assertThat(listEvents).hasSize(2);
93+
assertThat(listEvents.get(0).getEventType()).isEqualTo(TaskHistoryEventType.DELETED.getName());
94+
assertThat(listEvents.get(1).getEventType()).isEqualTo(TaskHistoryEventType.DELETED.getName());
9195
}
9296

9397
@Test
@@ -119,7 +123,7 @@ void should_NotDeleteHistoryEvents_When_TaskIsDeletedWithHistoryDeletionDisabled
119123
listEvents =
120124
taskHistoryQueryMapper.queryHistoryEvents(
121125
(TaskHistoryQueryImpl) historyService.createTaskHistoryQuery().taskIdIn(taskId));
122-
assertThat(listEvents).hasSize(2);
126+
assertThat(listEvents).hasSize(3);
123127
}
124128

125129
@Test
@@ -152,7 +156,7 @@ void should_NotDeleteHistoryEvents_When_TasksAreDeletedWithHistoryDeletionDisabl
152156
taskHistoryQueryMapper.queryHistoryEvents(
153157
(TaskHistoryQueryImpl)
154158
historyService.createTaskHistoryQuery().taskIdIn(taskId_1, taskId_2));
155-
assertThat(listEvents).hasSize(2);
159+
assertThat(listEvents).hasSize(4);
156160
}
157161

158162
private void createTaskanaEngineWithNewConfig(boolean deleteHistoryOnTaskDeletionEnabled)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package pro.taskana.spi.history.api.events.task;
2+
3+
import java.time.Instant;
4+
import pro.taskana.task.api.models.TaskSummary;
5+
6+
public class TaskDeletedEvent extends TaskHistoryEvent {
7+
8+
public TaskDeletedEvent(
9+
String id,
10+
TaskSummary taskSummary,
11+
String oldState,
12+
String newState,
13+
String userId,
14+
String details) {
15+
super(id, taskSummary, userId, details);
16+
eventType = TaskHistoryEventType.DELETED.getName();
17+
created = Instant.now();
18+
this.oldValue = oldState;
19+
this.newValue = newState;
20+
}
21+
}

lib/taskana-core/src/main/java/pro/taskana/spi/history/api/events/task/TaskHistoryEventType.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ public enum TaskHistoryEventType {
1010
COMPLETED("COMPLETED"),
1111
CANCELLED("CANCELLED"),
1212
TERMINATED("TERMINATED"),
13-
TRANSFERRED("TRANSFERRED");
13+
TRANSFERRED("TRANSFERRED"),
14+
DELETED("DELETED");
1415

1516
private String name;
1617

lib/taskana-core/src/main/java/pro/taskana/task/internal/TaskServiceImpl.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import java.util.stream.Collectors;
2222
import java.util.stream.Stream;
2323
import org.apache.ibatis.exceptions.PersistenceException;
24+
import org.json.JSONArray;
25+
import org.json.JSONObject;
2426
import org.slf4j.Logger;
2527
import org.slf4j.LoggerFactory;
2628
import pro.taskana.classification.api.ClassificationService;
@@ -46,6 +48,7 @@
4648
import pro.taskana.spi.history.api.events.task.TaskClaimedEvent;
4749
import pro.taskana.spi.history.api.events.task.TaskCompletedEvent;
4850
import pro.taskana.spi.history.api.events.task.TaskCreatedEvent;
51+
import pro.taskana.spi.history.api.events.task.TaskDeletedEvent;
4952
import pro.taskana.spi.history.api.events.task.TaskRequestChangesEvent;
5053
import pro.taskana.spi.history.api.events.task.TaskRequestReviewEvent;
5154
import pro.taskana.spi.history.api.events.task.TaskTerminatedEvent;
@@ -636,6 +639,11 @@ public BulkOperationResults<String, TaskanaException> deleteTasks(List<String> t
636639
}
637640

638641
if (!taskIds.isEmpty()) {
642+
List<TaskSummary> tasksToDelete = null;
643+
if (historyEventManager.isEnabled()) {
644+
tasksToDelete =
645+
createTaskQuery().idIn(taskIds.toArray(new String[0])).list();
646+
}
639647
attachmentMapper.deleteMultipleByTaskIds(taskIds);
640648
objectReferenceMapper.deleteMultipleByTaskIds(taskIds);
641649
taskMapper.deleteMultiple(taskIds);
@@ -647,6 +655,9 @@ public BulkOperationResults<String, TaskanaException> deleteTasks(List<String> t
647655
.isDeleteHistoryEventsOnTaskDeletionEnabled()) {
648656
historyEventManager.deleteEvents(taskIds);
649657
}
658+
if (historyEventManager.isEnabled()) {
659+
tasksToDelete.forEach(this::createTaskDeletedEvent);
660+
}
650661
}
651662
return bulkLog;
652663
} finally {
@@ -1627,6 +1638,10 @@ private void deleteTask(String taskId, boolean forceDelete)
16271638
historyEventManager.deleteEvents(Collections.singletonList(taskId));
16281639
}
16291640

1641+
if (historyEventManager.isEnabled()) {
1642+
createTaskDeletedEvent(task.asSummary());
1643+
}
1644+
16301645
if (LOGGER.isDebugEnabled()) {
16311646
LOGGER.debug("Task {} deleted.", taskId);
16321647
}
@@ -2157,6 +2172,30 @@ private void createTasksCompletedEvents(List<? extends TaskSummary> taskSummarie
21572172
taskanaEngine.getEngine().getCurrentUserContext().getUserid())));
21582173
}
21592174

2175+
private void createTaskDeletedEvent(TaskSummary taskSummary) {
2176+
TaskSummaryImpl emptyTaskSummary = new TaskSummaryImpl();
2177+
String changeDetails =
2178+
ObjectAttributeChangeDetector.determineChangesInAttributes(taskSummary, emptyTaskSummary);
2179+
JSONObject jsonObject = new JSONObject(changeDetails);
2180+
JSONArray changesArray = jsonObject.getJSONArray("changes");
2181+
for (int i = 0; i < changesArray.length(); i++) {
2182+
JSONObject changeObject = changesArray.getJSONObject(i);
2183+
2184+
if (changeObject.getString("fieldName").equals("state")) {
2185+
changeObject.put("newValue", "DELETED");
2186+
}
2187+
}
2188+
String modifiedChangeDetails = jsonObject.toString();
2189+
historyEventManager.createEvent(
2190+
new TaskDeletedEvent(
2191+
IdGenerator.generateWithPrefix(IdGenerator.ID_PREFIX_TASK_HISTORY_EVENT),
2192+
taskSummary,
2193+
taskSummary.getState().toString(),
2194+
"DELETED",
2195+
taskanaEngine.getEngine().getCurrentUserContext().getUserid(),
2196+
modifiedChangeDetails));
2197+
}
2198+
21602199
private TaskImpl duplicateTaskExactly(TaskImpl task) {
21612200
TaskImpl oldTask = task.copy();
21622201
oldTask.setId(task.getId());

0 commit comments

Comments
 (0)