diff --git a/api-channel-jira/src/main/java/com/blackduck/integration/alert/api/channel/jira/JiraConstants.java b/api-channel-jira/src/main/java/com/blackduck/integration/alert/api/channel/jira/JiraConstants.java index b814e63a32..798e39ec90 100644 --- a/api-channel-jira/src/main/java/com/blackduck/integration/alert/api/channel/jira/JiraConstants.java +++ b/api-channel-jira/src/main/java/com/blackduck/integration/alert/api/channel/jira/JiraConstants.java @@ -7,6 +7,8 @@ */ package com.blackduck.integration.alert.api.channel.jira; +import com.blackduck.integration.alert.api.channel.jira.distribution.search.JiraIssuePropertyKeys; + public final class JiraConstants { public static final String DEFAULT_ISSUE_TYPE = "Task"; // This String must always match the String found in the atlassian-connect.json file under key. @@ -19,21 +21,27 @@ public final class JiraConstants { public static final String JIRA_SEARCH_KEY_JIRA_PROJECT = "project"; - // These Strings must always match the Strings found in the atlassian-connect.json file under modules.jiraEntityProperties.keyConfigurations.propertyKey["com-blackduck-integration-alert"].extractions.objectName. - public static final String JIRA_ISSUE_PROPERTY_OBJECT_KEY_PROVIDER = "provider"; - public static final String JIRA_ISSUE_PROPERTY_OBJECT_KEY_PROVIDER_URL = "providerUrl"; - public static final String JIRA_ISSUE_PROPERTY_OBJECT_KEY_TOPIC_NAME = "topicName"; - public static final String JIRA_ISSUE_PROPERTY_OBJECT_KEY_TOPIC_VALUE = "topicValue"; - public static final String JIRA_ISSUE_PROPERTY_OBJECT_KEY_SUB_TOPIC_NAME = "subTopicName"; - public static final String JIRA_ISSUE_PROPERTY_OBJECT_KEY_SUB_TOPIC_VALUE = "subTopicValue"; - public static final String JIRA_ISSUE_PROPERTY_OBJECT_KEY_CATEGORY = "category"; - public static final String JIRA_ISSUE_PROPERTY_OBJECT_KEY_COMPONENT_NAME = "componentName"; - public static final String JIRA_ISSUE_PROPERTY_OBJECT_KEY_COMPONENT_VALUE = "componentValue"; - public static final String JIRA_ISSUE_PROPERTY_OBJECT_KEY_SUB_COMPONENT_NAME = "subComponentName"; - public static final String JIRA_ISSUE_PROPERTY_OBJECT_KEY_SUB_COMPONENT_VALUE = "subComponentValue"; - public static final String JIRA_ISSUE_PROPERTY_OBJECT_KEY_ADDITIONAL_KEY = "additionalKey"; + public static String createCommentMigrationJQL() { + // find tickets created by alert first: + // 1. A summary that starts with "Alert - Black Duck" + // 2. A summary that isn't an Alert test message + // 3. Then check if the new property key exists on that issue + // 4. Then check if the new property alert9Migrated is empty or has the text "true". - public static final String JIRA_ISSUE_VALIDATION_ERROR_MESSAGE = "There are issues with the configuration."; + return "(summary ~ \"Alert - Black Duck\" OR summary !~ \"Alert Test Message\")" + + " AND " + + "(issue.property[" + + JiraConstants.JIRA_ISSUE_PROPERTY_KEY + + "].topicName IS NOT EMPTY " + + "AND " + + "(issue.property[" + + JiraConstants.JIRA_ISSUE_PROPERTY_KEY + + "].alert9Migrated IS EMPTY " + + "OR issue.property[" + + JiraConstants.JIRA_ISSUE_PROPERTY_KEY + + "].alert9Migrated != 'true'" + + "))"; + } private JiraConstants() { } diff --git a/api-channel-jira/src/main/java/com/blackduck/integration/alert/api/channel/jira/JiraIssueSearchProperties.java b/api-channel-jira/src/main/java/com/blackduck/integration/alert/api/channel/jira/JiraIssueSearchProperties.java index e22e9a437f..0fff0828b1 100644 --- a/api-channel-jira/src/main/java/com/blackduck/integration/alert/api/channel/jira/JiraIssueSearchProperties.java +++ b/api-channel-jira/src/main/java/com/blackduck/integration/alert/api/channel/jira/JiraIssueSearchProperties.java @@ -24,6 +24,7 @@ public class JiraIssueSearchProperties extends Stringable implements Serializabl private String subComponentName; private String subComponentValue; private String additionalKey; + private String alert9Migrated; public JiraIssueSearchProperties() { // For serialization @@ -55,6 +56,7 @@ public JiraIssueSearchProperties( this.subComponentName = subComponentName; this.subComponentValue = subComponentValue; this.additionalKey = additionalKey; + this.alert9Migrated = "true"; } public String getProvider() { @@ -105,4 +107,6 @@ public String getAdditionalKey() { return additionalKey; } + public String getAlert9Migrated() { return alert9Migrated; } + } diff --git a/api-channel-jira/src/main/java/com/blackduck/integration/alert/api/channel/jira/distribution/delegate/JiraIssueCreator.java b/api-channel-jira/src/main/java/com/blackduck/integration/alert/api/channel/jira/distribution/delegate/JiraIssueCreator.java index 58f85ab7b3..046af4a07f 100644 --- a/api-channel-jira/src/main/java/com/blackduck/integration/alert/api/channel/jira/distribution/delegate/JiraIssueCreator.java +++ b/api-channel-jira/src/main/java/com/blackduck/integration/alert/api/channel/jira/distribution/delegate/JiraIssueCreator.java @@ -7,13 +7,11 @@ */ package com.blackduck.integration.alert.api.channel.jira.distribution.delegate; -import java.nio.charset.StandardCharsets; -import java.util.Base64; import java.util.List; import java.util.Optional; -import com.blackduck.integration.alert.api.channel.jira.distribution.search.JiraIssuePropertyKeys; -import io.opencensus.trace.Link; +import com.blackduck.integration.alert.api.channel.jira.JiraConstants; +import com.blackduck.integration.alert.api.channel.jira.distribution.search.SearchCommentCreator; import org.apache.commons.lang3.StringUtils; import com.blackduck.integration.alert.api.channel.issue.tracker.callback.IssueTrackerCallbackInfoCreator; @@ -239,15 +237,14 @@ private JiraIssueSearchProperties createSearchProperties(ProjectIssueModel alert @Override protected Optional getAlertSearchKeys(ExistingIssueDetails existingIssueDetails, @Nullable ProjectIssueModel alertIssueSource) { - StringBuilder keyBuilder = new StringBuilder(); if(alertIssueSource == null) { return Optional.empty(); } + LinkableItem provider = alertIssueSource.getProvider(); // the uuid for project and project version is the last uuid // use the component and version name // category and if policy include the policy name - LinkableItem project = alertIssueSource.getProject(); LinkableItem projectVersion = alertIssueSource.getProjectVersion() @@ -255,59 +252,17 @@ protected Optional getAlertSearchKeys(ExistingIssueDetails exist IssueBomComponentDetails bomComponent = alertIssueSource.getBomComponentDetails(); - String projectId = StringUtils.substringAfterLast(project.getUrl().orElse(""), "/"); - int versionUUIDStart = StringUtils.lastIndexOf(projectVersion.getUrl().orElse(""), "versions/") +"versions/".length(); - int versionUUIDEnd = StringUtils.indexOf(projectVersion.getUrl().orElse(""), "/", versionUUIDStart); - String projectVersionId = StringUtils.substring(projectVersion.getUrl().orElse(""), versionUUIDStart, versionUUIDEnd); - String componentName = bomComponent.getComponent().getValue(); - Optional componentVersionName = bomComponent.getComponentVersion(); - keyBuilder.append("This comment was automatically created by Alert. DO NOT REMOVE."); - keyBuilder.append(JiraIssuePropertyKeys.JIRA_ISSUE_KEY_START_HEADER); - keyBuilder.append(StringUtils.SPACE); - keyBuilder.append(JiraIssuePropertyKeys.JIRA_ISSUE_KEY_PROJECT_ID); - keyBuilder.append(JiraIssuePropertyKeys.JIRA_ISSUE_KEY_SEPARATOR); - keyBuilder.append(projectId); - keyBuilder.append(StringUtils.SPACE); - keyBuilder.append(JiraIssuePropertyKeys.JIRA_ISSUE_KEY_PROJECT_VERSION_ID); - keyBuilder.append(JiraIssuePropertyKeys.JIRA_ISSUE_KEY_SEPARATOR); - keyBuilder.append(projectVersionId); - keyBuilder.append(StringUtils.SPACE); - keyBuilder.append(JiraIssuePropertyKeys.JIRA_ISSUE_KEY_COMPONENT_NAME); - keyBuilder.append(JiraIssuePropertyKeys.JIRA_ISSUE_KEY_SEPARATOR); - keyBuilder.append(componentName); - keyBuilder.append(StringUtils.SPACE); - if(componentVersionName.isPresent()) { - keyBuilder.append(JiraIssuePropertyKeys.JIRA_ISSUE_KEY_COMPONENT_VERSION_NAME); - keyBuilder.append(JiraIssuePropertyKeys.JIRA_ISSUE_KEY_SEPARATOR); - keyBuilder.append(componentVersionName.get().getValue()); - keyBuilder.append(StringUtils.SPACE); - } + Optional componentVersion = bomComponent.getComponentVersion(); Optional policyDetails = alertIssueSource.getPolicyDetails(); - Optional category = Optional.empty(); + String policyName = policyDetails.map(IssuePolicyDetails::getName).orElse(null); + ComponentConcernType category = null; if (alertIssueSource.getVulnerabilityDetails().isPresent()) { - category = Optional.of(ComponentConcernType.VULNERABILITY); + category = ComponentConcernType.VULNERABILITY; } else if(policyDetails.isPresent()) { - category = Optional.of(ComponentConcernType.POLICY); + category = ComponentConcernType.POLICY; } else if(alertIssueSource.getComponentUnknownVersionDetails().isPresent()) { - category = Optional.of(ComponentConcernType.UNKNOWN_VERSION); - } - if(category.isPresent()) { - keyBuilder.append(JiraIssuePropertyKeys.JIRA_ISSUE_KEY_CATEGORY); - keyBuilder.append(JiraIssuePropertyKeys.JIRA_ISSUE_KEY_SEPARATOR); - keyBuilder.append(category.get().name()); - keyBuilder.append(StringUtils.SPACE); + category = ComponentConcernType.UNKNOWN_VERSION; } - - if(policyDetails.isPresent()) { - // add policy name - keyBuilder.append(JiraIssuePropertyKeys.JIRA_ISSUE_KEY_POLICY_NAME); - keyBuilder.append(JiraIssuePropertyKeys.JIRA_ISSUE_KEY_SEPARATOR); - keyBuilder.append(JiraIssueSearchPropertyStringCompatibilityUtils.createPolicyAdditionalKey(policyDetails.get().getName())); - keyBuilder.append(StringUtils.SPACE); - } - keyBuilder.append(StringUtils.SPACE); - keyBuilder.append(JiraIssuePropertyKeys.JIRA_ISSUE_KEY_END_HEADER); - - return Optional.of(keyBuilder.toString()); + return Optional.of(SearchCommentCreator.createSearchComment(provider, project, projectVersion, bomComponent.getComponent(), componentVersion.orElse(null),category, policyName)); } } diff --git a/api-channel-jira/src/main/java/com/blackduck/integration/alert/api/channel/jira/distribution/search/JiraIssuePropertyKeys.java b/api-channel-jira/src/main/java/com/blackduck/integration/alert/api/channel/jira/distribution/search/JiraIssuePropertyKeys.java index e69e947b8f..78eb3f3f55 100644 --- a/api-channel-jira/src/main/java/com/blackduck/integration/alert/api/channel/jira/distribution/search/JiraIssuePropertyKeys.java +++ b/api-channel-jira/src/main/java/com/blackduck/integration/alert/api/channel/jira/distribution/search/JiraIssuePropertyKeys.java @@ -23,10 +23,12 @@ public final class JiraIssuePropertyKeys { public static final String JIRA_ISSUE_PROPERTY_OBJECT_KEY_SUB_COMPONENT_NAME = "subComponentName"; public static final String JIRA_ISSUE_PROPERTY_OBJECT_KEY_SUB_COMPONENT_VALUE = "subComponentValue"; public static final String JIRA_ISSUE_PROPERTY_OBJECT_KEY_ADDITIONAL_KEY = "additionalKey"; + public static final String JIRA_ISSUE_PROPERTY_OBJECT_KEY_ALERT_9_MIGRATED = "alert9Migrated"; public static final String JIRA_ISSUE_KEY_SEPARATOR = ": "; public static final String JIRA_ISSUE_KEY_START_HEADER = "=== BEGIN JIRA ISSUE KEYS ==="; public static final String JIRA_ISSUE_KEY_END_HEADER = "=== END JIRA ISSUE KEYS ==="; + public static final String JIRA_ISSUE_KEY_PROVIDER = "provider"; public static final String JIRA_ISSUE_KEY_PROJECT_ID = "projectId"; public static final String JIRA_ISSUE_KEY_PROJECT_VERSION_ID = "projectVersionId"; public static final String JIRA_ISSUE_KEY_COMPONENT_NAME = "componentName"; diff --git a/api-channel-jira/src/main/java/com/blackduck/integration/alert/api/channel/jira/distribution/search/JqlStringCreator.java b/api-channel-jira/src/main/java/com/blackduck/integration/alert/api/channel/jira/distribution/search/JqlStringCreator.java index 7d96f114e0..8c2b4d8933 100644 --- a/api-channel-jira/src/main/java/com/blackduck/integration/alert/api/channel/jira/distribution/search/JqlStringCreator.java +++ b/api-channel-jira/src/main/java/com/blackduck/integration/alert/api/channel/jira/distribution/search/JqlStringCreator.java @@ -27,7 +27,7 @@ public static String createBlackDuckProjectIssuesSearchString( LinkableItem project ) { StringBuilder jqlBuilder = new StringBuilder(); - appendBlackDuckCommentSearchStrings(jqlBuilder, jiraProjectKey, project, null, null, null, null, null); + appendBlackDuckCommentSearchStrings(jqlBuilder, jiraProjectKey, provider, project, null, null, null, null, null); jqlBuilder.append(" OR "); jqlBuilder.append("("); appendBlackDuckProjectSearchStrings(jqlBuilder, jiraProjectKey, provider, project); @@ -43,7 +43,7 @@ public static String createBlackDuckProjectVersionIssuesSearchString( LinkableItem projectVersion ) { StringBuilder jqlBuilder = new StringBuilder(); - appendBlackDuckCommentSearchStrings(jqlBuilder, jiraProjectKey, project, projectVersion, null, null, null, null); + appendBlackDuckCommentSearchStrings(jqlBuilder, jiraProjectKey, provider, project, projectVersion, null, null, null, null); jqlBuilder.append(" OR "); jqlBuilder.append("("); appendBlackDuckProjectVersionSearchStrings(jqlBuilder, jiraProjectKey, provider, project, projectVersion); @@ -61,7 +61,7 @@ public static String createBlackDuckComponentIssuesSearchString( @Nullable LinkableItem componentVersion ) { StringBuilder jqlBuilder = new StringBuilder(); - appendBlackDuckCommentSearchStrings(jqlBuilder, jiraProjectKey, project, projectVersion, component, componentVersion, null, null); + appendBlackDuckCommentSearchStrings(jqlBuilder, jiraProjectKey, provider, project, projectVersion, component, componentVersion, null, null); jqlBuilder.append(" OR "); jqlBuilder.append("("); appendBlackDuckComponentSearchStrings(jqlBuilder, jiraProjectKey, provider, project, projectVersion, component, componentVersion); @@ -81,7 +81,7 @@ public static String createBlackDuckComponentConcernIssuesSearchString( @Nullable String policyName ) { StringBuilder jqlBuilder = new StringBuilder(); - appendBlackDuckCommentSearchStrings(jqlBuilder, jiraProjectKey, project, projectVersion, component, componentVersion, concernType, policyName); + appendBlackDuckCommentSearchStrings(jqlBuilder, jiraProjectKey, provider, project, projectVersion, component, componentVersion, concernType, policyName); jqlBuilder.append(" OR "); jqlBuilder.append("("); appendBlackDuckComponentSearchStrings(jqlBuilder, jiraProjectKey, provider, project, projectVersion, component, componentVersion); @@ -103,6 +103,7 @@ public static String createBlackDuckComponentConcernIssuesSearchString( private static void appendBlackDuckCommentSearchStrings( StringBuilder jqlBuilder, String jiraProjectKey, + LinkableItem provider, LinkableItem project, @Nullable LinkableItem projectVersion, @Nullable LinkableItem component, @@ -117,50 +118,51 @@ private static void appendBlackDuckCommentSearchStrings( jqlBuilder.append(String.format("comment ~\"%s\"", JiraIssuePropertyKeys.JIRA_ISSUE_KEY_START_HEADER)); jqlBuilder.append(StringUtils.SPACE); + if(provider != null && provider.getUrl().isPresent()) { + jqlBuilder.append(SEARCH_CONJUNCTION); + jqlBuilder.append(StringUtils.SPACE); + appendCommentSearchString(jqlBuilder, JiraIssuePropertyKeys.JIRA_ISSUE_KEY_PROVIDER, provider.getUrl().get()); + } + if(project != null && project.getUrl().isPresent()) { - String projectId = extractUuid(project.getUrl().get(), "/api/projects/"); - appendCommentSearchString(jqlBuilder, JiraIssuePropertyKeys.JIRA_ISSUE_KEY_PROJECT_ID, projectId); + jqlBuilder.append(SEARCH_CONJUNCTION); + jqlBuilder.append(StringUtils.SPACE); + appendCommentSearchString(jqlBuilder, JiraIssuePropertyKeys.JIRA_ISSUE_KEY_PROJECT_ID, project.getValue()); } if(projectVersion != null && projectVersion.getUrl().isPresent()) { - String projectVersionId = extractUuid(projectVersion.getUrl().get(), "/versions/"); - appendCommentSearchString(jqlBuilder, JiraIssuePropertyKeys.JIRA_ISSUE_KEY_PROJECT_VERSION_ID, projectVersionId); + jqlBuilder.append(SEARCH_CONJUNCTION); + jqlBuilder.append(StringUtils.SPACE); + appendCommentSearchString(jqlBuilder, JiraIssuePropertyKeys.JIRA_ISSUE_KEY_PROJECT_VERSION_ID, projectVersion.getValue()); } if(component != null) { + jqlBuilder.append(SEARCH_CONJUNCTION); + jqlBuilder.append(StringUtils.SPACE); appendCommentSearchString(jqlBuilder, JiraIssuePropertyKeys.JIRA_ISSUE_KEY_COMPONENT_NAME, component.getValue()); } if(componentVersion != null) { + jqlBuilder.append(SEARCH_CONJUNCTION); + jqlBuilder.append(StringUtils.SPACE); appendCommentSearchString(jqlBuilder, JiraIssuePropertyKeys.JIRA_ISSUE_KEY_COMPONENT_VERSION_NAME, componentVersion.getValue()); } if(concernType != null) { + jqlBuilder.append(SEARCH_CONJUNCTION); + jqlBuilder.append(StringUtils.SPACE); appendCommentSearchString(jqlBuilder, JiraIssuePropertyKeys.JIRA_ISSUE_KEY_CATEGORY, concernType.name()); } if(StringUtils.isNotBlank(policyName)) { String escapedPolicyName = JiraIssueSearchPropertyStringCompatibilityUtils.createPolicyAdditionalKey(policyName); + jqlBuilder.append(SEARCH_CONJUNCTION); + jqlBuilder.append(StringUtils.SPACE); appendCommentSearchString(jqlBuilder, JiraIssuePropertyKeys.JIRA_ISSUE_KEY_POLICY_NAME, escapedPolicyName); } jqlBuilder.append(")"); } - private static @NotNull String extractUuid(String url, String pathSearchToken) { - int searchTokenStart = StringUtils.indexOf(url, pathSearchToken); - int startIndex = searchTokenStart + pathSearchToken.length(); - int endSlashIndex = StringUtils.indexOf(url, '/', startIndex); - String uuid; - if(endSlashIndex > startIndex) { - uuid = StringUtils.substring(url, startIndex, endSlashIndex); - } else { - uuid = StringUtils.substring(url, startIndex); - } - - return uuid; - } - - private static void appendBlackDuckComponentSearchStrings( StringBuilder jqlBuilder, String jiraProjectKey, @@ -212,8 +214,7 @@ private static void appendProjectKey(StringBuilder jqlBuilder, String jiraProjec } private static void appendCommentSearchString(StringBuilder jqlBuilder, String key, String value) { - jqlBuilder.append(SEARCH_CONJUNCTION); - jqlBuilder.append(" comment ~ \""); + jqlBuilder.append("comment ~ \""); jqlBuilder.append(String.format("%s%s%s", key,JiraIssuePropertyKeys.JIRA_ISSUE_KEY_SEPARATOR, escapeSearchString(value))); jqlBuilder.append("\""); jqlBuilder.append(StringUtils.SPACE); diff --git a/api-channel-jira/src/main/java/com/blackduck/integration/alert/api/channel/jira/distribution/search/SearchCommentCreator.java b/api-channel-jira/src/main/java/com/blackduck/integration/alert/api/channel/jira/distribution/search/SearchCommentCreator.java new file mode 100644 index 0000000000..2fe4642789 --- /dev/null +++ b/api-channel-jira/src/main/java/com/blackduck/integration/alert/api/channel/jira/distribution/search/SearchCommentCreator.java @@ -0,0 +1,78 @@ +package com.blackduck.integration.alert.api.channel.jira.distribution.search; + +import com.blackduck.integration.alert.api.processor.extract.model.project.ComponentConcernType; +import com.blackduck.integration.alert.common.message.model.LinkableItem; +import org.apache.commons.lang3.StringUtils; + +import javax.annotation.Nullable; +import java.util.Optional; + +public final class SearchCommentCreator { + + public static String createSearchComment(LinkableItem provider, LinkableItem project, LinkableItem projectVersion, LinkableItem component, @Nullable LinkableItem componentVersion, @Nullable ComponentConcernType componentConcernType, @Nullable String policyName) { + String providerName = provider.getLabel(); + String projectId = project.getValue(); + String projectVersionId = projectVersion.getValue(); + String componentName = component.getValue(); + String componentVersionName = null; + String category = Optional.ofNullable(componentConcernType).map(ComponentConcernType::name).orElse(null); + if(null != componentVersion) { + componentVersionName = componentVersion.getValue(); + } + return createSearchComment(providerName, projectId, projectVersionId, componentName, componentVersionName, category, policyName); + } + + public static String createSearchComment(String providerId, String projectId, String projectVersionId, String componentName, @Nullable String componentVersion, @Nullable String category, @Nullable String policyName) { + StringBuilder keyBuilder = new StringBuilder(); + + keyBuilder.append("This comment was automatically created by Alert. DO NOT REMOVE."); + keyBuilder.append(JiraIssuePropertyKeys.JIRA_ISSUE_KEY_START_HEADER); + keyBuilder.append(StringUtils.SPACE); + keyBuilder.append(JiraIssuePropertyKeys.JIRA_ISSUE_KEY_PROVIDER); + keyBuilder.append(JiraIssuePropertyKeys.JIRA_ISSUE_KEY_SEPARATOR); + keyBuilder.append(providerId); + keyBuilder.append(StringUtils.SPACE); + keyBuilder.append(JiraIssuePropertyKeys.JIRA_ISSUE_KEY_PROJECT_ID); + keyBuilder.append(JiraIssuePropertyKeys.JIRA_ISSUE_KEY_SEPARATOR); + keyBuilder.append(projectId); + keyBuilder.append(StringUtils.SPACE); + keyBuilder.append(JiraIssuePropertyKeys.JIRA_ISSUE_KEY_PROJECT_VERSION_ID); + keyBuilder.append(JiraIssuePropertyKeys.JIRA_ISSUE_KEY_SEPARATOR); + keyBuilder.append(projectVersionId); + keyBuilder.append(StringUtils.SPACE); + keyBuilder.append(JiraIssuePropertyKeys.JIRA_ISSUE_KEY_COMPONENT_NAME); + keyBuilder.append(JiraIssuePropertyKeys.JIRA_ISSUE_KEY_SEPARATOR); + keyBuilder.append(componentName); + keyBuilder.append(StringUtils.SPACE); + if(StringUtils.isNotBlank(componentVersion)) { + keyBuilder.append(JiraIssuePropertyKeys.JIRA_ISSUE_KEY_COMPONENT_VERSION_NAME); + keyBuilder.append(JiraIssuePropertyKeys.JIRA_ISSUE_KEY_SEPARATOR); + keyBuilder.append(componentVersion); + keyBuilder.append(StringUtils.SPACE); + } + + if(StringUtils.isNotBlank(category)) { + keyBuilder.append(JiraIssuePropertyKeys.JIRA_ISSUE_KEY_CATEGORY); + keyBuilder.append(JiraIssuePropertyKeys.JIRA_ISSUE_KEY_SEPARATOR); + keyBuilder.append(category); + keyBuilder.append(StringUtils.SPACE); + } + + if(StringUtils.isNotBlank(policyName)) { + // add policy name + keyBuilder.append(JiraIssuePropertyKeys.JIRA_ISSUE_KEY_POLICY_NAME); + keyBuilder.append(JiraIssuePropertyKeys.JIRA_ISSUE_KEY_SEPARATOR); + keyBuilder.append(JiraIssueSearchPropertyStringCompatibilityUtils.createPolicyAdditionalKey(policyName)); + keyBuilder.append(StringUtils.SPACE); + } + keyBuilder.append(StringUtils.SPACE); + keyBuilder.append(JiraIssuePropertyKeys.JIRA_ISSUE_KEY_END_HEADER); + + return keyBuilder.toString(); + } + + private SearchCommentCreator() { + // cannot instantiate this class. + } + +} diff --git a/api-channel-jira/src/main/java/com/blackduck/integration/alert/api/channel/jira/lifecycle/JiraPropertyTask.java b/api-channel-jira/src/main/java/com/blackduck/integration/alert/api/channel/jira/lifecycle/JiraPropertyTask.java new file mode 100644 index 0000000000..c0d959336c --- /dev/null +++ b/api-channel-jira/src/main/java/com/blackduck/integration/alert/api/channel/jira/lifecycle/JiraPropertyTask.java @@ -0,0 +1,82 @@ +package com.blackduck.integration.alert.api.channel.jira.lifecycle; + +import com.blackduck.integration.alert.api.channel.jira.JiraConstants; +import com.blackduck.integration.alert.api.channel.jira.JiraIssueSearchProperties; +import com.blackduck.integration.alert.api.task.ScheduledTask; +import com.blackduck.integration.alert.api.task.TaskManager; +import com.blackduck.integration.alert.api.task.TaskMetaData; +import com.blackduck.integration.alert.api.task.TaskMetaDataProperty; +import com.blackduck.integration.exception.IntegrationException; +import com.blackduck.integration.jira.common.model.response.IssuePropertyResponseModel; +import com.blackduck.integration.jira.common.rest.service.IssuePropertyService; +import com.google.gson.Gson; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.scheduling.TaskScheduler; + +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; + +public abstract class JiraPropertyTask extends JiraTask { + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + // find tickets created by alert first: + // 1. A summary that starts with "Alert - Black Duck" + // 2. A summary that isn't an Alert test message and have a comment "This issue was automatically created by Alert." + // 3. Then check if the new property key exists on that issue. Only works because the new property key is indexed with the new plugin. + protected static final String JQL_QUERY_FOR_ISSUE_PROPERTY_MIGRATION = String.format("(summary ~ \"Alert - Black Duck\" OR (summary !~ \"Alert Test Message\" AND comment ~ \"This issue was automatically created by Alert.\")) AND issue.property[%s].topicName IS EMPTY ORDER BY created DESC", JiraConstants.JIRA_ISSUE_PROPERTY_KEY); + + public static String createProjectSpecificQuery(String projectName) { + String projectQueryValue = String.format("\"%s\"", projectName); + return String.format("project = %s AND issue.property[%s].topicName IS EMPTY ORDER BY created DESC", projectQueryValue, JiraConstants.JIRA_ISSUE_PROPERTY_KEY); + } + + protected JiraPropertyTask(TaskScheduler taskScheduler, TaskManager taskManager, String configId, String configName, String taskNameSuffix, Gson gson) { + super(taskScheduler, taskManager, configId, configName, taskNameSuffix, gson); + } + + @Override + public final void runTask() { + executeTaskImplementation(); + } + + protected abstract void executeTaskImplementation(); + + protected void updateIssues(List issueKeys,Integer totalIssues, IssuePropertyService issuePropertyService) throws InterruptedException { + ExecutorService executorService = getExecutorService(); + for (String issueKey : issueKeys) { + executorService.submit(() -> setIssueProperty(issueKey, issuePropertyService)); + } + executorService.shutdown(); + boolean success = executorService.awaitTermination(1, TimeUnit.MINUTES); + if (success) { + logger.info("Jira property migrator task remaining issues {} ", totalIssues); + } else { + logger.info("Jira property migrator task timed out updating issues; will resume with the next iteration."); + } + } + + protected void setIssueProperty(String issueKey, IssuePropertyService issuePropertyService) { + try { + String propertyValue = getCurrentPropertyValue(issueKey, issuePropertyService); + issuePropertyService.setProperty(issueKey, JiraConstants.JIRA_ISSUE_PROPERTY_KEY, propertyValue); + } catch (IntegrationException ex) { + logger.error("Error migrating issue property for issue: {} cause: {}", issueKey, ex.getMessage()); + logger.debug("Caused by: ", ex); + } + } + + protected String getCurrentPropertyValue(String issueKey, IssuePropertyService issuePropertyService) { + // empty property value + String jsonPropertyValue = getGson().toJson(JiraTask.EMPTY_SEARCH_PROPERTIES); + try { + IssuePropertyResponseModel issuePropertyResponse = issuePropertyService.getProperty(issueKey, JiraConstants.JIRA_ISSUE_PROPERTY_OLD_KEY); + jsonPropertyValue = getGson().toJson(issuePropertyResponse.getValue()); + } catch (IntegrationException ex) { + logger.debug("Error old issue property for issue: {} cause: {}", issueKey, ex.getMessage()); + logger.debug("Caused by: ", ex); + } + return jsonPropertyValue; + } +} diff --git a/api-channel-jira/src/main/java/com/blackduck/integration/alert/api/channel/jira/lifecycle/JiraSchedulingManager.java b/api-channel-jira/src/main/java/com/blackduck/integration/alert/api/channel/jira/lifecycle/JiraSchedulingManager.java index 10f54be36b..f1c075676e 100644 --- a/api-channel-jira/src/main/java/com/blackduck/integration/alert/api/channel/jira/lifecycle/JiraSchedulingManager.java +++ b/api-channel-jira/src/main/java/com/blackduck/integration/alert/api/channel/jira/lifecycle/JiraSchedulingManager.java @@ -37,12 +37,12 @@ public List scheduleTasks(List tasks) { public void unscheduleTasks(String configId) { logger.debug("Performing unscheduling jira tasks for config: id={}", configId); - List tasks = taskManager.getTasksByClass(JiraTask.class) + List tasks = taskManager.getTasksByClass(JiraPropertyTask.class) .stream() .filter(task -> task.getConfigId().equals(configId)) .toList(); - for (JiraTask task : tasks) { + for (JiraPropertyTask task : tasks) { unscheduleTask(task); } logger.debug("Finished unscheduling jira tasks for config: id={}", configId); diff --git a/api-channel-jira/src/main/java/com/blackduck/integration/alert/api/channel/jira/lifecycle/JiraTask.java b/api-channel-jira/src/main/java/com/blackduck/integration/alert/api/channel/jira/lifecycle/JiraTask.java index d07d81ea2d..872621bb3b 100644 --- a/api-channel-jira/src/main/java/com/blackduck/integration/alert/api/channel/jira/lifecycle/JiraTask.java +++ b/api-channel-jira/src/main/java/com/blackduck/integration/alert/api/channel/jira/lifecycle/JiraTask.java @@ -1,43 +1,23 @@ package com.blackduck.integration.alert.api.channel.jira.lifecycle; -import com.blackduck.integration.alert.api.channel.jira.JiraConstants; import com.blackduck.integration.alert.api.channel.jira.JiraIssueSearchProperties; import com.blackduck.integration.alert.api.task.ScheduledTask; import com.blackduck.integration.alert.api.task.TaskManager; import com.blackduck.integration.alert.api.task.TaskMetaData; import com.blackduck.integration.alert.api.task.TaskMetaDataProperty; -import com.blackduck.integration.exception.IntegrationException; -import com.blackduck.integration.jira.common.model.response.IssuePropertyResponseModel; -import com.blackduck.integration.jira.common.rest.service.IssuePropertyService; import com.google.gson.Gson; import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.scheduling.TaskScheduler; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; public abstract class JiraTask extends ScheduledTask { - private final Logger logger = LoggerFactory.getLogger(this.getClass()); - // find tickets created by alert first: - // 1. A summary that starts with "Alert - Black Duck" - // 2. A summary that isn't an Alert test message and have a comment "This issue was automatically created by Alert." - // 3. Then check if the new property key exists on that issue. Only works because the new property key is indexed with the new plugin. - protected static final String JQL_QUERY_FOR_ISSUE_PROPERTY_MIGRATION = String.format("(summary ~ \"Alert - Black Duck\" OR (summary !~ \"Alert Test Message\" AND comment ~ \"This issue was automatically created by Alert.\")) AND issue.property[%s].topicName IS EMPTY ORDER BY created DESC", JiraConstants.JIRA_ISSUE_PROPERTY_KEY); protected static final int JQL_QUERY_MAX_RESULTS = 100; protected static final JiraIssueSearchProperties EMPTY_SEARCH_PROPERTIES = new JiraIssueSearchProperties(StringUtils.EMPTY, StringUtils.EMPTY, StringUtils.EMPTY, StringUtils.EMPTY, StringUtils.EMPTY, StringUtils.EMPTY, StringUtils.EMPTY, StringUtils.EMPTY, StringUtils.EMPTY, StringUtils.EMPTY, StringUtils.EMPTY, StringUtils.EMPTY); - - public static String createProjectSpecificQuery(String projectName) { - String projectQueryValue = String.format("\"%s\"", projectName); - return String.format("project = %s AND issue.property[%s].topicName IS EMPTY ORDER BY created DESC", projectQueryValue, JiraConstants.JIRA_ISSUE_PROPERTY_KEY); - } - - private final TaskManager taskManager; private final String taskName; private final String configId; @@ -53,50 +33,6 @@ protected JiraTask(TaskScheduler taskScheduler, TaskManager taskManager, String this.gson = gson; } - @Override - public final void runTask() { - executeTaskImplementation(); - } - - protected abstract void executeTaskImplementation(); - - protected void updateIssues(List issueKeys,Integer totalIssues, IssuePropertyService issuePropertyService) throws InterruptedException { - ExecutorService executorService = getExecutorService(); - for (String issueKey : issueKeys) { - executorService.submit(() -> setIssueProperty(issueKey, issuePropertyService)); - } - executorService.shutdown(); - boolean success = executorService.awaitTermination(1, TimeUnit.MINUTES); - if (success) { - logger.info("Jira property migrator task remaining issues {} ", totalIssues); - } else { - logger.info("Jira property migrator task timed out updating issues; will resume with the next iteration."); - } - } - - protected void setIssueProperty(String issueKey, IssuePropertyService issuePropertyService) { - try { - String propertyValue = getCurrentPropertyValue(issueKey, issuePropertyService); - issuePropertyService.setProperty(issueKey, JiraConstants.JIRA_ISSUE_PROPERTY_KEY, propertyValue); - } catch (IntegrationException ex) { - logger.error("Error migrating issue property for issue: {} cause: {}", issueKey, ex.getMessage()); - logger.debug("Caused by: ", ex); - } - } - - protected String getCurrentPropertyValue(String issueKey, IssuePropertyService issuePropertyService) { - // empty property value - String jsonPropertyValue = gson.toJson(EMPTY_SEARCH_PROPERTIES); - try { - IssuePropertyResponseModel issuePropertyResponse = issuePropertyService.getProperty(issueKey, JiraConstants.JIRA_ISSUE_PROPERTY_OLD_KEY); - jsonPropertyValue = gson.toJson(issuePropertyResponse.getValue()); - } catch (IntegrationException ex) { - logger.debug("Error old issue property for issue: {} cause: {}", issueKey, ex.getMessage()); - logger.debug("Caused by: ", ex); - } - return jsonPropertyValue; - } - @Override public String getTaskName() { return taskName; diff --git a/api-channel-jira/src/test/java/com/blackduck/integration/alert/api/channel/jira/distribution/search/JqlStringCreatorTest.java b/api-channel-jira/src/test/java/com/blackduck/integration/alert/api/channel/jira/distribution/search/JqlStringCreatorTest.java index 0715bad0a8..1bb5d522b0 100644 --- a/api-channel-jira/src/test/java/com/blackduck/integration/alert/api/channel/jira/distribution/search/JqlStringCreatorTest.java +++ b/api-channel-jira/src/test/java/com/blackduck/integration/alert/api/channel/jira/distribution/search/JqlStringCreatorTest.java @@ -55,8 +55,9 @@ void policySearchStringTest() { "(" + "project = 'TEST'" + " AND comment ~\"=== BEGIN JIRA ISSUE KEYS ===\"" - + " AND comment ~ \"projectId: project-uuid\"" - + " AND comment ~ \"projectVersionId: project-version-uuid\"" + + " AND comment ~ \"provider: http://providerUrl\"" + + " AND comment ~ \"projectId: project_with_single_quote\\'\"" + + " AND comment ~ \"projectVersionId: v1.0 \\\\& v2.0\"" + " AND comment ~ \"componentName: myVulnerableComponent\"" + " AND comment ~ \"componentVersionName: 1.1.2\"" + " AND comment ~ \"category: POLICY\"" @@ -96,8 +97,9 @@ void vulnerabilitySearchStringTest() { "(" + "project = 'TEST'" + " AND comment ~\"=== BEGIN JIRA ISSUE KEYS ===\"" - + " AND comment ~ \"projectId: project-uuid\"" - + " AND comment ~ \"projectVersionId: project-version-uuid\"" + + " AND comment ~ \"provider: http://providerUrl\"" + + " AND comment ~ \"projectId: project_with_single_quote\\'\"" + + " AND comment ~ \"projectVersionId: v1.0 \\\\& v2.0\"" + " AND comment ~ \"componentName: myVulnerableComponent\"" + " AND comment ~ \"componentVersionName: 1.1.2\"" + " AND comment ~ \"category: VULNERABILITY\"" @@ -141,8 +143,9 @@ void injectionTest() { "(" + "project = 'TEST'" + " AND comment ~\"=== BEGIN JIRA ISSUE KEYS ===\"" - + " AND comment ~ \"projectId: project-uuid\"" - + " AND comment ~ \"projectVersionId: projectVersionUrl\"" + + " AND comment ~ \"provider: http://providerUrl\"" + + " AND comment ~ \"projectId: project_with_single_quote\\'\"" + + " AND comment ~ \"projectVersionId: INJECTED\\\\\\' OR project = \"SomeOtherProject\" OR summary ~ 2021-01-01\\\\\\\\\\\\\"" + " AND comment ~ \"componentName: myVulnerableComponent\"" + " AND comment ~ \"componentVersionName: 1.1.2\"" + " AND comment ~ \"category: VULNERABILITY\"" diff --git a/channel-jira-cloud/src/main/java/com/blackduck/integration/alert/channel/jira/cloud/task/JiraCloudSchedulingManager.java b/channel-jira-cloud/src/main/java/com/blackduck/integration/alert/channel/jira/cloud/task/JiraCloudSchedulingManager.java index 29f1bc896c..65f24cd9d2 100644 --- a/channel-jira-cloud/src/main/java/com/blackduck/integration/alert/channel/jira/cloud/task/JiraCloudSchedulingManager.java +++ b/channel-jira-cloud/src/main/java/com/blackduck/integration/alert/channel/jira/cloud/task/JiraCloudSchedulingManager.java @@ -46,7 +46,8 @@ public void unscheduleTasks(String configId) { private List createTasks(FieldModel fieldModel, Set projectNameOrKeys) { String configId = fieldModel.getId(); String configName = fieldModel.getFieldValue(ChannelDescriptor.KEY_NAME).orElse(""); - JiraPropertyUpdateTask task = new JiraPropertyUpdateTask(taskScheduler, taskManager, jiraPropertiesFactory, gson, configId, configName, "JiraCloud", projectNameOrKeys); - return List.of(task); + JiraPropertyUpdateTask propertyTask = new JiraPropertyUpdateTask(taskScheduler, taskManager, jiraPropertiesFactory, gson, configId, configName, "JiraCloud", projectNameOrKeys); + JiraSearchCommentUpdateTask searchCommentTask = new JiraSearchCommentUpdateTask(taskScheduler, taskManager, jiraPropertiesFactory, gson, configId, configName, "JiraCloud"); + return List.of(propertyTask, searchCommentTask); } } diff --git a/channel-jira-cloud/src/main/java/com/blackduck/integration/alert/channel/jira/cloud/task/JiraPropertyUpdateTask.java b/channel-jira-cloud/src/main/java/com/blackduck/integration/alert/channel/jira/cloud/task/JiraPropertyUpdateTask.java index cc119854af..922da14a6c 100644 --- a/channel-jira-cloud/src/main/java/com/blackduck/integration/alert/channel/jira/cloud/task/JiraPropertyUpdateTask.java +++ b/channel-jira-cloud/src/main/java/com/blackduck/integration/alert/channel/jira/cloud/task/JiraPropertyUpdateTask.java @@ -1,6 +1,6 @@ package com.blackduck.integration.alert.channel.jira.cloud.task; -import com.blackduck.integration.alert.api.channel.jira.lifecycle.JiraTask; +import com.blackduck.integration.alert.api.channel.jira.lifecycle.JiraPropertyTask; import com.blackduck.integration.alert.api.task.ScheduledTask; import com.blackduck.integration.alert.api.task.TaskManager; import com.blackduck.integration.alert.channel.jira.cloud.JiraCloudProperties; @@ -20,7 +20,7 @@ import java.util.List; import java.util.Set; -public class JiraPropertyUpdateTask extends JiraTask { +public class JiraPropertyUpdateTask extends JiraPropertyTask { private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final JiraCloudPropertiesFactory jiraPropertiesFactory; private final Set projectNamesOrKeys; diff --git a/channel-jira-cloud/src/main/java/com/blackduck/integration/alert/channel/jira/cloud/task/JiraSearchCommentUpdateTask.java b/channel-jira-cloud/src/main/java/com/blackduck/integration/alert/channel/jira/cloud/task/JiraSearchCommentUpdateTask.java new file mode 100644 index 0000000000..88dfabb693 --- /dev/null +++ b/channel-jira-cloud/src/main/java/com/blackduck/integration/alert/channel/jira/cloud/task/JiraSearchCommentUpdateTask.java @@ -0,0 +1,162 @@ +package com.blackduck.integration.alert.channel.jira.cloud.task; + +import com.blackduck.integration.alert.api.channel.jira.JiraConstants; +import com.blackduck.integration.alert.api.channel.jira.distribution.search.JiraIssuePropertyKeys; +import com.blackduck.integration.alert.api.channel.jira.distribution.search.SearchCommentCreator; +import com.blackduck.integration.alert.api.channel.jira.lifecycle.JiraTask; +import com.blackduck.integration.alert.api.task.ScheduledTask; +import com.blackduck.integration.alert.api.task.TaskManager; +import com.blackduck.integration.alert.channel.jira.cloud.JiraCloudProperties; +import com.blackduck.integration.alert.channel.jira.cloud.JiraCloudPropertiesFactory; +import com.blackduck.integration.exception.IntegrationException; +import com.blackduck.integration.jira.common.cloud.model.IssueSearchResponseModel; +import com.blackduck.integration.jira.common.cloud.service.IssueSearchService; +import com.blackduck.integration.jira.common.cloud.service.IssueService; +import com.blackduck.integration.jira.common.cloud.service.JiraCloudServiceFactory; +import com.blackduck.integration.jira.common.model.request.IssueCommentRequestModel; +import com.blackduck.integration.jira.common.model.response.IssuePropertyResponseModel; +import com.blackduck.integration.jira.common.model.response.IssueResponseModel; +import com.blackduck.integration.jira.common.rest.service.IssuePropertyService; +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import io.micrometer.common.util.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.scheduling.TaskScheduler; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; + +public class JiraSearchCommentUpdateTask extends JiraTask { + private Logger logger = LoggerFactory.getLogger(getClass()); + private final JiraCloudPropertiesFactory jiraPropertiesFactory; + + public JiraSearchCommentUpdateTask(TaskScheduler taskScheduler, TaskManager taskManager, JiraCloudPropertiesFactory jiraPropertiesFactory, Gson gson, String configId, String configName, String taskNameSuffix) { + super(taskScheduler, taskManager, configId, configName, taskNameSuffix, gson); + this.jiraPropertiesFactory = jiraPropertiesFactory; + } + + @Override + public void runTask() { + logger.info("Jira Cloud search comment task started."); + try { + JiraCloudProperties jiraProperties = jiraPropertiesFactory.createJiraProperties(); + JiraCloudServiceFactory serviceFactory = jiraProperties.createJiraServicesCloudFactory(logger, getGson()); + IssueSearchService issueSearchService = serviceFactory.createIssueSearchService(); + IssuePropertyService issuePropertyService = serviceFactory.createIssuePropertyService(); + IssueService issueService = serviceFactory.createIssueService(); + + IssueSearchResponseModel responseModel = issueSearchService.queryForIssuePage(JiraConstants.createCommentMigrationJQL(), 0, JQL_QUERY_MAX_RESULTS); + + int totalIssues = responseModel.getTotal(); + boolean foundIssues = totalIssues > 0; + + if(foundIssues) { + List issueKeys = responseModel.getIssues().stream() + .map(IssueResponseModel::getKey) + .toList(); + ExecutorService executorService = getExecutorService(); + for (String issueKey : issueKeys) { + executorService.submit(createUpdateRunnable(issueKey, issuePropertyService, issueService)); + } + executorService.shutdown(); + boolean success = executorService.awaitTermination(1, TimeUnit.MINUTES); + if (success) { + logger.info("Jira search key comment update task remaining issues {} ", totalIssues); + } else { + logger.info("Jira search key comment update task timed out updating issues; will resume with the next iteration."); + } + } else { + // unschedule the task because the query returned no results + unScheduleTask(); + } + logger.info("Jira Cloud search comment task ended."); + } catch (IntegrationException e) { + logger.error("Error getting Jira Cloud Configuration.", e); + } catch (InterruptedException e) { + logger.error("Error updating Jira Cloud issues with new search key comment.", e); + Thread.currentThread().interrupt(); + } + } + + private Runnable createUpdateRunnable(String issueKey, IssuePropertyService issuePropertyService, IssueService issueService) { + return () -> { + Optional propertyValue = getPropertyValue(issueKey, issuePropertyService, JiraConstants.JIRA_ISSUE_PROPERTY_KEY); + if (propertyValue.isEmpty()) { + // try the old key + propertyValue = getPropertyValue(issueKey, issuePropertyService, JiraConstants.JIRA_ISSUE_PROPERTY_OLD_KEY); + } + if(propertyValue.isPresent()) { + try { + JsonObject jsonObject = propertyValue.get().getValue(); + // projectName, projectVersionName, and componentName should all be populated but since an optional is returned use orElse(null) + String provider = getPropertyValue(jsonObject, JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_PROVIDER).orElse(null); + String providerUrl = getPropertyValue(jsonObject, JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_PROVIDER_URL).orElse(null); + String projectLabel = getPropertyValue(jsonObject, JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_PROJECT_LABEL).orElse(null); + String projectName = getPropertyValue(jsonObject, JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_PROJECT_NAME).orElse(null); + String projectVersionLabel = getPropertyValue(jsonObject, JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_PROJECT_VERSION_LABEL).orElse(null); + String projectVersionName = getPropertyValue(jsonObject, JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_PROJECT_VERSION_NAME).orElse(null); + String componentLabel = getPropertyValue(jsonObject, JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_COMPONENT_LABEL).orElse(null); + String componentName = getPropertyValue(jsonObject, JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_COMPONENT_VALUE).orElse(null); + String componentVersionLabel = getPropertyValue(jsonObject, JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_SUB_COMPONENT_NAME).orElse(null); + String componentVersionName = getPropertyValue(jsonObject, JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_SUB_COMPONENT_VALUE).orElse(null); + String category = getPropertyValue(jsonObject, JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_CATEGORY).orElse(null); + String policyName = getPropertyValue(jsonObject, JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_ADDITIONAL_KEY).orElse(null); + String commentString = SearchCommentCreator.createSearchComment(provider, projectName, projectVersionName, componentName, componentVersionName, category, policyName); + IssueCommentRequestModel comment = new IssueCommentRequestModel(issueKey, commentString); + issueService.addComment(comment); + + // mark the issue as migrated + JsonObject migratedProperty = new JsonObject(); + migratedProperty.addProperty(JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_PROVIDER, provider); + migratedProperty.addProperty(JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_PROVIDER_URL, providerUrl); + migratedProperty.addProperty(JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_PROJECT_LABEL, projectLabel); + migratedProperty.addProperty(JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_PROJECT_NAME, projectName); + migratedProperty.addProperty(JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_PROJECT_VERSION_LABEL, projectVersionLabel); + migratedProperty.addProperty(JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_PROJECT_VERSION_NAME, projectVersionName); + migratedProperty.addProperty(JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_COMPONENT_LABEL, componentLabel); + migratedProperty.addProperty(JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_COMPONENT_VALUE, componentName); + migratedProperty.addProperty(JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_SUB_COMPONENT_NAME, componentVersionLabel); + migratedProperty.addProperty(JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_SUB_COMPONENT_VALUE, componentVersionName); + migratedProperty.addProperty(JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_CATEGORY, category); + migratedProperty.addProperty(JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_ADDITIONAL_KEY, policyName); + migratedProperty.addProperty(JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_ALERT_9_MIGRATED, "true"); + String migratedPropertyValue = getGson().toJson(migratedProperty); + issuePropertyService.setProperty(issueKey, JiraConstants.JIRA_ISSUE_PROPERTY_KEY, migratedPropertyValue); + } catch (IntegrationException ex) { + logger.debug("Error updating issue {} with search key comment.", issueKey); + logger.debug("Caused by: ", ex); + } + } + }; + } + + public Optional getPropertyValue(JsonObject jsonObject, String propertyKey) { + Optional value = Optional.empty(); + + if(jsonObject.has(propertyKey)) { + value = Optional.of(jsonObject.get(propertyKey).getAsString()); + } + + return value; + } + + protected Optional getPropertyValue(String issueKey, IssuePropertyService issuePropertyService, String propertyKey) { + // empty property value + Optional responseModel = Optional.empty(); + try { + responseModel = Optional.ofNullable(issuePropertyService.getProperty(issueKey, propertyKey)); + } catch (IntegrationException ex) { + logger.debug("Error finding issue property {} for issue: {} cause: {}", propertyKey, issueKey, ex.getMessage()); + logger.debug("Caused by: ", ex); + } + return responseModel; + } + + @Override + public String scheduleCronExpression() { + return ScheduledTask.EVERY_30_SECONDS_CRON_EXPRESSION; + } +} diff --git a/channel-jira-server/src/main/java/com/blackduck/integration/alert/channel/jira/server/task/JiraPropertyUpdateTask.java b/channel-jira-server/src/main/java/com/blackduck/integration/alert/channel/jira/server/task/JiraPropertyUpdateTask.java index 2c033eea69..6f242ae440 100644 --- a/channel-jira-server/src/main/java/com/blackduck/integration/alert/channel/jira/server/task/JiraPropertyUpdateTask.java +++ b/channel-jira-server/src/main/java/com/blackduck/integration/alert/channel/jira/server/task/JiraPropertyUpdateTask.java @@ -1,6 +1,6 @@ package com.blackduck.integration.alert.channel.jira.server.task; -import com.blackduck.integration.alert.api.channel.jira.lifecycle.JiraTask; +import com.blackduck.integration.alert.api.channel.jira.lifecycle.JiraPropertyTask; import com.blackduck.integration.alert.api.task.ScheduledTask; import com.blackduck.integration.alert.api.task.TaskManager; import com.blackduck.integration.alert.channel.jira.server.JiraServerProperties; @@ -21,7 +21,7 @@ import java.util.Set; import java.util.UUID; -public class JiraPropertyUpdateTask extends JiraTask { +public class JiraPropertyUpdateTask extends JiraPropertyTask { private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final JiraServerPropertiesFactory jiraPropertiesFactory; private final Set projectNamesOrKeys; diff --git a/channel-jira-server/src/main/java/com/blackduck/integration/alert/channel/jira/server/task/JiraSearchCommentUpdateTask.java b/channel-jira-server/src/main/java/com/blackduck/integration/alert/channel/jira/server/task/JiraSearchCommentUpdateTask.java new file mode 100644 index 0000000000..3fbb10e9fe --- /dev/null +++ b/channel-jira-server/src/main/java/com/blackduck/integration/alert/channel/jira/server/task/JiraSearchCommentUpdateTask.java @@ -0,0 +1,167 @@ +package com.blackduck.integration.alert.channel.jira.server.task; + +import com.blackduck.integration.alert.api.channel.jira.JiraConstants; +import com.blackduck.integration.alert.api.channel.jira.distribution.search.JiraIssuePropertyKeys; +import com.blackduck.integration.alert.api.channel.jira.distribution.search.SearchCommentCreator; +import com.blackduck.integration.alert.api.channel.jira.lifecycle.JiraTask; +import com.blackduck.integration.alert.api.task.ScheduledTask; +import com.blackduck.integration.alert.api.task.TaskManager; +import com.blackduck.integration.alert.channel.jira.server.JiraServerProperties; +import com.blackduck.integration.alert.channel.jira.server.JiraServerPropertiesFactory; +import com.blackduck.integration.exception.IntegrationException; +import com.blackduck.integration.jira.common.model.request.IssueCommentRequestModel; +import com.blackduck.integration.jira.common.model.response.IssuePropertyResponseModel; +import com.blackduck.integration.jira.common.rest.service.IssuePropertyService; +import com.blackduck.integration.jira.common.server.model.IssueSearchIssueComponent; +import com.blackduck.integration.jira.common.server.model.IssueSearchResponseModel; +import com.blackduck.integration.jira.common.server.service.IssueSearchService; +import com.blackduck.integration.jira.common.server.service.IssueService; +import com.blackduck.integration.jira.common.server.service.JiraServerServiceFactory; +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import io.micrometer.common.util.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.scheduling.TaskScheduler; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; + +public class JiraSearchCommentUpdateTask extends JiraTask { + private Logger logger = LoggerFactory.getLogger(getClass()); + private final JiraServerPropertiesFactory jiraPropertiesFactory; + + public JiraSearchCommentUpdateTask(TaskScheduler taskScheduler, TaskManager taskManager, JiraServerPropertiesFactory jiraPropertiesFactory, Gson gson, String configId, String configName, String taskNameSuffix) { + super(taskScheduler, taskManager, configId, configName, taskNameSuffix, gson); + this.jiraPropertiesFactory = jiraPropertiesFactory; + } + + @Override + public void runTask() { + logger.info("Jira Server search comment task started."); + UUID configId = UUID.fromString(getConfigId()); + try { + JiraServerProperties jiraProperties = jiraPropertiesFactory.createJiraProperties(configId); + JiraServerServiceFactory serviceFactory = jiraProperties.createJiraServicesServerFactory(logger,getGson()); + IssueSearchService issueSearchService = serviceFactory.createIssueSearchService(); + IssuePropertyService issuePropertyService = serviceFactory.createIssuePropertyService(); + IssueSearchResponseModel responseModel = issueSearchService.queryForIssuePage(JiraConstants.createCommentMigrationJQL(), 0, JQL_QUERY_MAX_RESULTS); + IssueService issueService = serviceFactory.createIssueService(); + + int totalIssues = responseModel.getTotal(); + boolean foundIssues = totalIssues > 0; + + if(foundIssues) { + List issueKeys = responseModel.getIssues().stream() + .map(IssueSearchIssueComponent::getKey) + .toList(); + ExecutorService executorService = getExecutorService(); + for (String issueKey : issueKeys) { + executorService.submit(createUpdateRunnable(issueKey, issuePropertyService, issueService)); + } + executorService.shutdown(); + boolean success = executorService.awaitTermination(1, TimeUnit.MINUTES); + if (success) { + logger.info("Jira search key comment update task remaining issues {} ", totalIssues); + } else { + logger.info("Jira search key comment update task timed out updating issues; will resume with the next iteration."); + } + } else { + + // unschedule the task because the query returned no results + unScheduleTask(); + } + } catch (IntegrationException e) { + logger.error("Error getting Jira Server Configuration.", e); + } catch (InterruptedException e) { + logger.error("Error updating Jira Server issues with new search key comment.", e); + Thread.currentThread().interrupt(); + } + + logger.info("Jira Server search comment task ended."); + } + + private Runnable createUpdateRunnable(String issueKey, IssuePropertyService issuePropertyService, IssueService issueService) { + return () -> { + Optional propertyValue = getPropertyValue(issueKey, issuePropertyService, JiraConstants.JIRA_ISSUE_PROPERTY_KEY); + if (propertyValue.isEmpty()) { + // try the old key + propertyValue = getPropertyValue(issueKey, issuePropertyService, JiraConstants.JIRA_ISSUE_PROPERTY_OLD_KEY); + } + if(propertyValue.isPresent()) { + try { + JsonObject jsonObject = propertyValue.get().getValue(); + // projectName, projectVersionName, and componentName should all be populated but since an optional is returned use orElse(null) + String provider = getPropertyValue(jsonObject, JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_PROVIDER).orElse(null); + String providerUrl = getPropertyValue(jsonObject, JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_PROVIDER_URL).orElse(null); + String projectLabel = getPropertyValue(jsonObject, JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_PROJECT_LABEL).orElse(null); + String projectName = getPropertyValue(jsonObject, JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_PROJECT_NAME).orElse(null); + String projectVersionLabel = getPropertyValue(jsonObject, JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_PROJECT_VERSION_LABEL).orElse(null); + String projectVersionName = getPropertyValue(jsonObject, JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_PROJECT_VERSION_NAME).orElse(null); + String componentLabel = getPropertyValue(jsonObject, JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_COMPONENT_LABEL).orElse(null); + String componentName = getPropertyValue(jsonObject, JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_COMPONENT_VALUE).orElse(null); + String componentVersionLabel = getPropertyValue(jsonObject, JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_SUB_COMPONENT_NAME).orElse(null); + String componentVersionName = getPropertyValue(jsonObject, JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_SUB_COMPONENT_VALUE).orElse(null); + String category = getPropertyValue(jsonObject, JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_CATEGORY).orElse(null); + String policyName = getPropertyValue(jsonObject, JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_ADDITIONAL_KEY).orElse(null); + + String commentString = SearchCommentCreator.createSearchComment(provider, projectName, projectVersionName, componentName, componentVersionName, category, policyName); + IssueCommentRequestModel comment = new IssueCommentRequestModel(issueKey, commentString); + issueService.addComment(comment); + + // mark the issue as migrated + JsonObject migratedProperty = new JsonObject(); + migratedProperty.addProperty(JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_PROVIDER, provider); + migratedProperty.addProperty(JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_PROVIDER_URL, providerUrl); + migratedProperty.addProperty(JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_PROJECT_LABEL, projectLabel); + migratedProperty.addProperty(JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_PROJECT_NAME, projectName); + migratedProperty.addProperty(JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_PROJECT_VERSION_LABEL, projectVersionLabel); + migratedProperty.addProperty(JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_PROJECT_VERSION_NAME, projectVersionName); + migratedProperty.addProperty(JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_COMPONENT_LABEL, componentLabel); + migratedProperty.addProperty(JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_COMPONENT_VALUE, componentName); + migratedProperty.addProperty(JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_SUB_COMPONENT_NAME, componentVersionLabel); + migratedProperty.addProperty(JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_SUB_COMPONENT_VALUE, componentVersionName); + migratedProperty.addProperty(JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_CATEGORY, category); + migratedProperty.addProperty(JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_ADDITIONAL_KEY, policyName); + migratedProperty.addProperty(JiraIssuePropertyKeys.JIRA_ISSUE_PROPERTY_OBJECT_KEY_ALERT_9_MIGRATED, "true"); + String migratedPropertyValue = getGson().toJson(migratedProperty); + issuePropertyService.setProperty(issueKey, JiraConstants.JIRA_ISSUE_PROPERTY_KEY, migratedPropertyValue); + } catch (IntegrationException ex) { + logger.debug("Error updating issue {} with search key comment.", issueKey); + logger.debug("Caused by: ", ex); + } + } + }; + } + + public Optional getPropertyValue(JsonObject jsonObject, String propertyKey) { + Optional value = Optional.empty(); + + if(jsonObject.has(propertyKey)) { + value = Optional.of(jsonObject.get(propertyKey).getAsString()); + } + + return value; + } + + + protected Optional getPropertyValue(String issueKey, IssuePropertyService issuePropertyService, String propertyKey) { + // empty property value + Optional responseModel = Optional.empty(); + try { + responseModel = Optional.ofNullable(issuePropertyService.getProperty(issueKey, propertyKey)); + } catch (IntegrationException ex) { + logger.debug("Error finding issue property {} for issue: {} cause: {}", propertyKey, issueKey, ex.getMessage()); + logger.debug("Caused by: ", ex); + } + return responseModel; + } + + @Override + public String scheduleCronExpression() { + return ScheduledTask.EVERY_30_SECONDS_CRON_EXPRESSION; + } +} diff --git a/channel-jira-server/src/main/java/com/blackduck/integration/alert/channel/jira/server/task/JiraServerSchedulingManager.java b/channel-jira-server/src/main/java/com/blackduck/integration/alert/channel/jira/server/task/JiraServerSchedulingManager.java index 6d444bf372..41a4254762 100644 --- a/channel-jira-server/src/main/java/com/blackduck/integration/alert/channel/jira/server/task/JiraServerSchedulingManager.java +++ b/channel-jira-server/src/main/java/com/blackduck/integration/alert/channel/jira/server/task/JiraServerSchedulingManager.java @@ -44,7 +44,8 @@ public void unscheduleTasks(UUID configId) { } private List createTasks(JiraServerGlobalConfigModel configModel, Set projectNameOrKeys) { - JiraPropertyUpdateTask task = new JiraPropertyUpdateTask(taskScheduler, taskManager, jiraPropertiesFactory, gson, configModel.getId(), configModel.getName(), "JiraServer", projectNameOrKeys); - return List.of(task); + JiraPropertyUpdateTask propertyTask = new JiraPropertyUpdateTask(taskScheduler, taskManager, jiraPropertiesFactory, gson, configModel.getId(), configModel.getName(), "JiraServer", projectNameOrKeys); + JiraSearchCommentUpdateTask searchCommentTask = new JiraSearchCommentUpdateTask(taskScheduler, taskManager, jiraPropertiesFactory, gson, configModel.getId(), configModel.getName(), "JiraServer"); + return List.of(propertyTask, searchCommentTask); } }