Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions services/src/main/java/io/meeds/task/digest/TaskDigestLinePlugin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/**
* This file is part of the Meeds project (https://meeds.io/).
*
* Copyright (C) 2020 - 2026 Meeds Association contact@meeds.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package io.meeds.task.digest;

import org.apache.commons.lang3.StringUtils;

import org.exoplatform.commons.utils.CommonsUtils;
import org.exoplatform.container.ExoContainerContext;
import org.exoplatform.container.xml.InitParams;
import org.exoplatform.social.core.identity.model.Identity;
import org.exoplatform.social.core.manager.IdentityManager;
import org.exoplatform.task.dto.TaskDto;
import org.exoplatform.task.integration.notification.NotificationUtils;
import org.exoplatform.task.exception.EntityNotFoundException;
import org.exoplatform.task.service.TaskService;

import io.meeds.commons.digest.model.DigestItem;
import io.meeds.commons.digest.model.DigestLine;
import io.meeds.commons.digest.plugin.DigestLineContext;
import io.meeds.commons.digest.plugin.DigestLinePlugin;
import io.meeds.task.plugin.TaskPermanentLinkPlugin;

/**
* The digest email lines of the task notifications: assigned, added as
* coworker, mentioned. The task and its project are read fresh from the
* stored task id; a deleted task gives no line.
*/
public class TaskDigestLinePlugin extends DigestLinePlugin {

public static final String TASK_ASSIGN_PLUGIN = "TaskAssignPlugin";

public static final String TASK_COWORKER_PLUGIN = "TaskCoworkerPlugin";

public static final String TASK_MENTIONED_PLUGIN = "TaskMentionedPlugin";

/** The stored parameters: the very keys the notification plugins write */
static final String TASK_ID_PARAM = NotificationUtils.TASK_ID;

static final String CREATOR_PARAM = NotificationUtils.CREATOR.getKey();

static final String TASK_URL_PARAM = NotificationUtils.TASK_URL;

private static final String LINE_KEY_PREFIX = "digest.line.";

private TaskService taskService;

private IdentityManager identityManager;

public TaskDigestLinePlugin(InitParams params) {
super(params);
}

TaskDigestLinePlugin(InitParams params, TaskService taskService, IdentityManager identityManager) {
super(params);
this.taskService = taskService;
this.identityManager = identityManager;
}

@Override
public DigestLine buildLine(DigestItem item, DigestLineContext context) {
TaskDto task = findTask(item.getParam(TASK_ID_PARAM));
if (task == null) {
return null;
}
String key = LINE_KEY_PREFIX + item.getPluginId();
String project = task.getStatus() == null || task.getStatus().getProject() == null ? ""
: task.getStatus().getProject().getName();
String url = url(item, task);
return switch (item.getPluginId()) {
case TASK_ASSIGN_PLUGIN -> DigestLine.of(key, task.getTitle(), project).withUrl(url);
case TASK_COWORKER_PLUGIN, TASK_MENTIONED_PLUGIN ->
DigestLine.of(key, fullName(item.getParam(CREATOR_PARAM)), task.getTitle(), project).withUrl(url);
default -> null;
};
}

private TaskDto findTask(String taskId) {
if (StringUtils.isBlank(taskId)) {
return null;
}
try {
return getTaskService().getTask(Long.parseLong(taskId));
} catch (EntityNotFoundException | NumberFormatException e) {
return null;
}
}

/**
* The link the instant email used when it was stored, otherwise the task
* detail page of the platform
*/
protected String url(DigestItem item, TaskDto task) {
String stored = item.getParam(TASK_URL_PARAM);
if (StringUtils.startsWith(stored, "http")) {
return stored;
}
return CommonsUtils.getCurrentDomain()
+ String.format(TaskPermanentLinkPlugin.URL_FORMAT, CommonsUtils.getCurrentPortalOwner(), task.getId());
}

private String fullName(String username) {
if (StringUtils.isBlank(username)) {
return "";
}
Identity identity = getIdentityManager().getOrCreateUserIdentity(username);
String fullName = identity == null || identity.getProfile() == null ? null : identity.getProfile().getFullName();
return StringUtils.isBlank(fullName) ? username : fullName;
}

private TaskService getTaskService() {
if (taskService == null) {
taskService = ExoContainerContext.getService(TaskService.class);
}
return taskService;
}

private IdentityManager getIdentityManager() {
if (identityManager == null) {
identityManager = ExoContainerContext.getService(IdentityManager.class);
}
return identityManager;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/**
* This file is part of the Meeds project (https://meeds.io/).
*
* Copyright (C) 2020 - 2026 Meeds Association contact@meeds.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package io.meeds.task.digest;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.when;

import java.time.Instant;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import org.exoplatform.container.xml.InitParams;
import org.exoplatform.container.xml.ValuesParam;
import org.exoplatform.social.core.identity.model.Identity;
import org.exoplatform.social.core.identity.model.Profile;
import org.exoplatform.social.core.identity.provider.OrganizationIdentityProvider;
import org.exoplatform.social.core.manager.IdentityManager;
import org.exoplatform.task.dto.ProjectDto;
import org.exoplatform.task.dto.StatusDto;
import org.exoplatform.task.dto.TaskDto;
import org.exoplatform.task.exception.EntityNotFoundException;
import org.exoplatform.task.service.TaskService;

import io.meeds.commons.digest.model.DigestItem;
import io.meeds.commons.digest.model.DigestLine;
import io.meeds.commons.digest.plugin.DigestLineContext;

@RunWith(MockitoJUnitRunner.class)
public class TaskDigestLinePluginTest {

private static final DigestLineContext CONTEXT = new DigestLineContext("ayoub", Locale.ENGLISH, ZoneId.of("Europe/Paris"));

@Mock
private TaskService taskService;

@Mock
private IdentityManager identityManager;

private TaskDigestLinePlugin plugin;

@Before
public void setUp() throws Exception {
InitParams params = new InitParams();
ValuesParam pluginIds = new ValuesParam();
pluginIds.setName("pluginIds");
pluginIds.setValues(new ArrayList<>(List.of(TaskDigestLinePlugin.TASK_ASSIGN_PLUGIN,
TaskDigestLinePlugin.TASK_COWORKER_PLUGIN,
TaskDigestLinePlugin.TASK_MENTIONED_PLUGIN)));
params.addParameter(pluginIds);
// The platform link needs the running portal: only the stored link is
// exercised here
plugin = new TaskDigestLinePlugin(params, taskService, identityManager);

ProjectDto project = new ProjectDto();
project.setName("Website");
StatusDto status = new StatusDto();
status.setProject(project);
TaskDto task = new TaskDto();
task.setId(7);
task.setTitle("Write the release notes");
task.setStatus(status);
lenient().when(taskService.getTask(7)).thenReturn(task);
lenient().when(taskService.getTask(404)).thenThrow(new EntityNotFoundException(404, TaskDto.class));
Identity john = new Identity(OrganizationIdentityProvider.NAME, "john");
Profile profile = new Profile(john);
profile.setProperty(Profile.FULL_NAME, "John Smith");
john.setProfile(profile);
lenient().when(identityManager.getOrCreateUserIdentity("john")).thenReturn(john);
}

@Test
public void testAssignedLineHasNoActor() {
DigestLine line = plugin.buildLine(item(TaskDigestLinePlugin.TASK_ASSIGN_PLUGIN, "taskId", "7", "creator", "john",
"taskUrl", "https://platform/portal/dw/tasks/taskDetail/7"),
CONTEXT);
assertNotNull(line);
assertEquals("digest.line.TaskAssignPlugin", line.getLabelKey());
assertEquals(List.of("Write the release notes", "Website"), line.getArgs());
assertEquals("https://platform/portal/dw/tasks/taskDetail/7", line.getUrl());
}

@Test
public void testCoworkerAndMentionLinesNameTheActor() {
DigestLine coworker = plugin.buildLine(item(TaskDigestLinePlugin.TASK_COWORKER_PLUGIN, "taskId", "7", "creator", "john",
"taskUrl", "https://platform/t/7"),
CONTEXT);
DigestLine mention = plugin.buildLine(item(TaskDigestLinePlugin.TASK_MENTIONED_PLUGIN, "taskId", "7", "creator", "john",
"taskUrl", "https://platform/t/7"),
CONTEXT);
assertNotNull(coworker);
assertNotNull(mention);
assertEquals(List.of("John Smith", "Write the release notes", "Website"), coworker.getArgs());
assertEquals(List.of("John Smith", "Write the release notes", "Website"), mention.getArgs());
}

@Test
public void testDeletedTaskGivesNoLine() {
assertNull(plugin.buildLine(item(TaskDigestLinePlugin.TASK_ASSIGN_PLUGIN, "taskId", "404"), CONTEXT));
assertNull(plugin.buildLine(item(TaskDigestLinePlugin.TASK_ASSIGN_PLUGIN, "taskId", "not a number"), CONTEXT));
assertNull(plugin.buildLine(item(TaskDigestLinePlugin.TASK_ASSIGN_PLUGIN), CONTEXT));
}

@Test
public void testUnknownTypeGivesNoLine() {
assertNull(plugin.buildLine(item("TaskCompletedPlugin", "taskId", "7", "taskUrl", "https://platform/t/7"), CONTEXT));
}

private static DigestItem item(String pluginId, String... params) {
Map<String, String> map = new LinkedHashMap<>();
for (int i = 0; i + 1 < params.length; i += 2) {
map.put(params[i], params[i + 1]);
}
return new DigestItem(1, "ayoub", pluginId, "tasks", Instant.now(), map);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,7 @@ Notification.digest.more.TaskMentionedPlugin=You've been mentioned in $COUNT tas

# Digest mail notifications: label of the category this addon owns
digest.category.tasks=Tasks
# Digest mail notifications: one line per notification type
digest.line.TaskAssignPlugin=You were assigned to "{0}" ({1})
digest.line.TaskCoworkerPlugin={0} added you as coworker on "{1}" ({2})
digest.line.TaskMentionedPlugin={0} mentioned you in "{1}" ({2})
Original file line number Diff line number Diff line change
Expand Up @@ -408,5 +408,20 @@
</values-param>
</init-params>
</component-plugin>
<!-- The email lines of these types, built at send time from the stored ids -->
<component-plugin>
<name>task.digest.lines</name>
<set-method>addLineProvider</set-method>
<type>io.meeds.task.digest.TaskDigestLinePlugin</type>
<description>Builds the digest email lines of the task notifications</description>
<init-params>
<values-param>
<name>pluginIds</name>
<value>TaskAssignPlugin</value>
<value>TaskCoworkerPlugin</value>
<value>TaskMentionedPlugin</value>
</values-param>
</init-params>
</component-plugin>
</external-component-plugins>
</configuration>
Loading