From 711addd75cab62955c88d9213c3e10a01bb8715f Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Sat, 20 Dec 2025 13:51:06 +0100 Subject: [PATCH] updating kanban on * task creation * task update * task deletion --- frontend/src/routes/project/Kanban.svelte | 28 +++++++-- frontend/src/urls.svelte.js | 4 +- .../srsoftware/umbrella/task/TaskModule.java | 61 ++++++++++--------- translations/src/main/resources/de.json | 2 + 4 files changed, 62 insertions(+), 33 deletions(-) diff --git a/frontend/src/routes/project/Kanban.svelte b/frontend/src/routes/project/Kanban.svelte index d504b44..02b9872 100644 --- a/frontend/src/routes/project/Kanban.svelte +++ b/frontend/src/routes/project/Kanban.svelte @@ -103,20 +103,40 @@ } } - function handleUpdateEvent(evt){ + function handleCreateEvent(evt){ + handleEvent(evt,'create'); + } + + function handleEvent(evt,method){ let json = JSON.parse(evt.data); if (json.task && json.user){ + // drop from kanban for (let uid in tasks){ if (!uid) continue; for (let state in tasks[uid]) delete tasks[uid][state][json.task.id]; } - processTask(json.task); + + // (re) add to kanban + if (method != 'delete') processTask(json.task); + + // show notification if (json.user.id != user.id) { - info = t("user_updated_entity",{user:json.user.name,entity:json.task.name}); + let term = "user_updated_entity"; + if (method == 'create') term = "user_created_entity"; + if (method == 'delete') term = "user_deleted_entity"; + info = t(term,{user:json.user.name,entity:json.task.name}); setTimeout(() => { info = null; },2500); } } + } + function handleDeleteEvent(evt){ + console.log('delete task'); + handleEvent(evt,'delete'); + } + + function handleUpdateEvent(evt){ + handleEvent(evt,'update'); } function hover(ev,user_id,state){ @@ -135,7 +155,7 @@ async function load(){ try { - eventSource = eventStream(handleUpdateEvent); + eventSource = eventStream(handleCreateEvent,handleUpdateEvent,handleDeleteEvent); await loadProject(); loadTasks({project_id:+id,parent_task_id:0}); } catch (ignored) {} diff --git a/frontend/src/urls.svelte.js b/frontend/src/urls.svelte.js index e83dbe8..c021466 100644 --- a/frontend/src/urls.svelte.js +++ b/frontend/src/urls.svelte.js @@ -15,9 +15,11 @@ export function drop(url){ }); } -export function eventStream(updateHandler){ +export function eventStream(createHandler,updateHandler,deleteHandler){ const es = new EventSource(api('bus'), {withCredentials: true}); + if (createHandler) es.addEventListener('CREATE', createHandler); if (updateHandler) es.addEventListener('UPDATE', updateHandler); + if (deleteHandler) es.addEventListener('DELETE', deleteHandler); return es; } diff --git a/task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java b/task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java index 4fb3111..be622b5 100644 --- a/task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java +++ b/task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java @@ -11,6 +11,7 @@ import static de.srsoftware.umbrella.core.Util.mapValues; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*; import static de.srsoftware.umbrella.core.model.Permission.*; import static de.srsoftware.umbrella.core.model.Permission.OWNER; +import static de.srsoftware.umbrella.messagebus.Event.EventType.CREATE; import static de.srsoftware.umbrella.messagebus.Event.EventType.UPDATE; import static de.srsoftware.umbrella.messagebus.MessageBus.messageBus; import static de.srsoftware.umbrella.project.Constants.PERMISSIONS; @@ -31,6 +32,7 @@ import de.srsoftware.umbrella.core.model.*; import de.srsoftware.umbrella.core.model.Task; import de.srsoftware.umbrella.core.model.Token; import de.srsoftware.umbrella.core.model.UmbrellaUser; +import de.srsoftware.umbrella.messagebus.Event; import de.srsoftware.umbrella.messagebus.TaskEvent; import java.io.IOException; import java.util.*; @@ -40,6 +42,22 @@ import org.json.JSONObject; public class TaskModule extends BaseHandler implements TaskService { + private static class TaggedTask extends Task{ + private final Collection tags; + + public TaggedTask(Task task, Collection tags) { + super(task.id(), task.projectId(), task.parentTaskId(), task.name(), task.description(), task.status(), task.estimatedTime(), task.start(), task.dueDate(), task.showClosed(), task.noIndex(), task.members(), task.priority()); + this.tags = tags; + } + + @Override + public Map toMap() { + var map = super.toMap(); + map.put(TAGS,tags); + return map; + } + } + private final TaskDb taskDb; public TaskModule(Configuration config) throws UmbrellaException { @@ -63,6 +81,7 @@ public class TaskModule extends BaseHandler implements TaskService { taskDb.delete(task); noteService().deleteEntity(TASK, "" + taskId); tagService().deleteEntity(TASK, taskId); + messageBus().dispatch(new TaskEvent(user,task,Event.EventType.DELETE)); return sendContent(ex, Map.of(DELETED, taskId)); } @@ -261,6 +280,16 @@ public class TaskModule extends BaseHandler implements TaskService { return taskList; } + private boolean newParentIsSubtask(Task task, long newParent) { + var parent = taskDb.load(newParent); + while (parent != null) { + if (task.id() == parent.id()) return true; + if (parent.parentTaskId() == null) break; + parent = taskDb.load(parent.parentTaskId()); + } + return false; + } + private Map placeInTree(Task task, HashMap> taskTree, Map taskMap) { var mappedTask = task.toMap(); if (task.parentTaskId() != null) { @@ -319,16 +348,6 @@ public class TaskModule extends BaseHandler implements TaskService { return sendContent(ex, task); } - private boolean newParentIsSubtask(Task task, long newParent) { - var parent = taskDb.load(newParent); - while (parent != null) { - if (task.id() == parent.id()) return true; - if (parent.parentTaskId() == null) break; - parent = taskDb.load(parent.parentTaskId()); - } - return false; - } - private boolean postNewTask(UmbrellaUser user, HttpExchange ex) throws IOException { var json = json(ex); if (!(json.has(PROJECT_ID) && json.get(PROJECT_ID) instanceof Number pid)) throw missingFieldException(PROJECT_ID); @@ -385,7 +404,10 @@ public class TaskModule extends BaseHandler implements TaskService { if ((tagList == null || tagList.isEmpty()) && parentTask != null) tagList = tagService().getTags(TASK, parentTask.id(), user); if ((tagList == null || tagList.isEmpty())) tagList = tagService().getTags(PROJECT, projectId, user); if (tagList != null && !tagList.isEmpty()) tagService().save(TASK, task.id(), null, tagList); - return sendContent(ex, loadMembers(task)); + task = loadMembers(task); + + messageBus().dispatch(new TaskEvent(user,new TaggedTask(task,tagList), CREATE)); + return sendContent(ex, task); } private boolean postSearch(UmbrellaUser user, HttpExchange ex) throws IOException { @@ -424,23 +446,6 @@ public class TaskModule extends BaseHandler implements TaskService { return sendEmptyResponse(HTTP_NOT_IMPLEMENTED, ex); } - private static class TaggedTask extends Task{ - - private final Collection tags; - - public TaggedTask(Task task, Collection tags) { - super(task.id(), task.projectId(), task.parentTaskId(), task.name(), task.description(), task.status(), task.estimatedTime(), task.start(), task.dueDate(), task.showClosed(), task.noIndex(), task.members(), task.priority()); - this.tags = tags; - } - - @Override - public Map toMap() { - var map = super.toMap(); - map.put(TAGS,tags); - return map; - } - } - private Map addTags(Map taskList, Map> tags) { return taskList.values().stream().map(task -> new TaggedTask(task, tags.get(task.id()))).collect(Collectors.toMap(Task::id, t -> t)); } diff --git a/translations/src/main/resources/de.json b/translations/src/main/resources/de.json index 4478ecb..3c0e211 100644 --- a/translations/src/main/resources/de.json +++ b/translations/src/main/resources/de.json @@ -343,6 +343,8 @@ "user_list": "Benutzer-Liste", "user_module" : "Umbrella User-Verwaltung", "users": "Benutzer", + "user_created_entity": "{user} hat \"{entity}\" angelegt", + "user_deleted_entity": "{user} hat \"{entity}\" gelöscht", "user_updated_entity": "{user} hat \"{entity}\" bearbeitet", "website": "Website",