Improve email notification as well as service desk feature

Make service desk a separate setting from mail setting. Strip off quoted contents when post comments via email. Improve notification email to make the information more explicit, and much more...
This commit is contained in:
Robin Shen 2021-07-31 16:37:30 +08:00
parent 357670d463
commit f7f34f3493
157 changed files with 2192 additions and 2022 deletions

10
pom.xml
View File

@ -267,7 +267,7 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.19</version>
<version>1.21</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
@ -502,7 +502,7 @@
<dependency>
<groupId>org.apache.sshd</groupId>
<artifactId>sshd-core</artifactId>
<version>2.2.0</version>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
@ -558,12 +558,12 @@
</repository>
</repositories>
<properties>
<commons.version>2.0.11</commons.version>
<k8shelper.version>2.1.6</k8shelper.version>
<commons.version>2.0.12</commons.version>
<k8shelper.version>2.1.7</k8shelper.version>
<slf4j.version>1.7.5</slf4j.version>
<logback.version>1.2.0</logback.version>
<antlr.version>4.7.2</antlr.version>
<jetty.version>9.4.37.v20210219</jetty.version>
<jetty.version>9.4.43.v20210629</jetty.version>
<wicket.version>7.17.0</wicket.version>
<jersey.version>2.26</jersey.version>
<hibernate.version>5.4.9.Final</hibernate.version>

View File

@ -165,6 +165,8 @@ import io.onedev.server.entitymanager.impl.DefaultSettingManager;
import io.onedev.server.entitymanager.impl.DefaultSshKeyManager;
import io.onedev.server.entitymanager.impl.DefaultUserAuthorizationManager;
import io.onedev.server.entitymanager.impl.DefaultUserManager;
import io.onedev.server.entityreference.DefaultEntityReferenceManager;
import io.onedev.server.entityreference.EntityReferenceManager;
import io.onedev.server.git.GitFilter;
import io.onedev.server.git.GitSshCommandCreator;
import io.onedev.server.git.config.GitConfig;
@ -185,6 +187,9 @@ import io.onedev.server.maintenance.DefaultDataManager;
import io.onedev.server.maintenance.ResetAdminPassword;
import io.onedev.server.maintenance.RestoreDatabase;
import io.onedev.server.maintenance.Upgrade;
import io.onedev.server.markdown.DefaultMarkdownManager;
import io.onedev.server.markdown.MarkdownManager;
import io.onedev.server.markdown.MarkdownProcessor;
import io.onedev.server.model.Build;
import io.onedev.server.model.support.administration.GroovyScript;
import io.onedev.server.model.support.administration.authenticator.Authenticator;
@ -250,10 +255,6 @@ import io.onedev.server.util.jackson.git.GitObjectMapperConfigurator;
import io.onedev.server.util.jackson.hibernate.HibernateObjectMapperConfigurator;
import io.onedev.server.util.jetty.DefaultJettyLauncher;
import io.onedev.server.util.jetty.JettyLauncher;
import io.onedev.server.util.markdown.DefaultMarkdownManager;
import io.onedev.server.util.markdown.EntityReferenceManager;
import io.onedev.server.util.markdown.MarkdownManager;
import io.onedev.server.util.markdown.MarkdownProcessor;
import io.onedev.server.util.schedule.DefaultTaskScheduler;
import io.onedev.server.util.schedule.TaskScheduler;
import io.onedev.server.util.script.ScriptContribution;
@ -288,8 +289,8 @@ import io.onedev.server.web.editable.EditSupport;
import io.onedev.server.web.editable.EditSupportLocator;
import io.onedev.server.web.editable.EditSupportRegistry;
import io.onedev.server.web.mapper.DynamicPathPageMapper;
import io.onedev.server.web.page.layout.ContributedAdministrationSetting;
import io.onedev.server.web.page.layout.AdministrationSettingContribution;
import io.onedev.server.web.page.layout.ContributedAdministrationSetting;
import io.onedev.server.web.page.layout.DefaultMainMenuCustomization;
import io.onedev.server.web.page.layout.MainMenuCustomization;
import io.onedev.server.web.page.project.blob.render.BlobRendererContribution;
@ -385,7 +386,6 @@ public class CoreModule extends AbstractPluginModule {
bind(CommitNotificationManager.class);
bind(BuildNotificationManager.class);
bind(IssueNotificationManager.class);
bind(EntityReferenceManager.class);
bind(CodeCommentNotificationManager.class);
bind(CodeCommentManager.class).to(DefaultCodeCommentManager.class);
bind(IssueWatchManager.class).to(DefaultIssueWatchManager.class);
@ -405,6 +405,7 @@ public class CoreModule extends AbstractPluginModule {
bind(PullRequestAssignmentManager.class).to(DefaultPullRequestAssignmentManager.class);
bind(SshKeyManager.class).to(DefaultSshKeyManager.class);
bind(BuildMetricManager.class).to(DefaultBuildMetricManager.class);
bind(EntityReferenceManager.class).to(DefaultEntityReferenceManager.class);
bind(WebHookManager.class);

View File

@ -17,8 +17,6 @@ public interface IssueChangeManager extends EntityManager<IssueChange> {
void changeTitle(Issue issue, String title);
void changeDescription(Issue issue, @Nullable String description);
void changeMilestone(Issue issue, Milestone milestone);
void changeFields(Issue issue, Map<String, Object> fieldValues);

View File

@ -58,6 +58,9 @@ public interface IssueManager extends EntityManager<Issue> {
void fixFieldValueOrders();
void saveDescription(Issue issue, @Nullable String description);
@Override
void delete(Issue issue);
Collection<Long> getIssueNumbers(Long projectId);

View File

@ -1,7 +1,5 @@
package io.onedev.server.entitymanager;
import javax.annotation.Nullable;
import io.onedev.server.model.PullRequest;
import io.onedev.server.model.PullRequestChange;
import io.onedev.server.model.support.pullrequest.MergeStrategy;
@ -13,6 +11,4 @@ public interface PullRequestChangeManager extends EntityManager<PullRequestChang
void changeTitle(PullRequest request, String title);
void changeDescription(PullRequest request, @Nullable String description);
}

View File

@ -73,4 +73,6 @@ public interface PullRequestManager extends EntityManager<PullRequest> {
void delete(Collection<PullRequest> requests);
void saveDescription(PullRequest request, @Nullable String description);
}

View File

@ -124,6 +124,7 @@ public interface SettingManager extends EntityManager<Setting> {
void saveNotificationTemplateSetting(NotificationTemplateSetting notificationTemplateSetting);
@Nullable
ServiceDeskSetting getServiceDeskSetting();
void saveServiceDeskSetting(ServiceDeskSetting serviceDeskSetting);

View File

@ -57,7 +57,6 @@ import io.onedev.server.model.Project;
import io.onedev.server.model.PullRequest;
import io.onedev.server.model.support.issue.TransitionSpec;
import io.onedev.server.model.support.issue.changedata.IssueBatchUpdateData;
import io.onedev.server.model.support.issue.changedata.IssueDescriptionChangeData;
import io.onedev.server.model.support.issue.changedata.IssueFieldChangeData;
import io.onedev.server.model.support.issue.changedata.IssueMilestoneChangeData;
import io.onedev.server.model.support.issue.changedata.IssueStateChangeData;
@ -161,22 +160,6 @@ public class DefaultIssueChangeManager extends BaseEntityManager<IssueChange>
}
}
@Transactional
@Override
public void changeDescription(Issue issue, @Nullable String description) {
String prevDescription = issue.getDescription();
if (!Objects.equal(description, prevDescription)) {
issue.setDescription(description);
IssueChange change = new IssueChange();
change.setIssue(issue);
change.setDate(new Date());
change.setUser(SecurityUtils.getUser());
change.setData(new IssueDescriptionChangeData(prevDescription, issue.getDescription()));
save(change);
}
}
@Transactional
@Override
public void changeMilestone(Issue issue, @Nullable Milestone milestone) {

View File

@ -22,6 +22,7 @@ import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.apache.wicket.util.lang.Objects;
import org.hibernate.Session;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
@ -44,6 +45,9 @@ import io.onedev.server.entitymanager.ProjectManager;
import io.onedev.server.entitymanager.RoleManager;
import io.onedev.server.entitymanager.SettingManager;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.entityreference.EntityReferenceManager;
import io.onedev.server.entityreference.ReferenceMigrator;
import io.onedev.server.entityreference.ReferencedFromAware;
import io.onedev.server.event.entity.EntityRemoved;
import io.onedev.server.event.issue.IssueChangeEvent;
import io.onedev.server.event.issue.IssueEvent;
@ -61,9 +65,6 @@ import io.onedev.server.model.support.administration.GlobalIssueSetting;
import io.onedev.server.model.support.inputspec.choiceinput.choiceprovider.SpecifiedChoices;
import io.onedev.server.model.support.issue.NamedIssueQuery;
import io.onedev.server.model.support.issue.changedata.IssueChangeData;
import io.onedev.server.model.support.issue.changedata.IssueReferencedFromCodeCommentData;
import io.onedev.server.model.support.issue.changedata.IssueReferencedFromIssueData;
import io.onedev.server.model.support.issue.changedata.IssueReferencedFromPullRequestData;
import io.onedev.server.model.support.issue.field.spec.FieldSpec;
import io.onedev.server.persistence.TransactionManager;
import io.onedev.server.persistence.annotation.Sessional;
@ -82,7 +83,6 @@ import io.onedev.server.storage.AttachmentStorageManager;
import io.onedev.server.util.MilestoneAndState;
import io.onedev.server.util.Pair;
import io.onedev.server.util.ProjectScopedNumber;
import io.onedev.server.util.ReferenceMigrator;
import io.onedev.server.util.facade.IssueFacade;
import io.onedev.server.web.component.issue.workflowreconcile.UndefinedFieldResolution;
import io.onedev.server.web.component.issue.workflowreconcile.UndefinedFieldValue;
@ -112,6 +112,8 @@ public class DefaultIssueManager extends BaseEntityManager<Issue> implements Iss
private final TransactionManager transactionManager;
private final EntityReferenceManager entityReferenceManager;
private final RoleManager roleManager;
private final Map<Long, IssueFacade> cache = new HashMap<>();
@ -124,7 +126,7 @@ public class DefaultIssueManager extends BaseEntityManager<Issue> implements Iss
SettingManager settingManager, ListenerRegistry listenerRegistry,
ProjectManager projectManager, UserManager userManager,
RoleManager roleManager, AttachmentStorageManager attachmentStorageManager,
IssueCommentManager issueCommentManager) {
IssueCommentManager issueCommentManager, EntityReferenceManager entityReferenceManager) {
super(dao);
this.issueFieldManager = issueFieldManager;
this.issueQuerySettingManager = issueQuerySettingManager;
@ -136,6 +138,7 @@ public class DefaultIssueManager extends BaseEntityManager<Issue> implements Iss
this.roleManager = roleManager;
this.attachmentStorageManager = attachmentStorageManager;
this.issueCommentManager = issueCommentManager;
this.entityReferenceManager = entityReferenceManager;
}
@SuppressWarnings("unchecked")
@ -303,11 +306,8 @@ public class DefaultIssueManager extends BaseEntityManager<Issue> implements Iss
boolean minorChange = false;
if (event instanceof IssueChangeEvent) {
IssueChangeData changeData = ((IssueChangeEvent)event).getChange().getData();
if (changeData instanceof IssueReferencedFromCodeCommentData
|| changeData instanceof IssueReferencedFromIssueData
|| changeData instanceof IssueReferencedFromPullRequestData) {
if (changeData instanceof ReferencedFromAware)
minorChange = true;
}
}
if (!(event instanceof IssueOpened || minorChange))
@ -808,7 +808,18 @@ public class DefaultIssueManager extends BaseEntityManager<Issue> implements Iss
});
}
@Transactional
@Override
public void saveDescription(Issue issue, @Nullable String description) {
String prevDescription = issue.getDescription();
if (!Objects.equal(description, prevDescription)) {
issue.setDescription(description);
entityReferenceManager.addReferenceChange(issue, description);
save(issue);
}
}
@Transactional
@Override
public void delete(Collection<Issue> issues) {

View File

@ -2,19 +2,15 @@ package io.onedev.server.entitymanager.impl;
import java.util.Date;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
import com.google.common.base.Objects;
import io.onedev.commons.launcher.loader.ListenerRegistry;
import io.onedev.server.entitymanager.PullRequestChangeManager;
import io.onedev.server.event.pullrequest.PullRequestChangeEvent;
import io.onedev.server.model.PullRequest;
import io.onedev.server.model.PullRequestChange;
import io.onedev.server.model.support.pullrequest.MergeStrategy;
import io.onedev.server.model.support.pullrequest.changedata.PullRequestDescriptionChangeData;
import io.onedev.server.model.support.pullrequest.changedata.PullRequestMergeStrategyChangeData;
import io.onedev.server.model.support.pullrequest.changedata.PullRequestTitleChangeData;
import io.onedev.server.persistence.annotation.Transactional;
@ -69,20 +65,4 @@ public class DefaultPullRequestChangeManager extends BaseEntityManager<PullReque
}
}
@Transactional
@Override
public void changeDescription(PullRequest request, @Nullable String description) {
String prevDescription = request.getDescription();
if (!Objects.equal(prevDescription, description)) {
request.setDescription(description);
PullRequestChange change = new PullRequestChange();
change.setDate(new Date());
change.setRequest(request);
change.setData(new PullRequestDescriptionChangeData(prevDescription, description));
change.setUser(SecurityUtils.getUser());
save(change);
}
}
}

View File

@ -31,6 +31,7 @@ import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.apache.wicket.util.lang.Objects;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
@ -65,6 +66,8 @@ import io.onedev.server.entitymanager.PullRequestChangeManager;
import io.onedev.server.entitymanager.PullRequestManager;
import io.onedev.server.entitymanager.PullRequestReviewManager;
import io.onedev.server.entitymanager.PullRequestUpdateManager;
import io.onedev.server.entityreference.EntityReferenceManager;
import io.onedev.server.entityreference.ReferencedFromAware;
import io.onedev.server.event.RefUpdated;
import io.onedev.server.event.build.BuildEvent;
import io.onedev.server.event.entity.EntityRemoved;
@ -77,6 +80,7 @@ import io.onedev.server.event.pullrequest.PullRequestOpened;
import io.onedev.server.event.pullrequest.PullRequestUpdated;
import io.onedev.server.git.GitUtils;
import io.onedev.server.infomanager.CommitInfoManager;
import io.onedev.server.markdown.MarkdownManager;
import io.onedev.server.model.Build;
import io.onedev.server.model.Group;
import io.onedev.server.model.Project;
@ -99,9 +103,6 @@ import io.onedev.server.model.support.pullrequest.changedata.PullRequestChangeDa
import io.onedev.server.model.support.pullrequest.changedata.PullRequestDiscardData;
import io.onedev.server.model.support.pullrequest.changedata.PullRequestMergeData;
import io.onedev.server.model.support.pullrequest.changedata.PullRequestMergeStrategyChangeData;
import io.onedev.server.model.support.pullrequest.changedata.PullRequestReferencedFromCodeCommentData;
import io.onedev.server.model.support.pullrequest.changedata.PullRequestReferencedFromIssueData;
import io.onedev.server.model.support.pullrequest.changedata.PullRequestReferencedFromPullRequestData;
import io.onedev.server.model.support.pullrequest.changedata.PullRequestReopenData;
import io.onedev.server.model.support.pullrequest.changedata.PullRequestReviewerAddData;
import io.onedev.server.model.support.pullrequest.changedata.PullRequestReviewerRemoveData;
@ -123,7 +124,6 @@ import io.onedev.server.security.permission.ReadCode;
import io.onedev.server.util.ProjectAndBranch;
import io.onedev.server.util.ProjectScopedNumber;
import io.onedev.server.util.concurrent.Prioritized;
import io.onedev.server.util.markdown.MarkdownManager;
import io.onedev.server.util.reviewrequirement.ReviewRequirement;
import io.onedev.server.util.work.BatchWorkManager;
import io.onedev.server.util.work.BatchWorker;
@ -159,6 +159,8 @@ public class DefaultPullRequestManager extends BaseEntityManager<PullRequest> im
private final ExecutorService executorService;
private final EntityReferenceManager entityReferenceManager;
@Inject
public DefaultPullRequestManager(Dao dao, PullRequestUpdateManager pullRequestUpdateManager,
PullRequestReviewManager pullRequestReviewManager, MarkdownManager markdownManager,
@ -166,7 +168,8 @@ public class DefaultPullRequestManager extends BaseEntityManager<PullRequest> im
SessionManager sessionManager, PullRequestChangeManager pullRequestChangeManager,
ExecutorService executorService, BuildManager buildManager,
TransactionManager transactionManager, ProjectManager projectManager,
CommitInfoManager commitInfoManager, PullRequestAssignmentManager pullRequestAssignmentManager) {
CommitInfoManager commitInfoManager, PullRequestAssignmentManager pullRequestAssignmentManager,
EntityReferenceManager entityReferenceManager) {
super(dao);
this.pullRequestUpdateManager = pullRequestUpdateManager;
@ -181,6 +184,7 @@ public class DefaultPullRequestManager extends BaseEntityManager<PullRequest> im
this.projectManager = projectManager;
this.commitInfoManager = commitInfoManager;
this.pullRequestAssignmentManager = pullRequestAssignmentManager;
this.entityReferenceManager = entityReferenceManager;
}
@Transactional
@ -660,9 +664,7 @@ public class DefaultPullRequestManager extends BaseEntityManager<PullRequest> im
|| changeData instanceof PullRequestAssigneeRemoveData
|| changeData instanceof PullRequestSourceBranchDeleteData
|| changeData instanceof PullRequestSourceBranchRestoreData
|| changeData instanceof PullRequestReferencedFromCodeCommentData
|| changeData instanceof PullRequestReferencedFromIssueData
|| changeData instanceof PullRequestReferencedFromPullRequestData) {
|| changeData instanceof ReferencedFromAware) {
minorChange = true;
}
}
@ -1034,4 +1036,15 @@ public class DefaultPullRequestManager extends BaseEntityManager<PullRequest> im
delete(request);
}
@Transactional
@Override
public void saveDescription(PullRequest request, @Nullable String description) {
String prevDescription = request.getDescription();
if (!Objects.equal(description, prevDescription)) {
request.setDescription(description);
entityReferenceManager.addReferenceChange(request, description);
save(request);
}
}
}

View File

@ -1,4 +1,4 @@
package io.onedev.server.util.markdown;
package io.onedev.server.entityreference;
import javax.annotation.Nullable;
@ -8,6 +8,7 @@ import org.unbescape.html.HtmlEscape;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.BuildManager;
import io.onedev.server.markdown.MarkdownProcessor;
import io.onedev.server.model.Build;
import io.onedev.server.model.Project;
import io.onedev.server.util.ProjectScopedNumber;

View File

@ -1,4 +1,4 @@
package io.onedev.server.util.markdown;
package io.onedev.server.entityreference;
import java.util.Date;
@ -17,10 +17,9 @@ import io.onedev.server.entitymanager.PullRequestManager;
import io.onedev.server.event.codecomment.CodeCommentCreated;
import io.onedev.server.event.codecomment.CodeCommentUpdated;
import io.onedev.server.event.entity.EntityPersisted;
import io.onedev.server.event.issue.IssueChangeEvent;
import io.onedev.server.event.issue.IssueOpened;
import io.onedev.server.event.pullrequest.PullRequestChangeEvent;
import io.onedev.server.event.pullrequest.PullRequestOpened;
import io.onedev.server.markdown.MarkdownManager;
import io.onedev.server.model.CodeComment;
import io.onedev.server.model.CodeCommentReply;
import io.onedev.server.model.Issue;
@ -29,15 +28,11 @@ import io.onedev.server.model.IssueComment;
import io.onedev.server.model.PullRequest;
import io.onedev.server.model.PullRequestChange;
import io.onedev.server.model.PullRequestComment;
import io.onedev.server.model.support.issue.changedata.IssueDescriptionChangeData;
import io.onedev.server.model.support.issue.changedata.IssueTitleChangeData;
import io.onedev.server.model.support.pullrequest.changedata.PullRequestDescriptionChangeData;
import io.onedev.server.model.support.pullrequest.changedata.PullRequestTitleChangeData;
import io.onedev.server.persistence.annotation.Transactional;
import io.onedev.server.util.ProjectScopedNumber;
@Singleton
public class EntityReferenceManager {
public class DefaultEntityReferenceManager implements EntityReferenceManager {
private final IssueChangeManager issueChangeManager;
@ -46,14 +41,15 @@ public class EntityReferenceManager {
private final MarkdownManager markdownManager;
@Inject
public EntityReferenceManager(IssueChangeManager issueChangeManager,
public DefaultEntityReferenceManager(IssueChangeManager issueChangeManager,
PullRequestChangeManager pullRequestChangeManager, MarkdownManager markdownManager) {
this.issueChangeManager = issueChangeManager;
this.pullRequestChangeManager = pullRequestChangeManager;
this.markdownManager = markdownManager;
}
private void addReferenceChange(Issue issue, String markdown) {
@Override
public void addReferenceChange(Issue issue, String markdown) {
if (markdown != null) {
Document document = Jsoup.parseBodyFragment(markdownManager.render(markdown));
for (ProjectScopedNumber referencedIssueFQN: new ReferenceParser(Issue.class).parseReferences(document, issue.getProject())) {
@ -111,7 +107,8 @@ public class EntityReferenceManager {
}
}
private void addReferenceChange(PullRequest request, String markdown) {
@Override
public void addReferenceChange(PullRequest request, String markdown) {
if (markdown != null) {
Document document = Jsoup.parseBodyFragment(markdownManager.render(markdown));
for (ProjectScopedNumber referencedIssueFQN: new ReferenceParser(Issue.class).parseReferences(document, request.getTargetProject())) {
@ -169,7 +166,8 @@ public class EntityReferenceManager {
}
}
private void addReferenceChange(CodeComment comment, String markdown) {
@Override
public void addReferenceChange(CodeComment comment, String markdown) {
if (markdown != null) {
Document document = Jsoup.parseBodyFragment(markdownManager.render(markdown));
for (ProjectScopedNumber referencedIssueFQN: new ReferenceParser(Issue.class).parseReferences(document, comment.getProject())) {
@ -257,15 +255,6 @@ public class EntityReferenceManager {
addReferenceChange(event.getRequest(), event.getRequest().getDescription());
}
@Transactional
@Listen
public void on(PullRequestChangeEvent event) {
if (event.getChange().getData() instanceof PullRequestTitleChangeData)
addReferenceChange(event.getRequest(), event.getRequest().getTitle());
if (event.getChange().getData() instanceof PullRequestDescriptionChangeData)
addReferenceChange(event.getRequest(), event.getRequest().getDescription());
}
@Transactional
@Listen
public void on(IssueOpened event) {
@ -273,15 +262,6 @@ public class EntityReferenceManager {
addReferenceChange(event.getIssue(), event.getIssue().getDescription());
}
@Transactional
@Listen
public void on(IssueChangeEvent event) {
if (event.getChange().getData() instanceof IssueTitleChangeData)
addReferenceChange(event.getIssue(), event.getIssue().getTitle());
if (event.getChange().getData() instanceof IssueDescriptionChangeData)
addReferenceChange(event.getIssue(), event.getIssue().getDescription());
}
@Transactional
@Listen
public void on(CodeCommentCreated event) {

View File

@ -0,0 +1,17 @@
package io.onedev.server.entityreference;
import javax.annotation.Nullable;
import io.onedev.server.model.CodeComment;
import io.onedev.server.model.Issue;
import io.onedev.server.model.PullRequest;
public interface EntityReferenceManager {
void addReferenceChange(Issue issue, @Nullable String markdown);
void addReferenceChange(PullRequest request, @Nullable String markdown);
void addReferenceChange(CodeComment comment, @Nullable String markdown);
}

View File

@ -1,10 +1,11 @@
package io.onedev.server.util.markdown;
package io.onedev.server.entityreference;
import javax.annotation.Nullable;
import org.apache.wicket.request.cycle.RequestCycle;
import org.jsoup.nodes.Document;
import io.onedev.server.markdown.MarkdownProcessor;
import io.onedev.server.model.Issue;
import io.onedev.server.model.Project;
import io.onedev.server.util.ProjectScopedNumber;

View File

@ -1,10 +1,11 @@
package io.onedev.server.util.markdown;
package io.onedev.server.entityreference;
import javax.annotation.Nullable;
import org.apache.wicket.request.cycle.RequestCycle;
import org.jsoup.nodes.Document;
import io.onedev.server.markdown.MarkdownProcessor;
import io.onedev.server.model.Project;
import io.onedev.server.model.PullRequest;
import io.onedev.server.util.ProjectScopedNumber;

View File

@ -1,4 +1,4 @@
package io.onedev.server.util;
package io.onedev.server.entityreference;
import java.util.Map;

View File

@ -1,4 +1,4 @@
package io.onedev.server.util.markdown;
package io.onedev.server.entityreference;
import java.util.Collection;
import java.util.HashSet;
@ -14,13 +14,13 @@ import org.jsoup.select.NodeTraversor;
import com.google.common.collect.ImmutableSet;
import io.onedev.commons.utils.HtmlUtils;
import io.onedev.commons.utils.StringUtils;
import io.onedev.commons.utils.WordUtils;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.ProjectManager;
import io.onedev.server.model.AbstractEntity;
import io.onedev.server.model.Project;
import io.onedev.server.util.HtmlUtils;
import io.onedev.server.util.ProjectScopedNumber;
import io.onedev.server.util.TextNodeVisitor;
import io.onedev.server.util.validation.ProjectNameValidator;

View File

@ -1,4 +1,4 @@
package io.onedev.server.util;
package io.onedev.server.entityreference;
import io.onedev.server.model.Project;

View File

@ -0,0 +1,12 @@
package io.onedev.server.entityreference;
import javax.annotation.Nullable;
import io.onedev.server.model.AbstractEntity;
public interface ReferencedFromAware<T extends AbstractEntity> {
@Nullable
T getReferencedFrom();
}

View File

@ -1,7 +0,0 @@
package io.onedev.server.event;
import javax.annotation.Nullable;
public interface MarkdownAware {
@Nullable String getMarkdown();
}

View File

@ -1,15 +1,25 @@
package io.onedev.server.event;
import java.util.Date;
import java.util.Optional;
import javax.annotation.Nullable;
import io.onedev.server.OneDev;
import io.onedev.server.markdown.MarkdownManager;
import io.onedev.server.model.Project;
import io.onedev.server.model.User;
import io.onedev.server.model.support.LastUpdate;
import io.onedev.server.notification.ActivityDetail;
public abstract class ProjectEvent extends Event {
private final Project project;
private transient Optional<String> renderedMarkdown;
private transient Optional<String> processedMarkdown;
public ProjectEvent(User user, Date date, Project project) {
super(user, date);
this.project = project;
@ -21,6 +31,16 @@ public abstract class ProjectEvent extends Event {
public abstract String getActivity();
@Nullable
public String getMarkdown() {
return null;
}
@Nullable
public ActivityDetail getActivityDetail() {
return null;
}
public LastUpdate getLastUpdate() {
LastUpdate lastUpdate = new LastUpdate();
lastUpdate.setUser(getUser());
@ -28,5 +48,61 @@ public abstract class ProjectEvent extends Event {
lastUpdate.setDate(getDate());
return lastUpdate;
}
@Nullable
public String getRenderedMarkdown() {
if (renderedMarkdown == null) {
String markdown = getMarkdown();
if (markdown != null)
renderedMarkdown = Optional.of(OneDev.getInstance(MarkdownManager.class).render(markdown));
else
renderedMarkdown = Optional.empty();
}
return renderedMarkdown.orElse(null);
}
@Nullable
public String getProcessedMarkdown() {
if (processedMarkdown == null) {
String renderedMarkdown = getRenderedMarkdown();
if (renderedMarkdown != null) {
processedMarkdown = Optional.of(OneDev.getInstance(MarkdownManager.class)
.process(renderedMarkdown, getProject(), null, true));
} else {
processedMarkdown = Optional.empty();
}
}
return processedMarkdown.orElse(null);
}
@Nullable
public String getTextBody() {
ActivityDetail activityDetail = getActivityDetail();
String markdown = getMarkdown();
if (activityDetail != null && markdown != null)
return activityDetail.getTextVersion() + "\n\n" + markdown;
else if (activityDetail != null)
return activityDetail.getTextVersion();
else if (markdown != null)
return markdown;
else
return null;
}
@Nullable
public String getHtmlBody() {
ActivityDetail activityDetail = getActivityDetail();
String processedMarkdown = getProcessedMarkdown();
if (activityDetail != null && processedMarkdown != null)
return activityDetail.getHtmlVersion() + "<br>" + processedMarkdown;
else if (activityDetail != null)
return activityDetail.getHtmlVersion();
else if (processedMarkdown != null)
return processedMarkdown;
else
return null;
}
}

View File

@ -1,9 +1,8 @@
package io.onedev.server.event.codecomment;
import io.onedev.server.event.MarkdownAware;
import io.onedev.server.model.CodeComment;
public class CodeCommentCreated extends CodeCommentEvent implements MarkdownAware {
public class CodeCommentCreated extends CodeCommentEvent {
public CodeCommentCreated(CodeComment comment) {
super(comment.getUser(), comment.getCreateDate(), comment);

View File

@ -1,9 +1,8 @@
package io.onedev.server.event.codecomment;
import io.onedev.server.event.MarkdownAware;
import io.onedev.server.model.CodeCommentReply;
public class CodeCommentReplied extends CodeCommentEvent implements MarkdownAware {
public class CodeCommentReplied extends CodeCommentEvent {
private final CodeCommentReply reply;

View File

@ -2,11 +2,10 @@ package io.onedev.server.event.codecomment;
import java.util.Date;
import io.onedev.server.event.MarkdownAware;
import io.onedev.server.model.CodeComment;
import io.onedev.server.model.User;
public class CodeCommentUpdated extends CodeCommentEvent implements MarkdownAware {
public class CodeCommentUpdated extends CodeCommentEvent {
public CodeCommentUpdated(User user, CodeComment comment) {
super(user, new Date(), comment);

View File

@ -3,12 +3,12 @@ package io.onedev.server.event.issue;
import java.util.Collection;
import java.util.Map;
import io.onedev.server.event.MarkdownAware;
import io.onedev.server.model.Group;
import io.onedev.server.model.IssueChange;
import io.onedev.server.model.User;
import io.onedev.server.notification.ActivityDetail;
public class IssueChangeEvent extends IssueEvent implements MarkdownAware {
public class IssueChangeEvent extends IssueEvent {
private final IssueChange change;
@ -49,4 +49,9 @@ public class IssueChangeEvent extends IssueEvent implements MarkdownAware {
return getChange().getData().getActivity();
}
@Override
public ActivityDetail getActivityDetail() {
return getChange().getData().getActivityDetail();
}
}

View File

@ -2,10 +2,9 @@ package io.onedev.server.event.issue;
import java.util.Collection;
import io.onedev.server.event.MarkdownAware;
import io.onedev.server.model.IssueComment;
public class IssueCommented extends IssueEvent implements MarkdownAware {
public class IssueCommented extends IssueEvent {
private final IssueComment comment;

View File

@ -9,14 +9,13 @@ import java.util.stream.Collectors;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.GroupManager;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.event.MarkdownAware;
import io.onedev.server.model.Group;
import io.onedev.server.model.Issue;
import io.onedev.server.model.User;
import io.onedev.server.model.support.issue.field.spec.FieldSpec;
import io.onedev.server.util.Input;
public class IssueOpened extends IssueEvent implements MarkdownAware {
public class IssueOpened extends IssueEvent {
public IssueOpened(Issue issue) {
super(issue.getSubmitter(), issue.getSubmitDate(), issue);

View File

@ -2,7 +2,6 @@ package io.onedev.server.event.pullrequest;
import org.eclipse.jgit.lib.ObjectId;
import io.onedev.server.event.MarkdownAware;
import io.onedev.server.model.PullRequestChange;
import io.onedev.server.model.support.pullrequest.MergePreview;
import io.onedev.server.model.support.pullrequest.changedata.PullRequestDiscardData;
@ -10,7 +9,7 @@ import io.onedev.server.model.support.pullrequest.changedata.PullRequestMergeDat
import io.onedev.server.util.CommitAware;
import io.onedev.server.util.ProjectScopedCommit;
public class PullRequestChangeEvent extends PullRequestEvent implements MarkdownAware, CommitAware {
public class PullRequestChangeEvent extends PullRequestEvent implements CommitAware {
private final PullRequestChange change;

View File

@ -1,10 +1,9 @@
package io.onedev.server.event.pullrequest;
import io.onedev.server.event.MarkdownAware;
import io.onedev.server.model.CodeComment;
import io.onedev.server.model.PullRequest;
public class PullRequestCodeCommentCreated extends PullRequestCodeCommentEvent implements MarkdownAware {
public class PullRequestCodeCommentCreated extends PullRequestCodeCommentEvent {
public PullRequestCodeCommentCreated(PullRequest request, CodeComment comment) {
super(comment.getUser(), comment.getCreateDate(), request, comment);

View File

@ -2,12 +2,11 @@ package io.onedev.server.event.pullrequest;
import java.util.Date;
import io.onedev.server.event.MarkdownAware;
import io.onedev.server.model.CodeComment;
import io.onedev.server.model.PullRequest;
import io.onedev.server.model.User;
public abstract class PullRequestCodeCommentEvent extends PullRequestEvent implements MarkdownAware {
public abstract class PullRequestCodeCommentEvent extends PullRequestEvent {
private final CodeComment comment;

View File

@ -2,10 +2,9 @@ package io.onedev.server.event.pullrequest;
import java.util.Collection;
import io.onedev.server.event.MarkdownAware;
import io.onedev.server.model.PullRequestComment;
public class PullRequestCommented extends PullRequestEvent implements MarkdownAware {
public class PullRequestCommented extends PullRequestEvent {
private final PullRequestComment comment;

View File

@ -1,9 +1,8 @@
package io.onedev.server.event.pullrequest;
import io.onedev.server.event.MarkdownAware;
import io.onedev.server.model.PullRequest;
public class PullRequestOpened extends PullRequestEvent implements MarkdownAware {
public class PullRequestOpened extends PullRequestEvent {
public PullRequestOpened(PullRequest request) {
super(request.getSubmitter(), request.getSubmitDate(), request);

View File

@ -25,7 +25,7 @@ public class PullRequestUpdated extends PullRequestEvent {
@Override
public String getActivity() {
return "Commits added";
return "added commits";
}
public Collection<User> getCommitters() {

View File

@ -17,9 +17,10 @@ import org.apache.shiro.util.ThreadContext;
import org.apache.sshd.common.channel.ChannelOutputStream;
import org.apache.sshd.server.Environment;
import org.apache.sshd.server.ExitCallback;
import org.apache.sshd.server.SessionAware;
import org.apache.sshd.server.channel.ChannelSession;
import org.apache.sshd.server.command.Command;
import org.apache.sshd.server.session.ServerSession;
import org.apache.sshd.server.session.ServerSessionAware;
import org.eclipse.jgit.transport.RemoteConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -117,7 +118,7 @@ public class GitSshCommandCreator implements SshCommandCreator {
}
}
private abstract class GitSshCommand implements Command, SessionAware {
private abstract class GitSshCommand implements Command, ServerSessionAware {
private static final int PRIORITY = 2;
@ -156,7 +157,7 @@ public class GitSshCommandCreator implements SshCommandCreator {
}
@Override
public void start(Environment env) throws IOException {
public void start(ChannelSession channel, Environment env) throws IOException {
ThreadContext.bind(SecurityUtils.asSubject(authenticator.getPublicKeyOwnerId(session)));
File gitDir;
@ -206,7 +207,7 @@ public class GitSshCommandCreator implements SshCommandCreator {
protected abstract ExecutionResult execute(File gitDir, Map<String, String> gitEnvs);
@Override
public void destroy() throws Exception {
public void destroy(ChannelSession channel) throws Exception {
if (commandFuture != null)
commandFuture.cancel(true);
}

View File

@ -5,6 +5,7 @@ import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Set;
@ -225,8 +226,23 @@ public class DefaultDataManager implements DataManager, Serializable {
settingManager.saveProjectSetting(new GlobalProjectSetting());
}
setting = settingManager.getSetting(Key.SERVICE_DESK_SETTING);
if (setting == null) {
settingManager.saveServiceDeskSetting(new ServiceDeskSetting());
if (setting == null) {
settingManager.saveServiceDeskSetting(null);
} else if (setting.getValue() != null && !validator.validate(setting.getValue()).isEmpty()) {
manualConfigs.add(new ManualConfig("Specify Service Desk Setting", null,
setting.getValue(), new HashSet<>(), true) {
@Override
public Skippable getSkippable() {
return null;
}
@Override
public void complete() {
settingManager.saveServiceDeskSetting((ServiceDeskSetting) getSetting());
}
});
}
setting = settingManager.getSetting(Key.NOTIFICATION_TEMPLATE_SETTING);
if (setting == null) {
@ -345,8 +361,8 @@ public class DefaultDataManager implements DataManager, Serializable {
+ "%s",
url, Throwables.getStackTraceAsString(e));
mailManager.sendMail(Lists.newArrayList(root.getEmail()), Lists.newArrayList(),
"OneDev database auto-backup failed", htmlBody, textBody,
null, null);
Lists.newArrayList(), "[Backup] OneDev Database Auto-backup Failed",
htmlBody, textBody, null, null);
}
public Object writeReplace() throws ObjectStreamException {

View File

@ -1,4 +1,4 @@
package io.onedev.server.util.markdown;
package io.onedev.server.markdown;
import java.util.ArrayList;
import java.util.Collection;

View File

@ -1,4 +1,4 @@
package io.onedev.server.util.markdown;
package io.onedev.server.markdown;
import java.util.Collection;
import java.util.regex.Matcher;
@ -14,9 +14,9 @@ import org.jsoup.select.NodeTraversor;
import com.google.common.collect.ImmutableSet;
import io.onedev.commons.utils.HtmlUtils;
import io.onedev.server.git.GitUtils;
import io.onedev.server.model.Project;
import io.onedev.server.util.HtmlUtils;
import io.onedev.server.util.TextNodeVisitor;
import io.onedev.server.web.page.project.commits.CommitDetailPage;

View File

@ -1,4 +1,4 @@
package io.onedev.server.util.markdown;
package io.onedev.server.markdown;
import java.util.ArrayList;
import java.util.List;
@ -8,8 +8,6 @@ import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringEscapeUtils;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
@ -27,9 +25,9 @@ import com.vladsch.flexmark.parser.ParserEmulationProfile;
import com.vladsch.flexmark.util.options.MutableDataHolder;
import com.vladsch.flexmark.util.options.MutableDataSet;
import io.onedev.commons.utils.HtmlUtils;
import io.onedev.server.entitymanager.SettingManager;
import io.onedev.server.model.Project;
import io.onedev.server.util.HtmlUtils;
import io.onedev.server.web.resource.AttachmentResource;
@Singleton
@ -80,13 +78,6 @@ public class DefaultMarkdownManager implements MarkdownManager {
return HtmlRenderer.builder(options).softBreak("<br>").build().render(node);
}
@Override
public String escape(String markdown) {
markdown = StringEscapeUtils.escapeHtml4(markdown);
markdown = StringUtils.replace(markdown, "\n", "<br>");
return markdown;
}
@Override
public Document process(Document document, @Nullable Project project, @Nullable Object context, boolean forExternal) {
document = HtmlUtils.sanitize(document);
@ -100,7 +91,11 @@ public class DefaultMarkdownManager implements MarkdownManager {
src = settingManager.getSystemSetting().getServerUrl() + src;
element.attr("src", AttachmentResource.authorizeGroup(src));
}
element.attr("width", "100%");
String style = element.attr("style");
if (!style.endsWith(";"))
style += ";";
style += "max-width:100%";
element.attr("style", style);
}
for (Element element: document.body().getElementsByTag("a")) {
String href = element.attr("href");

View File

@ -1,4 +1,4 @@
package io.onedev.server.util.markdown;
package io.onedev.server.markdown;
import javax.annotation.Nullable;
@ -27,15 +27,4 @@ public interface MarkdownManager {
String process(String html, @Nullable Project project, @Nullable Object context, boolean forExternal);
/**
* Escape html characters in specified markdown so that the markdown plain text
* can be embedded in html content such as html email.
*
* @param markdown
* markdown to be escaped
* @return
* escaped markdown plain text
*/
String escape(String markdown);
}

View File

@ -1,4 +1,4 @@
package io.onedev.server.util.markdown;
package io.onedev.server.markdown;
import javax.annotation.Nullable;

View File

@ -1,4 +1,4 @@
package io.onedev.server.util.markdown;
package io.onedev.server.markdown;
import java.util.Collection;
import java.util.HashSet;
@ -12,7 +12,7 @@ import org.jsoup.select.NodeTraversor;
import com.google.common.collect.ImmutableSet;
import io.onedev.commons.utils.HtmlUtils;
import io.onedev.server.util.HtmlUtils;
import io.onedev.server.util.TextNodeVisitor;
public class MentionParser {

View File

@ -1,4 +1,4 @@
package io.onedev.server.util.markdown;
package io.onedev.server.markdown;
import org.apache.wicket.request.cycle.RequestCycle;
import org.jsoup.nodes.Document;

View File

@ -1,4 +1,4 @@
package io.onedev.server.util.markdown;
package io.onedev.server.markdown;
import java.io.IOException;

View File

@ -2648,13 +2648,21 @@ public class DataMigrator {
dom.writeToFile(file, false);
} else if (file.getName().startsWith("IssueChanges.xml")) {
VersionedXmlDoc dom = VersionedXmlDoc.fromFile(file);
for (Element element: dom.getRootElement().elements())
useUnknownUser(element, "user");
for (Element element: dom.getRootElement().elements()) {
if (element.element("data").attributeValue("class").contains("IssueDescriptionChangeData"))
element.detach();
else
useUnknownUser(element, "user");
}
dom.writeToFile(file, false);
} else if (file.getName().startsWith("PullRequestChanges.xml")) {
VersionedXmlDoc dom = VersionedXmlDoc.fromFile(file);
for (Element element: dom.getRootElement().elements())
useUnknownUser(element, "user");
for (Element element: dom.getRootElement().elements()) {
if (element.element("data").attributeValue("class").contains("PullRequestDescriptionChangeData"))
element.detach();
else
useUnknownUser(element, "user");
}
dom.writeToFile(file, false);
} else if (file.getName().startsWith("IssueComments.xml")) {
VersionedXmlDoc dom = VersionedXmlDoc.fromFile(file);
@ -2717,45 +2725,47 @@ public class DataMigrator {
if (id > maxId)
maxId = id;
}
Element serviceDeskSettingElement = dom.getRootElement().addElement("io.onedev.server.model.Setting");
serviceDeskSettingElement.addAttribute("revision", "0.0");
serviceDeskSettingElement.addElement("id").setText(String.valueOf(maxId+1));
serviceDeskSettingElement.addElement("key").setText("SERVICE_DESK_SETTING");
Element valueElement = serviceDeskSettingElement.addElement("value");
valueElement.addAttribute("class", "io.onedev.server.model.support.administration.ServiceDeskSetting");
Element senderAuthorizationsElement = valueElement.addElement("senderAuthorizations");
Element defaultProjectDesignationsElement = valueElement.addElement("defaultProjectDesignations");
Element issueCreationSettingsElement = valueElement.addElement("issueCreationSettings");
for (Element oldSenderAuthorizationElement: oldSenderAuthorizationElements) {
Element senderAuthorizationElement = senderAuthorizationsElement
.addElement("io.onedev.server.model.support.administration.SenderAuthorization");
Element defaultProjectDesignationElement = defaultProjectDesignationsElement
.addElement("io.onedev.server.model.support.administration.DefaultProjectDesignation");
Element issueCreationSettingElement = issueCreationSettingsElement
.addElement("io.onedev.server.model.support.administration.IssueCreationSetting");
Element senderEmailsElement = oldSenderAuthorizationElement.element("senderEmails");
if (senderEmailsElement != null) {
String senderEmails = senderEmailsElement.getText().trim();
senderAuthorizationElement.addElement("senderEmails").setText(senderEmails);
defaultProjectDesignationElement.addElement("senderEmails").setText(senderEmails);
issueCreationSettingElement.addElement("senderEmails").setText(senderEmails);
}
Element authorizedProjectsElement = oldSenderAuthorizationElement.element("authorizedProjects");
if (authorizedProjectsElement != null) {
senderAuthorizationElement.addElement("authorizedProjects")
.setText(authorizedProjectsElement.getText().trim());
}
senderAuthorizationElement.addElement("authorizedRoleName")
.setText(oldSenderAuthorizationElement.elementText("authorizedRoleName").trim());
defaultProjectDesignationElement.addElement("defaultProject")
.setText(oldSenderAuthorizationElement.elementText("defaultProject").trim());
Element issueFieldsElement = oldSenderAuthorizationElement.element("issueFields");
issueFieldsElement.detach();
issueCreationSettingElement.add(issueFieldsElement);
}
if (oldSenderAuthorizationElements != null && !oldSenderAuthorizationElements.isEmpty()) {
Element serviceDeskSettingElement = dom.getRootElement().addElement("io.onedev.server.model.Setting");
serviceDeskSettingElement.addAttribute("revision", "0.0");
serviceDeskSettingElement.addElement("id").setText(String.valueOf(maxId+1));
serviceDeskSettingElement.addElement("key").setText("SERVICE_DESK_SETTING");
Element valueElement = serviceDeskSettingElement.addElement("value");
valueElement.addAttribute("class", "io.onedev.server.model.support.administration.ServiceDeskSetting");
Element senderAuthorizationsElement = valueElement.addElement("senderAuthorizations");
Element projectDesignationsElement = valueElement.addElement("projectDesignations");
Element issueCreationSettingsElement = valueElement.addElement("issueCreationSettings");
for (Element oldSenderAuthorizationElement: oldSenderAuthorizationElements) {
Element senderAuthorizationElement = senderAuthorizationsElement
.addElement("io.onedev.server.model.support.administration.SenderAuthorization");
Element projectDesignationElement = projectDesignationsElement
.addElement("io.onedev.server.model.support.administration.ProjectDesignation");
Element issueCreationSettingElement = issueCreationSettingsElement
.addElement("io.onedev.server.model.support.administration.IssueCreationSetting");
Element senderEmailsElement = oldSenderAuthorizationElement.element("senderEmails");
if (senderEmailsElement != null) {
String senderEmails = senderEmailsElement.getText().trim();
senderAuthorizationElement.addElement("senderEmails").setText(senderEmails);
projectDesignationElement.addElement("senderEmails").setText(senderEmails);
issueCreationSettingElement.addElement("senderEmails").setText(senderEmails);
}
Element authorizedProjectsElement = oldSenderAuthorizationElement.element("authorizedProjects");
if (authorizedProjectsElement != null) {
senderAuthorizationElement.addElement("authorizedProjects")
.setText(authorizedProjectsElement.getText().trim());
}
senderAuthorizationElement.addElement("authorizedRoleName")
.setText(oldSenderAuthorizationElement.elementText("authorizedRoleName").trim());
projectDesignationElement.addElement("project")
.setText(oldSenderAuthorizationElement.elementText("defaultProject").trim());
Element issueFieldsElement = oldSenderAuthorizationElement.element("issueFields");
issueFieldsElement.detach();
issueCreationSettingElement.add(issueFieldsElement);
}
}
dom.writeToFile(file, false);
}
}

View File

@ -67,6 +67,7 @@ import io.onedev.server.buildspec.param.spec.ParamSpec;
import io.onedev.server.buildspec.param.spec.SecretParam;
import io.onedev.server.buildspec.param.supply.ParamSupply;
import io.onedev.server.entitymanager.BuildManager;
import io.onedev.server.entityreference.Referenceable;
import io.onedev.server.git.GitUtils;
import io.onedev.server.git.RefInfo;
import io.onedev.server.infomanager.CommitInfoManager;
@ -86,7 +87,6 @@ import io.onedev.server.util.IssueUtils;
import io.onedev.server.util.JobSecretAuthorizationContext;
import io.onedev.server.util.MatrixRunner;
import io.onedev.server.util.ProjectScopedNumber;
import io.onedev.server.util.Referenceable;
import io.onedev.server.util.facade.BuildFacade;
import io.onedev.server.util.match.WildcardUtils;
import io.onedev.server.util.patternset.PatternSet;

View File

@ -53,6 +53,7 @@ import io.onedev.server.entitymanager.GroupManager;
import io.onedev.server.entitymanager.PullRequestManager;
import io.onedev.server.entitymanager.SettingManager;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.entityreference.Referenceable;
import io.onedev.server.infomanager.CommitInfoManager;
import io.onedev.server.infomanager.PullRequestInfoManager;
import io.onedev.server.infomanager.UserInfoManager;
@ -66,7 +67,6 @@ import io.onedev.server.storage.AttachmentStorageSupport;
import io.onedev.server.util.CollectionUtils;
import io.onedev.server.util.Input;
import io.onedev.server.util.ProjectScopedNumber;
import io.onedev.server.util.Referenceable;
import io.onedev.server.util.facade.IssueFacade;
import io.onedev.server.web.editable.BeanDescriptor;
import io.onedev.server.web.editable.PropertyDescriptor;
@ -298,6 +298,13 @@ public class Issue extends AbstractEntity implements Referenceable, AttachmentSt
this.threadingReference = threadingReference;
}
public String getEffectiveThreadingReference() {
String threadingReference = getThreadingReference();
if (threadingReference == null)
threadingReference = "<" + getUUID() + "@onedev>";
return threadingReference;
}
@Override
public long getNumber() {
return number;

View File

@ -56,6 +56,7 @@ import com.google.common.collect.Lists;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.PullRequestManager;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.entityreference.Referenceable;
import io.onedev.server.git.GitUtils;
import io.onedev.server.infomanager.PullRequestInfoManager;
import io.onedev.server.infomanager.UserInfoManager;
@ -73,7 +74,6 @@ import io.onedev.server.util.ComponentContext;
import io.onedev.server.util.IssueUtils;
import io.onedev.server.util.ProjectAndBranch;
import io.onedev.server.util.ProjectScopedNumber;
import io.onedev.server.util.Referenceable;
import io.onedev.server.web.util.PullRequestAware;
import io.onedev.server.web.util.WicketUtils;
@ -1175,5 +1175,14 @@ public class PullRequest extends AbstractEntity implements Referenceable, Attach
return "Source branch already exists";
return null;
}
public String getStatusName() {
if (isMerged())
return "Merged";
else if (isDiscarded())
return "Discarded";
else
return "Open";
}
}

View File

@ -71,7 +71,8 @@ public class MailSetting implements Serializable {
}
@Editable(order=410, name="System Email Address", description="This email address will be used as sender "
+ "address for various notifications")
+ "address for various notifications. Its inbox will also be checked if <tt>Check Incoming Email</tt>"
+ "option is enabled below")
@NotEmpty
public String getEmailAddress() {
return emailAddress;
@ -81,10 +82,9 @@ public class MailSetting implements Serializable {
this.emailAddress = emailAddress;
}
@Editable(order=450, name="Check Incoming Email", description="Enable this to use the service desk feature (creating issue, "
+ "posting issue or pull request comments from email) <br>"
@Editable(order=450, name="Check Incoming Email", description="Enable this to post issue and pull request comments via email<br>"
+ "<b class='text-danger'>NOTE:</b> <a href='https://en.wikipedia.org/wiki/Email_address#Subaddressing' target='_blank'>Sub addressing</a> "
+ "needs to be enabled for your mail server, as OneDev needs to use this to track context of sent email and received email")
+ "needs to be enabled for system email address, as OneDev uses it to track issue and pull request contexts")
public ReceiveMailSetting getReceiveMailSetting() {
return receiveMailSetting;
}

View File

@ -15,13 +15,13 @@ import io.onedev.server.web.editable.annotation.NameOfEmptyValue;
import io.onedev.server.web.editable.annotation.Patterns;
@Editable
public class DefaultProjectDesignation implements Serializable {
public class ProjectDesignation implements Serializable {
private static final long serialVersionUID = 1L;
private String senderEmails;
private String defaultProject;
private String project;
@Editable(order=100, name="Applicable Senders", description="Specify space-separated sender "
+ "email addresses applicable for this entry. Use '*' or '?' for wildcard match. "
@ -39,12 +39,12 @@ public class DefaultProjectDesignation implements Serializable {
@Editable(order=200)
@ChoiceProvider("getProjectChoices")
@NotEmpty
public String getDefaultProject() {
return defaultProject;
public String getProject() {
return project;
}
public void setDefaultProject(String defaultProject) {
this.defaultProject = defaultProject;
public void setProject(String project) {
this.project = project;
}
@SuppressWarnings("unused")

View File

@ -7,38 +7,49 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import javax.validation.ConstraintValidatorContext;
import edu.emory.mathcs.backport.java.util.Collections;
import io.onedev.commons.utils.ExplicitException;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.ProjectManager;
import io.onedev.server.entitymanager.SettingManager;
import io.onedev.server.model.Project;
import io.onedev.server.model.support.issue.field.supply.FieldSupply;
import io.onedev.server.util.match.Matcher;
import io.onedev.server.util.match.StringMatcher;
import io.onedev.server.util.patternset.PatternSet;
import io.onedev.server.util.usage.Usage;
import io.onedev.server.util.validation.Validatable;
import io.onedev.server.util.validation.annotation.ClassValidating;
import io.onedev.server.web.component.issue.workflowreconcile.UndefinedFieldResolution;
import io.onedev.server.web.component.issue.workflowreconcile.UndefinedFieldValue;
import io.onedev.server.web.component.issue.workflowreconcile.UndefinedFieldValuesResolution;
import io.onedev.server.web.editable.annotation.Editable;
import io.onedev.server.web.editable.annotation.OmitName;
@Editable
public class ServiceDeskSetting implements Serializable {
@ClassValidating
public class ServiceDeskSetting implements Serializable, Validatable {
private static final long serialVersionUID = 1L;
public static final String PROP_SENDER_AUTHORIZATIONS = "senderAuthorizations";
public static final String PROP_DEFAULT_PROJECT_DESIGNATIONS = "defaultProjectDesignations";
private static final String PROP_PROJECT_DESIGNATIONS = "projectDesignations";
private static final String PROP_ISSUE_CREATION_SETTINGS = "issueCreationSettings";
private List<SenderAuthorization> senderAuthorizations = new ArrayList<>();
private List<DefaultProjectDesignation> defaultProjectDesignations = new ArrayList<>();
private List<ProjectDesignation> projectDesignations = new ArrayList<>();
private List<IssueCreationSetting> issueCreationSettings = new ArrayList<>();
@Editable
@OmitName
@Editable(order=100, description="When sender email address can not be mapped to an existing user, "
+ "OneDev will use entries defined here to determine if the sender has permission to "
+ "create issues. For a particular sender, the first matching entry will take "
+ "effect")
public List<SenderAuthorization> getSenderAuthorizations() {
return senderAuthorizations;
}
@ -47,16 +58,29 @@ public class ServiceDeskSetting implements Serializable {
this.senderAuthorizations = senderAuthorizations;
}
@Editable
@OmitName
public List<DefaultProjectDesignation> getDefaultProjectDesignations() {
return defaultProjectDesignations;
@Editable(order=200, description="When email is sent to system email address without specifying "
+ "project information, OneDev will use entries defined here to decide in which "
+ "project to create issues. For a particular sender, the first matching entry will "
+ "take effect")
public List<ProjectDesignation> getProjectDesignations() {
return projectDesignations;
}
public void setDefaultProjectDesignations(List<DefaultProjectDesignation> defaultProjectDesignations) {
this.defaultProjectDesignations = defaultProjectDesignations;
public void setProjectDesignations(List<ProjectDesignation> projectDesignations) {
this.projectDesignations = projectDesignations;
}
@SuppressWarnings("unused")
private static List<String> getProjectChoices() {
List<String> projectNames = OneDev.getInstance(ProjectManager.class)
.query().stream().map(it->it.getName()).collect(Collectors.toList());
Collections.sort(projectNames);
return projectNames;
}
@Editable(order=300, description="Specify issue creation settings. For a particular sender and project, "
+ "the first matching entry will take effect. If no entry matches, default issue creation "
+ "settings defined below will be used")
public List<IssueCreationSetting> getIssueCreationSettings() {
return issueCreationSettings;
}
@ -65,6 +89,11 @@ public class ServiceDeskSetting implements Serializable {
this.issueCreationSettings = issueCreationSettings;
}
@SuppressWarnings("unused")
private static Collection<String> getIssueFieldNames() {
return OneDev.getInstance(SettingManager.class).getIssueSetting().getPromptFieldsUponIssueOpen();
}
@Nullable
public SenderAuthorization getSenderAuthorization(String senderAddress) {
Matcher matcher = new StringMatcher();
@ -79,21 +108,20 @@ public class ServiceDeskSetting implements Serializable {
return null;
}
@Nullable
public DefaultProjectDesignation getDefaultProjectDesignation(String senderAddress) {
public String getDesignatedProject(String senderAddress) {
Matcher matcher = new StringMatcher();
for (DefaultProjectDesignation designation: defaultProjectDesignations) {
for (ProjectDesignation designation: projectDesignations) {
String patterns = designation.getSenderEmails();
if (patterns == null)
patterns = "*";
PatternSet patternSet = PatternSet.parse(patterns);
if (patternSet.matches(matcher, senderAddress))
return designation;
return designation.getProject();
}
return null;
throw new ExplicitException("No project designated for sender: " + senderAddress);
}
public IssueCreationSetting getIssueCreationSetting(String senderAddress, Project project) {
public List<FieldSupply> getIssueCreationSetting(String senderAddress, Project project) {
Matcher matcher = new StringMatcher();
for (IssueCreationSetting setting: issueCreationSettings) {
String senderPatterns = setting.getSenderEmails();
@ -107,9 +135,9 @@ public class ServiceDeskSetting implements Serializable {
PatternSet projectPatternSet = PatternSet.parse(projectPatterns);
if (senderPatternSet.matches(matcher, senderAddress) && projectPatternSet.matches(matcher, project.getName()))
return setting;
return setting.getIssueFields();
}
String errorMessage = String.format("No issue creation setting found (sender: %s, project: %s)",
String errorMessage = String.format("No issue creation setting (sender: %s, project: %s)",
senderAddress, project.getName());
throw new ExplicitException(errorMessage);
}
@ -143,9 +171,9 @@ public class ServiceDeskSetting implements Serializable {
if (authorization.getAuthorizedProjects().length() == 0)
authorization.setAuthorizedProjects(null);
}
for (DefaultProjectDesignation designation: getDefaultProjectDesignations()) {
if (designation.getDefaultProject().equals(oldName))
designation.setDefaultProject(newName);
for (ProjectDesignation designation: getProjectDesignations()) {
if (designation.getProject().equals(oldName))
designation.setProject(newName);
}
for (IssueCreationSetting setting: getIssueCreationSettings()) {
PatternSet patternSet = PatternSet.parse(setting.getApplicableProjects());
@ -171,9 +199,9 @@ public class ServiceDeskSetting implements Serializable {
}
index = 0;
for (DefaultProjectDesignation designation: getDefaultProjectDesignations()) {
if (designation.getDefaultProject().equals(projectName))
usage.add("default project designation #" + index + ": default project");
for (ProjectDesignation senderProject: getProjectDesignations()) {
if (senderProject.getProject().equals(projectName))
usage.add("sender project #" + index + ": project");
index++;
}
@ -210,5 +238,47 @@ public class ServiceDeskSetting implements Serializable {
for (IssueCreationSetting setting: getIssueCreationSettings())
setting.fixUndefinedFieldValues(resolutions);
}
@Override
public boolean isValid(ConstraintValidatorContext context) {
boolean isValid = true;
boolean foundDefault = false;
for (ProjectDesignation designation: getProjectDesignations()) {
if (designation.getSenderEmails() == null) {
foundDefault = true;
break;
}
}
if (!foundDefault) {
String errorMessage = "An entry with any sender should be defined to be used as "
+ "default project designation";
context.buildConstraintViolationWithTemplate(errorMessage)
.addPropertyNode(PROP_PROJECT_DESIGNATIONS)
.addConstraintViolation();
isValid = false;
}
foundDefault = false;
for (IssueCreationSetting setting: getIssueCreationSettings()) {
if (setting.getSenderEmails() == null && setting.getApplicableProjects() == null) {
foundDefault = true;
break;
}
}
if (!foundDefault) {
String errorMessage = "An entry with any sender and any project should be defined "
+ "to be use as default issue creation setting";
context.buildConstraintViolationWithTemplate(errorMessage)
.addPropertyNode(PROP_ISSUE_CREATION_SETTINGS)
.addConstraintViolation();
isValid = false;
}
if (!isValid)
context.disableDefaultConstraintViolation();
return isValid;
}
}

View File

@ -31,7 +31,7 @@ public class NotificationTemplateSetting implements Serializable {
+ "When evaluating this template, below variables will be available:"
+ "<ul class='mb-0'>"
+ "<li><code>event:</code> <a href='https://code.onedev.io/projects/onedev-server/blob/main/server-core/src/main/java/io/onedev/server/event/Event.java' target='_blank'>event object</a> triggering the notification"
+ "<li><code>eventSummary:</code> a string representing summary of the event"
+ "<li><code>eventSummary:</code> a string representing summary of the event. May be <code>null</code>"
+ "<li><code>eventBody:</code> a string representing body of the event. May be <code>null</code>"
+ "<li><code>eventUrl:</code> a string representing event detail url"
+ "<li><code>replyable:</code> a boolean indiciating whether or not topic comment can be created directly by replying the email"

View File

@ -1,6 +1,8 @@
<%
print "<b>${eventSummary}</b>"
print "<br><br>"
if (eventSummary != null) {
print "<b>${eventSummary}</b>"
print "<br><br>"
}
if (eventBody != null) {
print eventBody
@ -21,7 +23,7 @@
if (unsubscribable != null) {
print """
<div style='border-top:1px solid #EEE; margin-top:1em; padding-top:1em; color:#666; font-size:0.9em;'>You received this as you
are participating or participated previously in this topic.
are participating in this topic.
"""
if (unsubscribable.getEmailAddress() != null)
print """

View File

@ -5,15 +5,9 @@ import java.util.Map;
import javax.annotation.Nullable;
import org.apache.wicket.Component;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.IssueChangeManager;
import io.onedev.server.model.IssueChange;
import io.onedev.server.model.Milestone;
import io.onedev.server.util.CommentAware;
import io.onedev.server.util.Input;
import io.onedev.server.web.component.issue.activities.activity.IssueFieldChangePanel;
public class IssueBatchUpdateData extends IssueFieldChangeData {
@ -96,21 +90,6 @@ public class IssueBatchUpdateData extends IssueFieldChangeData {
};
}
@Override
public Component render(String componentId, IssueChange change) {
Long changeId = change.getId();
return new IssueFieldChangePanel(componentId, true) {
private static final long serialVersionUID = 1L;
@Override
protected IssueChange getChange() {
return OneDev.getInstance(IssueChangeManager.class).load(changeId);
}
};
}
@Override
public String getActivity() {
return "batch edited";

View File

@ -6,26 +6,29 @@ import java.util.Map;
import javax.annotation.Nullable;
import org.apache.wicket.Component;
import io.onedev.server.model.Group;
import io.onedev.server.model.IssueChange;
import io.onedev.server.model.User;
import io.onedev.server.notification.ActivityDetail;
import io.onedev.server.util.CommentAware;
public interface IssueChangeData extends Serializable {
public abstract class IssueChangeData implements Serializable {
Component render(String componentId, IssueChange change);
String getActivity();
private static final long serialVersionUID = 1L;
public abstract String getActivity();
@Nullable
CommentAware getCommentAware();
public abstract CommentAware getCommentAware();
Map<String, Collection<User>> getNewUsers();
public abstract Map<String, Collection<User>> getNewUsers();
Map<String, Group> getNewGroups();
public abstract Map<String, Group> getNewGroups();
boolean affectsBoards();
public abstract boolean affectsBoards();
@Nullable
public ActivityDetail getActivityDetail() {
return null;
}
}

View File

@ -1,65 +0,0 @@
package io.onedev.server.model.support.issue.changedata;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.apache.wicket.Component;
import io.onedev.server.model.Group;
import io.onedev.server.model.IssueChange;
import io.onedev.server.model.User;
import io.onedev.server.util.CommentAware;
public class IssueDescriptionChangeData implements IssueChangeData {
private static final long serialVersionUID = 1L;
private final String oldDescription;
private final String newDescription;
public IssueDescriptionChangeData(String oldDescription, String newDescription) {
this.oldDescription = oldDescription;
this.newDescription = newDescription;
}
public String getOldDescription() {
return oldDescription;
}
public String getNewDescription() {
return newDescription;
}
@Override
public Component render(String componentId, IssueChange change) {
throw new UnsupportedOperationException();
}
@Override
public String getActivity() {
return "changed description";
}
@Override
public CommentAware getCommentAware() {
return null;
}
@Override
public Map<String, Collection<User>> getNewUsers() {
return new HashMap<>();
}
@Override
public Map<String, Group> getNewGroups() {
return new HashMap<>();
}
@Override
public boolean affectsBoards() {
return false;
}
}

View File

@ -9,21 +9,18 @@ import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.wicket.Component;
import io.onedev.commons.utils.StringUtils;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.GroupManager;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.model.Group;
import io.onedev.server.model.IssueChange;
import io.onedev.server.model.User;
import io.onedev.server.model.support.issue.field.spec.FieldSpec;
import io.onedev.server.notification.ActivityDetail;
import io.onedev.server.util.CommentAware;
import io.onedev.server.util.Input;
import io.onedev.server.web.component.propertychangepanel.PropertyChangePanel;
public class IssueFieldChangeData implements IssueChangeData {
public class IssueFieldChangeData extends IssueChangeData {
private static final long serialVersionUID = 1L;
@ -63,11 +60,6 @@ public class IssueFieldChangeData implements IssueChangeData {
return field.getName() + ": " + StringUtils.join(field.getValues(), ", ");
}
@Override
public Component render(String componentId, IssueChange change) {
return new PropertyChangePanel(componentId, getOldFieldValues(), getNewFieldValues(), false);
}
@Override
public String getActivity() {
return "changed fields";
@ -80,11 +72,6 @@ public class IssueFieldChangeData implements IssueChangeData {
return lines;
}
@Override
public CommentAware getCommentAware() {
return null;
}
@Override
public Map<String, Collection<User>> getNewUsers() {
UserManager userManager = OneDev.getInstance(UserManager.class);
@ -151,4 +138,14 @@ public class IssueFieldChangeData implements IssueChangeData {
return true;
}
@Override
public CommentAware getCommentAware() {
return null;
}
@Override
public ActivityDetail getActivityDetail() {
return ActivityDetail.compare(getOldFieldValues(), getNewFieldValues(), false);
}
}

View File

@ -6,17 +6,13 @@ import java.util.Map;
import javax.annotation.Nullable;
import org.apache.wicket.Component;
import io.onedev.server.model.Group;
import io.onedev.server.model.IssueChange;
import io.onedev.server.model.Milestone;
import io.onedev.server.model.User;
import io.onedev.server.util.CollectionUtils;
import io.onedev.server.notification.ActivityDetail;
import io.onedev.server.util.CommentAware;
import io.onedev.server.web.component.propertychangepanel.PropertyChangePanel;
public class IssueMilestoneChangeData implements IssueChangeData {
public class IssueMilestoneChangeData extends IssueChangeData {
private static final long serialVersionUID = 1L;
@ -29,14 +25,6 @@ public class IssueMilestoneChangeData implements IssueChangeData {
this.newMilestone = newMilestone!=null?newMilestone.getName():null;
}
@Override
public Component render(String componentId, IssueChange change) {
return new PropertyChangePanel(componentId,
CollectionUtils.newHashMap("Milestone", oldMilestone),
CollectionUtils.newHashMap("Milestone", newMilestone),
true);
}
public String getOldMilestone() {
return oldMilestone;
}
@ -69,5 +57,14 @@ public class IssueMilestoneChangeData implements IssueChangeData {
public boolean affectsBoards() {
return true;
}
@Override
public ActivityDetail getActivityDetail() {
Map<String, String> oldFieldValues = new HashMap<>();
oldFieldValues.put("Milestone", oldMilestone);
Map<String, String> newFieldValues = new HashMap<>();
oldFieldValues.put("Milestone", newMilestone);
return ActivityDetail.compare(oldFieldValues, newFieldValues, true);
}
}

View File

@ -4,17 +4,17 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.apache.wicket.Component;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.CodeCommentManager;
import io.onedev.server.entityreference.ReferencedFromAware;
import io.onedev.server.model.CodeComment;
import io.onedev.server.model.Group;
import io.onedev.server.model.IssueChange;
import io.onedev.server.model.User;
import io.onedev.server.notification.ActivityDetail;
import io.onedev.server.rest.annotation.EntityId;
import io.onedev.server.util.CommentAware;
import io.onedev.server.web.component.codecomment.referencedfrom.ReferencedFromCodeCommentPanel;
public class IssueReferencedFromCodeCommentData implements IssueChangeData {
public class IssueReferencedFromCodeCommentData extends IssueChangeData implements ReferencedFromAware<CodeComment> {
private static final long serialVersionUID = 1L;
@ -34,11 +34,6 @@ public class IssueReferencedFromCodeCommentData implements IssueChangeData {
return "Referenced from code comment";
}
@Override
public Component render(String componentId, IssueChange change) {
return new ReferencedFromCodeCommentPanel(componentId, commentId);
}
@Override
public CommentAware getCommentAware() {
return null;
@ -59,4 +54,14 @@ public class IssueReferencedFromCodeCommentData implements IssueChangeData {
return false;
}
@Override
public ActivityDetail getActivityDetail() {
return ActivityDetail.referencedFrom(getReferencedFrom());
}
@Override
public CodeComment getReferencedFrom() {
return OneDev.getInstance(CodeCommentManager.class).get(commentId);
}
}

View File

@ -4,17 +4,17 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.apache.wicket.Component;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.IssueManager;
import io.onedev.server.entityreference.ReferencedFromAware;
import io.onedev.server.model.Group;
import io.onedev.server.model.Issue;
import io.onedev.server.model.IssueChange;
import io.onedev.server.model.User;
import io.onedev.server.notification.ActivityDetail;
import io.onedev.server.rest.annotation.EntityId;
import io.onedev.server.util.CommentAware;
import io.onedev.server.web.component.issue.referencedfrom.ReferencedFromIssuePanel;
public class IssueReferencedFromIssueData implements IssueChangeData {
public class IssueReferencedFromIssueData extends IssueChangeData implements ReferencedFromAware<Issue> {
private static final long serialVersionUID = 1L;
@ -29,11 +29,6 @@ public class IssueReferencedFromIssueData implements IssueChangeData {
return issueId;
}
@Override
public Component render(String componentId, IssueChange change) {
return new ReferencedFromIssuePanel(componentId, issueId);
}
@Override
public String getActivity() {
return "Referenced from other issue";
@ -58,5 +53,15 @@ public class IssueReferencedFromIssueData implements IssueChangeData {
public boolean affectsBoards() {
return false;
}
@Override
public Issue getReferencedFrom() {
return OneDev.getInstance(IssueManager.class).get(issueId);
}
@Override
public ActivityDetail getActivityDetail() {
return ActivityDetail.referencedFrom(getReferencedFrom());
}
}

View File

@ -4,17 +4,17 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.apache.wicket.Component;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.PullRequestManager;
import io.onedev.server.entityreference.ReferencedFromAware;
import io.onedev.server.model.Group;
import io.onedev.server.model.IssueChange;
import io.onedev.server.model.PullRequest;
import io.onedev.server.model.User;
import io.onedev.server.notification.ActivityDetail;
import io.onedev.server.rest.annotation.EntityId;
import io.onedev.server.util.CommentAware;
import io.onedev.server.web.component.pullrequest.referencedfrom.ReferencedFromPullRequestPanel;
public class IssueReferencedFromPullRequestData implements IssueChangeData {
public class IssueReferencedFromPullRequestData extends IssueChangeData implements ReferencedFromAware<PullRequest> {
private static final long serialVersionUID = 1L;
@ -29,11 +29,6 @@ public class IssueReferencedFromPullRequestData implements IssueChangeData {
return requestId;
}
@Override
public Component render(String componentId, IssueChange change) {
return new ReferencedFromPullRequestPanel(componentId, requestId);
}
@Override
public String getActivity() {
return "Referenced from pull request";
@ -58,5 +53,15 @@ public class IssueReferencedFromPullRequestData implements IssueChangeData {
public boolean affectsBoards() {
return false;
}
@Override
public PullRequest getReferencedFrom() {
return OneDev.getInstance(PullRequestManager.class).get(requestId);
}
@Override
public ActivityDetail getActivityDetail() {
return ActivityDetail.referencedFrom(getReferencedFrom());
}
}

View File

@ -5,14 +5,9 @@ import java.util.Map;
import javax.annotation.Nullable;
import org.apache.wicket.Component;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.IssueChangeManager;
import io.onedev.server.model.IssueChange;
import io.onedev.server.notification.ActivityDetail;
import io.onedev.server.util.CommentAware;
import io.onedev.server.util.Input;
import io.onedev.server.web.component.issue.activities.activity.IssueFieldChangePanel;
public class IssueStateChangeData extends IssueFieldChangeData {
@ -76,25 +71,14 @@ public class IssueStateChangeData extends IssueFieldChangeData {
};
}
@Override
public Component render(String componentId, IssueChange change) {
Long changeId = change.getId();
return new IssueFieldChangePanel(componentId, true) {
private static final long serialVersionUID = 1L;
@Override
protected IssueChange getChange() {
return OneDev.getInstance(IssueChangeManager.class).load(changeId);
}
};
}
@Override
public String getActivity() {
return "changed state to '" + newState + "'";
}
@Override
public ActivityDetail getActivityDetail() {
return ActivityDetail.compare(getOldFieldValues(), getNewFieldValues(), true);
}
}

View File

@ -4,16 +4,12 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.apache.wicket.Component;
import io.onedev.server.model.Group;
import io.onedev.server.model.IssueChange;
import io.onedev.server.model.User;
import io.onedev.server.util.CollectionUtils;
import io.onedev.server.notification.ActivityDetail;
import io.onedev.server.util.CommentAware;
import io.onedev.server.web.component.propertychangepanel.PropertyChangePanel;
public class IssueTitleChangeData implements IssueChangeData {
public class IssueTitleChangeData extends IssueChangeData {
private static final long serialVersionUID = 1L;
@ -26,14 +22,6 @@ public class IssueTitleChangeData implements IssueChangeData {
this.newTitle = newTitle;
}
@Override
public Component render(String componentId, IssueChange change) {
return new PropertyChangePanel(componentId,
CollectionUtils.newHashMap("Title", oldTitle),
CollectionUtils.newHashMap("Title", newTitle),
true);
}
@Override
public String getActivity() {
return "changed title";
@ -58,5 +46,14 @@ public class IssueTitleChangeData implements IssueChangeData {
public boolean affectsBoards() {
return false;
}
@Override
public ActivityDetail getActivityDetail() {
Map<String, String> oldFieldValues = new HashMap<>();
oldFieldValues.put("Title", oldTitle);
Map<String, String> newFieldValues = new HashMap<>();
oldFieldValues.put("Title", newTitle);
return ActivityDetail.compare(oldFieldValues, newFieldValues, true);
}
}

View File

@ -2,14 +2,9 @@ package io.onedev.server.model.support.pullrequest.changedata;
import javax.annotation.Nullable;
import org.apache.wicket.Component;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.PullRequestChangeManager;
import io.onedev.server.model.PullRequestChange;
import io.onedev.server.util.CommentAware;
public class PullRequestApproveData implements PullRequestChangeData {
public class PullRequestApproveData extends PullRequestChangeData {
private static final long serialVersionUID = 1L;
@ -24,21 +19,6 @@ public class PullRequestApproveData implements PullRequestChangeData {
return "approved";
}
@Override
public Component render(String componentId, PullRequestChange change) {
Long changeId = change.getId();
return new PullRequestChangeCommentPanel(componentId) {
private static final long serialVersionUID = 1L;
@Override
protected PullRequestChange getChange() {
return OneDev.getInstance(PullRequestChangeManager.class).load(changeId);
}
};
}
@Override
public CommentAware getCommentAware() {
return new CommentAware() {

View File

@ -1,11 +1,8 @@
package io.onedev.server.model.support.pullrequest.changedata;
import org.apache.wicket.Component;
import io.onedev.server.model.PullRequestChange;
import io.onedev.server.util.CommentAware;
public class PullRequestAssigneeAddData implements PullRequestChangeData {
public class PullRequestAssigneeAddData extends PullRequestChangeData {
private static final long serialVersionUID = 1L;
@ -25,9 +22,4 @@ public class PullRequestAssigneeAddData implements PullRequestChangeData {
return null;
}
@Override
public Component render(String componentId, PullRequestChange change) {
return null;
}
}

View File

@ -1,11 +1,8 @@
package io.onedev.server.model.support.pullrequest.changedata;
import org.apache.wicket.Component;
import io.onedev.server.model.PullRequestChange;
import io.onedev.server.util.CommentAware;
public class PullRequestAssigneeRemoveData implements PullRequestChangeData {
public class PullRequestAssigneeRemoveData extends PullRequestChangeData {
private static final long serialVersionUID = 1L;
@ -25,9 +22,4 @@ public class PullRequestAssigneeRemoveData implements PullRequestChangeData {
return null;
}
@Override
public Component render(String componentId, PullRequestChange change) {
return null;
}
}

View File

@ -1,75 +0,0 @@
package io.onedev.server.model.support.pullrequest.changedata;
import java.util.List;
import org.apache.wicket.ajax.AjaxRequestTarget;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.PullRequestChangeManager;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.model.Project;
import io.onedev.server.model.PullRequestChange;
import io.onedev.server.model.User;
import io.onedev.server.security.SecurityUtils;
import io.onedev.server.web.component.markdown.AttachmentSupport;
import io.onedev.server.web.component.markdown.ContentVersionSupport;
import io.onedev.server.web.component.project.comment.ProjectCommentPanel;
import io.onedev.server.web.util.DeleteCallback;
import io.onedev.server.web.util.ProjectAttachmentSupport;
@SuppressWarnings("serial")
public abstract class PullRequestChangeCommentPanel extends ProjectCommentPanel {
public PullRequestChangeCommentPanel(String id) {
super(id);
}
@Override
protected String getComment() {
return getChange().getData().getCommentAware().getComment();
}
@Override
protected List<User> getMentionables() {
return OneDev.getInstance(UserManager.class).queryAndSort(getChange().getRequest().getParticipants());
}
@Override
protected void onSaveComment(AjaxRequestTarget target, String comment) {
getChange().getData().getCommentAware().setComment(comment);
OneDev.getInstance(PullRequestChangeManager.class).save(getChange());
}
@Override
protected Project getProject() {
return getChange().getRequest().getTargetProject();
}
@Override
protected AttachmentSupport getAttachmentSupport() {
return new ProjectAttachmentSupport(getProject(), getChange().getRequest().getUUID(),
SecurityUtils.canManagePullRequests(getProject()));
}
@Override
protected boolean canModifyOrDeleteComment() {
return SecurityUtils.canModifyOrDelete(getChange());
}
@Override
protected String getRequiredLabel() {
return null;
}
@Override
protected ContentVersionSupport getContentVersionSupport() {
return null;
}
@Override
protected DeleteCallback getDeleteCallback() {
return null;
}
protected abstract PullRequestChange getChange();
}

View File

@ -4,18 +4,21 @@ import java.io.Serializable;
import javax.annotation.Nullable;
import org.apache.wicket.Component;
import io.onedev.server.model.PullRequestChange;
import io.onedev.server.notification.ActivityDetail;
import io.onedev.server.util.CommentAware;
public interface PullRequestChangeData extends Serializable {
public abstract class PullRequestChangeData implements Serializable {
Component render(String componentId, PullRequestChange change);
String getActivity();
private static final long serialVersionUID = 1L;
public abstract String getActivity();
@Nullable
CommentAware getCommentAware();
public abstract CommentAware getCommentAware();
@Nullable
public ActivityDetail getActivityDetail() {
return null;
}
}

View File

@ -1,44 +0,0 @@
package io.onedev.server.model.support.pullrequest.changedata;
import org.apache.wicket.Component;
import io.onedev.server.model.PullRequestChange;
import io.onedev.server.util.CommentAware;
public class PullRequestDescriptionChangeData implements PullRequestChangeData {
private static final long serialVersionUID = 1L;
private final String oldDescription;
private final String newDescription;
public PullRequestDescriptionChangeData(String oldDescription, String newDescription) {
this.oldDescription = oldDescription;
this.newDescription = newDescription;
}
public String getOldDescription() {
return oldDescription;
}
public String getNewDescription() {
return newDescription;
}
@Override
public String getActivity() {
return "changed description";
}
@Override
public Component render(String componentId, PullRequestChange change) {
throw new UnsupportedOperationException();
}
@Override
public CommentAware getCommentAware() {
return null;
}
}

View File

@ -2,14 +2,9 @@ package io.onedev.server.model.support.pullrequest.changedata;
import javax.annotation.Nullable;
import org.apache.wicket.Component;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.PullRequestChangeManager;
import io.onedev.server.model.PullRequestChange;
import io.onedev.server.util.CommentAware;
public class PullRequestDiscardData implements PullRequestChangeData {
public class PullRequestDiscardData extends PullRequestChangeData {
private static final long serialVersionUID = 1L;
@ -43,19 +38,4 @@ public class PullRequestDiscardData implements PullRequestChangeData {
};
}
@Override
public Component render(String componentId, PullRequestChange change) {
Long changeId = change.getId();
return new PullRequestChangeCommentPanel(componentId) {
private static final long serialVersionUID = 1L;
@Override
protected PullRequestChange getChange() {
return OneDev.getInstance(PullRequestChangeManager.class).load(changeId);
}
};
}
}

View File

@ -2,12 +2,9 @@ package io.onedev.server.model.support.pullrequest.changedata;
import javax.annotation.Nullable;
import org.apache.wicket.Component;
import io.onedev.server.model.PullRequestChange;
import io.onedev.server.util.CommentAware;
public class PullRequestMergeData implements PullRequestChangeData {
public class PullRequestMergeData extends PullRequestChangeData {
private static final long serialVersionUID = 1L;
@ -25,11 +22,6 @@ public class PullRequestMergeData implements PullRequestChangeData {
return "merged";
}
@Override
public Component render(String componentId, PullRequestChange change) {
return null;
}
@Override
public CommentAware getCommentAware() {
return null;

View File

@ -1,14 +1,13 @@
package io.onedev.server.model.support.pullrequest.changedata;
import org.apache.wicket.Component;
import java.util.HashMap;
import java.util.Map;
import io.onedev.server.model.PullRequestChange;
import io.onedev.server.model.support.pullrequest.MergeStrategy;
import io.onedev.server.util.CollectionUtils;
import io.onedev.server.notification.ActivityDetail;
import io.onedev.server.util.CommentAware;
import io.onedev.server.web.component.propertychangepanel.PropertyChangePanel;
public class PullRequestMergeStrategyChangeData implements PullRequestChangeData {
public class PullRequestMergeStrategyChangeData extends PullRequestChangeData {
private static final long serialVersionUID = 1L;
@ -26,17 +25,19 @@ public class PullRequestMergeStrategyChangeData implements PullRequestChangeData
return "changed merge strategy";
}
@Override
public Component render(String componentId, PullRequestChange change) {
return new PropertyChangePanel(componentId,
CollectionUtils.newHashMap("Merge Strategy", oldStrategy.toString()),
CollectionUtils.newHashMap("Merge Strategy", newStrategy.toString()),
true);
}
@Override
public CommentAware getCommentAware() {
return null;
}
@Override
public ActivityDetail getActivityDetail() {
Map<String, String> oldProperties = new HashMap<>();
oldProperties.put("Merge Strategy", oldStrategy.name());
Map<String, String> newProperties = new HashMap<>();
oldProperties.put("Merge Strategy", newStrategy.name());
return ActivityDetail.compare(oldProperties, newProperties, true);
}
}

View File

@ -1,14 +1,15 @@
package io.onedev.server.model.support.pullrequest.changedata;
import org.apache.wicket.Component;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.CodeCommentManager;
import io.onedev.server.entityreference.ReferencedFromAware;
import io.onedev.server.model.CodeComment;
import io.onedev.server.model.PullRequestChange;
import io.onedev.server.notification.ActivityDetail;
import io.onedev.server.rest.annotation.EntityId;
import io.onedev.server.util.CommentAware;
import io.onedev.server.web.component.codecomment.referencedfrom.ReferencedFromCodeCommentPanel;
public class PullRequestReferencedFromCodeCommentData implements PullRequestChangeData {
public class PullRequestReferencedFromCodeCommentData
extends PullRequestChangeData implements ReferencedFromAware<CodeComment> {
private static final long serialVersionUID = 1L;
@ -23,11 +24,6 @@ public class PullRequestReferencedFromCodeCommentData implements PullRequestChan
return commentId;
}
@Override
public Component render(String componentId, PullRequestChange change) {
return new ReferencedFromCodeCommentPanel(componentId, commentId);
}
@Override
public String getActivity() {
return "Referenced from code comment";
@ -38,4 +34,14 @@ public class PullRequestReferencedFromCodeCommentData implements PullRequestChan
return null;
}
@Override
public CodeComment getReferencedFrom() {
return OneDev.getInstance(CodeCommentManager.class).get(commentId);
}
@Override
public ActivityDetail getActivityDetail() {
return ActivityDetail.referencedFrom(getReferencedFrom());
}
}

View File

@ -1,14 +1,15 @@
package io.onedev.server.model.support.pullrequest.changedata;
import org.apache.wicket.Component;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.IssueManager;
import io.onedev.server.entityreference.ReferencedFromAware;
import io.onedev.server.model.Issue;
import io.onedev.server.model.PullRequestChange;
import io.onedev.server.notification.ActivityDetail;
import io.onedev.server.rest.annotation.EntityId;
import io.onedev.server.util.CommentAware;
import io.onedev.server.web.component.issue.referencedfrom.ReferencedFromIssuePanel;
public class PullRequestReferencedFromIssueData implements PullRequestChangeData {
public class PullRequestReferencedFromIssueData
extends PullRequestChangeData implements ReferencedFromAware<Issue> {
private static final long serialVersionUID = 1L;
@ -23,11 +24,6 @@ public class PullRequestReferencedFromIssueData implements PullRequestChangeData
return issueId;
}
@Override
public Component render(String componentId, PullRequestChange change) {
return new ReferencedFromIssuePanel(componentId, issueId);
}
@Override
public String getActivity() {
return "Referenced from issue";
@ -38,4 +34,14 @@ public class PullRequestReferencedFromIssueData implements PullRequestChangeData
return null;
}
@Override
public Issue getReferencedFrom() {
return OneDev.getInstance(IssueManager.class).get(issueId);
}
@Override
public ActivityDetail getActivityDetail() {
return ActivityDetail.referencedFrom(getReferencedFrom());
}
}

View File

@ -1,14 +1,15 @@
package io.onedev.server.model.support.pullrequest.changedata;
import org.apache.wicket.Component;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.PullRequestManager;
import io.onedev.server.entityreference.ReferencedFromAware;
import io.onedev.server.model.PullRequest;
import io.onedev.server.model.PullRequestChange;
import io.onedev.server.notification.ActivityDetail;
import io.onedev.server.rest.annotation.EntityId;
import io.onedev.server.util.CommentAware;
import io.onedev.server.web.component.pullrequest.referencedfrom.ReferencedFromPullRequestPanel;
public class PullRequestReferencedFromPullRequestData implements PullRequestChangeData {
public class PullRequestReferencedFromPullRequestData
extends PullRequestChangeData implements ReferencedFromAware<PullRequest> {
private static final long serialVersionUID = 1L;
@ -23,11 +24,6 @@ public class PullRequestReferencedFromPullRequestData implements PullRequestChan
return requestId;
}
@Override
public Component render(String componentId, PullRequestChange change) {
return new ReferencedFromPullRequestPanel(componentId, requestId);
}
@Override
public String getActivity() {
return "Referenced from other pull request";
@ -38,4 +34,14 @@ public class PullRequestReferencedFromPullRequestData implements PullRequestChan
return null;
}
@Override
public PullRequest getReferencedFrom() {
return OneDev.getInstance(PullRequestManager.class).get(requestId);
}
@Override
public ActivityDetail getActivityDetail() {
return ActivityDetail.referencedFrom(getReferencedFrom());
}
}

View File

@ -2,14 +2,9 @@ package io.onedev.server.model.support.pullrequest.changedata;
import javax.annotation.Nullable;
import org.apache.wicket.Component;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.PullRequestChangeManager;
import io.onedev.server.model.PullRequestChange;
import io.onedev.server.util.CommentAware;
public class PullRequestReopenData implements PullRequestChangeData {
public class PullRequestReopenData extends PullRequestChangeData {
private static final long serialVersionUID = 1L;
@ -43,19 +38,4 @@ public class PullRequestReopenData implements PullRequestChangeData {
};
}
@Override
public Component render(String componentId, PullRequestChange change) {
Long changeId = change.getId();
return new PullRequestChangeCommentPanel(componentId) {
private static final long serialVersionUID = 1L;
@Override
protected PullRequestChange getChange() {
return OneDev.getInstance(PullRequestChangeManager.class).load(changeId);
}
};
}
}

View File

@ -2,14 +2,9 @@ package io.onedev.server.model.support.pullrequest.changedata;
import javax.annotation.Nullable;
import org.apache.wicket.Component;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.PullRequestChangeManager;
import io.onedev.server.model.PullRequestChange;
import io.onedev.server.util.CommentAware;
public class PullRequestRequestedForChangesData implements PullRequestChangeData {
public class PullRequestRequestedForChangesData extends PullRequestChangeData {
private static final long serialVersionUID = 1L;
@ -43,19 +38,4 @@ public class PullRequestRequestedForChangesData implements PullRequestChangeData
};
}
@Override
public Component render(String componentId, PullRequestChange change) {
Long changeId = change.getId();
return new PullRequestChangeCommentPanel(componentId) {
private static final long serialVersionUID = 1L;
@Override
protected PullRequestChange getChange() {
return OneDev.getInstance(PullRequestChangeManager.class).load(changeId);
}
};
}
}
}

View File

@ -1,11 +1,8 @@
package io.onedev.server.model.support.pullrequest.changedata;
import org.apache.wicket.Component;
import io.onedev.server.model.PullRequestChange;
import io.onedev.server.util.CommentAware;
public class PullRequestReviewerAddData implements PullRequestChangeData {
public class PullRequestReviewerAddData extends PullRequestChangeData {
private static final long serialVersionUID = 1L;
@ -25,9 +22,4 @@ public class PullRequestReviewerAddData implements PullRequestChangeData {
return null;
}
@Override
public Component render(String componentId, PullRequestChange change) {
return null;
}
}

View File

@ -1,11 +1,8 @@
package io.onedev.server.model.support.pullrequest.changedata;
import org.apache.wicket.Component;
import io.onedev.server.model.PullRequestChange;
import io.onedev.server.util.CommentAware;
public class PullRequestReviewerRemoveData implements PullRequestChangeData {
public class PullRequestReviewerRemoveData extends PullRequestChangeData {
private static final long serialVersionUID = 1L;
@ -25,9 +22,4 @@ public class PullRequestReviewerRemoveData implements PullRequestChangeData {
return null;
}
@Override
public Component render(String componentId, PullRequestChange change) {
return null;
}
}

View File

@ -2,14 +2,9 @@ package io.onedev.server.model.support.pullrequest.changedata;
import javax.annotation.Nullable;
import org.apache.wicket.Component;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.PullRequestChangeManager;
import io.onedev.server.model.PullRequestChange;
import io.onedev.server.util.CommentAware;
public class PullRequestSourceBranchDeleteData implements PullRequestChangeData {
public class PullRequestSourceBranchDeleteData extends PullRequestChangeData {
private static final long serialVersionUID = 1L;
@ -43,19 +38,4 @@ public class PullRequestSourceBranchDeleteData implements PullRequestChangeData
};
}
@Override
public Component render(String componentId, PullRequestChange change) {
Long changeId = change.getId();
return new PullRequestChangeCommentPanel(componentId) {
private static final long serialVersionUID = 1L;
@Override
protected PullRequestChange getChange() {
return OneDev.getInstance(PullRequestChangeManager.class).load(changeId);
}
};
}
}

View File

@ -2,14 +2,9 @@ package io.onedev.server.model.support.pullrequest.changedata;
import javax.annotation.Nullable;
import org.apache.wicket.Component;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.PullRequestChangeManager;
import io.onedev.server.model.PullRequestChange;
import io.onedev.server.util.CommentAware;
public class PullRequestSourceBranchRestoreData implements PullRequestChangeData {
public class PullRequestSourceBranchRestoreData extends PullRequestChangeData {
private static final long serialVersionUID = 1L;
@ -43,19 +38,4 @@ public class PullRequestSourceBranchRestoreData implements PullRequestChangeData
};
}
@Override
public Component render(String componentId, PullRequestChange change) {
Long changeId = change.getId();
return new PullRequestChangeCommentPanel(componentId) {
private static final long serialVersionUID = 1L;
@Override
protected PullRequestChange getChange() {
return OneDev.getInstance(PullRequestChangeManager.class).load(changeId);
}
};
}
}

View File

@ -1,13 +1,12 @@
package io.onedev.server.model.support.pullrequest.changedata;
import org.apache.wicket.Component;
import java.util.HashMap;
import java.util.Map;
import io.onedev.server.model.PullRequestChange;
import io.onedev.server.util.CollectionUtils;
import io.onedev.server.notification.ActivityDetail;
import io.onedev.server.util.CommentAware;
import io.onedev.server.web.component.propertychangepanel.PropertyChangePanel;
public class PullRequestTitleChangeData implements PullRequestChangeData {
public class PullRequestTitleChangeData extends PullRequestChangeData {
private static final long serialVersionUID = 1L;
@ -25,17 +24,19 @@ public class PullRequestTitleChangeData implements PullRequestChangeData {
return "changed title";
}
@Override
public Component render(String componentId, PullRequestChange change) {
return new PropertyChangePanel(componentId,
CollectionUtils.newHashMap("Title", oldTitle),
CollectionUtils.newHashMap("Title", newTitle),
true);
}
@Override
public CommentAware getCommentAware() {
return null;
}
@Override
public ActivityDetail getActivityDetail() {
Map<String, String> oldProperties = new HashMap<>();
oldProperties.put("Title", oldTitle);
Map<String, String> newProperties = new HashMap<>();
oldProperties.put("Title", newTitle);
return ActivityDetail.compare(oldProperties, newProperties, true);
}
}

View File

@ -13,17 +13,14 @@ import groovy.text.SimpleTemplateEngine;
import io.onedev.commons.utils.StringUtils;
import io.onedev.server.entitymanager.SettingManager;
import io.onedev.server.event.Event;
import io.onedev.server.event.MarkdownAware;
import io.onedev.server.event.ProjectEvent;
import io.onedev.server.event.entity.EntityPersisted;
import io.onedev.server.event.issue.IssueEvent;
import io.onedev.server.event.pullrequest.PullRequestEvent;
import io.onedev.server.markdown.MarkdownManager;
import io.onedev.server.model.AbstractEntity;
import io.onedev.server.model.Project;
import io.onedev.server.model.PullRequestAssignment;
import io.onedev.server.model.PullRequestReview;
import io.onedev.server.model.support.administration.notificationtemplate.NotificationTemplateSetting;
import io.onedev.server.util.markdown.MarkdownManager;
public abstract class AbstractNotificationManager {
@ -36,23 +33,14 @@ public abstract class AbstractNotificationManager {
this.settingManager = settingManager;
}
protected String getHtmlBody(Event event, String eventSummary, @Nullable String eventBody,
protected String getHtmlBody(Event event, @Nullable String eventSummary, @Nullable String eventBody,
String eventUrl, boolean replyable, @Nullable Unsubscribable unsubscribable) {
if (eventBody == null && event instanceof MarkdownAware) {
eventBody = ((MarkdownAware) event).getMarkdown();
if (eventBody != null) {
Project project = null;
if (event instanceof ProjectEvent)
project = ((ProjectEvent) event).getProject();
eventBody = markdownManager.process(markdownManager.render(eventBody), project, null, true);
}
}
String template = null;
Map<String, Object> bindings = new HashMap<>();
eventSummary = HtmlEscape.escapeHtml5(eventSummary);
if (eventSummary != null)
eventSummary = HtmlEscape.escapeHtml5(eventSummary);
eventUrl = HtmlEscape.escapeHtml5(eventUrl);
bindings.put("event", event);
@ -88,13 +76,11 @@ public abstract class AbstractNotificationManager {
}
}
protected String getTextBody(Event event, String eventSummary, @Nullable String eventBody,
protected String getTextBody(Event event, @Nullable String eventSummary, @Nullable String eventBody,
String eventUrl, boolean replyable, @Nullable Unsubscribable unsubscribable) {
StringBuilder textBody = new StringBuilder(eventSummary).append("\n\n");
if (eventBody == null && event instanceof MarkdownAware)
eventBody = ((MarkdownAware) event).getMarkdown();
StringBuilder textBody = new StringBuilder();
if (eventSummary != null)
textBody.append(eventSummary).append("\n\n");
if (eventBody != null)
textBody.append(eventBody).append("\n\n");
@ -104,8 +90,8 @@ public abstract class AbstractNotificationManager {
textBody.append("Visit " + eventUrl + " for details");
if (unsubscribable != null) {
textBody.append("\n\n---------------------------------------------\nYou received this as you "
+ "are participating or participated previously in this topic. ");
textBody.append("\n\n---------------------------------------------\nYou received this notification as you "
+ "are participating in this topic. ");
if (unsubscribable.getEmailAddress() != null) {
textBody.append(String.format("Mail to %s with any content to unsubscribe",
unsubscribable.getEmailAddress()));

View File

@ -0,0 +1,185 @@
package io.onedev.server.notification;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import org.apache.wicket.Component;
import org.apache.wicket.markup.html.basic.Label;
import org.unbescape.html.HtmlEscape;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.UrlManager;
import io.onedev.server.model.CodeComment;
import io.onedev.server.model.Issue;
import io.onedev.server.model.PullRequest;
import io.onedev.server.util.HtmlUtils;
import io.onedev.server.util.PropertyChange;
import io.onedev.server.web.component.codecomment.referencedfrom.ReferencedFromCodeCommentPanel;
import io.onedev.server.web.component.issue.referencedfrom.ReferencedFromIssuePanel;
import io.onedev.server.web.component.pullrequest.referencedfrom.ReferencedFromPullRequestPanel;
public class ActivityDetail implements Serializable {
private static final long serialVersionUID = 1L;
private final String htmlVersion;
private final String textVersion;
public ActivityDetail(String htmlVersion, String textVersion) {
this.htmlVersion = htmlVersion;
this.textVersion = textVersion;
}
public String getHtmlVersion() {
return htmlVersion;
}
public String getTextVersion() {
return textVersion;
}
public Component render(String componentId) {
return new Label(componentId, htmlVersion).setEscapeModelStrings(false);
}
private static String compareAsHtml(Map<String, String> oldProperties, Map<String, String> newProperties,
boolean hideNameIfOnlyOneRow) {
List<PropertyChange> changes = PropertyChange.listOf(oldProperties, newProperties);
StringBuilder builder = new StringBuilder();
builder.append("<table class='field-compare' style='border:1px solid #EBEDF3; border-collapse:collapse;'>");
builder.append(" <thead>");
builder.append(" <tr>");
if (changes.size() != 1 || !hideNameIfOnlyOneRow)
builder.append(" <th style='padding:0.4em 0.6em; border-bottom:1px solid #EBEDF3; font-size:0.9em; text-align:left;'>Name</th>");
builder.append(" <th style='padding:0.4em 0.6em; border-bottom:1px solid #EBEDF3; font-size:0.9em; text-align:left;'>Previous Value</th>");
builder.append(" <th style='padding:0.4em 0.6em; border-bottom:1px solid #EBEDF3; font-size:0.9em; text-align:left;'>Current Value</th>");
builder.append(" </tr>");
builder.append(" </thead>");
builder.append(" <tbody>");
for (PropertyChange change: changes) {
builder.append("<tr>");
if (changes.size() != 1 || !hideNameIfOnlyOneRow) {
builder.append(" <td style='padding:0.4em 0.6em; border-bottom:1px solid #EBEDF3; font-size:0.9em; text-align:left;'>");
builder.append(HtmlUtils.formatAsHtml(change.getName()));
builder.append(" </td>");
}
builder.append(" <td style='padding:0.4em 0.6em; border-bottom:1px solid #EBEDF3; font-size:0.9em; text-align:left;'>");
if (change.getOldValue() != null)
builder.append(HtmlUtils.formatAsHtml(change.getOldValue()));
else
builder.append("<i>empty</i>");
builder.append(" </td>");
builder.append(" <td style='padding:0.4em 0.6em; border-bottom:1px solid #EBEDF3; font-size:0.9em; text-align:left;'>");
if (change.getNewValue() != null)
builder.append(HtmlUtils.formatAsHtml(change.getNewValue()));
else
builder.append("<i>empty</i>");
builder.append(" </td>");
builder.append("</tr>");
}
builder.append(" </tbody>");
builder.append(" </table>");
return builder.toString();
}
private static String compareAsText(Map<String, String> oldProperties, Map<String, String> newProperties,
boolean hideNameIfOnlyOneRow) {
List<PropertyChange> changes = PropertyChange.listOf(oldProperties, newProperties);
StringBuilder builder = new StringBuilder();
for (PropertyChange change: changes) {
builder.append("----------------------------------------\n");
if (changes.size() != 1 || !hideNameIfOnlyOneRow) {
builder.append("Name: ").append(change.getName());
builder.append("\n");
}
builder.append("Previous Value: ");
if (change.getOldValue() != null)
builder.append(change.getOldValue());
else
builder.append("<empty>");
builder.append("\n");
builder.append("Current Value: ");
if (change.getNewValue() != null)
builder.append(change.getNewValue());
else
builder.append("<empty>");
builder.append("\n");
}
builder.append("----------------------------------------\n");
return builder.toString();
}
public static ActivityDetail compare(Map<String, String> oldProperties, Map<String, String> newProperties,
boolean hideNameIfOnlyOneRow) {
String htmlVersion = compareAsHtml(oldProperties, newProperties, hideNameIfOnlyOneRow);
String textVersion = compareAsText(oldProperties, newProperties, hideNameIfOnlyOneRow);
return new ActivityDetail(htmlVersion, textVersion);
}
public static ActivityDetail referencedFrom(CodeComment comment) {
String url = OneDev.getInstance(UrlManager.class).urlFor(comment, null);
String htmlVersion = String.format("<div><a href='%s'>%s</a></div>",
url, HtmlEscape.escapeHtml5(comment.getMark().getPath()));
String textVersion = comment.getMark().getPath() + "\n";
Long commentId = comment.getId();
return new ActivityDetail(htmlVersion, textVersion) {
private static final long serialVersionUID = 1L;
@Override
public Component render(String componentId) {
return new ReferencedFromCodeCommentPanel(componentId, commentId);
}
};
}
public static ActivityDetail referencedFrom(Issue issue) {
String url = OneDev.getInstance(UrlManager.class).urlFor(issue);
String htmlVersion = String.format("<div><a href='%s'>[%s] %s</a></div>",
url, issue.getFQN(), HtmlEscape.escapeHtml5(issue.getTitle()));
String textVersion = String.format("[%s] %s\n", issue.getFQN(), issue.getTitle());
Long issueId = issue.getId();
return new ActivityDetail(htmlVersion, textVersion) {
private static final long serialVersionUID = 1L;
@Override
public Component render(String componentId) {
return new ReferencedFromIssuePanel(componentId, issueId);
}
};
}
public static ActivityDetail referencedFrom(PullRequest request) {
String url = OneDev.getInstance(UrlManager.class).urlFor(request);
String htmlVersion = String.format("<div><a href='%s'>[%s] %s</a></div>",
url, request.getFQN(), HtmlEscape.escapeHtml5(request.getTitle()));
String textVersion = String.format("[%s] %s\n", request.getFQN(), request.getTitle());
Long requestId = request.getId();
return new ActivityDetail(htmlVersion, textVersion) {
private static final long serialVersionUID = 1L;
@Override
public Component render(String componentId) {
return new ReferencedFromPullRequestPanel(componentId, requestId);
}
};
}
}

View File

@ -20,6 +20,7 @@ import io.onedev.server.entitymanager.UrlManager;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.event.build.BuildEvent;
import io.onedev.server.event.build.BuildUpdated;
import io.onedev.server.markdown.MarkdownManager;
import io.onedev.server.model.Build;
import io.onedev.server.model.BuildQuerySetting;
import io.onedev.server.model.Project;
@ -27,7 +28,6 @@ import io.onedev.server.model.User;
import io.onedev.server.model.support.NamedQuery;
import io.onedev.server.persistence.annotation.Sessional;
import io.onedev.server.search.entity.build.BuildQuery;
import io.onedev.server.util.markdown.MarkdownManager;
@Singleton
public class BuildNotificationManager extends AbstractNotificationManager {
@ -64,22 +64,17 @@ public class BuildNotificationManager extends AbstractNotificationManager {
public void notify(BuildEvent event, Collection<String> emails) {
Build build = event.getBuild();
String subject = String.format("[%s] %s - %s", build.getStatus().getDisplayName(),
build.getProject().getName(), build.getJobName());
String subject = String.format("[Build %s] %s", build.getFQN(), build.getJobName());
String summary;
if (build.getVersion() != null)
summary = String.format("Build %s (%s)", build.getFQN(), build.getVersion());
else
summary = String.format("Build %s", build.getFQN());
summary += " is " + build.getStatus().getDisplayName().toLowerCase();
String summary = build.getStatus().getDisplayName();
if (build.getVersion() != null)
summary = build.getVersion() + " " + summary;
String url = urlManager.urlFor(build);
String threadingReferences = build.getProject().getName() + "-build" + build.getNumber() + "@onedev";
String threadingReferences = "<" + build.getProject().getName() + "-build-" + build.getNumber() + "@onedev>";
String htmlBody = getHtmlBody(event, summary, null, url, false, null);
String textBody = getTextBody(event, summary, null, url, false, null);
mailManager.sendMailAsync(Lists.newArrayList(), emails, subject, htmlBody,
mailManager.sendMailAsync(Lists.newArrayList(), Lists.newArrayList(), emails, subject, htmlBody,
textBody, null, threadingReferences);
}

View File

@ -10,14 +10,13 @@ import io.onedev.commons.launcher.loader.Listen;
import io.onedev.server.entitymanager.SettingManager;
import io.onedev.server.entitymanager.UrlManager;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.event.MarkdownAware;
import io.onedev.server.event.codecomment.CodeCommentCreated;
import io.onedev.server.event.codecomment.CodeCommentEvent;
import io.onedev.server.event.codecomment.CodeCommentReplied;
import io.onedev.server.markdown.MarkdownManager;
import io.onedev.server.markdown.MentionParser;
import io.onedev.server.model.User;
import io.onedev.server.persistence.annotation.Transactional;
import io.onedev.server.util.markdown.MarkdownManager;
import io.onedev.server.util.markdown.MentionParser;
@Singleton
public class CodeCommentNotificationManager extends AbstractNotificationManager {
@ -41,11 +40,11 @@ public class CodeCommentNotificationManager extends AbstractNotificationManager
@Listen
public void on(CodeCommentEvent event) {
if (event.getComment().getRequest() == null) {
MarkdownAware markdownAware = (MarkdownAware) event;
String markdown = markdownAware.getMarkdown();
String rendered = markdownManager.render(markdown);
String markdown = event.getMarkdown();
String renderedMarkdown = markdownManager.render(markdown);
String processedMarkdown = markdownManager.process(renderedMarkdown, event.getProject(), null, true);
for (String userName: new MentionParser().parseMentions(rendered)) {
for (String userName: new MentionParser().parseMentions(renderedMarkdown)) {
User user = userManager.findByName(userName);
if (user != null) {
String url;
@ -57,14 +56,17 @@ public class CodeCommentNotificationManager extends AbstractNotificationManager
url = null;
if (url != null) {
String subject = "[File Comment] " + event.getComment().getMark().getPath();
String summary = String.format("%s: %s commented on file",
event.getProject().getName(), event.getUser().getDisplayName());
String threadingReferences = event.getComment().getProject().getName()
+ "-codecomment" + event.getComment().getId() + "@onedev";
mailManager.sendMailAsync(Sets.newHashSet(user.getEmail()), Lists.newArrayList(), subject,
getHtmlBody(event, summary, null, url, false, null),
getTextBody(event, summary, null, url, false, null),
String subject = String.format("[Code Comment] (Mentioned You) %s:%s",
event.getProject().getName(), event.getComment().getMark().getPath());
String summary = String.format("%s added code comment",
event.getUser().getDisplayName());
String threadingReferences = "<" + event.getComment().getProject().getName()
+ "-codecomment-" + event.getComment().getId() + "@onedev>";
mailManager.sendMailAsync(Sets.newHashSet(user.getEmail()), Lists.newArrayList(),
Lists.newArrayList(), subject,
getHtmlBody(event, summary, processedMarkdown, url, false, null),
getTextBody(event, summary, markdown, url, false, null),
null, threadingReferences);
}
}

View File

@ -22,13 +22,13 @@ import io.onedev.server.entitymanager.SettingManager;
import io.onedev.server.entitymanager.UrlManager;
import io.onedev.server.event.RefUpdated;
import io.onedev.server.git.GitUtils;
import io.onedev.server.markdown.MarkdownManager;
import io.onedev.server.model.CommitQuerySetting;
import io.onedev.server.model.Project;
import io.onedev.server.model.User;
import io.onedev.server.model.support.NamedQuery;
import io.onedev.server.persistence.annotation.Sessional;
import io.onedev.server.search.commit.CommitQuery;
import io.onedev.server.util.markdown.MarkdownManager;
@Singleton
public class CommitNotificationManager extends AbstractNotificationManager {
@ -99,33 +99,30 @@ public class CommitNotificationManager extends AbstractNotificationManager {
RevCommit commit = project.getRevCommit(event.getNewCommitId(), false);
if (commit != null) {
String subject;
String branchName = GitUtils.ref2branch(event.getRefName());
if (branchName != null) {
subject = String.format("[%s] %s", branchName, commit.getShortMessage());
} else {
String tagName = GitUtils.ref2tag(event.getRefName());
if (tagName != null) {
subject = String.format("[%s] %s", tagName, commit.getShortMessage());
} else {
subject = String.format("[%s] %s", event.getRefName(), commit.getShortMessage());
}
String target = GitUtils.ref2branch(event.getRefName());
if (target == null) {
target = GitUtils.ref2tag(event.getRefName());
if (target == null)
target = event.getRefName();
}
String subject = String.format("[Commit %s:%s] (%s) %s",
project.getName(), GitUtils.abbreviateSHA(commit.name()), target, commit.getShortMessage());
String url = urlManager.urlFor(project, commit);
String summary = String.format("Commit %s:%s - Authored by %s", project.getName(),
GitUtils.abbreviateSHA(commit.name()), commit.getAuthorIdent().getName());
String summary = String.format("Authored by %s", commit.getAuthorIdent().getName());
String textMessage = GitUtils.getDetailMessage(commit);
String htmlMessage = null;
if (textMessage != null)
htmlMessage = "<pre>" + HtmlEscape.escapeHtml5(textMessage) + "</pre>";
mailManager.sendMailAsync(Lists.newArrayList(), notifyEmails, subject,
String threadingReferences = "<commit-" + commit.name() + "@onedev>";
mailManager.sendMailAsync(Lists.newArrayList(), Lists.newArrayList(), notifyEmails, subject,
getHtmlBody(event, summary, htmlMessage, url, false, null),
getTextBody(event, summary, textMessage, url, false, null),
null, null);
null, threadingReferences);
}
}
}

View File

@ -15,12 +15,14 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.mail.Folder;
import javax.mail.FolderClosedException;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
@ -44,6 +46,10 @@ import org.apache.shiro.authz.UnauthorizedException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.nodes.TextNode;
import org.jsoup.select.NodeTraversor;
import org.jsoup.select.NodeVisitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -65,6 +71,7 @@ import io.onedev.server.entitymanager.PullRequestCommentManager;
import io.onedev.server.entitymanager.PullRequestManager;
import io.onedev.server.entitymanager.PullRequestWatchManager;
import io.onedev.server.entitymanager.SettingManager;
import io.onedev.server.entitymanager.UrlManager;
import io.onedev.server.entitymanager.UserAuthorizationManager;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.event.entity.EntityPersisted;
@ -81,9 +88,7 @@ import io.onedev.server.model.Role;
import io.onedev.server.model.Setting;
import io.onedev.server.model.User;
import io.onedev.server.model.UserAuthorization;
import io.onedev.server.model.support.administration.DefaultProjectDesignation;
import io.onedev.server.model.support.administration.GlobalIssueSetting;
import io.onedev.server.model.support.administration.IssueCreationSetting;
import io.onedev.server.model.support.administration.MailSetting;
import io.onedev.server.model.support.administration.ReceiveMailSetting;
import io.onedev.server.model.support.administration.SenderAuthorization;
@ -96,6 +101,7 @@ import io.onedev.server.security.permission.AccessProject;
import io.onedev.server.security.permission.ProjectPermission;
import io.onedev.server.security.permission.ReadCode;
import io.onedev.server.util.EmailAddress;
import io.onedev.server.util.HtmlUtils;
import io.onedev.server.util.validation.UserNameValidator;
@Singleton
@ -127,6 +133,8 @@ public class DefaultMailManager implements MailManager {
private final UserManager userManager;
private final UrlManager urlManager;
private volatile boolean stopping;
private volatile Thread thread;
@ -137,7 +145,8 @@ public class DefaultMailManager implements MailManager {
UserAuthorizationManager authorizationManager, IssueManager issueManager,
IssueCommentManager issueCommentManager, IssueWatchManager issueWatchManager,
PullRequestManager pullRequestManager, PullRequestCommentManager pullRequestCommentManager,
PullRequestWatchManager pullRequestWatchManager, ExecutorService executorService) {
PullRequestWatchManager pullRequestWatchManager, ExecutorService executorService,
UrlManager urlManager) {
this.transactionManager = transactionManager;
this.settingManager = setingManager;
this.userManager = userManager;
@ -150,12 +159,13 @@ public class DefaultMailManager implements MailManager {
this.pullRequestCommentManager = pullRequestCommentManager;
this.pullRequestWatchManager = pullRequestWatchManager;
this.executorService = executorService;
this.urlManager = urlManager;
}
@Sessional
@Override
public void sendMailAsync(Collection<String> toList, Collection<String> ccList, String subject,
String htmlBody, String textBody, String replyAddress, String references) {
public void sendMailAsync(Collection<String> toList, Collection<String> ccList, Collection<String> bccList,
String subject, String htmlBody, String textBody, String replyAddress, String references) {
transactionManager.runAfterCommit(new Runnable() {
@Override
@ -165,7 +175,7 @@ public class DefaultMailManager implements MailManager {
@Override
public void run() {
try {
sendMail(toList, ccList, subject, htmlBody, textBody, replyAddress, references);
sendMail(toList, ccList, bccList, subject, htmlBody, textBody, replyAddress, references);
} catch (Exception e) {
logger.error("Error sending email (to: " + toList + ", subject: " + subject + ")", e);
}
@ -194,8 +204,9 @@ public class DefaultMailManager implements MailManager {
@Override
public void sendMail(MailSetting mailSetting, Collection<String> toList, Collection<String> ccList,
String subject, String htmlBody, String textBody, String replyAddress, String references) {
if (toList.isEmpty() && ccList.isEmpty())
Collection<String> bccList, String subject, String htmlBody, String textBody,
String replyAddress, String references) {
if (toList.isEmpty() && ccList.isEmpty() && bccList.isEmpty())
return;
if (mailSetting == null)
@ -232,6 +243,8 @@ public class DefaultMailManager implements MailManager {
email.addTo(address);
for (String address: ccList)
email.addCc(address);
for (String address: bccList)
email.addBcc(address);
email.setHostName(mailSetting.getSmtpHost());
email.setSmtpPort(mailSetting.getSmtpPort());
@ -254,9 +267,9 @@ public class DefaultMailManager implements MailManager {
}
@Override
public void sendMail(Collection<String> toList, Collection<String> ccList, String subject,
String htmlBody, String textBody, String replyAddress, String references) {
sendMail(settingManager.getMailSetting(), toList, ccList, subject, htmlBody,
public void sendMail(Collection<String> toList, Collection<String> ccList, Collection<String> bccList,
String subject, String htmlBody, String textBody, String replyAddress, String references) {
sendMail(settingManager.getMailSetting(), toList, ccList, bccList, subject, htmlBody,
textBody, replyAddress, references);
}
@ -305,10 +318,13 @@ public class DefaultMailManager implements MailManager {
User user = userManager.findByEmail(from.getAddress());
SenderAuthorization authorization = null;
String designatedProject = null;
ServiceDeskSetting serviceDeskSetting = settingManager.getServiceDeskSetting();
SenderAuthorization authorization = serviceDeskSetting.getSenderAuthorization(from.getAddress());
DefaultProjectDesignation designation = serviceDeskSetting.getDefaultProjectDesignation(from.getAddress());
if (serviceDeskSetting != null) {
authorization = serviceDeskSetting.getSenderAuthorization(from.getAddress());
designatedProject = serviceDeskSetting.getDesignatedProject(from.getAddress());
}
EmailAddress systemAddress = EmailAddress.parse(mailSetting.getEmailAddress());
Collection<Issue> issues = new ArrayList<>();
@ -327,18 +343,19 @@ public class DefaultMailManager implements MailManager {
for (InternetAddress receiver: receivers) {
EmailAddress receiverAddress = EmailAddress.parse(receiver.getAddress());
if (receiverAddress.toString().equals(systemAddress.toString())) {
if (designation == null)
throw new ExplicitException("No default project for sender: " + from.getAddress());
String projectName = designation.getDefaultProject();
Project project = projectManager.find(projectName);
if (project == null) {
String errorMessage = String.format(
"Default project does not exist (sender: %s, project: %s)",
from.getAddress(), projectName);
throw new ExplicitException(errorMessage);
if (serviceDeskSetting != null) {
Project project = projectManager.find(designatedProject);
if (project == null) {
String errorMessage = String.format(
"Sender project does not exist (sender: %s, project: %s)",
from.getAddress(), designatedProject);
throw new ExplicitException(errorMessage);
}
checkPermission(from, project, new AccessProject(), user, authorization);
issues.add(openIssue(message, project, from, user, authorization));
} else {
throw new ExplicitException("Unable to create issue from email as service desk is not enabled");
}
checkPermission(from, project, new AccessProject(), user, authorization);
issues.add(openIssue(message, project, from, user, authorization));
} else if (receiverAddress.getDomain().equals(systemAddress.getDomain())
&& receiverAddress.getPrefix().startsWith(systemAddress.getPrefix() + "+")) {
String subAddress = receiverAddress.getPrefix().substring(systemAddress.getPrefix().length()+1);
@ -351,8 +368,12 @@ public class DefaultMailManager implements MailManager {
String remaining = StringUtils.substringAfter(subAddress, "~");
if (remaining.length() == 0) {
checkPermission(from, project, new AccessProject(), user, authorization);
issues.add(openIssue(message, project, from, user, authorization));
if (serviceDeskSetting != null) {
checkPermission(from, project, new AccessProject(), user, authorization);
issues.add(openIssue(message, project, from, user, authorization));
} else {
throw new ExplicitException("Unable to create issue from email as service desk is not enabled");
}
} else if (remaining.startsWith("issue")) {
remaining = remaining.substring("issue".length());
Long issueNumber;
@ -375,7 +396,8 @@ public class DefaultMailManager implements MailManager {
+ "However if you subscribed to certain issue queries, you may still get notifications of newly "
+ "created issues matching those queries. In this case, you will need to login to your account "
+ "and unsubscribe those queries.";
sendMailAsync(Lists.newArrayList(from.getAddress()), Lists.newArrayList(), subject, body, body, null, getMessageId(message));
sendMailAsync(Lists.newArrayList(from.getAddress()), Lists.newArrayList(), Lists.newArrayList(),
subject, body, body, null, getMessageId(message));
}
}
} else {
@ -406,7 +428,8 @@ public class DefaultMailManager implements MailManager {
+ " unless mentioned. However if you subscribed to certain pull request queries, you may still "
+ "get notifications of newly submitted pull request matching those queries. In this case, you "
+ "will need to login to your account and unsubscribe those queries.";
sendMailAsync(Lists.newArrayList(from.getAddress()), Lists.newArrayList(), subject, body, body, null, getMessageId(message));
sendMailAsync(Lists.newArrayList(from.getAddress()), Lists.newArrayList(), Lists.newArrayList(),
subject, body, body, null, getMessageId(message));
}
}
} else {
@ -425,7 +448,8 @@ public class DefaultMailManager implements MailManager {
for (Issue issue: issues) {
for (InternetAddress each: involved) {
user = userManager.findByEmail(each.getAddress());
authorization = serviceDeskSetting.getSenderAuthorization(each.getAddress());
if (serviceDeskSetting != null)
authorization = serviceDeskSetting.getSenderAuthorization(each.getAddress());
try {
checkPermission(each, issue.getProject(), new AccessProject(), user, authorization);
if (user == null)
@ -439,7 +463,8 @@ public class DefaultMailManager implements MailManager {
for (PullRequest pullRequest: pullRequests) {
for (InternetAddress each: involved) {
user = userManager.findByEmail(each.getAddress());
authorization = serviceDeskSetting.getSenderAuthorization(each.getAddress());
if (serviceDeskSetting != null)
authorization = serviceDeskSetting.getSenderAuthorization(each.getAddress());
try {
checkPermission(each, pullRequest.getProject(), new ReadCode(), user, authorization);
if (user == null)
@ -456,6 +481,73 @@ public class DefaultMailManager implements MailManager {
}
}
private void removeNodesAfter(Node node) {
Node current = node;
while (current != null) {
Node nextSibling = current.nextSibling();
while (nextSibling != null) {
Node temp = nextSibling.nextSibling();
nextSibling.remove();
nextSibling = temp;
}
current = current.parent();
}
}
@Nullable
private String stripQuotation(String content) {
String quotedSender = settingManager.getMailSetting().getEmailAddress();
Pattern pattern = Pattern.compile("(^|\\W)" + quotedSender.replace(".", "\\.") + "($|\\W)");
Document document = HtmlUtils.parse(content);
Element quotedSenderElement = null;
for (Element element: document.getElementsContainingOwnText(quotedSender)) {
if (pattern.matcher(element.text()).find()) {
quotedSenderElement = element;
break;
}
}
if (quotedSenderElement != null) {
Element quotedSenderBlockElement = quotedSenderElement.parent();
while (quotedSenderBlockElement != null
&& !quotedSenderBlockElement.tagName().equals("div")
&& !quotedSenderBlockElement.tagName().equals("p")) {
quotedSenderBlockElement = quotedSenderBlockElement.parent();
}
if (quotedSenderBlockElement != null) {
removeNodesAfter(quotedSenderBlockElement);
quotedSenderBlockElement.remove();
}
}
AtomicReference<Node> lastContentNodeRef = new AtomicReference<>(null);
new NodeTraversor(new NodeVisitor() {
@Override
public void tail(Node node, int depth) {
if (node instanceof Element && ((Element) node).tagName().equals("img")
|| node instanceof TextNode && StringUtils.isNotBlank(((TextNode) node).getWholeText())) {
lastContentNodeRef.set(node);
}
}
@Override
public void head(Node node, int depth) {
}
}).traverse(document);
Node lastContentNode = lastContentNodeRef.get();
if (lastContentNode != null) {
removeNodesAfter(lastContentNode);
return document.body().html();
} else {
return null;
}
}
private void addComment(Issue issue, Message message, InternetAddress author,
Collection<String> receiverEmailAddresses, @Nullable User user,
@Nullable SenderAuthorization authorization) throws IOException, MessagingException {
@ -464,8 +556,8 @@ public class DefaultMailManager implements MailManager {
if (user == null)
user = createUserIfNotExist(author, issue.getProject(), authorization.getAuthorizedRole());
comment.setUser(user);
String content = readText(issue.getProject(), issue.getUUID(), message);
if (StringUtils.isNotBlank(content)) {
String content = stripQuotation(readText(issue.getProject(), issue.getUUID(), message));
if (content != null) {
comment.setContent(content);
issueCommentManager.save(comment, receiverEmailAddresses);
}
@ -479,8 +571,8 @@ public class DefaultMailManager implements MailManager {
if (user == null)
user = createUserIfNotExist(author, pullRequest.getProject(), authorization.getAuthorizedRole());
comment.setUser(user);
String content = readText(pullRequest.getProject(), pullRequest.getUUID(), message);
if (StringUtils.isNotBlank(content)) {
String content = stripQuotation(readText(pullRequest.getProject(), pullRequest.getUUID(), message));
if (content != null) {
comment.setContent(content);
pullRequestCommentManager.save(comment, receiverEmailAddresses);
}
@ -519,15 +611,24 @@ public class DefaultMailManager implements MailManager {
GlobalIssueSetting issueSetting = settingManager.getIssueSetting();
issue.setState(issueSetting.getInitialStateSpec().getName());
IssueCreationSetting issueCreationSetting = settingManager.getServiceDeskSetting()
List<FieldSupply> issueFields = settingManager.getServiceDeskSetting()
.getIssueCreationSetting(submitter.getAddress(), project);
for (FieldSupply supply: issueCreationSetting.getIssueFields()) {
for (FieldSupply supply: issueFields) {
Object fieldValue = issueSetting.getFieldSpec(supply.getName())
.convertToObject(supply.getValueProvider().getValue());
issue.setFieldValue(supply.getName(), fieldValue);
}
issueManager.open(issue);
String htmlBody = String.format("Issue <a href='%s'>%s</a> is created. You may reply this email to add more comments",
urlManager.urlFor(issue), issue.getFQN());
String textBody = String.format("Issue %s is created. You may reply this email to add more comments",
issue.getFQN());
sendMailAsync(Lists.newArrayList(submitter.getAddress()), Lists.newArrayList(), Lists.newArrayList(),
"Re: " + issue.getTitle(), htmlBody, textBody, getReplyAddress(issue),
issue.getEffectiveThreadingReference());
return issue;
}
@ -538,7 +639,7 @@ public class DefaultMailManager implements MailManager {
user.setName(UserNameValidator.suggestUserName(EmailAddress.parse(address.getAddress()).getPrefix()));
user.setEmail(address.getAddress());
user.setFullName(address.getPersonal());
user.setPassword("12345");
user.setPassword("impossible password");
userManager.save(user);
}
@ -712,8 +813,13 @@ public class DefaultMailManager implements MailManager {
@Override
public void run() {
try {
while (!stopping.get())
inboxRef.get().idle();
while (!stopping.get()) {
try {
inboxRef.get().idle();
} catch (FolderClosedException e) {
Thread.sleep(1000);
}
}
} catch (Exception e) {
exception.set(ExceptionUtils.unchecked(e));
} finally {

View File

@ -5,7 +5,6 @@ import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.inject.Inject;
@ -19,28 +18,23 @@ import io.onedev.server.entitymanager.IssueWatchManager;
import io.onedev.server.entitymanager.SettingManager;
import io.onedev.server.entitymanager.UrlManager;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.event.MarkdownAware;
import io.onedev.server.entityreference.ReferencedFromAware;
import io.onedev.server.event.issue.IssueChangeEvent;
import io.onedev.server.event.issue.IssueCommented;
import io.onedev.server.event.issue.IssueEvent;
import io.onedev.server.infomanager.UserInfoManager;
import io.onedev.server.markdown.MarkdownManager;
import io.onedev.server.markdown.MentionParser;
import io.onedev.server.model.Group;
import io.onedev.server.model.Issue;
import io.onedev.server.model.IssueWatch;
import io.onedev.server.model.User;
import io.onedev.server.model.support.NamedQuery;
import io.onedev.server.model.support.QuerySetting;
import io.onedev.server.model.support.issue.changedata.IssueChangeData;
import io.onedev.server.model.support.issue.changedata.IssueDescriptionChangeData;
import io.onedev.server.model.support.issue.changedata.IssueReferencedFromCodeCommentData;
import io.onedev.server.model.support.issue.changedata.IssueReferencedFromIssueData;
import io.onedev.server.model.support.issue.changedata.IssueReferencedFromPullRequestData;
import io.onedev.server.persistence.annotation.Transactional;
import io.onedev.server.search.entity.EntityQuery;
import io.onedev.server.search.entity.QueryWatchBuilder;
import io.onedev.server.search.entity.issue.IssueQuery;
import io.onedev.server.util.markdown.MarkdownManager;
import io.onedev.server.util.markdown.MentionParser;
@Singleton
public class IssueNotificationManager extends AbstractNotificationManager {
@ -73,8 +67,6 @@ public class IssueNotificationManager extends AbstractNotificationManager {
Issue issue = event.getIssue();
User user = event.getUser();
String subject = String.format("[%s] %s", issue.getState(), issue.getTitle());
String url;
if (event instanceof IssueCommented)
url = urlManager.urlFor(((IssueCommented)event).getComment());
@ -82,7 +74,13 @@ public class IssueNotificationManager extends AbstractNotificationManager {
url = urlManager.urlFor(((IssueChangeEvent)event).getChange());
else
url = urlManager.urlFor(issue);
String summary = "[" + issue.getState() + "] ";
if (user != null)
summary = summary + user.getDisplayName() + " " + event.getActivity();
else
summary = summary + event.getActivity();
for (Map.Entry<User, Boolean> entry: new QueryWatchBuilder<Issue>() {
@Override
@ -141,113 +139,102 @@ public class IssueNotificationManager extends AbstractNotificationManager {
if (!user.isSystem())
issueWatchManager.watch(issue, user, true);
}
Map<String, Group> newGroups = event.getNewGroups();
Map<String, Collection<User>> newUsers = event.getNewUsers();
String replyAddress = mailManager.getReplyAddress(issue);
boolean replyable = replyAddress != null;
String threadingReferences = issue.getThreadingReference();
if (threadingReferences == null)
threadingReferences = "<" + issue.getUUID() + "@onedev>";
for (Map.Entry<String, Group> entry: newGroups.entrySet()) {
String summary = String.format("Issue %s: %s: You", issue.getFQN(), entry.getKey());
Set<String> emails = entry.getValue().getMembers()
.stream()
.filter(it->!it.equals(user))
.map(it->it.getEmail())
.collect(Collectors.toSet());
mailManager.sendMailAsync(emails, Lists.newArrayList(), subject,
getHtmlBody(event, summary, null, url, replyable, null),
getTextBody(event, summary, null, url, replyable, null),
replyAddress, threadingReferences);
String subject = String.format("[Issue %s] (%s: You) %s", issue.getFQN(), entry.getKey(), issue.getTitle());
String threadingReferences = String.format("<you-in-field-%s-%s@onedev>", entry.getKey(), issue.getUUID());
for (User member: entry.getValue().getMembers()) {
if (!member.equals(user)) {
mailManager.sendMailAsync(Sets.newHashSet(member.getEmail()),
Lists.newArrayList(), Lists.newArrayList(), subject,
getHtmlBody(event, summary, event.getHtmlBody(), url, replyable, null),
getTextBody(event, summary, event.getTextBody(), url, replyable, null),
replyAddress, threadingReferences);
}
}
for (User member: entry.getValue().getMembers())
issueWatchManager.watch(issue, member, true);
notifiedUsers.addAll(entry.getValue().getMembers());
}
for (Map.Entry<String, Collection<User>> entry: newUsers.entrySet()) {
String summary = String.format("Issue %s: %s: You", issue.getFQN(), entry.getKey());
Set<String> emails = entry.getValue()
.stream()
.filter(it->!it.equals(user))
.map(it->it.getEmail())
.collect(Collectors.toSet());
mailManager.sendMailAsync(emails, Lists.newArrayList(), subject,
getHtmlBody(event, summary, null, url, replyable, null),
getTextBody(event, summary, null, url, replyable, null),
replyAddress, threadingReferences);
String subject = String.format("[Issue %s] (%s: You) %s", issue.getFQN(), entry.getKey(), issue.getTitle());
String threadingReferences = String.format("<you-in-field-%s-%s@onedev>", entry.getKey(), issue.getUUID());
for (User member: entry.getValue()) {
if (!member.equals(user)) {
mailManager.sendMailAsync(Sets.newHashSet(member.getEmail()),
Lists.newArrayList(), Lists.newArrayList(), subject,
getHtmlBody(event, summary, event.getHtmlBody(), url, replyable, null),
getTextBody(event, summary, event.getTextBody(), url, replyable, null),
replyAddress, threadingReferences);
}
}
for (User each: entry.getValue())
issueWatchManager.watch(issue, each, true);
notifiedUsers.addAll(entry.getValue());
}
Collection<User> mentionedUsers = new HashSet<>();
if (event instanceof MarkdownAware) {
MarkdownAware markdownAware = (MarkdownAware) event;
String markdown = markdownAware.getMarkdown();
if (markdown != null) {
String rendered = markdownManager.render(markdown);
for (String userName: new MentionParser().parseMentions(rendered)) {
User mentionedUser = userManager.findByName(userName);
if (mentionedUser != null && notifiedUsers.add(mentionedUser)) {
issueWatchManager.watch(issue, mentionedUser, true);
mentionedUsers.add(mentionedUser);
Collection<String> notifiedEmailAddresses;
if (event instanceof IssueCommented)
notifiedEmailAddresses = ((IssueCommented) event).getNotifiedEmailAddresses();
else
notifiedEmailAddresses = new ArrayList<>();
if (event.getRenderedMarkdown() != null) {
for (String userName: new MentionParser().parseMentions(event.getRenderedMarkdown())) {
User mentionedUser = userManager.findByName(userName);
if (mentionedUser != null) {
issueWatchManager.watch(issue, mentionedUser, true);
if (!notifiedEmailAddresses.stream().anyMatch(mentionedUser.getEmails()::contains)) {
String subject = String.format("[Issue %s] (Mentioned You) %s", issue.getFQN(), issue.getTitle());
String threadingReferences = String.format("<mentioned-%s@onedev>", issue.getUUID());
mailManager.sendMailAsync(Sets.newHashSet(mentionedUser.getEmail()),
Sets.newHashSet(), Sets.newHashSet(), subject,
getHtmlBody(event, summary, event.getHtmlBody(), url, replyable, null),
getTextBody(event, summary, event.getTextBody(), url, replyable, null),
replyAddress, threadingReferences);
notifiedUsers.add(mentionedUser);
}
}
}
}
boolean notifyWatchers = false;
if (event instanceof IssueChangeEvent) {
IssueChangeData changeData = ((IssueChangeEvent) event).getChange().getData();
if (!(changeData instanceof IssueReferencedFromCodeCommentData
|| changeData instanceof IssueReferencedFromIssueData
|| changeData instanceof IssueReferencedFromPullRequestData
|| changeData instanceof IssueDescriptionChangeData)) {
notifyWatchers = true;
}
} else {
notifyWatchers = true;
}
if (!mentionedUsers.isEmpty() || notifyWatchers) {
Collection<User> ccUsers = new HashSet<>();
if (!(event instanceof IssueChangeEvent)
|| !(((IssueChangeEvent) event).getChange().getData() instanceof ReferencedFromAware)) {
Collection<User> bccUsers = new HashSet<>();
Collection<String> notifiedEmailAddresses;
if (event instanceof IssueCommented)
notifiedEmailAddresses = ((IssueCommented) event).getNotifiedEmailAddresses();
else
notifiedEmailAddresses = new ArrayList<>();
for (IssueWatch watch: issue.getWatches()) {
Date visitDate = userInfoManager.getIssueVisitDate(watch.getUser(), issue);
if (watch.isWatching()
&& (visitDate == null || visitDate.before(event.getDate()))
&& !notifiedUsers.contains(watch.getUser())
&& !notifiedEmailAddresses.stream().anyMatch(watch.getUser().getEmails()::contains)) {
ccUsers.add(watch.getUser());
bccUsers.add(watch.getUser());
}
}
if (!mentionedUsers.isEmpty() || !ccUsers.isEmpty()) {
String summary;
if (user != null)
summary = String.format("Issue %s: %s %s", issue.getFQN(), user.getDisplayName(), event.getActivity());
else
summary = "Issue " + issue.getFQN() + ": " + event.getActivity();
if (!bccUsers.isEmpty()) {
String subject = String.format("[Issue %s] (Updated) %s", issue.getFQN(), issue.getTitle());
Unsubscribable unsubscribable = new Unsubscribable(mailManager.getUnsubscribeAddress(issue));
String htmlBody = getHtmlBody(event, summary, null, url, replyable, unsubscribable);
String textBody = getTextBody(event, summary, null, url, replyable, unsubscribable);
mailManager.sendMailAsync(
mentionedUsers.stream().map(User::getEmail).collect(Collectors.toList()),
ccUsers.stream().map(User::getEmail).collect(Collectors.toList()),
String htmlBody = getHtmlBody(event, summary, event.getHtmlBody(), url, replyable, unsubscribable);
String textBody = getTextBody(event, summary, event.getTextBody(), url, replyable, unsubscribable);
String threadingReferences = issue.getEffectiveThreadingReference();
mailManager.sendMailAsync(Sets.newHashSet(), Sets.newHashSet(),
bccUsers.stream().map(User::getEmail).collect(Collectors.toList()),
subject, htmlBody, textBody, replyAddress, threadingReferences);
}
}
}
}

View File

@ -12,16 +12,18 @@ public interface MailManager {
public static final String TEST_SUB_ADDRESSING = "test-sub-addressing";
void sendMail(Collection<String> toList, Collection<String> ccList, String subject,
String htmlBody, String textBody, @Nullable String replyAddress, @Nullable String references);
void sendMail(Collection<String> toList, Collection<String> ccList,
Collection<String> bccList, String subject, String htmlBody,
String textBody, @Nullable String replyAddress, @Nullable String references);
void sendMail(MailSetting mailSetting, Collection<String> toList, Collection<String> ccList,
Collection<String> bccList, String subject, String htmlBody, String textBody,
@Nullable String replyAddress, @Nullable String references);
void sendMailAsync(Collection<String> toList, Collection<String> ccList, Collection<String> bccList,
String subject, String htmlBody, String textBody, @Nullable String replyAddress,
@Nullable String references);
void sendMailAsync(Collection<String> toList, Collection<String> ccList, String subject,
String htmlBody, String textBody, @Nullable String replyAddress, @Nullable String references);
@Nullable
String getReplyAddress(Issue issue);

View File

@ -10,6 +10,8 @@ import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.commons.text.WordUtils;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
@ -18,7 +20,6 @@ import io.onedev.server.entitymanager.PullRequestWatchManager;
import io.onedev.server.entitymanager.SettingManager;
import io.onedev.server.entitymanager.UrlManager;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.event.MarkdownAware;
import io.onedev.server.event.entity.EntityPersisted;
import io.onedev.server.event.pullrequest.PullRequestBuildEvent;
import io.onedev.server.event.pullrequest.PullRequestChangeEvent;
@ -30,6 +31,8 @@ import io.onedev.server.event.pullrequest.PullRequestMergePreviewCalculated;
import io.onedev.server.event.pullrequest.PullRequestOpened;
import io.onedev.server.event.pullrequest.PullRequestUpdated;
import io.onedev.server.infomanager.UserInfoManager;
import io.onedev.server.markdown.MarkdownManager;
import io.onedev.server.markdown.MentionParser;
import io.onedev.server.model.PullRequest;
import io.onedev.server.model.PullRequestAssignment;
import io.onedev.server.model.PullRequestReview;
@ -48,8 +51,6 @@ import io.onedev.server.search.entity.EntityQuery;
import io.onedev.server.search.entity.QueryWatchBuilder;
import io.onedev.server.search.entity.pullrequest.PullRequestQuery;
import io.onedev.server.security.SecurityUtils;
import io.onedev.server.util.markdown.MarkdownManager;
import io.onedev.server.util.markdown.MentionParser;
@Singleton
public class PullRequestNotificationManager extends AbstractNotificationManager {
@ -76,23 +77,12 @@ public class PullRequestNotificationManager extends AbstractNotificationManager
this.userManager = userManager;
}
private String getSubject(PullRequest request) {
String state;
if (request.isMerged())
state = "Merged";
else if (request.isDiscarded())
state = "Discarded";
else
state = "Open";
return String.format("[%s] %s", state, request.getTitle());
}
@Transactional
@Listen
public void on(PullRequestEvent event) {
PullRequest request = event.getRequest();
User user = event.getUser();
String url;
if (event instanceof PullRequestCommented)
url = urlManager.urlFor(((PullRequestCommented)event).getComment());
@ -178,6 +168,14 @@ public class PullRequestNotificationManager extends AbstractNotificationManager
}
}
String summary = "[" + request.getStatusName() + "] ";
if (user != null)
summary = summary + user.getDisplayName() + " " + event.getActivity();
else if (committer != null)
summary = summary + committer.getDisplayName() + " " + event.getActivity();
else
summary = summary + event.getActivity();
if (event instanceof PullRequestOpened) {
for (PullRequestReview review: request.getReviews()) {
if (review.getResult() == null) {
@ -189,43 +187,49 @@ public class PullRequestNotificationManager extends AbstractNotificationManager
String replyAddress = mailManager.getReplyAddress(request);
boolean replyable = replyAddress != null;
String threadingReferences = getThreadingReferences(request);
if (event instanceof PullRequestChangeEvent
&& request.getSubmitter() != null
&& !notifiedUsers.contains(request.getSubmitter())) {
PullRequestChangeEvent changeEvent = (PullRequestChangeEvent) event;
PullRequestChangeData changeData = changeEvent.getChange().getData();
String summary = null;
if (changeData instanceof PullRequestApproveData)
summary = user.getDisplayName() + " approved";
else if (changeData instanceof PullRequestRequestedForChangesData)
summary = user.getDisplayName() + " requested changes";
else if (changeData instanceof PullRequestDiscardData)
summary = user.getDisplayName() + " discarded";
if (summary != null) {
summary = "Pull request " + request.getFQN() + ": " + summary;
if (changeData instanceof PullRequestApproveData
|| changeData instanceof PullRequestRequestedForChangesData
|| changeData instanceof PullRequestDiscardData) {
String subject = String.format("[Pull Request %s] (%s) %s", request.getFQN(),
WordUtils.capitalize(changeData.getActivity()), request.getTitle());
String threadingReferences = String.format("<%s-%s@onedev>",
changeData.getActivity().replace(' ', '-'), request.getUUID());
mailManager.sendMailAsync(Lists.newArrayList(request.getSubmitter().getEmail()),
Lists.newArrayList(), getSubject(request),
getHtmlBody(event, summary, null, url, replyable, null),
getTextBody(event, summary, null, url, replyable, null),
Lists.newArrayList(), Lists.newArrayList(), subject,
getHtmlBody(event, summary, event.getHtmlBody(), url, replyable, null),
getTextBody(event, summary, event.getTextBody(), url, replyable, null),
replyAddress, threadingReferences);
notifiedUsers.add(request.getSubmitter());
}
}
Collection<User> mentionedUsers = new HashSet<>();
if (event instanceof MarkdownAware) {
MarkdownAware markdownAware = (MarkdownAware) event;
String markdown = markdownAware.getMarkdown();
if (markdown != null) {
String rendered = markdownManager.render(markdown);
for (String userName: new MentionParser().parseMentions(rendered)) {
User mentionedUser = userManager.findByName(userName);
if (mentionedUser != null && notifiedUsers.add(mentionedUser)) {
pullRequestWatchManager.watch(request, mentionedUser, true);
mentionedUsers.add(mentionedUser);
}
Collection<String> notifiedEmailAddresses;
if (event instanceof PullRequestCommented)
notifiedEmailAddresses = ((PullRequestCommented) event).getNotifiedEmailAddresses();
else
notifiedEmailAddresses = new ArrayList<>();
if (event.getRenderedMarkdown() != null) {
for (String userName: new MentionParser().parseMentions(event.getRenderedMarkdown())) {
User mentionedUser = userManager.findByName(userName);
if (mentionedUser != null) {
pullRequestWatchManager.watch(request, mentionedUser, true);
if (!notifiedEmailAddresses.stream().anyMatch(mentionedUser.getEmails()::contains)) {
String subject = String.format("[Pull Request %s] (Mentioned You) %s", request.getFQN(), request.getTitle());
String threadingReferences = String.format("<mentioned-%s@onedev>", request.getUUID());
mailManager.sendMailAsync(Sets.newHashSet(mentionedUser.getEmail()),
Sets.newHashSet(), Sets.newHashSet(), subject,
getHtmlBody(event, summary, event.getHtmlBody(), url, replyable, null),
getTextBody(event, summary, event.getTextBody(), url, replyable, null),
replyAddress, threadingReferences);
notifiedUsers.add(mentionedUser);
}
}
}
}
@ -244,14 +248,9 @@ public class PullRequestNotificationManager extends AbstractNotificationManager
notifyWatchers = true;
}
if (!mentionedUsers.isEmpty() || notifyWatchers) {
Collection<User> ccUsers = new HashSet<>();
if (notifyWatchers) {
Collection<User> bccUsers = new HashSet<>();
Collection<String> notifiedEmailAddresses;
if (event instanceof PullRequestCommented)
notifiedEmailAddresses = ((PullRequestCommented) event).getNotifiedEmailAddresses();
else
notifiedEmailAddresses = new ArrayList<>();
for (PullRequestWatch watch: request.getWatches()) {
Date visitDate = userInfoManager.getPullRequestVisitDate(watch.getUser(), request);
if (watch.isWatching()
@ -259,34 +258,24 @@ public class PullRequestNotificationManager extends AbstractNotificationManager
&& (!(event instanceof PullRequestUpdated) || !watch.getUser().equals(request.getSubmitter()))
&& !notifiedUsers.contains(watch.getUser())
&& !notifiedEmailAddresses.stream().anyMatch(watch.getUser().getEmails()::contains)) {
ccUsers.add(watch.getUser());
bccUsers.add(watch.getUser());
}
}
if (!mentionedUsers.isEmpty() || !ccUsers.isEmpty()) {
String summary;
if (user != null)
summary = String.format("Pull request %s: %s %s", request.getFQN(), user.getDisplayName(), event.getActivity());
else if (committer != null)
summary = String.format("Pull request %s: %s added commits", request.getFQN(), committer.getDisplayName());
else
summary = "Pull request " + request.getFQN() + ": " + event.getActivity();
if (!bccUsers.isEmpty()) {
String subject = String.format("[Pull Request %s] (Updated) %s", request.getFQN(), request.getTitle());
String threadingReferences = "<" + request.getUUID() + "@onedev>";
Unsubscribable unsubscribable = new Unsubscribable(mailManager.getUnsubscribeAddress(request));
String htmlBody = getHtmlBody(event, summary, null, url, replyable, unsubscribable);
String textBody = getTextBody(event, summary, null, url, replyable, unsubscribable);
String htmlBody = getHtmlBody(event, summary, event.getHtmlBody(), url, replyable, unsubscribable);
String textBody = getTextBody(event, summary, event.getTextBody(), url, replyable, unsubscribable);
mailManager.sendMailAsync(
mentionedUsers.stream().map(User::getEmail).collect(Collectors.toList()),
ccUsers.stream().map(User::getEmail).collect(Collectors.toList()),
getSubject(request), htmlBody, textBody, replyAddress, threadingReferences);
Lists.newArrayList(), Lists.newArrayList(),
bccUsers.stream().map(User::getEmail).collect(Collectors.toList()),
subject, htmlBody, textBody, replyAddress, threadingReferences);
}
}
}
private String getThreadingReferences(PullRequest request) {
return "<" + request.getUUID() + "@onedev>";
}
@Transactional
@Listen
public void on(EntityPersisted event) {
@ -297,13 +286,15 @@ public class PullRequestNotificationManager extends AbstractNotificationManager
if (review.getResult() == null && !review.getUser().equals(SecurityUtils.getUser())) {
pullRequestWatchManager.watch(request, review.getUser(), true);
String url = urlManager.urlFor(request);
String summary = "Pull request " + request.getFQN() + ": You are invited to review";
String subject = String.format("[Pull Request %s] (Review Invitation) %s",
request.getFQN(), request.getTitle());
String threadingReferences = "<review-invitation-" + request.getUUID() + "@onedev>";
String replyAddress = mailManager.getReplyAddress(request);
mailManager.sendMailAsync(Lists.newArrayList(review.getUser().getEmail()),
Lists.newArrayList(), getSubject(request),
getHtmlBody(event, summary, null, url, replyAddress != null, null),
getTextBody(event, summary, null, url, replyAddress != null, null),
replyAddress, getThreadingReferences(request));
Lists.newArrayList(), Lists.newArrayList(), subject,
getHtmlBody(event, null, null, url, replyAddress != null, null),
getTextBody(event, null, null, url, replyAddress != null, null),
replyAddress, threadingReferences);
}
} else if (event.getEntity() instanceof PullRequestAssignment) {
PullRequestAssignment assignment = (PullRequestAssignment) event.getEntity();
@ -311,13 +302,14 @@ public class PullRequestNotificationManager extends AbstractNotificationManager
if (!assignment.getUser().equals(SecurityUtils.getUser())) {
pullRequestWatchManager.watch(request, assignment.getUser(), true);
String url = urlManager.urlFor(request);
String summary = "Pull request " + request.getFQN() + ": You are assigned and expected to merge";
String subject = String.format("[Pull Request %s] (Assigned) %s", request.getFQN(), request.getTitle());
String threadingReferences = "<assigned-" + request.getUUID() + "@onedev>";
String replyAddress = mailManager.getReplyAddress(request);
mailManager.sendMailAsync(Lists.newArrayList(assignment.getUser().getEmail()),
Lists.newArrayList(), getSubject(request),
getHtmlBody(event, summary, null, url, replyAddress != null, null),
getTextBody(event, summary, null, url, replyAddress != null, null),
replyAddress, getThreadingReferences(request));
Lists.newArrayList(), Lists.newArrayList(), subject,
getHtmlBody(event, null, null, url, replyAddress != null, null),
getTextBody(event, null, null, url, replyAddress != null, null),
replyAddress, threadingReferences);
}
}
}

View File

@ -245,7 +245,7 @@ public class IssueResource {
Issue issue = issueManager.load(issueId);
if (!SecurityUtils.canModify(issue))
throw new UnauthorizedException();
issueChangeManager.changeDescription(issue, description);
issueManager.saveDescription(issue, description);
return Response.ok().build();
}

View File

@ -289,7 +289,7 @@ public class PullRequestResource {
PullRequest request = pullRequestManager.load(requestId);
if (!SecurityUtils.canModify(request))
throw new UnauthorizedException();
pullRequestChangeManager.changeDescription(request, description);
pullRequestManager.saveDescription(request, description);
return Response.ok().build();
}

View File

@ -3,22 +3,24 @@ package io.onedev.server.ssh;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.sshd.common.Factory;
import org.apache.sshd.server.Environment;
import org.apache.sshd.server.ExitCallback;
import org.apache.sshd.server.SessionAware;
import org.apache.sshd.server.channel.ChannelSession;
import org.apache.sshd.server.command.Command;
import org.apache.sshd.server.session.ServerSession;
import org.apache.sshd.server.session.ServerSessionAware;
import org.apache.sshd.server.shell.ShellFactory;
import org.eclipse.jgit.lib.Constants;
public class DisableShellAccess implements Factory<Command> {
public class DisableShellAccess implements ShellFactory {
@Override
public Command create() {
public Command createShell(ChannelSession channel) {
return new WelcomeMessage();
}
private static class WelcomeMessage implements Command, SessionAware {
private static class WelcomeMessage implements Command, ServerSessionAware {
private InputStream in;
private OutputStream out;
@ -26,7 +28,7 @@ public class DisableShellAccess implements Factory<Command> {
private ExitCallback callback;
@Override
public void start(Environment env) throws IOException {
public void start(ChannelSession channel, Environment env) throws IOException {
err.write(Constants.encode(generateWelcomeMessage()));
err.flush();
callback.onExit(0);
@ -41,7 +43,7 @@ public class DisableShellAccess implements Factory<Command> {
}
@Override
public void destroy() throws Exception {
public void destroy(ChannelSession channel) throws Exception {
}
@Override

View File

@ -50,7 +50,7 @@ public class SshServerLauncher {
server.setPublickeyAuthenticator(new CachingPublicKeyAuthenticator(authenticator));
server.setKeyboardInteractiveAuthenticator(null);
server.setCommandFactory(command -> {
server.setCommandFactory((channel, command) -> {
for (SshCommandCreator creator: commandCreators) {
Command sshCommand = creator.createCommand(command);
if (sshCommand != null)

View File

@ -0,0 +1,125 @@
package io.onedev.server.util;
import java.util.Collection;
import java.util.regex.Matcher;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Attribute;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.nodes.TextNode;
import org.jsoup.safety.Cleaner;
import org.jsoup.safety.Whitelist;
import org.unbescape.html.HtmlEscape;
import com.google.common.collect.Lists;
import io.onedev.commons.utils.StringUtils;
public class HtmlUtils {
private static final String[] SAFE_TAGS = new String[] { "h1", "h2", "h3", "h4", "h5", "h6", "h7", "h8", "br", "b",
"i", "strong", "em", "a", "pre", "code", "img", "tt", "div", "ins", "del", "sup", "sub", "p", "ol", "ul",
"li", "table", "thead", "tbody", "tfoot", "th", "tr", "td", "rt", "rp", "blockquote", "dl", "dt", "dd",
"kbd", "q", "hr", "strike", "caption", "cite", "col", "colgroup", "small", "span", "u", "input", "video", "source"};
private static final String[] SAFE_ATTRIBUTES = new String[] { "abbr", "accept", "accept-charset", "accesskey",
"action", "align", "alt", "axis", "border", "cellpadding", "cellspacing", "char", "charoff", "charset",
"checked", "cite", "clear", "cols", "colspan", "color", "compact", "coords", "datetime", "details", "dir",
"disabled", "enctype", "for", "frame", "headers", "height", "hreflang", "hspace", "ismap", "label", "lang",
"longdesc", "maxlength", "media", "method", "multiple", "name", "nohref", "noshade", "nowrap", "prompt",
"readonly", "rel", "rev", "rows", "rowspan", "rules", "scope", "selected", "shape", "size", "span", "start",
"style", "summary", "tabindex", "target", "title", "type", "usemap", "valign", "value", "vspace", "width",
"itemprop", "class", "controls", "id"};
private static final String[] SAFE_ANCHOR_SCHEMES = new String[] { "http", "https", "mailto", };
private static final Whitelist whiteList;
static {
whiteList = new Whitelist() {
@Override
protected boolean isSafeAttribute(String tagName, Element el, Attribute attr) {
if (attr.getKey().startsWith("data-"))
return true;
else
return super.isSafeAttribute(tagName, el, attr);
}
};
whiteList.addTags(SAFE_TAGS)
.addAttributes("a", "href", "title")
.addAttributes("img", "align", "alt", "height", "src", "title", "width")
.addAttributes("div", "itemscope", "itemtype")
.addAttributes("source", "src")
.addAttributes(":all", SAFE_ATTRIBUTES)
.addProtocols("a", "href", SAFE_ANCHOR_SCHEMES)
.addProtocols("blockquote", "cite", "http", "https")
.addProtocols("cite", "cite", "http", "https")
.addProtocols("img", "src", "http", "https")
.addProtocols("q", "cite", "http", "https")
.preserveRelativeLinks(true);
}
public static boolean hasAncestor(Node node, Collection<String> tags) {
Node parent = node.parentNode();
while (parent != null) {
if (parent instanceof Element) {
Element e = (Element) parent;
if (tags.contains(e.tagName().toLowerCase())) {
return true;
}
}
parent = parent.parentNode();
}
return false;
}
public static boolean hasAncestor(Node node, String tag) {
return hasAncestor(node, Lists.newArrayList(tag));
}
public static void appendReplacement(Matcher matcher, Node node, String replacement) {
StringBuffer buffer = new StringBuffer();
matcher.appendReplacement(buffer, "");
if (buffer.length() != 0)
node.before(new TextNode(buffer.toString(), node.baseUri()));
node.before(replacement);
}
public static void appendTail(Matcher matcher, Node node) {
StringBuffer buffer = new StringBuffer();
matcher.appendTail(buffer);
if (buffer.length() != 0)
node.before(new TextNode(buffer.toString(), node.baseUri()));
node.remove();
}
public static Document sanitize(Document doc) {
return new Cleaner(whiteList).clean(doc);
}
public static Document parse(String html) {
// Use a faked baseURI, otherwise all relative urls will be stripped out
return Jsoup.parseBodyFragment(html, "http://localhost/sanitize");
}
/**
* Escape text and preserving line breaks and white spaces
* @param text
* @return
*/
public static String formatAsHtml(String text) {
text = HtmlEscape.escapeHtml5(text);
text = StringUtils.replace(text, "\n", "<br>");
text = StringUtils.replace(text, " ", "&nbsp;");
text = StringUtils.replace(text, "\t", "&nbsp;&nbsp;&nbsp;&nbsp;");
return text;
}
}

View File

@ -0,0 +1,66 @@
package io.onedev.server.util;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import com.google.common.collect.MapDifference;
import com.google.common.collect.Maps;
import com.google.common.collect.MapDifference.ValueDifference;
public class PropertyChange implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private String oldValue;
private String newValue;
public PropertyChange(String name, String oldValue, String newValue) {
this.name = name;
this.oldValue = oldValue;
this.newValue = newValue;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getOldValue() {
return oldValue;
}
public void setOldValue(String oldValue) {
this.oldValue = oldValue;
}
public String getNewValue() {
return newValue;
}
public void setNewValue(String newValue) {
this.newValue = newValue;
}
public static List<PropertyChange> listOf(Map<String, String> oldProperties, Map<String, String> newProperties) {
List<PropertyChange> changes = new ArrayList<>();
MapDifference<String, String> diff = Maps.difference(oldProperties, newProperties);
for (Map.Entry<String, ValueDifference<String>> entry: diff.entriesDiffering().entrySet()) {
changes.add(new PropertyChange(entry.getKey(),
entry.getValue().leftValue(), entry.getValue().rightValue()));
}
for (Map.Entry<String, String> entry: diff.entriesOnlyOnLeft().entrySet())
changes.add(new PropertyChange(entry.getKey(), entry.getValue(), null));
for (Map.Entry<String, String> entry: diff.entriesOnlyOnRight().entrySet())
changes.add(new PropertyChange(entry.getKey(), null, entry.getValue()));
return changes;
}
}

View File

@ -17,12 +17,20 @@ public abstract class ManualConfig implements Serializable {
private final Collection<String> excludedProperties;
private final boolean forceOrdinaryStyle;
public ManualConfig(String title, @Nullable String description, Serializable setting,
Collection<String> excludedProperties) {
Collection<String> excludedProperties, boolean forceOrdinaryStyle) {
this.title = title;
this.description = description;
this.setting = setting;
this.excludedProperties = excludedProperties;
this.forceOrdinaryStyle = forceOrdinaryStyle;
}
public ManualConfig(String title, @Nullable String description, Serializable setting,
Collection<String> excludedProperties) {
this(title, description, setting, excludedProperties, false);
}
public ManualConfig(String title, @Nullable String description, Serializable setting) {
@ -41,6 +49,10 @@ public abstract class ManualConfig implements Serializable {
return setting;
}
public boolean isForceOrdinaryStyle() {
return forceOrdinaryStyle;
}
public Collection<String> getExcludeProperties() {
return excludedProperties;
}

View File

@ -35,9 +35,7 @@ import io.onedev.server.web.page.admin.role.RoleDetailPage;
import io.onedev.server.web.page.admin.role.RoleListPage;
import io.onedev.server.web.page.admin.serverinformation.ServerInformationPage;
import io.onedev.server.web.page.admin.serverlog.ServerLogPage;
import io.onedev.server.web.page.admin.servicedesk.DefaultProjectDesignationListPage;
import io.onedev.server.web.page.admin.servicedesk.IssueCreationSettingListPage;
import io.onedev.server.web.page.admin.servicedesk.SenderAuthorizationListPage;
import io.onedev.server.web.page.admin.servicedesk.ServiceDeskSettingPage;
import io.onedev.server.web.page.admin.ssh.SshSettingPage;
import io.onedev.server.web.page.admin.sso.SsoConnectorListPage;
import io.onedev.server.web.page.admin.sso.SsoProcessPage;
@ -219,12 +217,8 @@ public class OneUrlMapper extends CompoundRequestMapper {
add(new DynamicPathPageMapper("administration/settings/system", SystemSettingPage.class));
add(new DynamicPathPageMapper("administration/settings/mail", MailSettingPage.class));
add(new DynamicPathPageMapper("administration/settings/sender-authorizations",
SenderAuthorizationListPage.class));
add(new DynamicPathPageMapper("administration/settings/default-projects",
DefaultProjectDesignationListPage.class));
add(new DynamicPathPageMapper("administration/settings/issue-creation-settings",
IssueCreationSettingListPage.class));
add(new DynamicPathPageMapper("administration/settings/service-desk-setting",
ServiceDeskSettingPage.class));
add(new DynamicPathPageMapper("administration/settings/issue-notification-template",
IssueNotificationTemplatePage.class));
add(new DynamicPathPageMapper("administration/settings/pull-request-notification-template",

View File

@ -2835,4 +2835,8 @@ a.badge.badge-light-dark:hover, a.badge.badge-light-dark:focus {
.form-text {
margin-top: 0.3rem;
}
.form-group {
margin-bottom: 1.2rem;
}

Some files were not shown because too many files have changed in this diff Show More