diff --git a/core/src/main/java/de/srsoftware/umbrella/core/api/TagService.java b/core/src/main/java/de/srsoftware/umbrella/core/api/TagService.java index 8afc1d3..4bb15ec 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/api/TagService.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/api/TagService.java @@ -4,12 +4,23 @@ package de.srsoftware.umbrella.core.api; import de.srsoftware.umbrella.core.exceptions.UmbrellaException; import de.srsoftware.umbrella.core.model.UmbrellaUser; import java.util.Collection; +import java.util.Map; public interface TagService { void deleteEntity(String task, long taskId); Collection getTags(String module, long entityId, UmbrellaUser user) throws UmbrellaException; + /** + * Loads the tags for all the entities denominated by the collection entityIds. + * @param module the realm the entities belong to + * @param entityIds the set of entities of the respective realm + * @param user the user, for whom the tags shall be loaded + * @return a map from entity ids to assigned tags + * @throws UmbrellaException + */ + Map> getTags(String module, Collection entityIds, UmbrellaUser user) throws UmbrellaException; + void save(String module, long entityId, Collection userIds, Collection tags); String save(String module, long entityId, Collection userIds, String tag); diff --git a/frontend/src/routes/project/Kanban.svelte b/frontend/src/routes/project/Kanban.svelte index f3726d5..7f0cd14 100644 --- a/frontend/src/routes/project/Kanban.svelte +++ b/frontend/src/routes/project/Kanban.svelte @@ -18,7 +18,6 @@ let highlight = $state({}); let filter = $derived(filter_input.toLowerCase()); let project = $state(null); - let ready = $state(false); let tasks = $state({}); let users = {}; let columns = $derived(project.allowed_states?Object.keys(project.allowed_states).length+1:1); @@ -96,8 +95,6 @@ try { await loadProject(); await loadTasks({project_id:+id,parent_task_id:0}); - ready = true; - loadTags(); } catch (ignored) {} } @@ -120,31 +117,6 @@ } } - async function loadTag(task){ - try { - const url = api(`tags/task/${task.id}`); - const resp = await fetch(url,{ - credentials:'include', - signal: signal - }); - if (resp.ok) { - const tags = await resp.json(); - if (tags.length) task.tags = tags.sort(); - } - } catch (ignored) {} - } - - function loadTags(){ - for (let uid of Object.keys(tasks)){ - for (let state of Object.keys(tasks[uid])){ - for (let tid of Object.keys(tasks[uid][state])){ - const task = tasks[uid][state][tid]; - loadTag(task); - } - } - } - } - async function loadTasks(selector){ const url = api('task/list'); selector.show_closed = true; @@ -205,7 +177,7 @@ {error} {/if} -{#if ready} +{#if project}
diff --git a/project/src/main/java/de/srsoftware/umbrella/project/SqliteDb.java b/project/src/main/java/de/srsoftware/umbrella/project/SqliteDb.java index 8d1224e..6dcaedd 100644 --- a/project/src/main/java/de/srsoftware/umbrella/project/SqliteDb.java +++ b/project/src/main/java/de/srsoftware/umbrella/project/SqliteDb.java @@ -16,13 +16,12 @@ import de.srsoftware.umbrella.core.exceptions.UmbrellaException; import de.srsoftware.umbrella.core.model.Permission; import de.srsoftware.umbrella.core.model.Project; import de.srsoftware.umbrella.core.model.Status; -import org.json.JSONObject; - import java.sql.Connection; import java.sql.SQLException; import java.util.Collection; import java.util.HashMap; import java.util.Map; +import org.json.JSONObject; public class SqliteDb extends BaseDb implements ProjectDb { private static final System.Logger LOG = System.getLogger("ProjectDb"); diff --git a/tags/src/main/java/de/srsoftware/umbrella/tags/SqliteDb.java b/tags/src/main/java/de/srsoftware/umbrella/tags/SqliteDb.java index 9ca1138..119b1da 100644 --- a/tags/src/main/java/de/srsoftware/umbrella/tags/SqliteDb.java +++ b/tags/src/main/java/de/srsoftware/umbrella/tags/SqliteDb.java @@ -253,8 +253,13 @@ CREATE TABLE IF NOT EXISTS {0} ( public Set list(long userId, String module, long entityId) { try { var tags = new HashSet(); + + // load tags assigned to user var rs = select(TAG).from(TABLE_TAGS).where(MODULE,equal(module)).where(ENTITY_ID,equal(entityId)).where(USER_ID,equal(userId)).exec(db); while (rs.next()) tags.add(rs.getString(1)); + rs.close(); + + // load tags assigned to no user rs = select(TAG).from(TABLE_TAGS).where(MODULE,equal(module)).where(ENTITY_ID,equal(entityId)).where(USER_ID,isNull()).exec(db); while (rs.next()) tags.add(rs.getString(1)); rs.close(); @@ -264,6 +269,26 @@ CREATE TABLE IF NOT EXISTS {0} ( } } + @Override + public Map> list(long userId, String module, Collection entityIds) { + try { + var tags = new HashMap>(); + + // load tags assigned to user + var rs = select(ENTITY_ID,TAG).from(TABLE_TAGS).where(MODULE,equal(module)).where(ENTITY_ID,in(entityIds.toArray())).where(USER_ID,equal(userId)).exec(db); + while (rs.next()) tags.computeIfAbsent(rs.getLong(ENTITY_ID), k -> new HashSet<>()).add(rs.getString(TAG)); + rs.close(); + + // load tags assigned to no user + rs = select(ENTITY_ID,TAG).from(TABLE_TAGS).where(MODULE,equal(module)).where(ENTITY_ID,in(entityIds.toArray())).where(USER_ID,isNull()).exec(db); + while (rs.next()) tags.computeIfAbsent(rs.getLong(ENTITY_ID), k -> new HashSet<>()).add(rs.getString(TAG)); + rs.close(); + return tags; + } catch (SQLException e) { + throw new UmbrellaException("Failed to load tags"); + } + } + @Override public void save(Collection userIds, String module, long entityId, Collection tags) { try { diff --git a/tags/src/main/java/de/srsoftware/umbrella/tags/TagDB.java b/tags/src/main/java/de/srsoftware/umbrella/tags/TagDB.java index 0f5aa6b..57effd3 100644 --- a/tags/src/main/java/de/srsoftware/umbrella/tags/TagDB.java +++ b/tags/src/main/java/de/srsoftware/umbrella/tags/TagDB.java @@ -1,6 +1,7 @@ /* © SRSoftware 2025 */ package de.srsoftware.umbrella.tags; + import java.util.Collection; import java.util.List; import java.util.Map; @@ -15,8 +16,16 @@ public interface TagDB { Set list(long userId, String module, long entityId); + /** + * Loads the tags for all the entities denominated by the collection entityIds. + * @param module the realm the entities belong to + * @param entityIds the set of entities of the respective realm + * @param userId the id of the user, for whom the tags shall be loaded + * @return a map from entity ids to assigned tags + */ + Map> list(long userId, String module, Collection entityIds); + void save(Collection userIds, String module, long entityId, Collection tags); void updateId(String module, Object oldId, Object newId); - } diff --git a/tags/src/main/java/de/srsoftware/umbrella/tags/TagModule.java b/tags/src/main/java/de/srsoftware/umbrella/tags/TagModule.java index 2019d67..07277bf 100644 --- a/tags/src/main/java/de/srsoftware/umbrella/tags/TagModule.java +++ b/tags/src/main/java/de/srsoftware/umbrella/tags/TagModule.java @@ -121,6 +121,11 @@ public class TagModule extends BaseHandler implements TagService { return tagDb.list(user.id(),module,entityId); } + @Override + public Map> getTags(String module, Collection entityIds, UmbrellaUser user) throws UmbrellaException { + return tagDb.list(user.id(),module,entityIds); + } + @Override public void save(String module, long entityId, Collection userIds, Collection tags) { tagDb.save(userIds,module,entityId,tags); 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 479fab6..c53f0c8 100644 --- a/task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java +++ b/task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java @@ -13,8 +13,8 @@ import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*; import static de.srsoftware.umbrella.core.model.Permission.*; import static de.srsoftware.umbrella.project.Constants.PERMISSIONS; import static de.srsoftware.umbrella.task.Constants.*; +import static java.lang.System.Logger.Level.DEBUG; import static java.lang.System.Logger.Level.WARNING; -import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; import com.sun.net.httpserver.HttpExchange; import de.srsoftware.configuration.Configuration; @@ -385,14 +385,20 @@ public class TaskModule extends BaseHandler implements TaskService { private boolean postTaskList(UmbrellaUser user, HttpExchange ex) throws IOException { var json = json(ex); LOG.log(WARNING, "Missing permission check in {0}.postTaskList!", getClass().getSimpleName()); - var showClosed = json.has(SHOW_CLOSED) && json.get(SHOW_CLOSED) instanceof Boolean bool ? bool : false; - var noIndex = json.has(NO_INDEX) && json.get(NO_INDEX) instanceof Boolean bool ? bool : false; - var projectId = json.has(PROJECT_ID) && json.get(PROJECT_ID) instanceof Number number ? number.longValue() : null; + var showClosed = json.has(SHOW_CLOSED) && json.get(SHOW_CLOSED) instanceof Boolean bool ? bool : false; + var noIndex = json.has(NO_INDEX) && json.get(NO_INDEX) instanceof Boolean bool ? bool : false; + var projectId = json.has(PROJECT_ID) && json.get(PROJECT_ID) instanceof Number number ? number.longValue() : null; var parentTaskId = json.has(PARENT_TASK_ID) && json.get(PARENT_TASK_ID) instanceof Number number ? number.longValue() : null; if (isSet(projectId)) { - if (parentTaskId == null) return sendContent(ex, mapValues(taskDb.listRootTasks(projectId, user, showClosed))); + if (parentTaskId == null) { + var list = taskDb.listRootTasks(projectId, user, showClosed); + return sendContent(ex, mapValues(list)); + } var projectTasks = taskDb.listProjectTasks(projectId, parentTaskId, noIndex); loadMembers(projectTasks.values()); + var tags = tagService().getTags(TASK,projectTasks.keySet(),user); + + projectTasks = addTags(projectTasks, tags); return sendContent(ex, mapValues(projectTasks)); } if (isSet(parentTaskId)) return sendContent(ex, mapValues(taskDb.listChildrenOf(parentTaskId, user, showClosed))); @@ -400,4 +406,25 @@ public class TaskModule extends BaseHandler implements TaskService { if (isSet(taskIds)) return sendContent(ex, mapValues(taskDb.load(taskIds))); 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)); + } }