Fix issue #1169 - Improve issue / pull request fuzzy search to include comments

This commit is contained in:
Robin Shen 2023-02-02 22:01:13 +08:00
parent d319013e0c
commit ca2c02831a
63 changed files with 1684 additions and 1469 deletions

View File

@ -10,11 +10,6 @@ public interface AttachmentManager {
File getAttachmentGroupDirLocal(Long projectId, String attachmentGroup);
void moveAttachmentGroupTargetLocal(Long targetProjectId, Long sourceProjectId, String attachmentGroup);
void copyAttachmentGroupTargetLocal(Long targetProjectId, String targetAttachmentGroup,
Long sourceProjectId, String sourceAttachmentGroup);
String saveAttachment(Long projectId, String attachmentGroup, String suggestedAttachmentName,
InputStream attachmentStream);

View File

@ -14,11 +14,15 @@ import io.onedev.server.entitymanager.SettingManager;
import io.onedev.server.event.Listen;
import io.onedev.server.event.entity.EntityPersisted;
import io.onedev.server.event.entity.EntityRemoved;
import io.onedev.server.event.project.issue.IssuesCopied;
import io.onedev.server.event.project.issue.IssuesMoved;
import io.onedev.server.event.system.SystemStarted;
import io.onedev.server.event.system.SystemStopping;
import io.onedev.server.model.Issue;
import io.onedev.server.persistence.TransactionManager;
import io.onedev.server.persistence.annotation.Sessional;
import io.onedev.server.persistence.annotation.Transactional;
import io.onedev.server.persistence.dao.Dao;
import io.onedev.server.storage.StorageManager;
import io.onedev.server.util.AttachmentTooLargeException;
import io.onedev.server.util.artifact.FileInfo;
@ -39,10 +43,7 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;
import java.io.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
import java.util.*;
import static io.onedev.commons.bootstrap.Bootstrap.BUFFER_SIZE;
@ -57,6 +58,8 @@ public class DefaultAttachmentManager implements AttachmentManager, SchedulableT
private static final String TEMP = "temp";
private final Dao dao;
private final StorageManager storageManager;
private final TransactionManager transactionManager;
@ -72,9 +75,10 @@ public class DefaultAttachmentManager implements AttachmentManager, SchedulableT
private String taskId;
@Inject
public DefaultAttachmentManager(StorageManager storageManager, TransactionManager transactionManager,
TaskScheduler taskScheduler, SettingManager settingManager, ProjectManager projectManager,
ClusterManager clusterManager) {
public DefaultAttachmentManager(Dao dao, StorageManager storageManager, TransactionManager transactionManager,
TaskScheduler taskScheduler, SettingManager settingManager,
ProjectManager projectManager, ClusterManager clusterManager) {
this.dao = dao;
this.storageManager = storageManager;
this.transactionManager = transactionManager;
this.taskScheduler = taskScheduler;
@ -97,26 +101,40 @@ public class DefaultAttachmentManager implements AttachmentManager, SchedulableT
return new File(baseDir, TEMP + "/" + attachmentGroup);
}
@Override
public void moveAttachmentGroupTargetLocal(Long targetProjectId, Long sourceProjectId, String attachmentGroup) {
@Sessional
@Listen
public void on(IssuesMoved event) {
Long sourceProjectId = event.getSourceProject().getId();
Long targetProjectId = event.getProject().getId();
File targetBaseDir = storageManager.getProjectAttachmentDir(targetProjectId);
File targetGroupDir = getPermanentAttachmentGroupDir(targetBaseDir, attachmentGroup);
UUID sourceStorageServerUUID = projectManager.getStorageServerUUID(sourceProjectId, true);
if (sourceStorageServerUUID.equals(clusterManager.getLocalServerUUID())) {
File sourceBaseDir = storageManager.getProjectAttachmentDir(sourceProjectId);
File sourceGroupDir = getPermanentAttachmentGroupDir(sourceBaseDir, attachmentGroup);
FileUtils.createDir(targetGroupDir.getParentFile());
try {
FileUtils.moveDirectory(sourceGroupDir, targetGroupDir);
} catch (IOException e) {
throw new RuntimeException(e);
}
for (Long issueId: event.getIssueIds()) {
Issue issue = dao.load(Issue.class, issueId);
String attachmentGroup = issue.getAttachmentGroup();
File targetGroupDir = getPermanentAttachmentGroupDir(targetBaseDir, attachmentGroup);
File sourceGroupDir = getPermanentAttachmentGroupDir(sourceBaseDir, attachmentGroup);
FileUtils.createDir(targetGroupDir.getParentFile());
try {
FileUtils.moveDirectory(sourceGroupDir, targetGroupDir);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
} else {
FileUtils.createDir(targetGroupDir);
downloadAttachments(targetGroupDir, sourceStorageServerUUID,
sourceProjectId, attachmentGroup);
Collection<String> attachmentGroups = new HashSet<>();
for (Long issueId: event.getIssueIds()) {
Issue issue = dao.load(Issue.class, issueId);
String attachmentGroup = issue.getAttachmentGroup();
attachmentGroups.add(attachmentGroup);
File targetGroupDir = getPermanentAttachmentGroupDir(targetBaseDir, attachmentGroup);
FileUtils.createDir(targetGroupDir);
downloadAttachments(targetGroupDir, sourceStorageServerUUID,
sourceProjectId, attachmentGroup);
}
projectManager.runOnProjectServer(sourceProjectId, new ClusterTask<Void>() {
private static final long serialVersionUID = 1L;
@ -124,35 +142,51 @@ public class DefaultAttachmentManager implements AttachmentManager, SchedulableT
@Override
public Void call() throws Exception {
File sourceBaseDir = storageManager.getProjectAttachmentDir(sourceProjectId);
File sourceGroupDir = getPermanentAttachmentGroupDir(sourceBaseDir, attachmentGroup);
FileUtils.deleteDir(sourceGroupDir);
for (var attachmentGroup: attachmentGroups)
FileUtils.deleteDir(getPermanentAttachmentGroupDir(sourceBaseDir, attachmentGroup));
return null;
}
});
}
}
public void copyAttachmentGroupTargetLocal(Long targetProjectId, String targetAttachmentGroup,
Long sourceProjectId, String sourceAttachmentGroup) {
});
}
}
@Sessional
@Listen
public void on(IssuesCopied event) {
Long sourceProjectId = event.getSourceProject().getId();
Long targetProjectId = event.getProject().getId();
File targetBaseDir = storageManager.getProjectAttachmentDir(targetProjectId);
File targetGroupDir = getPermanentAttachmentGroupDir(targetBaseDir, targetAttachmentGroup);
UUID sourceStorageServerUUID = projectManager.getStorageServerUUID(sourceProjectId, true);
if (sourceStorageServerUUID.equals(clusterManager.getLocalServerUUID())) {
File sourceBaseDir = storageManager.getProjectAttachmentDir(sourceProjectId);
File sourceGroupDir = getPermanentAttachmentGroupDir(sourceBaseDir, sourceAttachmentGroup);
FileUtils.createDir(targetGroupDir.getParentFile());
try {
FileUtils.copyDirectory(sourceGroupDir, targetGroupDir);
} catch (IOException e) {
throw new RuntimeException(e);
for (var entry: event.getIssueIdMapping().entrySet()) {
Issue sourceIssue = dao.load(Issue.class, entry.getKey());
Issue targetIssue = dao.load(Issue.class, entry.getValue());
File sourceGroupDir = getPermanentAttachmentGroupDir(sourceBaseDir, sourceIssue.getAttachmentGroup());
File targetGroupDir = getPermanentAttachmentGroupDir(targetBaseDir, targetIssue.getAttachmentGroup());
FileUtils.createDir(targetGroupDir.getParentFile());
try {
FileUtils.copyDirectory(sourceGroupDir, targetGroupDir);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
} else {
FileUtils.createDir(targetGroupDir);
downloadAttachments(targetGroupDir, sourceStorageServerUUID,
sourceProjectId, sourceAttachmentGroup);
}
for (var entry: event.getIssueIdMapping().entrySet()) {
Issue sourceIssue = dao.load(Issue.class, entry.getKey());
Issue targetIssue = dao.load(Issue.class, entry.getValue());
File targetGroupDir = getPermanentAttachmentGroupDir(targetBaseDir, targetIssue.getAttachmentGroup());
FileUtils.createDir(targetGroupDir);
downloadAttachments(targetGroupDir, sourceStorageServerUUID,
sourceProjectId, sourceIssue.getAttachmentGroup());
}
}
}
private void downloadAttachments(File targetDir, UUID sourceStorageServerUUID,

View File

@ -22,6 +22,8 @@ public interface IssueChangeManager extends EntityManager<IssueChange> {
void removeLink(LinkSpec spec, Issue issue, Issue linkedIssue, boolean opposite);
void changeTitle(Issue issue, String title);
void changeDescription(Issue issue, String description);
void changeConfidential(Issue issue, boolean confidential);
@ -29,7 +31,7 @@ public interface IssueChangeManager extends EntityManager<IssueChange> {
void changeMilestones(Issue issue, Collection<Milestone> milestones);
void save(IssueChange change, @Nullable String note);
void create(IssueChange change, @Nullable String note);
void addSchedule(Issue issue, Milestone milestone);

View File

@ -7,6 +7,10 @@ import io.onedev.server.persistence.dao.EntityManager;
public interface IssueCommentManager extends EntityManager<IssueComment> {
void save(IssueComment comment, Collection<String> notifiedEmailAddresses);
void create(IssueComment comment, Collection<String> notifiedEmailAddresses);
void delete(IssueComment comment);
void update(IssueComment comment);
}

View File

@ -1,11 +1,5 @@
package io.onedev.server.entitymanager;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import io.onedev.server.model.Issue;
import io.onedev.server.model.Milestone;
import io.onedev.server.model.Project;
@ -21,6 +15,11 @@ import io.onedev.server.web.component.issue.workflowreconcile.UndefinedFieldValu
import io.onedev.server.web.component.issue.workflowreconcile.UndefinedFieldValuesResolution;
import io.onedev.server.web.component.issue.workflowreconcile.UndefinedStateResolution;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public interface IssueManager extends EntityManager<Issue> {
@Nullable
@ -62,16 +61,14 @@ public interface IssueManager extends EntityManager<Issue> {
void fixStateAndFieldOrdinals();
void saveDescription(Issue issue, @Nullable String description);
@Override
void delete(Issue issue);
void move(Collection<Issue> issues, Project targetProject);
void move(Collection<Issue> issues, Project sourceProject, Project targetProject);
void copy(Collection<Issue> issues, Project targetProject);
void copy(Collection<Issue> issues, Project sourceProject, Project targetProject);
void delete(Collection<Issue> issues);
void delete(Collection<Issue> issues, Project project);
Collection<MilestoneAndIssueState> queryMilestoneAndIssueStates(Project project, Collection<Milestone> milestones);

View File

@ -1,18 +1,14 @@
package io.onedev.server.entitymanager.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
import io.onedev.commons.utils.ExplicitException;
import io.onedev.server.entityreference.EntityReferenceManager;
import io.onedev.server.model.support.issue.changedata.*;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
@ -56,17 +52,6 @@ import io.onedev.server.model.Milestone;
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.IssueConfidentialChangeData;
import io.onedev.server.model.support.issue.changedata.IssueFieldChangeData;
import io.onedev.server.model.support.issue.changedata.IssueLinkAddData;
import io.onedev.server.model.support.issue.changedata.IssueLinkChangeData;
import io.onedev.server.model.support.issue.changedata.IssueLinkRemoveData;
import io.onedev.server.model.support.issue.changedata.IssueMilestoneAddData;
import io.onedev.server.model.support.issue.changedata.IssueMilestoneChangeData;
import io.onedev.server.model.support.issue.changedata.IssueMilestoneRemoveData;
import io.onedev.server.model.support.issue.changedata.IssueStateChangeData;
import io.onedev.server.model.support.issue.changedata.IssueTitleChangeData;
import io.onedev.server.model.support.issue.transitiontrigger.BranchUpdateTrigger;
import io.onedev.server.model.support.issue.transitiontrigger.BuildSuccessfulTrigger;
import io.onedev.server.model.support.issue.transitiontrigger.DiscardPullRequestTrigger;
@ -124,13 +109,16 @@ public class DefaultIssueChangeManager extends BaseEntityManager<IssueChange>
private final ClusterManager clusterManager;
private final EntityReferenceManager entityReferenceManager;
private String taskId;
@Inject
public DefaultIssueChangeManager(Dao dao, IssueManager issueManager, IssueFieldManager issueFieldManager,
ProjectManager projectManager, ListenerRegistry listenerRegistry, TaskScheduler taskScheduler,
IssueScheduleManager issueScheduleManager, IssueLinkManager issueLinkManager,
ClusterManager clusterManager) {
ProjectManager projectManager, ListenerRegistry listenerRegistry,
TaskScheduler taskScheduler, IssueScheduleManager issueScheduleManager,
IssueLinkManager issueLinkManager, ClusterManager clusterManager,
EntityReferenceManager entityReferenceManager) {
super(dao);
this.issueManager = issueManager;
this.issueFieldManager = issueFieldManager;
@ -140,11 +128,12 @@ public class DefaultIssueChangeManager extends BaseEntityManager<IssueChange>
this.issueScheduleManager = issueScheduleManager;
this.issueLinkManager = issueLinkManager;
this.clusterManager = clusterManager;
this.entityReferenceManager = entityReferenceManager;
}
@Transactional
@Override
public void save(IssueChange change, String note) {
public void create(IssueChange change, @Nullable String note) {
dao.persist(change);
if (note != null) {
IssueComment comment = new IssueComment();
@ -159,11 +148,6 @@ public class DefaultIssueChangeManager extends BaseEntityManager<IssueChange>
listenerRegistry.post(new IssueChanged(change, note));
}
@Override
public void save(IssueChange change) {
save(change, null);
}
@Transactional
@Override
public void changeTitle(Issue issue, String title) {
@ -175,7 +159,25 @@ public class DefaultIssueChangeManager extends BaseEntityManager<IssueChange>
change.setIssue(issue);
change.setUser(SecurityUtils.getUser());
change.setData(new IssueTitleChangeData(prevTitle, issue.getTitle()));
save(change);
create(change, null);
dao.persist(issue);
}
}
@Override
public void changeDescription(Issue issue, @Nullable String description) {
String prevDescription = issue.getDescription();
if (!Objects.equals(description, prevDescription)) {
if (description != null && description.length() > Issue.MAX_DESCRIPTION_LEN)
throw new ExplicitException("Description too long");
issue.setDescription(description);
entityReferenceManager.addReferenceChange(issue, description);
IssueChange change = new IssueChange();
change.setIssue(issue);
change.setUser(SecurityUtils.getUser());
change.setData(new IssueDescriptionChangeData(prevDescription, issue.getDescription()));
create(change, null);
dao.persist(issue);
}
}
@ -191,7 +193,7 @@ public class DefaultIssueChangeManager extends BaseEntityManager<IssueChange>
change.setIssue(issue);
change.setUser(SecurityUtils.getUser());
change.setData(new IssueConfidentialChangeData(prevConfidential, issue.isConfidential()));
save(change);
create(change, null);
dao.persist(issue);
}
}
@ -205,7 +207,7 @@ public class DefaultIssueChangeManager extends BaseEntityManager<IssueChange>
change.setIssue(issue);
change.setData(new IssueMilestoneAddData(milestone.getName()));
change.setUser(SecurityUtils.getUser());
save(change);
create(change, null);
}
@Transactional
@ -218,7 +220,7 @@ public class DefaultIssueChangeManager extends BaseEntityManager<IssueChange>
change.setIssue(issue);
change.setData(new IssueMilestoneRemoveData(milestone.getName()));
change.setUser(SecurityUtils.getUser());
save(change);
create(change, null);
}
}
@ -234,7 +236,7 @@ public class DefaultIssueChangeManager extends BaseEntityManager<IssueChange>
change.setIssue(issue);
change.setUser(SecurityUtils.getUser());
change.setData(new IssueFieldChangeData(prevFields, issue.getFieldInputs()));
save(change);
create(change, null);
}
}
@ -256,7 +258,7 @@ public class DefaultIssueChangeManager extends BaseEntityManager<IssueChange>
change.setUser(SecurityUtils.getUser());
change.setData(new IssueStateChangeData(prevState, issue.getState(),
prevFields, issue.getFieldInputs()));
save(change, comment);
create(change, comment);
}
@Transactional
@ -296,7 +298,7 @@ public class DefaultIssueChangeManager extends BaseEntityManager<IssueChange>
change.setData(new IssueBatchUpdateData(prevState, issue.getState(),
prevConfidential, issue.isConfidential(), prevMilestoneList,
currentMilestoneList, prevFields, issue.getFieldInputs()));
save(change, comment);
create(change, comment);
}
}
}
@ -622,7 +624,7 @@ public class DefaultIssueChangeManager extends BaseEntityManager<IssueChange>
List<Milestone> currentMilestoneList = new ArrayList<>(milestones);
currentMilestoneList.sort(new Milestone.DatesAndStatusComparator());
change.setData(new IssueMilestoneChangeData(prevMilestoneList, currentMilestoneList));
save(change);
create(change, null);
}
}
@ -645,7 +647,7 @@ public class DefaultIssueChangeManager extends BaseEntityManager<IssueChange>
getLinkedIssueInfo(issue, prevLinkedIssue),
getLinkedIssueInfo(issue, linkedIssue));
change.setData(data);
save(change);
create(change, null);
logLinkedSideChange(spec, issue, prevLinkedIssue, linkedIssue, opposite);
}
@ -670,7 +672,7 @@ public class DefaultIssueChangeManager extends BaseEntityManager<IssueChange>
change.setData(new IssueLinkRemoveData(linkName, prevIssueSummary));
else
change.setData(new IssueLinkChangeData(linkName, prevIssueSummary, null));
save(change);
create(change, null);
}
if (linkedIssue != null) {
IssueChange change = new IssueChange();
@ -681,7 +683,7 @@ public class DefaultIssueChangeManager extends BaseEntityManager<IssueChange>
change.setData(new IssueLinkAddData(linkName, issueSummary));
else
change.setData(new IssueLinkChangeData(linkName, null, issueSummary));
save(change);
create(change, null);
}
}
@ -711,7 +713,7 @@ public class DefaultIssueChangeManager extends BaseEntityManager<IssueChange>
String linkName = spec.getName(opposite);
IssueLinkAddData data = new IssueLinkAddData(linkName, getLinkedIssueInfo(issue, linkedIssue));
change.setData(data);
save(change);
create(change, null);
logLinkedSideChange(spec, issue, null, linkedIssue, opposite);
}
@ -730,7 +732,7 @@ public class DefaultIssueChangeManager extends BaseEntityManager<IssueChange>
String linkName = spec.getName(opposite);
IssueLinkRemoveData data = new IssueLinkRemoveData(linkName, getLinkedIssueInfo(issue, linkedIssue));
change.setData(data);
save(change);
create(change, null);
logLinkedSideChange(spec, issue, linkedIssue, null, opposite);
}

View File

@ -1,25 +1,23 @@
package io.onedev.server.entitymanager.impl;
import java.util.Collection;
import javax.inject.Inject;
import javax.inject.Singleton;
import com.google.common.collect.Lists;
import io.onedev.server.entitymanager.IssueCommentManager;
import io.onedev.server.event.ListenerRegistry;
import io.onedev.server.event.project.issue.IssueCommented;
import io.onedev.server.event.project.issue.IssueCommentCreated;
import io.onedev.server.event.project.issue.IssueCommentDeleted;
import io.onedev.server.event.project.issue.IssueCommentUpdated;
import io.onedev.server.model.IssueComment;
import io.onedev.server.persistence.SessionManager;
import io.onedev.server.persistence.annotation.Transactional;
import io.onedev.server.persistence.dao.BaseEntityManager;
import io.onedev.server.persistence.dao.Dao;
@Singleton
public class DefaultIssueCommentManager extends BaseEntityManager<IssueComment>
implements IssueCommentManager {
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Collection;
@Singleton
public class DefaultIssueCommentManager extends BaseEntityManager<IssueComment> implements IssueCommentManager {
private final ListenerRegistry listenerRegistry;
private final SessionManager sessionManager;
@ -33,35 +31,26 @@ public class DefaultIssueCommentManager extends BaseEntityManager<IssueComment>
@Transactional
@Override
public void save(IssueComment comment) {
save(comment, Lists.newArrayList());
public void update(IssueComment comment) {
dao.persist(comment);
listenerRegistry.post(new IssueCommentUpdated(comment));
}
@Transactional
@Override
public void delete(IssueComment comment) {
super.delete(comment);
dao.remove(comment);
comment.getIssue().setCommentCount(comment.getIssue().getCommentCount()-1);
listenerRegistry.post(new IssueCommentDeleted(comment));
}
@Transactional
@Override
public void save(IssueComment comment, Collection<String> notifiedEmailAddresses) {
boolean isNew = comment.isNew();
public void create(IssueComment comment, Collection<String> notifiedEmailAddresses) {
dao.persist(comment);
if (isNew) {
comment.getIssue().setCommentCount(comment.getIssue().getCommentCount()+1);
Long commentId = comment.getId();
sessionManager.runAsyncAfterCommit(new Runnable() {
@Override
public void run() {
listenerRegistry.post(new IssueCommented(load(commentId), notifiedEmailAddresses));
}
});
}
comment.getIssue().setCommentCount(comment.getIssue().getCommentCount()+1);
listenerRegistry.post(new IssueCommentCreated(comment, notifiedEmailAddresses));
}
}

View File

@ -5,22 +5,13 @@ import com.google.common.collect.Lists;
import com.hazelcast.core.HazelcastInstance;
import edu.emory.mathcs.backport.java.util.Collections;
import io.onedev.commons.loader.ManagedSerializedForm;
import io.onedev.commons.utils.ExplicitException;
import io.onedev.server.attachment.AttachmentManager;
import io.onedev.server.cluster.ClusterManager;
import io.onedev.server.cluster.ClusterRunnable;
import io.onedev.server.cluster.ClusterTask;
import io.onedev.server.entitymanager.*;
import io.onedev.server.entityreference.EntityReferenceManager;
import io.onedev.server.entityreference.ReferenceMigrator;
import io.onedev.server.entityreference.ReferencedFromAware;
import io.onedev.server.event.Listen;
import io.onedev.server.event.ListenerRegistry;
import io.onedev.server.event.entity.EntityRemoved;
import io.onedev.server.event.project.issue.IssueCommitsAttached;
import io.onedev.server.event.project.issue.IssueChanged;
import io.onedev.server.event.project.issue.IssueEvent;
import io.onedev.server.event.project.issue.IssueOpened;
import io.onedev.server.event.project.issue.*;
import io.onedev.server.event.system.SystemStarted;
import io.onedev.server.migration.VersionedXmlDoc;
import io.onedev.server.model.*;
@ -29,7 +20,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.StateSpec;
import io.onedev.server.model.support.issue.changedata.IssueChangeData;
import io.onedev.server.model.support.issue.changedata.IssueProjectChangeData;
import io.onedev.server.model.support.issue.field.spec.FieldSpec;
import io.onedev.server.persistence.SequenceGenerator;
@ -47,7 +37,10 @@ import io.onedev.server.search.entity.issue.IssueQueryParseOption;
import io.onedev.server.search.entity.issue.IssueQueryUpdater;
import io.onedev.server.security.SecurityUtils;
import io.onedev.server.security.permission.AccessProject;
import io.onedev.server.util.*;
import io.onedev.server.util.MilestoneAndIssueState;
import io.onedev.server.util.ProjectIssueStats;
import io.onedev.server.util.ProjectScope;
import io.onedev.server.util.ProjectScopedNumber;
import io.onedev.server.util.criteria.Criteria;
import io.onedev.server.util.validation.ProjectPathValidator;
import io.onedev.server.web.component.issue.workflowreconcile.UndefinedFieldResolution;
@ -55,9 +48,6 @@ import io.onedev.server.web.component.issue.workflowreconcile.UndefinedFieldValu
import io.onedev.server.web.component.issue.workflowreconcile.UndefinedFieldValuesResolution;
import io.onedev.server.web.component.issue.workflowreconcile.UndefinedStateResolution;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutableTriple;
import org.apache.commons.lang3.tuple.Triple;
import org.apache.wicket.util.lang.Objects;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
import org.hibernate.query.Query;
@ -68,7 +58,6 @@ import org.unbescape.java.JavaEscape;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.*;
import java.io.ObjectStreamException;
import java.io.Serializable;
@ -104,10 +93,6 @@ public class DefaultIssueManager extends BaseEntityManager<Issue> implements Iss
private final IssueQueryPersonalizationManager queryPersonalizationManager;
private final AttachmentManager attachmentManager;
private final IssueCommentManager commentManager;
private final SettingManager settingManager;
private final ProjectManager projectManager;
@ -118,8 +103,6 @@ public class DefaultIssueManager extends BaseEntityManager<Issue> implements Iss
private final TransactionManager transactionManager;
private final EntityReferenceManager entityReferenceManager;
private final RoleManager roleManager;
private final LinkSpecManager linkSpecManager;
@ -128,10 +111,6 @@ public class DefaultIssueManager extends BaseEntityManager<Issue> implements Iss
private final ClusterManager clusterManager;
private final IssueChangeManager changeManager;
private final IssueScheduleManager scheduleManager;
private final SequenceGenerator numberGenerator;
private volatile Map<String, Long> issueIds;
@ -141,11 +120,8 @@ public class DefaultIssueManager extends BaseEntityManager<Issue> implements Iss
IssueQueryPersonalizationManager queryPersonalizationManager,
SettingManager settingManager, ListenerRegistry listenerRegistry,
ProjectManager projectManager, UserManager userManager, ClusterManager clusterManager,
RoleManager roleManager, AttachmentManager attachmentStorageManager,
IssueCommentManager commentManager, EntityReferenceManager entityReferenceManager,
LinkSpecManager linkSpecManager, IssueLinkManager linkManager,
IssueAuthorizationManager authorizationManager, IssueChangeManager changeManager,
IssueScheduleManager scheduleManager) {
RoleManager roleManager, LinkSpecManager linkSpecManager, IssueLinkManager linkManager,
IssueAuthorizationManager authorizationManager) {
super(dao);
this.fieldManager = fieldManager;
this.queryPersonalizationManager = queryPersonalizationManager;
@ -157,13 +133,8 @@ public class DefaultIssueManager extends BaseEntityManager<Issue> implements Iss
this.roleManager = roleManager;
this.linkSpecManager = linkSpecManager;
this.linkManager = linkManager;
this.attachmentManager = attachmentStorageManager;
this.commentManager = commentManager;
this.entityReferenceManager = entityReferenceManager;
this.authorizationManager = authorizationManager;
this.clusterManager = clusterManager;
this.changeManager = changeManager;
this.scheduleManager = scheduleManager;
numberGenerator = new SequenceGenerator(Issue.class, clusterManager, dao);
}
@ -186,7 +157,7 @@ public class DefaultIssueManager extends BaseEntityManager<Issue> implements Iss
Long issueId = (Long) fields[0];
Long projectId = (Long)fields[1];
Long issueNumber = (Long) fields[2];
issueIds.put(projectId + ":" + issueNumber, issueId);
issueIds.put(getCacheKey(projectId, issueNumber), issueId);
}
}
@ -233,7 +204,7 @@ public class DefaultIssueManager extends BaseEntityManager<Issue> implements Iss
lastActivity.setDate(issue.getSubmitDate());
issue.setLastActivity(lastActivity);
save(issue);
dao.persist(issue);
fieldManager.saveFields(issue);
for (IssueSchedule schedule: issue.getSchedules())
@ -245,28 +216,10 @@ public class DefaultIssueManager extends BaseEntityManager<Issue> implements Iss
issue.getAuthorizations().add(authorization);
authorizationManager.save(authorization);
updateCacheAfterCommit(Lists.newArrayList(issue));
listenerRegistry.post(new IssueOpened(issue));
}
@Transactional
@Override
public void save(Issue issue) {
super.save(issue);
Long projectId = issue.getProject().getId();
Long issueId = issue.getId();
Long issueNumber = issue.getNumber();
transactionManager.runAfterCommit(new Runnable() {
@Override
public void run() {
issueIds.put(projectId + ":" + issueNumber, issueId);
}
});
}
private List<javax.persistence.criteria.Order> getOrders(List<EntitySort> sorts, CriteriaBuilder builder, Root<Issue> root) {
List<javax.persistence.criteria.Order> orders = new ArrayList<>();
for (EntitySort sort: sorts) {
@ -321,16 +274,7 @@ public class DefaultIssueManager extends BaseEntityManager<Issue> implements Iss
@Transactional
@Listen
public void on(IssueEvent event) {
boolean minorChange = false;
if (event instanceof IssueCommitsAttached) {
minorChange = true;
} else if (event instanceof IssueChanged) {
IssueChangeData changeData = ((IssueChanged)event).getChange().getData();
if (changeData instanceof ReferencedFromAware)
minorChange = true;
}
if (!(event instanceof IssueOpened || minorChange))
if (!(event instanceof IssueOpened || event.isMinor()))
event.getIssue().setLastActivity(event.getLastUpdate());
}
@ -904,19 +848,51 @@ public class DefaultIssueManager extends BaseEntityManager<Issue> implements Iss
@Transactional
@Override
public void delete(Issue issue) {
super.delete(issue);
dao.remove(issue);
Long projectId = issue.getProject().getId();
Long issueNumber = issue.getNumber();
removeFromCacheAfterCommit(Lists.newArrayList(issue));
listenerRegistry.post(new IssuesDeleted(issue.getProject(), Lists.newArrayList(issue)));
}
private String getCacheKey(Issue issue) {
return getCacheKey(issue.getProject().getId(), issue.getNumber());
}
private String getCacheKey(Long projectId, Long issueNumber) {
return projectId + ":" + issueNumber;
}
private void removeFromCacheAfterCommit(Collection<Issue> issues) {
Collection<String> cacheKeysToDelete = new ArrayList<>();
for (Issue issue: issues)
cacheKeysToDelete.add(getCacheKey(issue));
transactionManager.runAfterCommit(new Runnable() {
@Override
public void run() {
issueIds.remove(new ProjectScopedNumber(projectId, issueNumber));
for (var issueKey: cacheKeysToDelete)
issueIds.remove(issueKey);
}
});
}
private void updateCacheAfterCommit(Collection<Issue> issues) {
Map<String, Long> cacheEntriesToUpdate = new HashMap<>();
for (Issue issue: issues)
cacheEntriesToUpdate.put(getCacheKey(issue), issue.getId());
transactionManager.runAfterCommit(new Runnable() {
@Override
public void run() {
for (var entry: cacheEntriesToUpdate.entrySet())
issueIds.put(entry.getKey(), entry.getValue());
}
});
}
@Transactional
@Listen
public void on(EntityRemoved event) {
@ -940,9 +916,16 @@ public class DefaultIssueManager extends BaseEntityManager<Issue> implements Iss
});
}
}
@Listen
@Sessional
public void on(IssuesImported event) {
for (var issueId: event.getIssueIds())
issueIds.put(getCacheKey(dao.load(Issue.class, issueId)), issueId);
}
private Long getIssueId(Long projectId, Long issueNumber) {
return issueIds.get(projectId + ":" + issueNumber);
return issueIds.get(getCacheKey(projectId, issueNumber));
}
@Sessional
@ -992,12 +975,11 @@ public class DefaultIssueManager extends BaseEntityManager<Issue> implements Iss
@Transactional
@Override
public void copy(Collection<Issue> issues, Project targetProject) {
public void copy(Collection<Issue> issues, Project sourceProject, Project targetProject) {
List<Issue> sortedIssues = new ArrayList<>(issues);
Collections.sort(sortedIssues);
Map<Issue, Issue> cloneMapping = new HashMap<>();
Map<Long, Long> numberMapping = new HashMap<>();
List<Triple<Long, String, String>> attachmentGroupInfos = new ArrayList<>();
sortedIssues.forEach(issue -> {
Issue clonedIssue = VersionedXmlDoc.cloneBean(issue);
@ -1009,10 +991,6 @@ public class DefaultIssueManager extends BaseEntityManager<Issue> implements Iss
clonedIssue.setNumber(getNextNumber(numberScope));
cloneMapping.put(issue, clonedIssue);
numberMapping.put(issue.getNumber(), clonedIssue.getNumber());
attachmentGroupInfos.add(new ImmutableTriple<>(
issue.getAttachmentProject().getId(),
issue.getAttachmentGroup(),
clonedIssue.getAttachmentGroup()));
});
cloneMapping.forEach((key, value) -> {
@ -1025,17 +1003,8 @@ public class DefaultIssueManager extends BaseEntityManager<Issue> implements Iss
.migratePrefixed(description, "#");
value.setDescription(description);
}
save(value);
dao.persist(value);
for (IssueSchedule schedule: key.getSchedules()) {
if (schedule.getMilestone().getProject().isSelfOrAncestorOf(targetProject)) {
IssueSchedule clonedSchedule = VersionedXmlDoc.cloneBean(schedule);
clonedSchedule.setId(null);
clonedSchedule.setIssue(value);
dao.persist(clonedSchedule);
}
}
key.getComments().forEach(comment -> {
var clonedComment = VersionedXmlDoc.cloneBean(comment);
clonedComment.setId(null);
@ -1050,13 +1019,6 @@ public class DefaultIssueManager extends BaseEntityManager<Issue> implements Iss
dao.persist(clonedComment);
});
key.getChanges().forEach(change -> {
var clonedChange = VersionedXmlDoc.cloneBean(change);
clonedChange.setId(null);
clonedChange.setIssue(value);
dao.persist(clonedChange);
});
key.getFields().forEach(field -> {
var clonedField = VersionedXmlDoc.cloneBean(field);
clonedField.setId(null);
@ -1093,52 +1055,26 @@ public class DefaultIssueManager extends BaseEntityManager<Issue> implements Iss
}
});
});
Long targetProjectId = targetProject.getId();
transactionManager.runAfterCommit(new ClusterRunnable() {
private static final long serialVersionUID = 1L;
@Override
public void run() {
projectManager.runOnProjectServer(targetProjectId, new ClusterTask<Void>() {
private static final long serialVersionUID = 1L;
@Override
public Void call() throws Exception {
for (var attachmentGroupInfo: attachmentGroupInfos) {
attachmentManager.copyAttachmentGroupTargetLocal(targetProjectId,
attachmentGroupInfo.getRight(), attachmentGroupInfo.getLeft(),
attachmentGroupInfo.getMiddle());
}
return null;
}
});
}
});
updateCacheAfterCommit(cloneMapping.values());
listenerRegistry.post(new IssuesCopied(sourceProject, targetProject, cloneMapping));
}
@Transactional
@Override
public void move(Collection<Issue> issues, Project targetProject) {
List<Pair<Long, String>> attachmentGroupInfos = new ArrayList<>();
public void move(Collection<Issue> issues, Project sourceProject, Project targetProject) {
Map<Long, Long> numberMapping = new HashMap<>();
List<Issue> sortedIssues = new ArrayList<>(issues);
Collections.sort(sortedIssues);
for (Issue issue: sortedIssues) {
attachmentGroupInfos.add(new Pair<>(issue.getAttachmentProject().getId(), issue.getAttachmentGroup()));
if (issue.getDescription() != null) {
issue.setDescription(issue.getDescription().replace(
issue.getAttachmentProject().getId() + "/attachments/" + issue.getAttachmentGroup(),
sourceProject.getId() + "/attachments/" + issue.getAttachmentGroup(),
targetProject.getId() + "/attachments/" + issue.getAttachmentGroup()));
}
for (IssueComment comment: issue.getComments()) {
comment.setContent(comment.getContent().replace(
issue.getAttachmentProject().getId() + "/attachments/" + issue.getAttachmentGroup(),
sourceProject.getId() + "/attachments/" + issue.getAttachmentGroup(),
targetProject.getId() + "/attachments/" + issue.getAttachmentGroup()));
}
@ -1163,7 +1099,7 @@ public class DefaultIssueManager extends BaseEntityManager<Issue> implements Iss
change.setIssue(issue);
change.setUser(SecurityUtils.getUser());
change.setData(new IssueProjectChangeData(oldProject.getPath(), targetProject.getPath()));
changeManager.save(change);
dao.persist(change);
}
for (Issue issue: sortedIssues) {
@ -1175,53 +1111,22 @@ public class DefaultIssueManager extends BaseEntityManager<Issue> implements Iss
for (IssueComment comment: issue.getComments()) {
comment.setContent(new ReferenceMigrator(Issue.class, numberMapping)
.migratePrefixed(comment.getContent(), "#"));
commentManager.save(comment);
dao.persist(comment);
}
save(issue);
dao.persist(issue);
}
Long targetProjectId = targetProject.getId();
transactionManager.runAfterCommit(new ClusterRunnable() {
private static final long serialVersionUID = 1L;
@Override
public void run() {
projectManager.runOnProjectServer(targetProjectId, new ClusterTask<Void>() {
private static final long serialVersionUID = 1L;
@Override
public Void call() throws Exception {
for (var attachmentGroupInfo: attachmentGroupInfos)
attachmentManager.moveAttachmentGroupTargetLocal(targetProjectId, attachmentGroupInfo.getFirst(), attachmentGroupInfo.getSecond());
return null;
}
});
}
});
updateCacheAfterCommit(issues);
listenerRegistry.post(new IssuesMoved(sourceProject, targetProject, issues));
}
@Transactional
@Override
public void saveDescription(Issue issue, @Nullable String description) {
String prevDescription = issue.getDescription();
if (!Objects.equal(description, prevDescription)) {
if (description != null && description.length() > Issue.MAX_DESCRIPTION_LEN)
throw new ExplicitException("Description too long");
issue.setDescription(description);
entityReferenceManager.addReferenceChange(issue, description);
save(issue);
}
}
@Transactional
@Override
public void delete(Collection<Issue> issues) {
public void delete(Collection<Issue> issues, Project project) {
for (Issue issue: issues)
delete(issue);
dao.remove(issue);
removeFromCacheAfterCommit(issues);
listenerRegistry.post(new IssuesDeleted(project, issues));
}
@Transactional

View File

@ -20,6 +20,7 @@ import io.onedev.server.cluster.ProjectServer;
import io.onedev.server.entitymanager.*;
import io.onedev.server.event.Listen;
import io.onedev.server.event.ListenerRegistry;
import io.onedev.server.event.project.ProjectDeleted;
import io.onedev.server.event.entity.EntityPersisted;
import io.onedev.server.event.entity.EntityRemoved;
import io.onedev.server.event.project.ProjectCreated;
@ -416,6 +417,8 @@ public class DefaultProjectManager extends BaseEntityManager<Project>
if (repository != null)
repository.close();
}
listenerRegistry.post(new ProjectDeleted(SecurityUtils.getUser(), new Date(), project));
}
@Override

View File

@ -19,7 +19,7 @@ import io.onedev.server.event.project.codecomment.CodeCommentReplied;
import io.onedev.server.event.project.codecomment.CodeCommentStatusChanged;
import io.onedev.server.event.project.codecomment.CodeCommentUpdated;
import io.onedev.server.event.project.issue.IssueChanged;
import io.onedev.server.event.project.issue.IssueCommented;
import io.onedev.server.event.project.issue.IssueCommentCreated;
import io.onedev.server.event.project.issue.IssueOpened;
import io.onedev.server.event.project.pullrequest.PullRequestChanged;
import io.onedev.server.event.project.pullrequest.PullRequestCommented;
@ -78,7 +78,7 @@ public class DefaultEntityReferenceManager implements EntityReferenceManager {
change.setUser(SecurityUtils.getUser());
change.setIssue(referencedIssue);
referencedIssue.getChanges().add(change);
issueChangeManager.save(change);
issueChangeManager.create(change, null);
}
}
}
@ -139,7 +139,7 @@ public class DefaultEntityReferenceManager implements EntityReferenceManager {
change.setUser(SecurityUtils.getUser());
change.setIssue(referencedIssue);
referencedIssue.getChanges().add(change);
issueChangeManager.save(change);
issueChangeManager.create(change, null);
}
}
}
@ -200,7 +200,7 @@ public class DefaultEntityReferenceManager implements EntityReferenceManager {
change.setUser(SecurityUtils.getUser());
change.setIssue(referencedIssue);
referencedIssue.getChanges().add(change);
issueChangeManager.save(change);
issueChangeManager.create(change, null);
}
}
}
@ -236,7 +236,7 @@ public class DefaultEntityReferenceManager implements EntityReferenceManager {
@Transactional
@Listen
public void on(IssueCommented event) {
public void on(IssueCommentCreated event) {
addReferenceChange(event.getIssue(), event.getComment().getContent());
}

View File

@ -3,9 +3,11 @@ package io.onedev.server.event;
import io.onedev.commons.loader.AppLoader;
import io.onedev.commons.loader.ManagedSerializedForm;
import io.onedev.commons.utils.LockUtils;
import io.onedev.server.cluster.ClusterManager;
import io.onedev.server.cluster.ClusterRunnable;
import io.onedev.server.cluster.ClusterTask;
import io.onedev.server.entitymanager.ProjectManager;
import io.onedev.server.event.project.ProjectDeleted;
import io.onedev.server.event.project.ProjectEvent;
import io.onedev.server.persistence.SessionManager;
import io.onedev.server.persistence.TransactionManager;
@ -29,15 +31,18 @@ public class DefaultListenerRegistry implements ListenerRegistry, Serializable {
private final SessionManager sessionManager;
private final ClusterManager clusterManager;
private volatile Map<Object, Collection<Method>> listenerMethods;
private final Map<Class<?>, List<Listener>> listeners = new ConcurrentHashMap<>();
@Inject
public DefaultListenerRegistry(ProjectManager projectManager,
TransactionManager transactionManager, SessionManager sessionManager) {
public DefaultListenerRegistry(ProjectManager projectManager, ClusterManager clusterManager,
TransactionManager transactionManager, SessionManager sessionManager) {
this.projectManager = projectManager;
this.transactionManager = transactionManager;
this.clusterManager = clusterManager;
this.sessionManager = sessionManager;
}
@ -103,7 +108,7 @@ public class DefaultListenerRegistry implements ListenerRegistry, Serializable {
if (event instanceof ProjectEvent) {
ProjectEvent projectEvent = (ProjectEvent) event;
Long projectId = projectEvent.getProject().getId();
transactionManager.runAfterCommit(new ClusterRunnable() {
private static final long serialVersionUID = 1L;
@ -128,11 +133,11 @@ public class DefaultListenerRegistry implements ListenerRegistry, Serializable {
public void run() {
invokeListeners(event);
}
});
return null;
}
});
} else {
sessionManager.run(new Runnable() {
@ -141,16 +146,49 @@ public class DefaultListenerRegistry implements ListenerRegistry, Serializable {
public void run() {
invokeListeners(event);
}
});
}
return null;
}
});
}
});
} else if (event instanceof ProjectDeleted) {
ProjectDeleted projectDeleted = (ProjectDeleted) event;
Long projectId = projectDeleted.getProjectId();
UUID projectStorageServerUUID = projectManager.getStorageServerUUID(projectId, false);
if (projectStorageServerUUID != null) {
transactionManager.runAfterCommit(new ClusterRunnable() {
private static final long serialVersionUID = 1L;
@Override
public void run() {
clusterManager.submitToServer(projectStorageServerUUID, new ClusterTask<Void>() {
private static final long serialVersionUID = 1L;
@Override
public Void call() throws Exception {
sessionManager.run(new Runnable() {
@Override
public void run() {
invokeListeners(event);
}
});
return null;
}
});
}
});
}
} else {
invokeListeners(event);
}

View File

@ -0,0 +1,39 @@
package io.onedev.server.event.project;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.model.Project;
import io.onedev.server.model.User;
import java.io.Serializable;
import java.util.Date;
public class ProjectDeleted implements Serializable {
private static final long serialVersionUID = 1L;
private final Long projectId;
private final Long userId;
private final Date date;
public ProjectDeleted(User user, Date date, Project project) {
userId = user.getId();
this.date = date;
projectId = project.getId();
}
public User getUser() {
return OneDev.getInstance(UserManager.class).load(userId);
}
public Date getDate() {
return date;
}
public Long getProjectId() {
return projectId;
}
}

View File

@ -113,4 +113,8 @@ public abstract class ProjectEvent implements Serializable {
return null;
}
public boolean isMinor() {
return false;
}
}

View File

@ -72,5 +72,9 @@ public class IssueChanged extends IssueEvent {
public String getUrl() {
return OneDev.getInstance(UrlManager.class).urlFor(getChange());
}
@Override
public boolean isMinor() {
return getChange().isMinor();
}
}

View File

@ -1,7 +1,5 @@
package io.onedev.server.event.project.issue;
import java.util.Collection;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.IssueCommentManager;
import io.onedev.server.entitymanager.UrlManager;
@ -9,7 +7,9 @@ import io.onedev.server.model.IssueComment;
import io.onedev.server.util.commenttext.CommentText;
import io.onedev.server.util.commenttext.MarkdownText;
public class IssueCommented extends IssueEvent {
import java.util.Collection;
public class IssueCommentCreated extends IssueEvent {
private static final long serialVersionUID = 1L;
@ -17,7 +17,7 @@ public class IssueCommented extends IssueEvent {
private final Collection<String> notifiedEmailAddresses;
public IssueCommented(IssueComment comment, Collection<String> notifiedEmailAddresses) {
public IssueCommentCreated(IssueComment comment, Collection<String> notifiedEmailAddresses) {
super(comment.getUser(), comment.getDate(), comment.getIssue());
commentId = comment.getId();
this.notifiedEmailAddresses = notifiedEmailAddresses;
@ -50,5 +50,5 @@ public class IssueCommented extends IssueEvent {
public String getUrl() {
return OneDev.getInstance(UrlManager.class).urlFor(getComment());
}
}

View File

@ -0,0 +1,40 @@
package io.onedev.server.event.project.issue;
import io.onedev.server.model.Issue;
import io.onedev.server.model.IssueComment;
import io.onedev.server.model.User;
import io.onedev.server.security.SecurityUtils;
import java.util.Date;
public class IssueCommentDeleted extends IssueEvent {
private final Long commentId;
private static final long serialVersionUID = 1L;
public IssueCommentDeleted(IssueComment comment) {
super(SecurityUtils.getUser(), new Date(), comment.getIssue());
this.commentId = comment.getId();
}
public Long getCommentId() {
return commentId;
}
@Override
public boolean affectsListing() {
return false;
}
@Override
public boolean isMinor() {
return true;
}
@Override
public String getActivity() {
return "comment deleted";
}
}

View File

@ -0,0 +1,40 @@
package io.onedev.server.event.project.issue;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.IssueCommentManager;
import io.onedev.server.model.IssueComment;
import io.onedev.server.security.SecurityUtils;
import java.util.Date;
public class IssueCommentUpdated extends IssueEvent {
private static final long serialVersionUID = 1L;
private final Long commentId;
public IssueCommentUpdated(IssueComment comment) {
super(SecurityUtils.getUser(), new Date(), comment.getIssue());
this.commentId = comment.getId();
}
public IssueComment getComment() {
return OneDev.getInstance(IssueCommentManager.class).load(commentId);
}
@Override
public boolean affectsListing() {
return false;
}
@Override
public boolean isMinor() {
return true;
}
@Override
public String getActivity() {
return "comment updated";
}
}

View File

@ -21,5 +21,10 @@ public class IssueCommitsAttached extends IssueEvent {
public String getActivity() {
return "commits attached";
}
@Override
public boolean isMinor() {
return true;
}
}

View File

@ -0,0 +1,49 @@
package io.onedev.server.event.project.issue;
import io.onedev.server.OneDev;
import io.onedev.server.event.project.ProjectEvent;
import io.onedev.server.model.Issue;
import io.onedev.server.model.Project;
import io.onedev.server.persistence.dao.Dao;
import io.onedev.server.security.SecurityUtils;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
public class IssuesCopied extends ProjectEvent {
private static final long serialVersionUID = 1L;
private final Long sourceProjectId;
private final Map<Long, Long> issueIdMapping;
public IssuesCopied(Project sourceProject, Project targetProject, Map<Issue, Issue> issueMapping) {
super(SecurityUtils.getUser(), new Date(), targetProject);
sourceProjectId = sourceProject.getId();
issueIdMapping = new HashMap<>();
for (var entry: issueMapping.entrySet())
issueIdMapping.put(entry.getKey().getId(), entry.getValue().getId());
}
public Project getSourceProject() {
return OneDev.getInstance(Dao.class).load(Project.class, sourceProjectId);
}
public Project getTargetProject() {
return getProject();
}
public Map<Long, Long> getIssueIdMapping() {
return issueIdMapping;
}
@Override
public String getActivity() {
return "issues copied";
}
}

View File

@ -0,0 +1,32 @@
package io.onedev.server.event.project.issue;
import io.onedev.server.event.project.ProjectEvent;
import io.onedev.server.model.Issue;
import io.onedev.server.model.Project;
import io.onedev.server.security.SecurityUtils;
import java.util.Collection;
import java.util.Date;
import java.util.stream.Collectors;
public class IssuesDeleted extends ProjectEvent {
private static final long serialVersionUID = 1L;
private final Collection<Long> issueIds;
public IssuesDeleted(Project project, Collection<Issue> issues) {
super(SecurityUtils.getUser(), new Date(), project);
issueIds = issues.stream().map(Issue::getId).collect(Collectors.toSet());
}
public Collection<Long> getIssueIds() {
return issueIds;
}
@Override
public String getActivity() {
return "issues deleted";
}
}

View File

@ -0,0 +1,32 @@
package io.onedev.server.event.project.issue;
import io.onedev.server.event.project.ProjectEvent;
import io.onedev.server.model.Issue;
import io.onedev.server.model.Project;
import io.onedev.server.security.SecurityUtils;
import java.util.Collection;
import java.util.Date;
import java.util.stream.Collectors;
public class IssuesImported extends ProjectEvent {
private static final long serialVersionUID = 1L;
private final Collection<Long> issueIds;
public IssuesImported(Project project, Collection<Issue> issues) {
super(SecurityUtils.getUser(), new Date(), project);
issueIds = issues.stream().map(Issue::getId).collect(Collectors.toSet());
}
public Collection<Long> getIssueIds() {
return issueIds;
}
@Override
public String getActivity() {
return "issues imported";
}
}

View File

@ -0,0 +1,45 @@
package io.onedev.server.event.project.issue;
import io.onedev.server.OneDev;
import io.onedev.server.event.project.ProjectEvent;
import io.onedev.server.model.Issue;
import io.onedev.server.model.Project;
import io.onedev.server.persistence.dao.Dao;
import io.onedev.server.security.SecurityUtils;
import java.util.Collection;
import java.util.Date;
import java.util.stream.Collectors;
public class IssuesMoved extends ProjectEvent {
private static final long serialVersionUID = 1L;
private final Long sourceProjectId;
private final Collection<Long> issueIds;
public IssuesMoved(Project sourceProject, Project targetProject, Collection<Issue> issues) {
super(SecurityUtils.getUser(), new Date(), targetProject);
sourceProjectId = sourceProject.getId();
issueIds = issues.stream().map(Issue::getId).collect(Collectors.toSet());
}
public Project getSourceProject() {
return OneDev.getInstance(Dao.class).load(Project.class, sourceProjectId);
}
public Project getTargetProject() {
return getProject();
}
public Collection<Long> getIssueIds() {
return issueIds;
}
@Override
public String getActivity() {
return "issues moved";
}
}

View File

@ -1,23 +1,6 @@
package io.onedev.server.infomanager;
import java.io.File;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.commons.lang.SerializationUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Preconditions;
import io.onedev.commons.loader.ManagedSerializedForm;
import io.onedev.commons.utils.FileUtils;
import io.onedev.server.cluster.ClusterManager;
@ -28,16 +11,17 @@ import io.onedev.server.entitymanager.IssueManager;
import io.onedev.server.entitymanager.ProjectManager;
import io.onedev.server.event.Listen;
import io.onedev.server.event.entity.EntityPersisted;
import io.onedev.server.event.entity.EntityRemoved;
import io.onedev.server.event.project.ProjectDeleted;
import io.onedev.server.event.project.issue.*;
import io.onedev.server.event.system.SystemStarted;
import io.onedev.server.model.Issue;
import io.onedev.server.model.IssueChange;
import io.onedev.server.model.Project;
import io.onedev.server.model.support.issue.changedata.IssueBatchUpdateData;
import io.onedev.server.model.support.issue.changedata.IssueStateChangeData;
import io.onedev.server.persistence.TransactionManager;
import io.onedev.server.persistence.annotation.Sessional;
import io.onedev.server.persistence.annotation.Transactional;
import io.onedev.server.persistence.dao.Dao;
import io.onedev.server.storage.StorageManager;
import io.onedev.server.util.Day;
import io.onedev.server.util.concurrent.BatchWorkManager;
@ -45,11 +29,20 @@ import io.onedev.server.util.concurrent.BatchWorker;
import io.onedev.server.util.concurrent.Prioritized;
import jetbrains.exodus.ArrayByteIterable;
import jetbrains.exodus.ByteIterable;
import jetbrains.exodus.env.Environment;
import jetbrains.exodus.env.Store;
import jetbrains.exodus.env.Transaction;
import jetbrains.exodus.env.TransactionalComputable;
import jetbrains.exodus.env.TransactionalExecutable;
import jetbrains.exodus.env.*;
import org.apache.commons.lang.SerializationUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.File;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@Singleton
public class DefaultIssueInfoManager extends AbstractMultiEnvironmentManager
@ -87,11 +80,14 @@ public class DefaultIssueInfoManager extends AbstractMultiEnvironmentManager
private final ClusterManager clusterManager;
private final Dao dao;
@Inject
public DefaultIssueInfoManager(TransactionManager transactionManager,
StorageManager storageManager, IssueManager issueManager,
IssueChangeManager issueChangeManager, BatchWorkManager batchWorkManager,
ProjectManager projectManager, ClusterManager clusterManager) {
public DefaultIssueInfoManager(Dao dao, TransactionManager transactionManager,
StorageManager storageManager, IssueManager issueManager,
IssueChangeManager issueChangeManager, BatchWorkManager batchWorkManager,
ProjectManager projectManager, ClusterManager clusterManager) {
this.dao = dao;
this.storageManager = storageManager;
this.issueManager = issueManager;
this.issueChangeManager = issueChangeManager;
@ -294,51 +290,61 @@ public class DefaultIssueInfoManager extends AbstractMultiEnvironmentManager
});
}
@Transactional
@Sessional
@Listen
public void on(EntityRemoved event) {
if (event.getEntity() instanceof Issue) {
Issue issue = (Issue) event.getEntity();
Long projectId = issue.getProject().getId();
Long issueId = issue.getId();
transactionManager.runAfterCommit(new ClusterRunnable() {
public void on(ProjectDeleted event) {
removeEnv(event.getProjectId().toString());
}
private static final long serialVersionUID = 1L;
@Sessional
@Listen
public void on(IssueOpened event) {
batchWorkManager.submit(getBatchWorker(event.getProject().getId()), new Prioritized(PRIORITY));
}
@Override
public void run() {
projectManager.submitToProjectServer(projectId, new ClusterTask<Void>() {
@Sessional
@Listen
public void on(IssuesCopied event) {
batchWorkManager.submit(getBatchWorker(event.getProject().getId()), new Prioritized(PRIORITY));
}
private static final long serialVersionUID = 1L;
@Sessional
@Listen
public void on(IssuesImported event) {
batchWorkManager.submit(getBatchWorker(event.getProject().getId()), new Prioritized(PRIORITY));
}
@Override
public Void call() throws Exception {
remove(projectId, issueId);
return null;
}
});
}
});
} else if (event.getEntity() instanceof Project) {
Long projectId = event.getEntity().getId();
UUID storageServerUUID = projectManager.getStorageServerUUID(projectId, false);
if (storageServerUUID != null) {
clusterManager.runOnServer(storageServerUUID, new ClusterTask<Void>() {
@Sessional
@Listen
public void on(IssueChanged event) {
batchWorkManager.submit(getBatchWorker(event.getProject().getId()), new Prioritized(PRIORITY));
}
@Sessional
@Listen
public void on(IssuesDeleted event) {
for (var issueId: event.getIssueIds())
remove(event.getProject().getId(), issueId);
}
@Sessional
@Listen
public void on(IssuesMoved event) {
Long sourceProjectId = event.getSourceProject().getId();
projectManager.submitToProjectServer(sourceProjectId, new ClusterTask<Void>() {
private static final long serialVersionUID = 1L;
private static final long serialVersionUID = 1L;
@Override
public Void call() throws Exception {
removeEnv(projectId.toString());
return null;
}
});
@Override
public Void call() throws Exception {
event.getIssueIds().forEach(it->remove(sourceProjectId, it));
return null;
}
}
});
batchWorkManager.submit(getBatchWorker(event.getProject().getId()), new Prioritized(PRIORITY));
}
@Sessional

View File

@ -680,7 +680,7 @@ public class DefaultMailManager implements MailManager, Serializable {
// Add double line breaks in the beginning and ending as otherwise plain text content
// received from email may not be formatted correctly with our markdown renderer.
comment.setContent(decorateContent(content));
issueCommentManager.save(comment, receiverEmailAddresses);
issueCommentManager.create(comment, receiverEmailAddresses);
}
}

View File

@ -1,50 +1,25 @@
package io.onedev.server.model;
import java.util.Date;
import javax.annotation.Nullable;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.apache.commons.lang3.StringUtils;
import io.onedev.server.model.support.CompareContext;
import io.onedev.server.model.support.EntityComment;
import javax.persistence.*;
@Entity
@Table(indexes={
@Index(columnList="o_comment_id"), @Index(columnList="o_user_id"),
@Index(columnList="o_pullRequest_id"),
})
public class CodeCommentReply extends AbstractEntity {
public class CodeCommentReply extends EntityComment {
private static final long serialVersionUID = 1L;
public static final int MAX_CONTENT_LEN = 14000;
public static final String PROP_COMPARE_CONTEXT = "compareContext";
public static final String PROP_CONTENT = "content";
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(nullable=false)
private CodeComment comment;
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(nullable=false)
private User user;
@Column(nullable=false)
private Date date;
@Column(nullable=false, length=MAX_CONTENT_LEN)
private String content;
@Embedded
private CompareContext compareContext;
@ -55,36 +30,6 @@ public class CodeCommentReply extends AbstractEntity {
public void setComment(CodeComment comment) {
this.comment = comment;
}
@Nullable
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public String getAnchor() {
return getClass().getSimpleName() + "-" + getId();
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = StringUtils.abbreviate(content, MAX_CONTENT_LEN);
}
public CompareContext getCompareContext() {
return compareContext;
}
@ -93,4 +38,9 @@ public class CodeCommentReply extends AbstractEntity {
this.compareContext = compareContext;
}
@Override
public AbstractEntity getEntity() {
return getComment();
}
}

View File

@ -75,4 +75,8 @@ public class IssueChange extends AbstractEntity {
return data.affectsListing();
}
public boolean isMinor() {
return getData().isMinor();
}
}

View File

@ -1,41 +1,19 @@
package io.onedev.server.model;
import java.util.Date;
import io.onedev.server.model.support.EntityComment;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.apache.commons.lang3.StringUtils;
import javax.persistence.*;
@Entity
@Table(indexes={
@Index(columnList="o_issue_id"), @Index(columnList="o_user_id")})
public class IssueComment extends AbstractEntity {
public class IssueComment extends EntityComment {
private static final long serialVersionUID = 1L;
public static final int MAX_CONTENT_LEN = 15000;
public static final String PROP_CONTENT = "content";
@ManyToOne
@JoinColumn(nullable=false)
private Issue issue;
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(nullable=false)
private User user;
@Column(nullable=false)
private Date date = new Date();
@Column(nullable=false, length=MAX_CONTENT_LEN)
private String content;
public Issue getIssue() {
return issue;
@ -45,32 +23,9 @@ public class IssueComment extends AbstractEntity {
this.issue = issue;
}
public User getUser() {
return user;
@Override
public AbstractEntity getEntity() {
return getIssue();
}
public void setUser(User user) {
this.user = user;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = StringUtils.abbreviate(content, MAX_CONTENT_LEN);
}
public String getAnchor() {
return getClass().getSimpleName() + "-" + getId();
}
}

View File

@ -1,44 +1,19 @@
package io.onedev.server.model;
import java.util.Date;
import io.onedev.server.model.support.EntityComment;
import javax.annotation.Nullable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.apache.commons.lang3.StringUtils;
import javax.persistence.*;
@Entity
@Table(indexes={@Index(columnList="o_request_id"), @Index(columnList="o_user_id")})
public class PullRequestComment extends AbstractEntity {
public class PullRequestComment extends EntityComment {
private static final long serialVersionUID = 1L;
public static final int MAX_CONTENT_LEN = 14000;
public static final int DIFF_CONTEXT_SIZE = 3;
public static final String PROP_CONTENT = "content";
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(nullable=false)
private PullRequest request;
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(nullable=false)
private User user;
@Column(nullable=false, length=MAX_CONTENT_LEN)
private String content;
@Column(nullable=false)
private Date date = new Date();
public PullRequest getRequest() {
return request;
}
@ -47,36 +22,13 @@ public class PullRequestComment extends AbstractEntity {
this.request = request;
}
public User getUser() {
return user;
}
public void setUser(@Nullable User user) {
this.user = user;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = StringUtils.abbreviate(content, MAX_CONTENT_LEN);
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public Project getProject() {
return request.getTargetProject();
}
public String getAnchor() {
return getClass().getSimpleName() + "-" + getId();
@Override
public AbstractEntity getEntity() {
return getRequest();
}
}

View File

@ -0,0 +1,61 @@
package io.onedev.server.model.support;
import io.onedev.server.model.AbstractEntity;
import io.onedev.server.model.Project;
import io.onedev.server.model.User;
import io.onedev.server.util.facade.ProjectBelongingFacade;
import org.apache.commons.lang3.StringUtils;
import javax.persistence.*;
import java.util.Date;
@MappedSuperclass
public abstract class EntityComment extends AbstractEntity {
private static final long serialVersionUID = 1L;
public static final int MAX_CONTENT_LEN = 15000;
public static final String PROP_CONTENT = "content";
@Column(nullable=false)
private Date date = new Date();
@ManyToOne(fetch= FetchType.LAZY)
@JoinColumn(nullable=false)
private User user;
@Column(nullable=false, length=MAX_CONTENT_LEN)
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = StringUtils.abbreviate(content, MAX_CONTENT_LEN);
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public String getAnchor() {
return getClass().getSimpleName() + "-" + getId();
}
public abstract AbstractEntity getEntity();
}

View File

@ -22,6 +22,10 @@ public abstract class IssueChangeData implements Serializable {
public abstract boolean affectsListing();
public boolean isMinor() {
return false;
}
@Nullable
public ActivityDetail getActivityDetail() {
return null;

View File

@ -0,0 +1,63 @@
package io.onedev.server.model.support.issue.changedata;
import io.onedev.server.model.Group;
import io.onedev.server.model.User;
import io.onedev.server.notification.ActivityDetail;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
public class IssueDescriptionChangeData extends IssueChangeData {
private static final long serialVersionUID = 1L;
private final String oldDescription;
private final String newDescription;
public IssueDescriptionChangeData(@Nullable String oldDescription, @Nullable String newDescription) {
if (oldDescription == null)
oldDescription = "";
this.oldDescription = oldDescription;
if (newDescription == null)
newDescription = "";
this.newDescription = newDescription;
}
@Override
public String getActivity() {
return "changed description";
}
@Override
public Map<String, Collection<User>> getNewUsers() {
return new HashMap<>();
}
@Override
public Map<String, Group> getNewGroups() {
return new HashMap<>();
}
@Override
public boolean affectsListing() {
return false;
}
@Override
public boolean isMinor() {
return true;
}
@Override
public ActivityDetail getActivityDetail() {
Map<String, String> oldFieldValues = new HashMap<>();
oldFieldValues.put("Description", oldDescription);
Map<String, String> newFieldValues = new HashMap<>();
newFieldValues.put("Description", newDescription);
return ActivityDetail.compare(oldFieldValues, newFieldValues, true);
}
}

View File

@ -41,6 +41,11 @@ public class IssueReferencedFromCodeCommentData extends IssueChangeData implemen
return new HashMap<>();
}
@Override
public boolean isMinor() {
return true;
}
@Override
public boolean affectsListing() {
return false;

View File

@ -46,6 +46,11 @@ public class IssueReferencedFromIssueData extends IssueChangeData implements Ref
return false;
}
@Override
public boolean isMinor() {
return true;
}
@Override
public Issue getReferencedFrom() {
return OneDev.getInstance(IssueManager.class).get(issueId);

View File

@ -51,6 +51,11 @@ public class IssueReferencedFromPullRequestData extends IssueChangeData implemen
return OneDev.getInstance(PullRequestManager.class).get(requestId);
}
@Override
public boolean isMinor() {
return true;
}
@Override
public ActivityDetail getActivityDetail() {
return ActivityDetail.referencedFrom(getReferencedFrom());

View File

@ -41,6 +41,11 @@ public class IssueTitleChangeData extends IssueChangeData {
return false;
}
@Override
public boolean isMinor() {
return true;
}
@Override
public ActivityDetail getActivityDetail() {
Map<String, String> oldFieldValues = new HashMap<>();

View File

@ -17,7 +17,6 @@ import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import io.onedev.server.entityreference.ReferencedFromAware;
import io.onedev.server.event.Listen;
import io.onedev.server.infomanager.VisitInfoManager;
import io.onedev.server.mail.MailManager;
@ -72,7 +71,7 @@ public class IssueNotificationManager extends AbstractNotificationManager {
@Transactional
@Listen
public void on(IssueEvent event) {
if (event instanceof IssueCommitsAttached)
if (event.isMinor())
return;
Issue issue = event.getIssue();
@ -202,8 +201,8 @@ public class IssueNotificationManager extends AbstractNotificationManager {
}
Collection<String> notifiedEmailAddresses;
if (event instanceof IssueCommented)
notifiedEmailAddresses = ((IssueCommented) event).getNotifiedEmailAddresses();
if (event instanceof IssueCommentCreated)
notifiedEmailAddresses = ((IssueCommentCreated) event).getNotifiedEmailAddresses();
else
notifiedEmailAddresses = new ArrayList<>();
@ -233,37 +232,34 @@ public class IssueNotificationManager extends AbstractNotificationManager {
}
}
if (!(event instanceof IssueChanged)
|| !(((IssueChanged) event).getChange().getData() instanceof ReferencedFromAware)) {
Collection<String> bccEmailAddresses = new HashSet<>();
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())
&& !isNotified(notifiedEmailAddresses, watch.getUser())
&& SecurityUtils.canAccess(watch.getUser().asSubject(), issue)) {
EmailAddress emailAddress = watch.getUser().getPrimaryEmailAddress();
if (emailAddress != null && emailAddress.isVerified())
bccEmailAddresses.add(emailAddress.getValue());
}
}
if (!bccEmailAddresses.isEmpty()) {
String subject = String.format("[Issue %s] (%s) %s",
issue.getFQN(), (event instanceof IssueOpened)?"Opened":"Updated", issue.getTitle());
Unsubscribable unsubscribable = new Unsubscribable(mailManager.getUnsubscribeAddress(issue));
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(),
bccEmailAddresses, subject, htmlBody, textBody,
replyAddress, threadingReferences);
Collection<String> bccEmailAddresses = new HashSet<>();
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())
&& !isNotified(notifiedEmailAddresses, watch.getUser())
&& SecurityUtils.canAccess(watch.getUser().asSubject(), issue)) {
EmailAddress emailAddress = watch.getUser().getPrimaryEmailAddress();
if (emailAddress != null && emailAddress.isVerified())
bccEmailAddresses.add(emailAddress.getValue());
}
}
if (!bccEmailAddresses.isEmpty()) {
String subject = String.format("[Issue %s] (%s) %s",
issue.getFQN(), (event instanceof IssueOpened)?"Opened":"Updated", issue.getTitle());
Unsubscribable unsubscribable = new Unsubscribable(mailManager.getUnsubscribeAddress(issue));
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(),
bccEmailAddresses, subject, htmlBody, textBody,
replyAddress, threadingReferences);
}
}
}

View File

@ -1,24 +1,18 @@
package io.onedev.server.rest;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.validation.constraints.NotNull;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.apache.shiro.authz.UnauthorizedException;
import io.onedev.server.entitymanager.IssueCommentManager;
import io.onedev.server.model.IssueComment;
import io.onedev.server.rest.annotation.Api;
import io.onedev.server.security.SecurityUtils;
import org.apache.shiro.authz.UnauthorizedException;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.validation.constraints.NotNull;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.ArrayList;
@Api(order=2300)
@Path("/issue-comments")
@ -52,7 +46,10 @@ public class IssueCommentResource {
throw new UnauthorizedException();
}
commentManager.save(comment);
if (comment.isNew())
commentManager.create(comment, new ArrayList<>());
else
commentManager.update(comment);
return comment.getId();
}

View File

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

View File

@ -1,30 +1,10 @@
package io.onedev.server.search.entitytext;
import java.io.ObjectStreamException;
import java.util.List;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.LongPoint;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.flexible.core.QueryNodeException;
import org.apache.lucene.queryparser.flexible.standard.StandardQueryParser;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BoostQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.WildcardQuery;
import io.onedev.commons.loader.ManagedSerializedForm;
import io.onedev.server.cluster.ClusterManager;
import io.onedev.server.entitymanager.ProjectManager;
import io.onedev.server.model.CodeComment;
import io.onedev.server.model.CodeCommentReply;
import io.onedev.server.model.Project;
import io.onedev.server.persistence.TransactionManager;
import io.onedev.server.persistence.dao.Dao;
@ -32,6 +12,27 @@ import io.onedev.server.storage.StorageManager;
import io.onedev.server.util.concurrent.BatchWorkManager;
import io.onedev.server.util.lucene.BooleanQueryBuilder;
import io.onedev.server.util.lucene.LuceneUtils;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.LongPoint;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BoostQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.WildcardQuery;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.ObjectStreamException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Singleton
public class DefaultCodeCommentTextManager extends ProjectTextManager<CodeComment>
@ -41,6 +42,8 @@ public class DefaultCodeCommentTextManager extends ProjectTextManager<CodeCommen
private static final String FIELD_COMMENT = "comment";
private static final String FIELD_REPLIES = "replies";
@Inject
public DefaultCodeCommentTextManager(Dao dao, StorageManager storageManager,
BatchWorkManager batchWorkManager, TransactionManager transactionManager,
@ -55,13 +58,18 @@ public class DefaultCodeCommentTextManager extends ProjectTextManager<CodeCommen
@Override
protected int getIndexVersion() {
return 2;
return 3;
}
@Override
protected void addFields(Document document, CodeComment entity) {
document.add(new StringField(FIELD_PATH, entity.getMark().getPath(), Store.NO));
document.add(new TextField(FIELD_COMMENT, entity.getContent(), Store.NO));
StringBuilder builder = new StringBuilder();
for (CodeCommentReply reply: entity.getReplies())
builder.append(reply.getContent()).append("\n");
if (builder.length() != 0)
document.add(new TextField(FIELD_REPLIES, builder.toString(), Store.NO));
}
private Query buildQuery(Project project, String queryString) {
@ -72,12 +80,13 @@ public class DefaultCodeCommentTextManager extends ProjectTextManager<CodeCommen
queryString = LuceneUtils.escape(queryString);
Query pathQuery = new BoostQuery(new WildcardQuery(new Term(FIELD_PATH, "*" + queryString + "*")), 0.75f);
try (Analyzer analyzer = newAnalyzer()) {
contentQueryBuilder.add(pathQuery, Occur.SHOULD);
StandardQueryParser parser = new StandardQueryParser(analyzer);
contentQueryBuilder.add(
new BoostQuery(parser.parse(queryString, FIELD_COMMENT), 0.5f),
Occur.SHOULD);
} catch (QueryNodeException e) {
Map<String, Float> boosts = new HashMap<>();
boosts.put(FIELD_COMMENT, 0.75f);
boosts.put(FIELD_REPLIES, 0.5f);
MultiFieldQueryParser parser = new MultiFieldQueryParser(
new String[] {FIELD_COMMENT, FIELD_REPLIES}, analyzer, boosts);
contentQueryBuilder.add(parser.parse(LuceneUtils.escape(queryString)), Occur.SHOULD);
} catch (ParseException e) {
throw new RuntimeException(e);
}
queryBuilder.add(contentQueryBuilder.build(), Occur.MUST);

View File

@ -1,17 +1,32 @@
package io.onedev.server.search.entitytext;
import java.io.ObjectStreamException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
import io.onedev.commons.loader.ManagedSerializedForm;
import io.onedev.server.cluster.ClusterManager;
import io.onedev.server.cluster.ClusterTask;
import io.onedev.server.entitymanager.IssueFieldManager;
import io.onedev.server.entitymanager.IssueLinkManager;
import io.onedev.server.entitymanager.ProjectManager;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.event.Listen;
import io.onedev.server.event.project.issue.*;
import io.onedev.server.model.Issue;
import io.onedev.server.model.IssueComment;
import io.onedev.server.model.Project;
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.IssueTitleChangeData;
import io.onedev.server.persistence.SessionManager;
import io.onedev.server.persistence.TransactionManager;
import io.onedev.server.persistence.annotation.Sessional;
import io.onedev.server.persistence.dao.Dao;
import io.onedev.server.security.SecurityUtils;
import io.onedev.server.security.permission.AccessProject;
import io.onedev.server.storage.StorageManager;
import io.onedev.server.util.ProjectScope;
import io.onedev.server.util.concurrent.BatchWorkManager;
import io.onedev.server.util.criteria.Criteria;
import io.onedev.server.util.lucene.BooleanQueryBuilder;
import io.onedev.server.util.lucene.LuceneUtils;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field.Store;
@ -24,23 +39,12 @@ import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.BoostQuery;
import org.apache.lucene.search.Query;
import io.onedev.commons.loader.ManagedSerializedForm;
import io.onedev.server.cluster.ClusterManager;
import io.onedev.server.entitymanager.IssueFieldManager;
import io.onedev.server.entitymanager.IssueLinkManager;
import io.onedev.server.entitymanager.ProjectManager;
import io.onedev.server.model.Issue;
import io.onedev.server.model.Project;
import io.onedev.server.persistence.TransactionManager;
import io.onedev.server.persistence.dao.Dao;
import io.onedev.server.security.SecurityUtils;
import io.onedev.server.security.permission.AccessProject;
import io.onedev.server.storage.StorageManager;
import io.onedev.server.util.ProjectScope;
import io.onedev.server.util.concurrent.BatchWorkManager;
import io.onedev.server.util.criteria.Criteria;
import io.onedev.server.util.lucene.BooleanQueryBuilder;
import io.onedev.server.util.lucene.LuceneUtils;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.ObjectStreamException;
import java.util.*;
import java.util.stream.Collectors;
@Singleton
public class DefaultIssueTextManager extends ProjectTextManager<Issue> implements IssueTextManager {
@ -53,19 +57,27 @@ public class DefaultIssueTextManager extends ProjectTextManager<Issue> implement
private static final String FIELD_DESCRIPTION = "description";
private static final String FIELD_COMMENTS = "comments";
private final IssueFieldManager fieldManager;
private final UserManager userManager;
private final IssueLinkManager linkManager;
private final SessionManager sessionManager;
@Inject
public DefaultIssueTextManager(Dao dao, StorageManager storageManager,
BatchWorkManager batchWorkManager, TransactionManager transactionManager,
ProjectManager projectManager, IssueFieldManager fieldManager,
IssueLinkManager linkManager, ClusterManager clusterManager) {
super(dao, storageManager, batchWorkManager, transactionManager,
projectManager, clusterManager);
public DefaultIssueTextManager(Dao dao, StorageManager storageManager, BatchWorkManager batchWorkManager,
TransactionManager transactionManager, ProjectManager projectManager,
IssueFieldManager fieldManager, IssueLinkManager linkManager,
ClusterManager clusterManager, UserManager userManager,
SessionManager sessionManager) {
super(dao, storageManager, batchWorkManager, transactionManager, projectManager, clusterManager);
this.fieldManager = fieldManager;
this.linkManager = linkManager;
this.userManager = userManager;
this.sessionManager = sessionManager;
}
public Object writeReplace() throws ObjectStreamException {
@ -74,7 +86,7 @@ public class DefaultIssueTextManager extends ProjectTextManager<Issue> implement
@Override
protected int getIndexVersion() {
return 3;
return 4;
}
@Override
@ -84,8 +96,92 @@ public class DefaultIssueTextManager extends ProjectTextManager<Issue> implement
document.add(new TextField(FIELD_TITLE, entity.getTitle(), Store.NO));
if (entity.getDescription() != null)
document.add(new TextField(FIELD_DESCRIPTION, entity.getDescription(), Store.NO));
StringBuilder builder = new StringBuilder();
for (IssueComment comment: entity.getComments()) {
if (!comment.getUser().equals(userManager.getSystem()))
builder.append(comment.getContent()).append("\n");
}
if (builder.length() != 0)
document.add(new TextField(FIELD_COMMENTS, builder.toString(), Store.NO));
}
@Sessional
@Listen
public void on(IssueOpened event) {
requestIndexLocal(event.getIssue());
}
@Sessional
@Listen
public void on(IssueChanged event) {
IssueChangeData data = event.getChange().getData();
if (data instanceof IssueTitleChangeData || data instanceof IssueDescriptionChangeData)
requestIndexLocal(event.getIssue());
}
@Sessional
@Listen
public void on(IssueCommentCreated event) {
requestIndexLocal(event.getIssue());
}
@Sessional
@Listen
public void on(IssueCommentUpdated event) {
requestIndexLocal(event.getIssue());
}
@Sessional
@Listen
public void on(IssueCommentDeleted event) {
requestIndexLocal(event.getIssue());
}
@Sessional
@Listen
public void on(IssuesDeleted event) {
deleteEntitiesLocal(event.getIssueIds());
}
@Sessional
@Listen
public void on(IssuesMoved event) {
UUID sourceProjectServerUUID = projectManager.getStorageServerUUID(
event.getSourceProject().getId(), true);
UUID targetProjectServerUUID = projectManager.getStorageServerUUID(
event.getTargetProject().getId(), true);
if (!sourceProjectServerUUID.equals(targetProjectServerUUID)) {
clusterManager.submitToServer(sourceProjectServerUUID, new ClusterTask<Void>() {
private static final long serialVersionUID = 1L;
@Override
public Void call() throws Exception {
deleteEntitiesLocal(event.getIssueIds());
return null;
}
});
}
for (Long issueId: event.getIssueIds())
requestIndexLocal(dao.load(Issue.class, issueId));
}
@Sessional
@Listen
public void on(IssuesCopied event) {
for (Long issueId: event.getIssueIdMapping().values())
requestIndexLocal(dao.load(Issue.class, issueId));
}
@Sessional
@Listen
public void on(IssuesImported event) {
for (Long issueId: event.getIssueIds())
requestIndexLocal(dao.load(Issue.class, issueId));
}
@Nullable
private Query buildQuery(@Nullable ProjectScope projectScope, String queryString) {
BooleanQueryBuilder queryBuilder = new BooleanQueryBuilder();
@ -134,8 +230,9 @@ public class DefaultIssueTextManager extends ProjectTextManager<Issue> implement
Map<String, Float> boosts = new HashMap<>();
boosts.put(FIELD_TITLE, 0.75f);
boosts.put(FIELD_DESCRIPTION, 0.5f);
boosts.put(FIELD_COMMENTS, 0.25f);
MultiFieldQueryParser parser = new MultiFieldQueryParser(
new String[] {FIELD_TITLE, FIELD_DESCRIPTION}, analyzer, boosts);
new String[] {FIELD_TITLE, FIELD_DESCRIPTION, FIELD_COMMENTS}, analyzer, boosts);
contentQueryBuilder.add(parser.parse(LuceneUtils.escape(queryString)), Occur.SHOULD);
} catch (ParseException e) {
throw new RuntimeException(e);

View File

@ -12,6 +12,8 @@ import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.model.PullRequestComment;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field.Store;
@ -49,20 +51,25 @@ public class DefaultPullRequestTextManager extends ProjectTextManager<PullReques
private static final String FIELD_TITLE = "title";
private static final String FIELD_DESCRIPTION = "description";
private static final String FIELD_COMMENTS = "comments";
private final PullRequestReviewManager reviewManager;
private final BuildManager buildManager;
private final UserManager userManager;
@Inject
public DefaultPullRequestTextManager(Dao dao, StorageManager storageManager,
BatchWorkManager batchWorkManager, TransactionManager transactionManager,
ProjectManager projectManager, PullRequestReviewManager reviewManager,
BuildManager buildManager, ClusterManager clusterManager) {
public DefaultPullRequestTextManager(Dao dao, StorageManager storageManager, UserManager userManager,
BatchWorkManager batchWorkManager, TransactionManager transactionManager,
ProjectManager projectManager, PullRequestReviewManager reviewManager,
BuildManager buildManager, ClusterManager clusterManager) {
super(dao, storageManager, batchWorkManager, transactionManager, projectManager,
clusterManager);
this.reviewManager = reviewManager;
this.buildManager = buildManager;
this.userManager = userManager;
}
public Object writeReplace() throws ObjectStreamException {
@ -71,7 +78,7 @@ public class DefaultPullRequestTextManager extends ProjectTextManager<PullReques
@Override
protected int getIndexVersion() {
return 2;
return 3;
}
@Override
@ -80,6 +87,13 @@ public class DefaultPullRequestTextManager extends ProjectTextManager<PullReques
document.add(new TextField(FIELD_TITLE, entity.getTitle(), Store.NO));
if (entity.getDescription() != null)
document.add(new TextField(FIELD_DESCRIPTION, entity.getDescription(), Store.NO));
StringBuilder builder = new StringBuilder();
for (PullRequestComment comment: entity.getComments()) {
if (!comment.getUser().equals(userManager.getSystem()))
builder.append(comment.getContent()).append("\n");
}
if (builder.length() != 0)
document.add(new TextField(FIELD_COMMENTS, builder.toString(), Store.NO));
}
@Nullable
@ -114,8 +128,9 @@ public class DefaultPullRequestTextManager extends ProjectTextManager<PullReques
Map<String, Float> boosts = new HashMap<>();
boosts.put(FIELD_TITLE, 0.75f);
boosts.put(FIELD_DESCRIPTION, 0.5f);
boosts.put(FIELD_COMMENTS, 0.25f);
MultiFieldQueryParser parser = new MultiFieldQueryParser(
new String[] {FIELD_TITLE, FIELD_DESCRIPTION}, analyzer, boosts);
new String[] {FIELD_TITLE, FIELD_DESCRIPTION, FIELD_COMMENTS}, analyzer, boosts);
contentQueryBuilder.add(parser.parse(LuceneUtils.escape(queryString)), Occur.SHOULD);
} catch (ParseException e) {
throw new RuntimeException(e);

View File

@ -1,20 +1,27 @@
package io.onedev.server.search.entitytext;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.annotation.Nullable;
import edu.emory.mathcs.backport.java.util.Collections;
import io.onedev.commons.utils.ExceptionUtils;
import io.onedev.commons.utils.FileUtils;
import io.onedev.commons.utils.WordUtils;
import io.onedev.server.cluster.ClusterManager;
import io.onedev.server.cluster.ClusterTask;
import io.onedev.server.entitymanager.ProjectManager;
import io.onedev.server.event.Listen;
import io.onedev.server.event.project.ProjectDeleted;
import io.onedev.server.event.system.SystemStarted;
import io.onedev.server.event.system.SystemStopping;
import io.onedev.server.model.AbstractEntity;
import io.onedev.server.model.support.ProjectBelonging;
import io.onedev.server.persistence.TransactionManager;
import io.onedev.server.persistence.annotation.Sessional;
import io.onedev.server.persistence.dao.Dao;
import io.onedev.server.persistence.dao.EntityCriteria;
import io.onedev.server.storage.StorageManager;
import io.onedev.server.util.ReflectionUtils;
import io.onedev.server.util.concurrent.BatchWorkManager;
import io.onedev.server.util.concurrent.BatchWorker;
import io.onedev.server.util.concurrent.Prioritized;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.CharArraySet;
import org.apache.lucene.analysis.WordlistLoader;
@ -38,21 +45,11 @@ import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.LongPoint;
import org.apache.lucene.document.StoredField;
import org.apache.lucene.document.StringField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexFormatTooOldException;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.*;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.SearcherManager;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.TotalHitCountCollector;
import org.apache.lucene.search.*;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.IOUtils;
@ -60,32 +57,12 @@ import org.hibernate.criterion.Restrictions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import edu.emory.mathcs.backport.java.util.Collections;
import io.onedev.commons.utils.ExceptionUtils;
import io.onedev.commons.utils.FileUtils;
import io.onedev.commons.utils.WordUtils;
import io.onedev.server.cluster.ClusterManager;
import io.onedev.server.cluster.ClusterRunnable;
import io.onedev.server.cluster.ClusterTask;
import io.onedev.server.entitymanager.ProjectManager;
import io.onedev.server.event.Listen;
import io.onedev.server.event.entity.EntityPersisted;
import io.onedev.server.event.entity.EntityRemoved;
import io.onedev.server.event.system.SystemStarted;
import io.onedev.server.event.system.SystemStopping;
import io.onedev.server.model.AbstractEntity;
import io.onedev.server.model.Project;
import io.onedev.server.model.support.ProjectBelonging;
import io.onedev.server.persistence.TransactionManager;
import io.onedev.server.persistence.annotation.Sessional;
import io.onedev.server.persistence.annotation.Transactional;
import io.onedev.server.persistence.dao.Dao;
import io.onedev.server.persistence.dao.EntityCriteria;
import io.onedev.server.storage.StorageManager;
import io.onedev.server.util.ReflectionUtils;
import io.onedev.server.util.concurrent.BatchWorkManager;
import io.onedev.server.util.concurrent.BatchWorker;
import io.onedev.server.util.concurrent.Prioritized;
import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.*;
public abstract class ProjectTextManager<T extends ProjectBelonging> implements Serializable {
private static final long serialVersionUID = 1L;
@ -136,7 +113,7 @@ public abstract class ProjectTextManager<T extends ProjectBelonging> implements
private final Class<T> entityClass;
private final Dao dao;
protected final Dao dao;
private final StorageManager storageManager;
@ -146,7 +123,7 @@ public abstract class ProjectTextManager<T extends ProjectBelonging> implements
protected final ProjectManager projectManager;
private final ClusterManager clusterManager;
protected final ClusterManager clusterManager;
private volatile SearcherManager searcherManager;
@ -232,137 +209,35 @@ public abstract class ProjectTextManager<T extends ProjectBelonging> implements
}
}
}
protected void deleteEntitiesLocal(Collection<Long> entityIds) {
doWithWriter(new WriterRunnable() {
@Transactional
@Listen
public void on(EntityPersisted event) {
if (entityClass.isAssignableFrom(event.getEntity().getClass())) {
ProjectBelonging projectBelonging = ((ProjectBelonging)event.getEntity());
Long entityId;
if (!event.isNew())
entityId = event.getEntity().getId();
else
entityId = null;
Long projectId = projectBelonging.getProject().getId();
UUID projectServerUUID = projectManager.getStorageServerUUID(projectId, true);
UUID oldProjectServerUUID;
if (projectBelonging.getOldVersion() != null) {
oldProjectServerUUID = projectManager.getStorageServerUUID(
projectBelonging.getOldVersion().getProjectId(), true);
} else {
oldProjectServerUUID = null;
@Override
public void run(IndexWriter writer) throws IOException {
for (Long entityId: entityIds)
writer.deleteDocuments(getTerm(FIELD_ENTITY_ID, String.valueOf(entityId)));
}
transactionManager.runAfterCommit(new ClusterRunnable() {
private static final long serialVersionUID = 1L;
@Override
public void run() {
if (oldProjectServerUUID != null && !oldProjectServerUUID.equals(projectServerUUID)) {
clusterManager.submitToServer(oldProjectServerUUID, new ClusterTask<Void>() {
private static final long serialVersionUID = 1L;
@Override
public Void call() throws Exception {
doWithWriter(new WriterRunnable() {
@Override
public void run(IndexWriter writer) throws IOException {
writer.deleteDocuments(getTerm(FIELD_ENTITY_ID, String.valueOf(entityId)));
}
});
return null;
}
});
}
clusterManager.submitToServer(projectServerUUID, new ClusterTask<Void>() {
private static final long serialVersionUID = 1L;
@Override
public Void call() throws Exception {
batchWorkManager.submit(getBatchWorker(), new IndexWork(INDEXING_PRIORITY, entityId));
return null;
}
});
}
});
}
});
}
@Transactional
@Sessional
@Listen
public void on(EntityRemoved event) {
if (entityClass.isAssignableFrom(event.getEntity().getClass())) {
Long entityId = event.getEntity().getId();
Long projectId = ((ProjectBelonging)event.getEntity()).getProject().getId();
transactionManager.runAfterCommit(new ClusterRunnable() {
private static final long serialVersionUID = 1L;
@Override
public void run() {
projectManager.submitToProjectServer(projectId, new ClusterTask<Void>() {
private static final long serialVersionUID = 1L;
@Override
public Void call() throws Exception {
doWithWriter(new WriterRunnable() {
@Override
public void run(IndexWriter writer) throws IOException {
writer.deleteDocuments(getTerm(FIELD_ENTITY_ID, String.valueOf(entityId)));
}
});
return null;
}
});
}
});
} else if (event.getEntity() instanceof Project) {
Long projectId = event.getEntity().getId();
UUID storageServerUUID = projectManager.getStorageServerUUID(projectId, false);
transactionManager.runAfterCommit(new ClusterRunnable() {
private static final long serialVersionUID = 1L;
@Override
public void run() {
if (storageServerUUID != null) {
clusterManager.submitToServer(storageServerUUID, new ClusterTask<Void>() {
public void on(ProjectDeleted event) {
Long projectId = event.getProjectId();
doWithWriter(new WriterRunnable() {
private static final long serialVersionUID = 1L;
@Override
public void run(IndexWriter writer) throws IOException {
writer.deleteDocuments(LongPoint.newExactQuery(FIELD_PROJECT_ID, projectId));
}
@Override
public Void call() throws Exception {
doWithWriter(new WriterRunnable() {
});
}
@Override
public void run(IndexWriter writer) throws IOException {
writer.deleteDocuments(LongPoint.newExactQuery(FIELD_PROJECT_ID, projectId));
}
});
return null;
}
});
}
}
});
}
protected void requestIndexLocal(T entity) {
batchWorkManager.submit(getBatchWorker(), new IndexWork(INDEXING_PRIORITY, entity.getId()));
}
protected void doWithWriter(WriterRunnable runnable) {
@ -417,7 +292,6 @@ public abstract class ProjectTextManager<T extends ProjectBelonging> implements
@Override
public void doWorks(Collection<Prioritized> works) {
String entityName = WordUtils.uncamel(entityClass.getSimpleName()).toLowerCase();
logger.debug("Indexing {}s...", entityName);
boolean checkNewEntities = false;
Collection<Long> entityIds = new HashSet<>();

View File

@ -82,7 +82,7 @@ public abstract class ChannelNotificationManager<T extends ChannelNotificationSe
@Sessional
@Listen
public void on(IssueEvent event) {
if (!(event instanceof IssueCommitsAttached) && (!(event instanceof IssueChanged) || !(((IssueChanged) event).getChange().getData() instanceof ReferencedFromAware))) {
if (!event.isMinor()) {
Issue issue = event.getIssue();
User user = event.getUser();
@ -94,7 +94,6 @@ public abstract class ChannelNotificationManager<T extends ChannelNotificationSe
postIfApplicable(issueInfo + " " + eventDescription, event);
}
}
@Sessional

View File

@ -1,373 +1,374 @@
package io.onedev.server.web.component.issue.activities;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import javax.servlet.http.Cookie;
import org.apache.wicket.AttributeModifier;
import org.apache.wicket.Component;
import org.apache.wicket.RestartResponseAtInterceptPageException;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink;
import org.apache.wicket.behavior.AttributeAppender;
import org.apache.wicket.behavior.Behavior;
import org.apache.wicket.core.request.handler.IPartialPageRequestHandler;
import org.apache.wicket.feedback.FencedFeedbackPanel;
import org.apache.wicket.markup.head.CssHeaderItem;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.markup.html.panel.Fragment;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.markup.repeater.RepeatingView;
import org.apache.wicket.model.Model;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.http.WebRequest;
import org.apache.wicket.request.http.WebResponse;
import com.google.common.collect.Lists;
import io.onedev.server.OneDev;
import io.onedev.server.attachment.AttachmentSupport;
import io.onedev.server.attachment.ProjectAttachmentSupport;
import io.onedev.server.entitymanager.IssueCommentManager;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.entityreference.ReferencedFromAware;
import io.onedev.server.model.Issue;
import io.onedev.server.model.IssueChange;
import io.onedev.server.model.IssueComment;
import io.onedev.server.model.Project;
import io.onedev.server.model.User;
import io.onedev.server.security.SecurityUtils;
import io.onedev.server.util.facade.UserCache;
import io.onedev.server.web.ajaxlistener.ConfirmLeaveListener;
import io.onedev.server.web.behavior.WebSocketObserver;
import io.onedev.server.web.component.issue.activities.activity.IssueActivity;
import io.onedev.server.web.component.issue.activities.activity.IssueChangeActivity;
import io.onedev.server.web.component.issue.activities.activity.IssueCommentedActivity;
import io.onedev.server.web.component.issue.activities.activity.IssueOpenedActivity;
import io.onedev.server.web.component.project.comment.CommentInput;
import io.onedev.server.web.component.user.ident.Mode;
import io.onedev.server.web.component.user.ident.UserIdentPanel;
import io.onedev.server.web.page.simple.security.LoginPage;
import io.onedev.server.web.util.DeleteCallback;
@SuppressWarnings("serial")
public abstract class IssueActivitiesPanel extends Panel {
private static final String COOKIE_SHOW_COMMENTS = "onedev.server.issue.showComments";
private static final String COOKIE_SHOW_CHANGE_HISTORY = "onedev.server.issue.showChangeHistory";
private RepeatingView activitiesView;
private boolean showComments = true;
private boolean showChangeHistory = true;
public IssueActivitiesPanel(String panelId) {
super(panelId);
WebRequest request = (WebRequest) RequestCycle.get().getRequest();
Cookie cookie = request.getCookie(COOKIE_SHOW_COMMENTS);
if (cookie != null)
showComments = Boolean.valueOf(cookie.getValue());
cookie = request.getCookie(COOKIE_SHOW_CHANGE_HISTORY);
if (cookie != null)
showChangeHistory = Boolean.valueOf(cookie.getValue());
}
@Override
protected void onBeforeRender() {
activitiesView = new RepeatingView("activities");
addOrReplace(activitiesView);
Issue issue = getIssue();
for (IssueActivity activity: getActivities()) {
if (issue.isVisitedAfter(activity.getDate())) {
activitiesView.add(newActivityRow(activitiesView.newChildId(), activity));
} else {
Component row = newActivityRow(activitiesView.newChildId(), activity);
row.add(AttributeAppender.append("class", "new"));
activitiesView.add(row);
}
}
super.onBeforeRender();
}
private List<IssueActivity> getActivities() {
List<IssueActivity> activities = new ArrayList<>();
activities.add(new IssueOpenedActivity(getIssue()));
List<IssueActivity> otherActivities = new ArrayList<>();
if (showChangeHistory) {
for (IssueChange change: getIssue().getChanges()) {
if (change.getData() instanceof ReferencedFromAware) {
ReferencedFromAware<?> referencedFromAware = (ReferencedFromAware<?>) change.getData();
if (referencedFromAware.getReferencedFrom() instanceof Issue) {
Issue issue = (Issue) referencedFromAware.getReferencedFrom();
if (SecurityUtils.canAccess(issue))
otherActivities.add(new IssueChangeActivity(change));
} else if (referencedFromAware.getReferencedFrom() != null) {
otherActivities.add(new IssueChangeActivity(change));
}
} else {
otherActivities.add(new IssueChangeActivity(change));
}
}
}
if (showComments) {
for (IssueComment comment: getIssue().getComments())
otherActivities.add(new IssueCommentedActivity(comment));
}
otherActivities.sort((o1, o2) -> {
if (o1.getDate().getTime()<o2.getDate().getTime())
return -1;
else if (o1.getDate().getTime()>o2.getDate().getTime())
return 1;
else
return 0;
});
activities.addAll(otherActivities);
return activities;
}
private Component newActivityRow(String id, IssueActivity activity) {
WebMarkupContainer row = new WebMarkupContainer(id, Model.of(activity));
row.setOutputMarkupId(true);
String anchor = activity.getAnchor();
if (anchor != null)
row.setMarkupId(anchor);
if (activity.getUser() != null) {
row.add(new UserIdentPanel("avatar", activity.getUser(), Mode.AVATAR));
row.add(AttributeAppender.append("class", "with-avatar"));
} else {
row.add(new WebMarkupContainer("avatar").setVisible(false));
}
row.add(activity.render("content", new DeleteCallback() {
@Override
public void onDelete(AjaxRequestTarget target) {
row.remove();
target.appendJavaScript(String.format("$('#%s').remove();", row.getMarkupId()));
}
}));
row.add(AttributeAppender.append("class", activity.getClass().getSimpleName()));
return row;
}
@Override
protected void onInitialize() {
super.onInitialize();
add(new WebSocketObserver() {
@Override
public void onObservableChanged(IPartialPageRequestHandler handler) {
updateActivities(handler);
}
private void updateActivities(IPartialPageRequestHandler handler) {
@SuppressWarnings("deprecation")
Component prevActivityRow = activitiesView.get(activitiesView.size()-1);
IssueActivity lastActivity = (IssueActivity) prevActivityRow.getDefaultModelObject();
List<IssueActivity> newActivities = new ArrayList<>();
for (IssueActivity activity: getActivities()) {
if (activity.getDate().getTime() > lastActivity.getDate().getTime())
newActivities.add(activity);
}
for (IssueActivity activity: newActivities) {
Component newActivityRow = newActivityRow(activitiesView.newChildId(), activity);
newActivityRow.add(AttributeAppender.append("class", "new"));
activitiesView.add(newActivityRow);
String script = String.format("$(\"<tr id='%s'></tr>\").insertAfter('#%s');",
newActivityRow.getMarkupId(), prevActivityRow.getMarkupId());
handler.prependJavaScript(script);
handler.add(newActivityRow);
prevActivityRow = newActivityRow;
}
}
@Override
public Collection<String> getObservables() {
return Lists.newArrayList(Issue.getWebSocketObservable(getIssue().getId()));
}
});
if (SecurityUtils.getUser() != null) {
Fragment fragment = new Fragment("addComment", "addCommentFrag", this);
fragment.setOutputMarkupId(true);
CommentInput input = new CommentInput("comment", Model.of(""), false) {
@Override
protected AttachmentSupport getAttachmentSupport() {
return new ProjectAttachmentSupport(getProject(), getIssue().getUUID(),
SecurityUtils.canManageIssues(getProject()));
}
@Override
protected Project getProject() {
return getIssue().getProject();
}
@Override
protected List<User> getMentionables() {
UserCache cache = OneDev.getInstance(UserManager.class).cloneCache();
List<User> users = new ArrayList<>(cache.getUsers());
users.sort(cache.comparingDisplayName(getIssue().getParticipants()));
return users;
}
@Override
protected List<Behavior> getInputBehaviors() {
return Lists.newArrayList(AttributeModifier.replace("placeholder", "Leave a comment"));
}
};
input.setRequired(true).setLabel(Model.of("Comment"));
Form<?> form = new Form<Void>("form");
form.add(new FencedFeedbackPanel("feedback", form));
form.add(input);
form.add(new AjaxSubmitLink("save") {
@Override
protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
super.onSubmit(target, form);
String content = input.getModelObject();
if (content.length() > IssueComment.MAX_CONTENT_LEN) {
error("Comment too long");
target.add(form);
} else {
IssueComment comment = new IssueComment();
comment.setContent(content);
comment.setUser(SecurityUtils.getUser());
comment.setDate(new Date());
comment.setIssue(getIssue());
OneDev.getInstance(IssueCommentManager.class).save(comment);
input.clearMarkdown();
target.add(fragment);
}
}
@Override
protected void onError(AjaxRequestTarget target, Form<?> form) {
super.onError(target, form);
target.add(form);
}
});
form.setOutputMarkupId(true);
fragment.add(form);
add(fragment);
} else {
Fragment fragment = new Fragment("addComment", "loginToCommentFrag", this);
fragment.add(new Link<Void>("login") {
@Override
public void onClick() {
throw new RestartResponseAtInterceptPageException(LoginPage.class);
}
});
add(fragment);
}
setOutputMarkupId(true);
}
@Override
public void renderHead(IHeaderResponse response) {
super.renderHead(response);
response.render(CssHeaderItem.forReference(new IssueActivitiesCssResourceReference()));
}
protected abstract Issue getIssue();
public Component renderOptions(String componentId) {
Fragment fragment = new Fragment(componentId, "optionsFrag", this);
fragment.add(new AjaxLink<Void>("showComments") {
@Override
protected void onInitialize() {
super.onInitialize();
if (showComments)
add(AttributeAppender.append("class", "active"));
}
@Override
protected void updateAjaxAttributes(AjaxRequestAttributes attributes) {
super.updateAjaxAttributes(attributes);
attributes.getAjaxCallListeners().add(new ConfirmLeaveListener());
}
@Override
public void onClick(AjaxRequestTarget target) {
showComments = !showComments;
WebResponse response = (WebResponse) RequestCycle.get().getResponse();
Cookie cookie = new Cookie(COOKIE_SHOW_COMMENTS, String.valueOf(showComments));
cookie.setPath("/");
cookie.setMaxAge(Integer.MAX_VALUE);
response.addCookie(cookie);
target.add(IssueActivitiesPanel.this);
target.appendJavaScript(String.format("$('#%s').toggleClass('active');", getMarkupId()));
}
});
fragment.add(new AjaxLink<Void>("showChangeHistory") {
@Override
protected void onInitialize() {
super.onInitialize();
if (showChangeHistory)
add(AttributeAppender.append("class", "active"));
}
@Override
protected void updateAjaxAttributes(AjaxRequestAttributes attributes) {
super.updateAjaxAttributes(attributes);
attributes.getAjaxCallListeners().add(new ConfirmLeaveListener());
}
@Override
public void onClick(AjaxRequestTarget target) {
showChangeHistory = !showChangeHistory;
WebResponse response = (WebResponse) RequestCycle.get().getResponse();
Cookie cookie = new Cookie(COOKIE_SHOW_CHANGE_HISTORY, String.valueOf(showChangeHistory));
cookie.setPath("/");
cookie.setMaxAge(Integer.MAX_VALUE);
response.addCookie(cookie);
target.add(IssueActivitiesPanel.this);
target.appendJavaScript(String.format("$('#%s').toggleClass('active');", getMarkupId()));
}
});
return fragment;
}
}
package io.onedev.server.web.component.issue.activities;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import javax.servlet.http.Cookie;
import io.onedev.server.model.support.issue.changedata.IssueDescriptionChangeData;
import org.apache.wicket.AttributeModifier;
import org.apache.wicket.Component;
import org.apache.wicket.RestartResponseAtInterceptPageException;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink;
import org.apache.wicket.behavior.AttributeAppender;
import org.apache.wicket.behavior.Behavior;
import org.apache.wicket.core.request.handler.IPartialPageRequestHandler;
import org.apache.wicket.feedback.FencedFeedbackPanel;
import org.apache.wicket.markup.head.CssHeaderItem;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.markup.html.panel.Fragment;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.markup.repeater.RepeatingView;
import org.apache.wicket.model.Model;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.http.WebRequest;
import org.apache.wicket.request.http.WebResponse;
import com.google.common.collect.Lists;
import io.onedev.server.OneDev;
import io.onedev.server.attachment.AttachmentSupport;
import io.onedev.server.attachment.ProjectAttachmentSupport;
import io.onedev.server.entitymanager.IssueCommentManager;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.entityreference.ReferencedFromAware;
import io.onedev.server.model.Issue;
import io.onedev.server.model.IssueChange;
import io.onedev.server.model.IssueComment;
import io.onedev.server.model.Project;
import io.onedev.server.model.User;
import io.onedev.server.security.SecurityUtils;
import io.onedev.server.util.facade.UserCache;
import io.onedev.server.web.ajaxlistener.ConfirmLeaveListener;
import io.onedev.server.web.behavior.WebSocketObserver;
import io.onedev.server.web.component.issue.activities.activity.IssueActivity;
import io.onedev.server.web.component.issue.activities.activity.IssueChangeActivity;
import io.onedev.server.web.component.issue.activities.activity.IssueCommentedActivity;
import io.onedev.server.web.component.issue.activities.activity.IssueOpenedActivity;
import io.onedev.server.web.component.project.comment.CommentInput;
import io.onedev.server.web.component.user.ident.Mode;
import io.onedev.server.web.component.user.ident.UserIdentPanel;
import io.onedev.server.web.page.simple.security.LoginPage;
import io.onedev.server.web.util.DeleteCallback;
@SuppressWarnings("serial")
public abstract class IssueActivitiesPanel extends Panel {
private static final String COOKIE_SHOW_COMMENTS = "onedev.server.issue.showComments";
private static final String COOKIE_SHOW_CHANGE_HISTORY = "onedev.server.issue.showChangeHistory";
private RepeatingView activitiesView;
private boolean showComments = true;
private boolean showChangeHistory = true;
public IssueActivitiesPanel(String panelId) {
super(panelId);
WebRequest request = (WebRequest) RequestCycle.get().getRequest();
Cookie cookie = request.getCookie(COOKIE_SHOW_COMMENTS);
if (cookie != null)
showComments = Boolean.valueOf(cookie.getValue());
cookie = request.getCookie(COOKIE_SHOW_CHANGE_HISTORY);
if (cookie != null)
showChangeHistory = Boolean.valueOf(cookie.getValue());
}
@Override
protected void onBeforeRender() {
activitiesView = new RepeatingView("activities");
addOrReplace(activitiesView);
Issue issue = getIssue();
for (IssueActivity activity: getActivities()) {
if (issue.isVisitedAfter(activity.getDate())) {
activitiesView.add(newActivityRow(activitiesView.newChildId(), activity));
} else {
Component row = newActivityRow(activitiesView.newChildId(), activity);
row.add(AttributeAppender.append("class", "new"));
activitiesView.add(row);
}
}
super.onBeforeRender();
}
private List<IssueActivity> getActivities() {
List<IssueActivity> activities = new ArrayList<>();
activities.add(new IssueOpenedActivity(getIssue()));
List<IssueActivity> otherActivities = new ArrayList<>();
if (showChangeHistory) {
for (IssueChange change: getIssue().getChanges()) {
if (change.getData() instanceof ReferencedFromAware) {
ReferencedFromAware<?> referencedFromAware = (ReferencedFromAware<?>) change.getData();
if (referencedFromAware.getReferencedFrom() instanceof Issue) {
Issue issue = (Issue) referencedFromAware.getReferencedFrom();
if (SecurityUtils.canAccess(issue))
otherActivities.add(new IssueChangeActivity(change));
} else if (referencedFromAware.getReferencedFrom() != null) {
otherActivities.add(new IssueChangeActivity(change));
}
} else if (!(change.getData() instanceof IssueDescriptionChangeData)) {
otherActivities.add(new IssueChangeActivity(change));
}
}
}
if (showComments) {
for (IssueComment comment: getIssue().getComments())
otherActivities.add(new IssueCommentedActivity(comment));
}
otherActivities.sort((o1, o2) -> {
if (o1.getDate().getTime()<o2.getDate().getTime())
return -1;
else if (o1.getDate().getTime()>o2.getDate().getTime())
return 1;
else
return 0;
});
activities.addAll(otherActivities);
return activities;
}
private Component newActivityRow(String id, IssueActivity activity) {
WebMarkupContainer row = new WebMarkupContainer(id, Model.of(activity));
row.setOutputMarkupId(true);
String anchor = activity.getAnchor();
if (anchor != null)
row.setMarkupId(anchor);
if (activity.getUser() != null) {
row.add(new UserIdentPanel("avatar", activity.getUser(), Mode.AVATAR));
row.add(AttributeAppender.append("class", "with-avatar"));
} else {
row.add(new WebMarkupContainer("avatar").setVisible(false));
}
row.add(activity.render("content", new DeleteCallback() {
@Override
public void onDelete(AjaxRequestTarget target) {
row.remove();
target.appendJavaScript(String.format("$('#%s').remove();", row.getMarkupId()));
}
}));
row.add(AttributeAppender.append("class", activity.getClass().getSimpleName()));
return row;
}
@Override
protected void onInitialize() {
super.onInitialize();
add(new WebSocketObserver() {
@Override
public void onObservableChanged(IPartialPageRequestHandler handler) {
updateActivities(handler);
}
private void updateActivities(IPartialPageRequestHandler handler) {
@SuppressWarnings("deprecation")
Component prevActivityRow = activitiesView.get(activitiesView.size()-1);
IssueActivity lastActivity = (IssueActivity) prevActivityRow.getDefaultModelObject();
List<IssueActivity> newActivities = new ArrayList<>();
for (IssueActivity activity: getActivities()) {
if (activity.getDate().getTime() > lastActivity.getDate().getTime())
newActivities.add(activity);
}
for (IssueActivity activity: newActivities) {
Component newActivityRow = newActivityRow(activitiesView.newChildId(), activity);
newActivityRow.add(AttributeAppender.append("class", "new"));
activitiesView.add(newActivityRow);
String script = String.format("$(\"<tr id='%s'></tr>\").insertAfter('#%s');",
newActivityRow.getMarkupId(), prevActivityRow.getMarkupId());
handler.prependJavaScript(script);
handler.add(newActivityRow);
prevActivityRow = newActivityRow;
}
}
@Override
public Collection<String> getObservables() {
return Lists.newArrayList(Issue.getWebSocketObservable(getIssue().getId()));
}
});
if (SecurityUtils.getUser() != null) {
Fragment fragment = new Fragment("addComment", "addCommentFrag", this);
fragment.setOutputMarkupId(true);
CommentInput input = new CommentInput("comment", Model.of(""), false) {
@Override
protected AttachmentSupport getAttachmentSupport() {
return new ProjectAttachmentSupport(getProject(), getIssue().getUUID(),
SecurityUtils.canManageIssues(getProject()));
}
@Override
protected Project getProject() {
return getIssue().getProject();
}
@Override
protected List<User> getMentionables() {
UserCache cache = OneDev.getInstance(UserManager.class).cloneCache();
List<User> users = new ArrayList<>(cache.getUsers());
users.sort(cache.comparingDisplayName(getIssue().getParticipants()));
return users;
}
@Override
protected List<Behavior> getInputBehaviors() {
return Lists.newArrayList(AttributeModifier.replace("placeholder", "Leave a comment"));
}
};
input.setRequired(true).setLabel(Model.of("Comment"));
Form<?> form = new Form<Void>("form");
form.add(new FencedFeedbackPanel("feedback", form));
form.add(input);
form.add(new AjaxSubmitLink("save") {
@Override
protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
super.onSubmit(target, form);
String content = input.getModelObject();
if (content.length() > IssueComment.MAX_CONTENT_LEN) {
error("Comment too long");
target.add(form);
} else {
IssueComment comment = new IssueComment();
comment.setContent(content);
comment.setUser(SecurityUtils.getUser());
comment.setDate(new Date());
comment.setIssue(getIssue());
OneDev.getInstance(IssueCommentManager.class).create(comment, new ArrayList<>());
input.clearMarkdown();
target.add(fragment);
}
}
@Override
protected void onError(AjaxRequestTarget target, Form<?> form) {
super.onError(target, form);
target.add(form);
}
});
form.setOutputMarkupId(true);
fragment.add(form);
add(fragment);
} else {
Fragment fragment = new Fragment("addComment", "loginToCommentFrag", this);
fragment.add(new Link<Void>("login") {
@Override
public void onClick() {
throw new RestartResponseAtInterceptPageException(LoginPage.class);
}
});
add(fragment);
}
setOutputMarkupId(true);
}
@Override
public void renderHead(IHeaderResponse response) {
super.renderHead(response);
response.render(CssHeaderItem.forReference(new IssueActivitiesCssResourceReference()));
}
protected abstract Issue getIssue();
public Component renderOptions(String componentId) {
Fragment fragment = new Fragment(componentId, "optionsFrag", this);
fragment.add(new AjaxLink<Void>("showComments") {
@Override
protected void onInitialize() {
super.onInitialize();
if (showComments)
add(AttributeAppender.append("class", "active"));
}
@Override
protected void updateAjaxAttributes(AjaxRequestAttributes attributes) {
super.updateAjaxAttributes(attributes);
attributes.getAjaxCallListeners().add(new ConfirmLeaveListener());
}
@Override
public void onClick(AjaxRequestTarget target) {
showComments = !showComments;
WebResponse response = (WebResponse) RequestCycle.get().getResponse();
Cookie cookie = new Cookie(COOKIE_SHOW_COMMENTS, String.valueOf(showComments));
cookie.setPath("/");
cookie.setMaxAge(Integer.MAX_VALUE);
response.addCookie(cookie);
target.add(IssueActivitiesPanel.this);
target.appendJavaScript(String.format("$('#%s').toggleClass('active');", getMarkupId()));
}
});
fragment.add(new AjaxLink<Void>("showChangeHistory") {
@Override
protected void onInitialize() {
super.onInitialize();
if (showChangeHistory)
add(AttributeAppender.append("class", "active"));
}
@Override
protected void updateAjaxAttributes(AjaxRequestAttributes attributes) {
super.updateAjaxAttributes(attributes);
attributes.getAjaxCallListeners().add(new ConfirmLeaveListener());
}
@Override
public void onClick(AjaxRequestTarget target) {
showChangeHistory = !showChangeHistory;
WebResponse response = (WebResponse) RequestCycle.get().getResponse();
Cookie cookie = new Cookie(COOKIE_SHOW_CHANGE_HISTORY, String.valueOf(showChangeHistory));
cookie.setPath("/");
cookie.setMaxAge(Integer.MAX_VALUE);
response.addCookie(cookie);
target.add(IssueActivitiesPanel.this);
target.appendJavaScript(String.format("$('#%s').toggleClass('active');", getMarkupId()));
}
});
return fragment;
}
}

View File

@ -1,16 +1,15 @@
package io.onedev.server.web.component.issue.activities.activity;
import java.util.Date;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.LoadableDetachableModel;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.IssueCommentManager;
import io.onedev.server.model.IssueComment;
import io.onedev.server.model.User;
import io.onedev.server.web.util.DeleteCallback;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.LoadableDetachableModel;
import java.util.Date;
@SuppressWarnings("serial")
public class IssueCommentedActivity implements IssueActivity {

View File

@ -67,7 +67,7 @@ class IssueCommentedPanel extends GenericPanel<IssueComment> {
if (comment.length() > IssueComment.MAX_CONTENT_LEN)
throw new ExplicitException("Comment too long");
IssueCommentedPanel.this.getComment().setContent(comment);
OneDev.getInstance(IssueCommentManager.class).save(IssueCommentedPanel.this.getComment());
OneDev.getInstance(IssueCommentManager.class).update(IssueCommentedPanel.this.getComment());
}
@Override

View File

@ -3,6 +3,7 @@ package io.onedev.server.web.component.issue.activities.activity;
import java.util.ArrayList;
import java.util.List;
import io.onedev.server.entitymanager.IssueChangeManager;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.behavior.AttributeAppender;
import org.apache.wicket.markup.html.basic.Label;
@ -49,7 +50,7 @@ class IssueOpenedPanel extends GenericPanel<Issue> {
@Override
protected void onSaveComment(AjaxRequestTarget target, String comment) {
OneDev.getInstance(IssueManager.class).saveDescription(getIssue(), comment);
OneDev.getInstance(IssueChangeManager.class).changeDescription(getIssue(), comment);
}
@Override

View File

@ -675,7 +675,7 @@ public abstract class IssueListPanel extends Panel {
Collection<Issue> issues = new ArrayList<>();
for (IModel<Issue> each: selectionColumn.getSelections())
issues.add(each.getObject());
OneDev.getInstance(IssueManager.class).move(issues, getTargetProject());
OneDev.getInstance(IssueManager.class).move(issues, getProject(), getTargetProject());
setResponsePage(ProjectIssueListPage.class,
ProjectIssueListPage.paramsOf(getTargetProject(), null, 0));
Session.get().success("Issues moved");
@ -757,7 +757,7 @@ public abstract class IssueListPanel extends Panel {
Collection<Issue> issues = new ArrayList<>();
for (IModel<Issue> each: selectionColumn.getSelections())
issues.add(each.getObject());
OneDev.getInstance(IssueManager.class).copy(issues, getTargetProject());
OneDev.getInstance(IssueManager.class).copy(issues, getProject(), getTargetProject());
setResponsePage(ProjectIssueListPage.class,
ProjectIssueListPage.paramsOf(getTargetProject(), null, 0));
Session.get().success("Issues copied");
@ -821,7 +821,7 @@ public abstract class IssueListPanel extends Panel {
Collection<Issue> issues = new ArrayList<>();
for (IModel<Issue> each: selectionColumn.getSelections())
issues.add(each.getObject());
OneDev.getInstance(IssueManager.class).delete(issues);
OneDev.getInstance(IssueManager.class).delete(issues, getProject());
selectionColumn.getSelections().clear();
target.add(body);
}
@ -978,7 +978,7 @@ public abstract class IssueListPanel extends Panel {
for (Iterator<Issue> it = (Iterator<Issue>) dataProvider.iterator(0, issuesTable.getItemCount()); it.hasNext();) {
issues.add(it.next());
}
OneDev.getInstance(IssueManager.class).move(issues, getTargetProject());
OneDev.getInstance(IssueManager.class).move(issues, getProject(), getTargetProject());
setResponsePage(ProjectIssueListPage.class,
ProjectIssueListPage.paramsOf(getTargetProject(), null, 0));
Session.get().success("Issues moved");
@ -1062,7 +1062,7 @@ public abstract class IssueListPanel extends Panel {
for (Iterator<Issue> it = (Iterator<Issue>) dataProvider.iterator(0, issuesTable.getItemCount()); it.hasNext();) {
issues.add(it.next());
}
OneDev.getInstance(IssueManager.class).copy(issues, getTargetProject());
OneDev.getInstance(IssueManager.class).copy(issues, getProject(), getTargetProject());
setResponsePage(ProjectIssueListPage.class,
ProjectIssueListPage.paramsOf(getTargetProject(), null, 0));
Session.get().success("Issues copied");
@ -1128,7 +1128,7 @@ public abstract class IssueListPanel extends Panel {
Collection<Issue> issues = new ArrayList<>();
for (Iterator<Issue> it = (Iterator<Issue>) dataProvider.iterator(0, issuesTable.getItemCount()); it.hasNext();)
issues.add(it.next());
OneDev.getInstance(IssueManager.class).delete(issues);
OneDev.getInstance(IssueManager.class).delete(issues, getProject());
dataProvider.detach();
selectionColumn.getSelections().clear();
target.add(body);

View File

@ -1,29 +1,6 @@
package io.onedev.server.plugin.imports.bitbucketcloud;
import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.validation.ConstraintValidatorContext;
import javax.validation.constraints.NotEmpty;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response;
import org.apache.http.client.utils.URIBuilder;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.JsonNode;
import io.onedev.commons.bootstrap.SensitiveMasker;
import io.onedev.commons.utils.ExplicitException;
import io.onedev.commons.utils.StringUtils;
@ -39,6 +16,26 @@ import io.onedev.server.util.validation.Validatable;
import io.onedev.server.util.validation.annotation.ClassValidating;
import io.onedev.server.web.editable.annotation.Editable;
import io.onedev.server.web.editable.annotation.Password;
import org.apache.http.client.utils.URIBuilder;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.validation.ConstraintValidatorContext;
import javax.validation.constraints.NotEmpty;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response;
import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Editable
@ClassValidating
@ -188,7 +185,8 @@ public class ImportServer implements Serializable, Validatable {
if (!isPrivate && option.getPublicRole() != null)
project.setDefaultRole(option.getPublicRole());
if (project.isNew() || project.getDefaultBranch() == null) {
boolean newlyCreated = project.isNew();
if (newlyCreated || project.getDefaultBranch() == null) {
logger.log("Cloning code from repository " + projectMapping.getBitbucketRepo() + "...");
String cloneUrl = null;
@ -216,7 +214,7 @@ public class ImportServer implements Serializable, Validatable {
if (dryRun) {
new LsRemoteCommand(builder.build().toString()).refs("HEAD").quiet(true).run();
} else {
if (project.isNew())
if (newlyCreated)
projectManager.create(project);
projectManager.clone(project, builder.build().toString());
}
@ -227,7 +225,6 @@ public class ImportServer implements Serializable, Validatable {
logger.warning("Skipping code clone as the project already has code");
}
}
return "Repositories imported successfully";
} catch (URISyntaxException e) {
throw new RuntimeException(e);

View File

@ -1,19 +1,18 @@
package io.onedev.server.plugin.imports.gitea;
import com.google.common.collect.Lists;
import io.onedev.commons.utils.TaskLogger;
import io.onedev.server.imports.IssueImporter;
import io.onedev.server.model.Project;
import io.onedev.server.model.User;
import io.onedev.server.web.util.ImportStep;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import com.google.common.collect.Lists;
import io.onedev.commons.utils.TaskLogger;
import io.onedev.server.imports.IssueImporter;
import io.onedev.server.model.Project;
import io.onedev.server.model.User;
import io.onedev.server.web.util.ImportStep;
public class GiteaIssueImporter implements IssueImporter {
private static final long serialVersionUID = 1L;
@ -83,8 +82,8 @@ public class GiteaIssueImporter implements IssueImporter {
logger.log("Importing issues from repository " + giteaRepo + "...");
Map<String, Optional<User>> users = new HashMap<>();
return server.importIssues(giteaRepo, project, option, users, dryRun, logger)
.toHtml("Issues imported successfully");
ImportResult result = server.importIssues(giteaRepo, project, option, users, dryRun, logger);
return result.toHtml("Issues imported successfully");
}
@Override

View File

@ -6,6 +6,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import io.onedev.server.model.Issue;
import org.unbescape.html.HtmlEscape;
public class ImportResult {
@ -18,7 +19,7 @@ public class ImportResult {
Set<String> nonExistentMilestones = new HashSet<>();
boolean issuesImported = false;
Set<Issue> importedIssues = new HashSet<>();
private String getEntryFeedback(String entryDescription, Collection<String> entries) {
if (entries.size() > MAX_DISPLAY_ENTRIES) {
@ -35,7 +36,7 @@ public class ImportResult {
boolean hasNotice = false;
if (!nonExistentMilestones.isEmpty() || !unmappedIssueLabels.isEmpty()
|| !nonExistentLogins.isEmpty() || issuesImported) {
|| !nonExistentLogins.isEmpty() || !importedIssues.isEmpty()) {
hasNotice = true;
}
@ -51,7 +52,7 @@ public class ImportResult {
nonExistentLogins));
}
if (issuesImported) {
if (!importedIssues.isEmpty()) {
feedback.append("<li> Attachments in issue description and comments are not imported as Gitea does not "
+ "provide attachment api currently");
feedback.append("<li> Issue dependencies are not imported as Gitea does not "

View File

@ -1,62 +1,18 @@
package io.onedev.server.plugin.imports.gitea;
import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Nullable;
import javax.validation.ConstraintValidatorContext;
import javax.validation.constraints.NotEmpty;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response;
import org.apache.http.client.utils.URIBuilder;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.client.oauth2.OAuth2ClientSupport;
import org.joda.time.format.ISODateTimeFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.unbescape.html.HtmlEscape;
import com.fasterxml.jackson.databind.JsonNode;
import edu.emory.mathcs.backport.java.util.Collections;
import io.onedev.commons.bootstrap.SensitiveMasker;
import io.onedev.commons.utils.ExplicitException;
import io.onedev.commons.utils.StringUtils;
import io.onedev.commons.utils.TaskLogger;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.IssueManager;
import io.onedev.server.entitymanager.MilestoneManager;
import io.onedev.server.entitymanager.ProjectManager;
import io.onedev.server.entitymanager.SettingManager;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.entitymanager.*;
import io.onedev.server.entityreference.ReferenceMigrator;
import io.onedev.server.event.ListenerRegistry;
import io.onedev.server.event.project.issue.IssuesImported;
import io.onedev.server.git.command.LsRemoteCommand;
import io.onedev.server.model.Issue;
import io.onedev.server.model.IssueComment;
import io.onedev.server.model.IssueField;
import io.onedev.server.model.IssueSchedule;
import io.onedev.server.model.Milestone;
import io.onedev.server.model.Project;
import io.onedev.server.model.User;
import io.onedev.server.model.*;
import io.onedev.server.model.support.LastActivity;
import io.onedev.server.model.support.administration.GlobalIssueSetting;
import io.onedev.server.model.support.inputspec.InputSpec;
@ -70,6 +26,29 @@ import io.onedev.server.util.validation.Validatable;
import io.onedev.server.util.validation.annotation.ClassValidating;
import io.onedev.server.web.editable.annotation.Editable;
import io.onedev.server.web.editable.annotation.Password;
import org.apache.http.client.utils.URIBuilder;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.client.oauth2.OAuth2ClientSupport;
import org.joda.time.format.ISODateTimeFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.unbescape.html.HtmlEscape;
import javax.annotation.Nullable;
import javax.validation.ConstraintValidatorContext;
import javax.validation.constraints.NotEmpty;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response;
import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
@Editable
@ClassValidating
@ -460,7 +439,7 @@ public class ImportServer implements Serializable, Validatable {
if (issue.getDescription() != null)
issue.setDescription(migrator.migratePrefixed(issue.getDescription(), "#"));
issueManager.save(issue);
dao.persist(issue);
for (IssueSchedule schedule: issue.getSchedules())
dao.persist(schedule);
for (IssueField field: issue.getFields())
@ -476,9 +455,10 @@ public class ImportServer implements Serializable, Validatable {
result.nonExistentLogins.addAll(nonExistentLogins);
result.nonExistentMilestones.addAll(nonExistentMilestones);
result.unmappedIssueLabels.addAll(unmappedIssueLabels);
result.importedIssues.addAll(issues);
if (numOfImportedIssues.get() != 0)
result.issuesImported = true;
if (!dryRun && !issues.isEmpty())
OneDev.getInstance(ListenerRegistry.class).post(new IssuesImported(oneDevProject, issues));
return result;
} finally {
@ -565,8 +545,8 @@ public class ImportServer implements Serializable, Validatable {
result.nonExistentLogins.addAll(currentResult.nonExistentLogins);
result.nonExistentMilestones.addAll(currentResult.nonExistentMilestones);
result.unmappedIssueLabels.addAll(currentResult.unmappedIssueLabels);
result.issuesImported |= currentResult.issuesImported;
}
result.importedIssues.addAll(currentResult.importedIssues);
}
}
return result.toHtml("Repositories imported successfully");

View File

@ -1,19 +1,19 @@
package io.onedev.server.plugin.imports.github;
import com.google.common.collect.Lists;
import io.onedev.commons.utils.TaskLogger;
import io.onedev.server.event.project.issue.IssuesImported;
import io.onedev.server.imports.IssueImporter;
import io.onedev.server.model.Project;
import io.onedev.server.model.User;
import io.onedev.server.web.util.ImportStep;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import com.google.common.collect.Lists;
import io.onedev.commons.utils.TaskLogger;
import io.onedev.server.imports.IssueImporter;
import io.onedev.server.model.Project;
import io.onedev.server.model.User;
import io.onedev.server.web.util.ImportStep;
public class GitHubIssueImporter implements IssueImporter {
private static final long serialVersionUID = 1L;
@ -79,7 +79,7 @@ public class GitHubIssueImporter implements IssueImporter {
logger.log("Importing issues from repository " + repositoryStep.getSetting().getRepository() + "...");
Map<String, Optional<User>> users = new HashMap<>();
ImportResult result = serverStep.getSetting().importIssues(repositoryStep.getSetting().getRepository(),
ImportResult result = serverStep.getSetting().importIssues(repositoryStep.getSetting().getRepository(),
project, optionStep.getSetting(), users, dryRun, logger);
return result.toHtml("Issues imported successfully");
}

View File

@ -6,6 +6,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import io.onedev.server.model.Issue;
import org.unbescape.html.HtmlEscape;
public class ImportResult {
@ -18,7 +19,7 @@ public class ImportResult {
Set<String> nonExistentMilestones = new HashSet<>();
boolean issuesImported = false;
Set<Issue> importedIssues = new HashSet<>();
private String getEntryFeedback(String entryDescription, Collection<String> entries) {
if (entries.size() > MAX_DISPLAY_ENTRIES) {
@ -35,7 +36,7 @@ public class ImportResult {
boolean hasNotice = false;
if (!nonExistentMilestones.isEmpty() || !unmappedIssueLabels.isEmpty()
|| !nonExistentLogins.isEmpty() || issuesImported) {
|| !nonExistentLogins.isEmpty() || !importedIssues.isEmpty()) {
hasNotice = true;
}
@ -53,7 +54,7 @@ public class ImportResult {
nonExistentLogins));
}
if (issuesImported)
if (!importedIssues.isEmpty())
feedback.append("<li> Attachments in issue description and comments are not imported due to GitHub limitation");
if (hasNotice)

View File

@ -9,6 +9,8 @@ import io.onedev.commons.utils.TaskLogger;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.*;
import io.onedev.server.entityreference.ReferenceMigrator;
import io.onedev.server.event.ListenerRegistry;
import io.onedev.server.event.project.issue.IssuesImported;
import io.onedev.server.git.command.LsRemoteCommand;
import io.onedev.server.model.*;
import io.onedev.server.model.support.LastActivity;
@ -412,7 +414,7 @@ public class ImportServer implements Serializable, Validatable {
if (issue.getDescription() != null)
issue.setDescription(migrator.migratePrefixed(issue.getDescription(), "#"));
issueManager.save(issue);
dao.persist(issue);
for (IssueSchedule schedule: issue.getSchedules())
dao.persist(schedule);
for (IssueField field: issue.getFields())
@ -428,9 +430,10 @@ public class ImportServer implements Serializable, Validatable {
result.nonExistentLogins.addAll(nonExistentLogins);
result.nonExistentMilestones.addAll(nonExistentMilestones);
result.unmappedIssueLabels.addAll(unmappedIssueLabels);
result.importedIssues.addAll(issues);
if (numOfImportedIssues.get() != 0)
result.issuesImported = true;
if (!dryRun && !issues.isEmpty())
OneDev.getInstance(ListenerRegistry.class).post(new IssuesImported(oneDevProject, issues));
return result;
} finally {
@ -592,8 +595,7 @@ public class ImportServer implements Serializable, Validatable {
result.nonExistentLogins.addAll(currentResult.nonExistentLogins);
result.nonExistentMilestones.addAll(currentResult.nonExistentMilestones);
result.unmappedIssueLabels.addAll(currentResult.unmappedIssueLabels);
result.issuesImported |= currentResult.issuesImported;
result.importedIssues.addAll(currentResult.importedIssues);
}
}

View File

@ -1,19 +1,18 @@
package io.onedev.server.plugin.imports.gitlab;
import com.google.common.collect.Lists;
import io.onedev.commons.utils.TaskLogger;
import io.onedev.server.imports.IssueImporter;
import io.onedev.server.model.Project;
import io.onedev.server.model.User;
import io.onedev.server.web.util.ImportStep;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import com.google.common.collect.Lists;
import io.onedev.commons.utils.TaskLogger;
import io.onedev.server.imports.IssueImporter;
import io.onedev.server.model.Project;
import io.onedev.server.model.User;
import io.onedev.server.web.util.ImportStep;
public class GitLabIssueImporter implements IssueImporter {
private static final long serialVersionUID = 1L;
@ -81,8 +80,9 @@ public class GitLabIssueImporter implements IssueImporter {
IssueImportOption option = optionStep.getSetting();
logger.log("Importing issues from project " + gitLabProject + "...");
Map<String, Optional<User>> users = new HashMap<>();
return server.importIssues(gitLabProject, project, option, users, dryRun, logger)
.toHtml("Issues imported successfully");
ImportResult result = server.importIssues(gitLabProject, project, option, users, dryRun, logger);
return result.toHtml("Issues imported successfully");
}
@Override

View File

@ -1,14 +1,9 @@
package io.onedev.server.plugin.imports.gitlab;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.unbescape.html.HtmlEscape;
import java.util.*;
public class ImportResult {
private static final int MAX_DISPLAY_ENTRIES = 100;
@ -22,7 +17,7 @@ public class ImportResult {
Set<String> tooLargeAttachments = new LinkedHashSet<>();
Set<String> errorAttachments = new LinkedHashSet<>();
private String getEntryFeedback(String entryDescription, Collection<String> entries) {
if (entries.size() > MAX_DISPLAY_ENTRIES) {
List<String> entriesToDisplay = new ArrayList<>(entries).subList(0, MAX_DISPLAY_ENTRIES);

View File

@ -1,23 +1,37 @@
package io.onedev.server.plugin.imports.gitlab;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.fasterxml.jackson.databind.JsonNode;
import edu.emory.mathcs.backport.java.util.Collections;
import io.onedev.commons.bootstrap.SensitiveMasker;
import io.onedev.commons.utils.ExplicitException;
import io.onedev.commons.utils.StringUtils;
import io.onedev.commons.utils.TaskLogger;
import io.onedev.server.OneDev;
import io.onedev.server.attachment.AttachmentManager;
import io.onedev.server.entitymanager.*;
import io.onedev.server.entityreference.ReferenceMigrator;
import io.onedev.server.event.ListenerRegistry;
import io.onedev.server.event.project.issue.IssuesImported;
import io.onedev.server.git.command.LsRemoteCommand;
import io.onedev.server.model.*;
import io.onedev.server.model.support.LastActivity;
import io.onedev.server.model.support.administration.GlobalIssueSetting;
import io.onedev.server.model.support.inputspec.InputSpec;
import io.onedev.server.model.support.issue.field.spec.FieldSpec;
import io.onedev.server.persistence.dao.Dao;
import io.onedev.server.util.*;
import io.onedev.server.util.JerseyUtils.PageDataConsumer;
import io.onedev.server.util.validation.Validatable;
import io.onedev.server.util.validation.annotation.ClassValidating;
import io.onedev.server.web.editable.annotation.Editable;
import io.onedev.server.web.editable.annotation.Password;
import org.apache.http.client.utils.URIBuilder;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.client.oauth2.OAuth2ClientSupport;
import org.joda.time.format.ISODateTimeFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.unbescape.html.HtmlEscape;
import javax.annotation.Nullable;
import javax.validation.ConstraintValidatorContext;
@ -27,53 +41,15 @@ import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response;
import org.apache.http.client.utils.URIBuilder;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.client.oauth2.OAuth2ClientSupport;
import org.joda.time.format.ISODateTimeFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.unbescape.html.HtmlEscape;
import com.fasterxml.jackson.databind.JsonNode;
import edu.emory.mathcs.backport.java.util.Collections;
import io.onedev.commons.bootstrap.SensitiveMasker;
import io.onedev.commons.utils.ExplicitException;
import io.onedev.commons.utils.StringUtils;
import io.onedev.commons.utils.TaskLogger;
import io.onedev.server.OneDev;
import io.onedev.server.attachment.AttachmentManager;
import io.onedev.server.entitymanager.IssueManager;
import io.onedev.server.entitymanager.MilestoneManager;
import io.onedev.server.entitymanager.ProjectManager;
import io.onedev.server.entitymanager.SettingManager;
import io.onedev.server.entitymanager.UserManager;
import io.onedev.server.entityreference.ReferenceMigrator;
import io.onedev.server.git.command.LsRemoteCommand;
import io.onedev.server.model.Issue;
import io.onedev.server.model.IssueComment;
import io.onedev.server.model.IssueField;
import io.onedev.server.model.IssueSchedule;
import io.onedev.server.model.Milestone;
import io.onedev.server.model.Project;
import io.onedev.server.model.User;
import io.onedev.server.model.support.LastActivity;
import io.onedev.server.model.support.administration.GlobalIssueSetting;
import io.onedev.server.model.support.inputspec.InputSpec;
import io.onedev.server.model.support.issue.field.spec.FieldSpec;
import io.onedev.server.persistence.dao.Dao;
import io.onedev.server.util.AttachmentTooLargeException;
import io.onedev.server.util.CollectionUtils;
import io.onedev.server.util.DateUtils;
import io.onedev.server.util.JerseyUtils;
import io.onedev.server.util.JerseyUtils.PageDataConsumer;
import io.onedev.server.util.Pair;
import io.onedev.server.util.validation.Validatable;
import io.onedev.server.util.validation.annotation.ClassValidating;
import io.onedev.server.web.editable.annotation.Editable;
import io.onedev.server.web.editable.annotation.Password;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Editable
@ClassValidating
@ -342,7 +318,7 @@ public class ImportServer implements Serializable, Validatable {
result.errorAttachments.addAll(currentResult.errorAttachments);
}
}
return result.toHtml("Projects imported successfully");
} catch (URISyntaxException e) {
throw new RuntimeException(e);
@ -679,7 +655,7 @@ public class ImportServer implements Serializable, Validatable {
if (issue.getDescription() != null)
issue.setDescription(migrator.migratePrefixed(issue.getDescription(), "#"));
issueManager.save(issue);
dao.persist(issue);
for (IssueSchedule schedule: issue.getSchedules())
dao.persist(schedule);
for (IssueField field: issue.getFields())
@ -698,6 +674,9 @@ public class ImportServer implements Serializable, Validatable {
result.tooLargeAttachments.addAll(tooLargeAttachments);
result.errorAttachments.addAll(errorAttachments);
if (!dryRun && !issues.isEmpty())
OneDev.getInstance(ListenerRegistry.class).post(new IssuesImported(oneDevProject, issues));
return result;
} finally {
if (!dryRun)

View File

@ -1,14 +1,9 @@
package io.onedev.server.plugin.imports.jiracloud;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.unbescape.html.HtmlEscape;
import java.util.*;
public class ImportResult {
private static final int MAX_DISPLAY_ENTRIES = 100;

View File

@ -33,6 +33,9 @@ import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import io.onedev.server.event.ListenerRegistry;
import io.onedev.server.event.project.issue.IssuesImported;
import io.onedev.server.security.SecurityUtils;
import org.apache.http.client.utils.URIBuilder;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature;
@ -337,7 +340,6 @@ public class ImportServer implements Serializable, Validatable {
try {
Map<String, Optional<User>> users = new HashMap<>();
ImportResult result = new ImportResult();
for (ProjectMapping projectMapping: projects.getProjectMappings()) {
String jiraProject = projectMapping.getJiraProject();
JsonNode projectNode = projectNodes.get(jiraProject);
@ -821,7 +823,7 @@ public class ImportServer implements Serializable, Validatable {
if (issue.getDescription() != null)
issue.setDescription(migrator.migratePrefixed(issue.getDescription(), jiraProjectKey + "-"));
issueManager.save(issue);
dao.persist(issue);
for (IssueSchedule schedule: issue.getSchedules())
dao.persist(schedule);
for (IssueField field: issue.getFields())
@ -841,6 +843,9 @@ public class ImportServer implements Serializable, Validatable {
result.tooLargeAttachments.addAll(tooLargeAttachments);
result.errorAttachments.addAll(errorAttachments);
if (!dryRun && !issues.isEmpty())
OneDev.getInstance(ListenerRegistry.class).post(new IssuesImported(oneDevProject, issues));
return result;
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);

View File

@ -1,15 +1,5 @@
package io.onedev.server.plugin.imports.url;
import java.io.Serializable;
import java.net.URISyntaxException;
import javax.annotation.Nullable;
import javax.validation.ConstraintValidatorContext;
import javax.validation.constraints.NotEmpty;
import org.apache.http.client.utils.URIBuilder;
import org.apache.shiro.authz.UnauthorizedException;
import io.onedev.commons.bootstrap.SensitiveMasker;
import io.onedev.commons.utils.ExplicitException;
import io.onedev.commons.utils.StringUtils;
@ -24,6 +14,14 @@ import io.onedev.server.util.validation.Validatable;
import io.onedev.server.util.validation.annotation.ClassValidating;
import io.onedev.server.web.editable.annotation.Editable;
import io.onedev.server.web.editable.annotation.Password;
import org.apache.http.client.utils.URIBuilder;
import org.apache.shiro.authz.UnauthorizedException;
import javax.annotation.Nullable;
import javax.validation.ConstraintValidatorContext;
import javax.validation.constraints.NotEmpty;
import java.io.Serializable;
import java.net.URISyntaxException;
@Editable
@ClassValidating
@ -116,7 +114,8 @@ public class ImportServer implements Serializable, Validatable {
if (dryRun) {
new LsRemoteCommand(builder.build().toString()).refs("HEAD").quiet(true).run();
} else {
if (project.isNew())
boolean newlyCreated = project.isNew();
if (newlyCreated)
getProjectManager().create(project);
getProjectManager().clone(project, builder.build().toString());
}

View File

@ -1,15 +1,9 @@
package io.onedev.server.plugin.imports.youtrack;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.unbescape.html.HtmlEscape;
import java.util.*;
public class ImportResult {
private static final int MAX_DISPLAY_ENTRIES = 100;

View File

@ -9,6 +9,8 @@ import io.onedev.server.OneDev;
import io.onedev.server.attachment.AttachmentManager;
import io.onedev.server.entitymanager.*;
import io.onedev.server.entityreference.ReferenceMigrator;
import io.onedev.server.event.ListenerRegistry;
import io.onedev.server.event.project.issue.IssuesImported;
import io.onedev.server.model.*;
import io.onedev.server.model.support.LastActivity;
import io.onedev.server.model.support.administration.GlobalIssueSetting;
@ -255,7 +257,7 @@ public class ImportServer implements Serializable, Validatable {
return option;
}
private ImportResult importIssues(String youTrackProjectId, Project oneDevProject,
private ImportResult doImportIssues(String youTrackProjectId, Project oneDevProject,
ImportOption option, boolean dryRun, TaskLogger logger) {
IssueManager issueManager = OneDev.getInstance(IssueManager.class);
Client client = newClient();
@ -977,7 +979,7 @@ public class ImportServer implements Serializable, Validatable {
if (issue.getDescription() != null)
issue.setDescription(migrator.migratePrefixed(issue.getDescription(), youTrackProjectShortName + "-"));
issueManager.save(issue);
dao.persist(issue);
for (IssueSchedule schedule: issue.getSchedules())
dao.persist(schedule);
for (IssueField field: issue.getFields())
@ -1019,6 +1021,9 @@ public class ImportServer implements Serializable, Validatable {
result.unmappedIssueLinks.addAll(unmappedIssueLinks);
result.unmappedIssueTags.addAll(unmappedIssueTags);
if (!dryRun && !issues.isEmpty())
OneDev.getInstance(ListenerRegistry.class).post(new IssuesImported(oneDevProject, issues));
return result;
} finally {
if (!dryRun)
@ -1070,20 +1075,21 @@ public class ImportServer implements Serializable, Validatable {
project.setDescription(youTrackProjectDescriptions.get(projectMapping.getYouTrackProject()));
project.setIssueManagement(true);
if (!dryRun && project.isNew())
boolean newlyCreated = project.isNew();
if (!dryRun && newlyCreated)
projectManager.create(project);
ImportResult currentResult = importIssues(youTrackProjectId, project,
option, dryRun, logger);
result.mismatchedIssueFields.putAll(currentResult.mismatchedIssueFields);
result.nonExistentLogins.addAll(currentResult.nonExistentLogins);
result.tooLargeAttachments.addAll(currentResult.tooLargeAttachments);
result.unmappedIssueFields.addAll(currentResult.unmappedIssueFields);
result.unmappedIssueStates.addAll(currentResult.unmappedIssueStates);
result.unmappedIssueLinks.addAll(currentResult.unmappedIssueLinks);
result.unmappedIssueTags.addAll(currentResult.unmappedIssueTags);
ImportResult currentResult = doImportIssues(youTrackProjectId, project,
option, dryRun, logger);
result.mismatchedIssueFields.putAll(currentResult.mismatchedIssueFields);
result.nonExistentLogins.addAll(currentResult.nonExistentLogins);
result.tooLargeAttachments.addAll(currentResult.tooLargeAttachments);
result.unmappedIssueFields.addAll(currentResult.unmappedIssueFields);
result.unmappedIssueStates.addAll(currentResult.unmappedIssueStates);
result.unmappedIssueLinks.addAll(currentResult.unmappedIssueLinks);
result.unmappedIssueTags.addAll(currentResult.unmappedIssueTags);
}
return result.toHtml("Projects imported successfully");
} finally {
client.close();
@ -1125,7 +1131,7 @@ public class ImportServer implements Serializable, Validatable {
String apiEndpoint = getApiEndpoint("/admin/projects?fields=id,name");
for (JsonNode projectNode: list(client, apiEndpoint, logger)) {
if (youTrackProject.equals(projectNode.get("name").asText())) {
ImportResult result = importIssues(projectNode.get("id").asText(),
ImportResult result = doImportIssues(projectNode.get("id").asText(),
project, option, dryRun, logger);
return result.toHtml("Issues imported successfully");
}