From 5c2bfdcb2d83ae2a951682823fb3a3e1f725b7d9 Mon Sep 17 00:00:00 2001 From: Alexey Belostotskiy Date: Thu, 3 Oct 2019 17:18:48 +0300 Subject: [PATCH] #33 related actions panel --- .../api/dto/audit/AuditLogEntryDto.java | 2 + .../api/entity/AuditLogIssueRelation.java | 16 +++ .../api/repository/AuditLogRepository.java | 7 ++ .../repository/AuditLogRepositoryImpl.java | 119 +++++++++++++++++- .../querydsl/QAuditLogIssueRelation.java | 14 +++ ...Upgrade001CreateAuditLogIssueRelation.java | 55 ++++++++ .../servlet/IssuePanelContextProvider.java | 38 ++++++ .../plugins/groovy/util/QueryDslTables.java | 1 + src/main/resources/atlassian-plugin.xml | 10 ++ .../plugins/groovy/templates/issue-panel.vm | 27 ++++ 10 files changed, 287 insertions(+), 2 deletions(-) create mode 100644 src/main/java/ru/mail/jira/plugins/groovy/api/entity/AuditLogIssueRelation.java create mode 100644 src/main/java/ru/mail/jira/plugins/groovy/impl/repository/querydsl/QAuditLogIssueRelation.java create mode 100644 src/main/java/ru/mail/jira/plugins/groovy/impl/upgrade/Upgrade001CreateAuditLogIssueRelation.java create mode 100644 src/main/java/ru/mail/jira/plugins/groovy/servlet/IssuePanelContextProvider.java create mode 100644 src/main/resources/ru/mail/jira/plugins/groovy/templates/issue-panel.vm diff --git a/src/main/java/ru/mail/jira/plugins/groovy/api/dto/audit/AuditLogEntryDto.java b/src/main/java/ru/mail/jira/plugins/groovy/api/dto/audit/AuditLogEntryDto.java index 81ac739b..2a9caa4e 100644 --- a/src/main/java/ru/mail/jira/plugins/groovy/api/dto/audit/AuditLogEntryDto.java +++ b/src/main/java/ru/mail/jira/plugins/groovy/api/dto/audit/AuditLogEntryDto.java @@ -24,4 +24,6 @@ public class AuditLogEntryDto extends AuditLogEntryForm { private Integer scriptId; @XmlElement private JiraUser user; + @XmlElement + private String url; } diff --git a/src/main/java/ru/mail/jira/plugins/groovy/api/entity/AuditLogIssueRelation.java b/src/main/java/ru/mail/jira/plugins/groovy/api/entity/AuditLogIssueRelation.java new file mode 100644 index 00000000..48dd9903 --- /dev/null +++ b/src/main/java/ru/mail/jira/plugins/groovy/api/entity/AuditLogIssueRelation.java @@ -0,0 +1,16 @@ +package ru.mail.jira.plugins.groovy.api.entity; + +import net.java.ao.Entity; +import net.java.ao.schema.Indexed; +import net.java.ao.schema.NotNull; +import net.java.ao.schema.Table; + +@Table("AUDIT_ISSUE_REL") +public interface AuditLogIssueRelation extends Entity { + @NotNull + AuditLogEntry getAuditLog(); + + @NotNull + @Indexed + Long getIssueId(); +} diff --git a/src/main/java/ru/mail/jira/plugins/groovy/api/repository/AuditLogRepository.java b/src/main/java/ru/mail/jira/plugins/groovy/api/repository/AuditLogRepository.java index cf4e2f2b..c2032c33 100644 --- a/src/main/java/ru/mail/jira/plugins/groovy/api/repository/AuditLogRepository.java +++ b/src/main/java/ru/mail/jira/plugins/groovy/api/repository/AuditLogRepository.java @@ -1,9 +1,11 @@ package ru.mail.jira.plugins.groovy.api.repository; +import com.atlassian.activeobjects.tx.Transactional; import com.atlassian.jira.user.ApplicationUser; import ru.mail.jira.plugins.groovy.api.dto.audit.AuditLogEntryDto; import ru.mail.jira.plugins.groovy.api.dto.audit.AuditLogEntryForm; import ru.mail.jira.plugins.groovy.api.dto.Page; +import ru.mail.jira.plugins.groovy.api.entity.AuditLogEntry; import ru.mail.jira.plugins.groovy.api.entity.EntityAction; import ru.mail.jira.plugins.groovy.api.entity.EntityType; @@ -16,4 +18,9 @@ public interface AuditLogRepository { List findAllForEntity(int id, EntityType entityType); Page getPagedEntries(int offset, int limit, Set users, Set categories, Set actions); + + @Transactional + void createRelations(AuditLogEntry auditLogEntry); + + List getRelated(long issueId); } diff --git a/src/main/java/ru/mail/jira/plugins/groovy/impl/repository/AuditLogRepositoryImpl.java b/src/main/java/ru/mail/jira/plugins/groovy/impl/repository/AuditLogRepositoryImpl.java index c434fb9a..bae2a0b0 100644 --- a/src/main/java/ru/mail/jira/plugins/groovy/impl/repository/AuditLogRepositoryImpl.java +++ b/src/main/java/ru/mail/jira/plugins/groovy/impl/repository/AuditLogRepositoryImpl.java @@ -2,7 +2,10 @@ import com.atlassian.activeobjects.external.ActiveObjects; import com.atlassian.jira.datetime.DateTimeFormatter; +import com.atlassian.jira.issue.IssueManager; +import com.atlassian.jira.issue.MutableIssue; import com.atlassian.jira.user.ApplicationUser; +import com.atlassian.jira.util.JiraKeyUtils; import com.atlassian.plugin.spring.scanner.annotation.export.ExportAsDevService; import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport; import com.atlassian.pocketknife.api.querydsl.DatabaseAccessor; @@ -23,15 +26,18 @@ import java.sql.Timestamp; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; import static ru.mail.jira.plugins.groovy.util.QueryDslTables.AUDIT_LOG_ENTRY; +import static ru.mail.jira.plugins.groovy.util.QueryDslTables.AUDIT_LOG_ISSUE_RELATION; @Component @ExportAsDevService(AuditLogRepository.class) public class AuditLogRepositoryImpl implements AuditLogRepository { + private final IssueManager issueManager; private final ActiveObjects activeObjects; private final DateTimeFormatter dateTimeFormatter; private final DatabaseAccessor databaseAccessor; @@ -40,12 +46,14 @@ public class AuditLogRepositoryImpl implements AuditLogRepository { @Autowired public AuditLogRepositoryImpl( + @ComponentImport IssueManager issueManager, @ComponentImport ActiveObjects activeObjects, @ComponentImport DateTimeFormatter dateTimeFormatter, DatabaseAccessor databaseAccessor, CustomFieldHelper customFieldHelper, UserMapper userMapper ) { + this.issueManager = issueManager; this.activeObjects = activeObjects; this.dateTimeFormatter = dateTimeFormatter; this.databaseAccessor = databaseAccessor; @@ -55,7 +63,7 @@ public AuditLogRepositoryImpl( @Override public void create(ApplicationUser user, AuditLogEntryForm form) { - activeObjects.create( + AuditLogEntry auditLogEntry = activeObjects.create( AuditLogEntry.class, new DBParam("DATE", new Timestamp(System.currentTimeMillis())), new DBParam("USER_KEY", user.getKey()), @@ -64,6 +72,8 @@ public void create(ApplicationUser user, AuditLogEntryForm form) { new DBParam("DESCRIPTION", form.getDescription()), new DBParam("ENTITY_ID", form.getEntityId()) ); + + createRelations(auditLogEntry); } @Override @@ -126,6 +136,109 @@ public Page getPagedEntries(int offset, int limit, Set }, OnRollback.NOOP); } + @Override + public void createRelations(AuditLogEntry entry) { + EntityAction action = entry.getAction(); + if (action == EntityAction.DISABLED || action == EntityAction.ENABLED || action == EntityAction.DELETED || + action == EntityAction.RESTORED || action == EntityAction.MOVED || + entry.getCategory() == EntityType.REGISTRY_DIRECTORY + ) { + return; + } + + Set issueKeys = new HashSet<>(JiraKeyUtils.getIssueKeysFromString(entry.getDescription())); + + Integer entityId = entry.getEntityId(); + //search for issue key in name only for CREATED action + if (action == EntityAction.CREATED && entityId != null) { + String name = null; + switch (entry.getCategory()) { + case ADMIN_SCRIPT: { + AdminScript script = activeObjects.get(AdminScript.class, entityId); + name = script.getName(); + break; + } + case REGISTRY_SCRIPT: { + Script script = activeObjects.get(Script.class, entityId); + name = script.getName(); + break; + } + case REGISTRY_DIRECTORY: { + ScriptDirectory directory = activeObjects.get(ScriptDirectory.class, entityId); + name = directory.getName(); + break; + } + case LISTENER: { + Listener listener = activeObjects.get(Listener.class, entityId); + name = listener.getName(); + break; + } + case REST: { + RestScript script = activeObjects.get(RestScript.class, entityId); + name = script.getName(); + break; + } + case CUSTOM_FIELD: { + Long fieldConfigId = activeObjects.get(FieldScript.class, entityId).getFieldConfigId(); + name = customFieldHelper.getFieldName(fieldConfigId); + break; + } + case SCHEDULED_TASK: { + ScheduledTask task = activeObjects.get(ScheduledTask.class, entityId); + name = task.getName(); + break; + } + case JQL_FUNCTION: + JqlFunctionScript function = activeObjects.get(JqlFunctionScript.class, entityId); + name = function.getName(); + break; + case GLOBAL_OBJECT: + GlobalObject globalObject = activeObjects.get(GlobalObject.class, entityId); + name = globalObject.getName(); + break; + } + if (name != null) { + issueKeys.addAll(JiraKeyUtils.getIssueKeysFromString(name)); + } + } + + databaseAccessor.run( + connection -> connection + .delete(AUDIT_LOG_ISSUE_RELATION) + .where(AUDIT_LOG_ISSUE_RELATION.AUDIT_LOG_ID.eq(entry.getID())) + .execute(), + OnRollback.NOOP + ); + for (String issueKey : issueKeys) { + MutableIssue issue = issueManager.getIssueObject(issueKey); + + if (issue != null) { + activeObjects.create( + AuditLogIssueRelation.class, + new DBParam("AUDIT_LOG_ID", entry.getID()), + new DBParam("ISSUE_ID", issue.getId()) + ); + } + } + } + + @Override + public List getRelated(long issueId) { + return databaseAccessor.run(connection -> + connection + .select(AUDIT_LOG_ENTRY.all()) + .from(AUDIT_LOG_ENTRY) + .join(AUDIT_LOG_ISSUE_RELATION).on(AUDIT_LOG_ENTRY.ID.eq(AUDIT_LOG_ISSUE_RELATION.AUDIT_LOG_ID)) + .where(AUDIT_LOG_ISSUE_RELATION.ISSUE_ID.eq(issueId)) + .orderBy(AUDIT_LOG_ENTRY.ID.desc()) + .fetch() + .stream() + .map(this::buildDto) + .collect(Collectors.toList()), + OnRollback.NOOP + ); + } + private void fillEntityData(AuditLogEntryDto result, EntityType category, Integer entityId) { if (entityId != null) { result.setScriptId(entityId); @@ -202,13 +315,15 @@ private AuditLogEntryDto buildDto(Tuple row) { AuditLogEntryDto result = new AuditLogEntryDto(); EntityType category = EntityType.valueOf(row.get(AUDIT_LOG_ENTRY.CATEGORY)); + Integer id = row.get(AUDIT_LOG_ENTRY.ID); result.setDate(dateTimeFormatter.forLoggedInUser().format(row.get(AUDIT_LOG_ENTRY.DATE))); - result.setId(row.get(AUDIT_LOG_ENTRY.ID)); + result.setId(id); result.setUser(userMapper.buildUser(row.get(AUDIT_LOG_ENTRY.USER_KEY))); result.setAction(EntityAction.valueOf(row.get(AUDIT_LOG_ENTRY.ACTION))); result.setCategory(category); result.setDescription(row.get(AUDIT_LOG_ENTRY.DESCRIPTION)); + result.setUrl(ScriptUtil.getPermalink(category, id)); fillEntityData(result, category, row.get(AUDIT_LOG_ENTRY.ENTITY_ID)); diff --git a/src/main/java/ru/mail/jira/plugins/groovy/impl/repository/querydsl/QAuditLogIssueRelation.java b/src/main/java/ru/mail/jira/plugins/groovy/impl/repository/querydsl/QAuditLogIssueRelation.java new file mode 100644 index 00000000..d862f487 --- /dev/null +++ b/src/main/java/ru/mail/jira/plugins/groovy/impl/repository/querydsl/QAuditLogIssueRelation.java @@ -0,0 +1,14 @@ +package ru.mail.jira.plugins.groovy.impl.repository.querydsl; + +import com.atlassian.pocketknife.spi.querydsl.EnhancedRelationalPathBase; +import com.querydsl.core.types.dsl.NumberPath; + +public class QAuditLogIssueRelation extends EnhancedRelationalPathBase { + public final NumberPath ID = createIntegerCol("ID").asPrimaryKey().build(); + public final NumberPath AUDIT_LOG_ID = createIntegerCol("AUDIT_LOG_ID").build(); + public final NumberPath ISSUE_ID = createLongCol("ISSUE_ID").build(); + + public QAuditLogIssueRelation() { + super(QAuditLogIssueRelation.class, "AO_2FC5DA_AUDIT_ISSUE_REL"); + } +} diff --git a/src/main/java/ru/mail/jira/plugins/groovy/impl/upgrade/Upgrade001CreateAuditLogIssueRelation.java b/src/main/java/ru/mail/jira/plugins/groovy/impl/upgrade/Upgrade001CreateAuditLogIssueRelation.java new file mode 100644 index 00000000..bda2b956 --- /dev/null +++ b/src/main/java/ru/mail/jira/plugins/groovy/impl/upgrade/Upgrade001CreateAuditLogIssueRelation.java @@ -0,0 +1,55 @@ +package ru.mail.jira.plugins.groovy.impl.upgrade; + +import com.atlassian.activeobjects.external.ActiveObjects; +import com.atlassian.plugin.spring.scanner.annotation.export.ExportAsService; +import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport; +import com.atlassian.sal.api.message.Message; +import com.atlassian.sal.api.upgrade.PluginUpgradeTask; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import ru.mail.jira.plugins.groovy.api.entity.AuditLogEntry; +import ru.mail.jira.plugins.groovy.api.repository.AuditLogRepository; +import ru.mail.jira.plugins.groovy.util.Const; + +import java.util.Collection; +import java.util.Collections; + +@Component +@ExportAsService(PluginUpgradeTask.class) +public class Upgrade001CreateAuditLogIssueRelation implements PluginUpgradeTask { + private final ActiveObjects activeObjects; + private final AuditLogRepository auditLogRepository; + + @Autowired + public Upgrade001CreateAuditLogIssueRelation( + @ComponentImport ActiveObjects activeObjects, + AuditLogRepository auditLogRepository + ) { + this.activeObjects = activeObjects; + this.auditLogRepository = auditLogRepository; + } + + @Override + public int getBuildNumber() { + return 100; + } + + @Override + public String getShortDescription() { + return "Created relation between audit log entry and Jira issues"; + } + + @Override + public Collection doUpgrade() throws Exception { + for (AuditLogEntry auditLogEntry : activeObjects.find(AuditLogEntry.class)) { + auditLogRepository.createRelations(auditLogEntry); + } + + return Collections.emptyList(); + } + + @Override + public String getPluginKey() { + return Const.PLUGIN_KEY; + } +} diff --git a/src/main/java/ru/mail/jira/plugins/groovy/servlet/IssuePanelContextProvider.java b/src/main/java/ru/mail/jira/plugins/groovy/servlet/IssuePanelContextProvider.java new file mode 100644 index 00000000..3241ad98 --- /dev/null +++ b/src/main/java/ru/mail/jira/plugins/groovy/servlet/IssuePanelContextProvider.java @@ -0,0 +1,38 @@ +package ru.mail.jira.plugins.groovy.servlet; + +import com.atlassian.jira.issue.Issue; +import com.atlassian.jira.plugin.webfragment.contextproviders.AbstractJiraContextProvider; +import com.atlassian.jira.plugin.webfragment.model.JiraHelper; +import com.atlassian.jira.user.ApplicationUser; +import com.google.common.collect.ImmutableMap; +import ru.mail.jira.plugins.groovy.api.repository.AuditLogRepository; + +import java.util.Collections; +import java.util.Map; + +public class IssuePanelContextProvider extends AbstractJiraContextProvider { + private final AuditLogRepository auditLogRepository; + + public IssuePanelContextProvider( + AuditLogRepository auditLogRepository + ) { + this.auditLogRepository = auditLogRepository; + } + + @Override + public Map getContextMap( + ApplicationUser applicationUser, JiraHelper jiraHelper + ) { + Map params = jiraHelper.getContextParams(); + + Issue issue = (Issue) params.get("issue"); + + if (issue == null) { + return ImmutableMap.of("changes", Collections.emptyList()); + } + + return ImmutableMap.of( + "changes", auditLogRepository.getRelated(issue.getId()) + ); + } +} diff --git a/src/main/java/ru/mail/jira/plugins/groovy/util/QueryDslTables.java b/src/main/java/ru/mail/jira/plugins/groovy/util/QueryDslTables.java index 613ddda3..d58f1f81 100644 --- a/src/main/java/ru/mail/jira/plugins/groovy/util/QueryDslTables.java +++ b/src/main/java/ru/mail/jira/plugins/groovy/util/QueryDslTables.java @@ -6,6 +6,7 @@ public final class QueryDslTables { private QueryDslTables() {} public static final QAuditLogEntry AUDIT_LOG_ENTRY = new QAuditLogEntry(); + public static final QAuditLogIssueRelation AUDIT_LOG_ISSUE_RELATION = new QAuditLogIssueRelation(); public static final QScriptExecution SCRIPT_EXECUTION = new QScriptExecution(); public static final QAbstractScript ADMIN_SCRIPT = new QAbstractScript("ADMIN_SCRIPT"); diff --git a/src/main/resources/atlassian-plugin.xml b/src/main/resources/atlassian-plugin.xml index a9440626..2bc39ad7 100644 --- a/src/main/resources/atlassian-plugin.xml +++ b/src/main/resources/atlassian-plugin.xml @@ -39,6 +39,7 @@ ru.mail.jira.plugins.groovy.api.entity.Listener ru.mail.jira.plugins.groovy.api.entity.ListenerChangelog ru.mail.jira.plugins.groovy.api.entity.AuditLogEntry + ru.mail.jira.plugins.groovy.api.entity.AuditLogIssueRelation ru.mail.jira.plugins.groovy.api.entity.RestScript ru.mail.jira.plugins.groovy.api.entity.RestChangelog ru.mail.jira.plugins.groovy.api.entity.FieldScript @@ -434,4 +435,13 @@ key="mygroovy-comment-search-extractor" class="ru.mail.jira.plugins.groovy.impl.jql.indexers.AdditionalFieldsCommentExtractor" /> + + + + + + + admin + + diff --git a/src/main/resources/ru/mail/jira/plugins/groovy/templates/issue-panel.vm b/src/main/resources/ru/mail/jira/plugins/groovy/templates/issue-panel.vm new file mode 100644 index 00000000..99200385 --- /dev/null +++ b/src/main/resources/ru/mail/jira/plugins/groovy/templates/issue-panel.vm @@ -0,0 +1,27 @@ +#* @vtlvariable name="changes" type="java.util.List" *# +#if ($changes) + #foreach($change in $changes) +
+
+ #if($change.action == 'CREATED') + $change.action + #elseif($change.action == 'UPDATED') + $change.action + #end + $i18n.getText($change.category.getI18nName()) + #if ($change.scriptId) + + + $change.scriptName + + + #end + $change.date +
+
+ $change.description +
+
+
+ #end +#end