net.thucydides.plugins.jira.requirements.JIRARequirementsProvider Maven / Gradle / Ivy
package net.thucydides.plugins.jira.requirements;
import ch.lambdaj.function.convert.Converter;
import com.google.common.base.Optional;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import net.thucydides.core.guice.Injectors;
import net.thucydides.core.model.TestOutcome;
import net.thucydides.core.model.TestTag;
import net.thucydides.core.requirements.RequirementsTagProvider;
import net.thucydides.core.requirements.model.Requirement;
import net.thucydides.core.util.EnvironmentVariables;
import net.thucydides.plugins.jira.client.JerseyJiraClient;
import net.thucydides.plugins.jira.domain.IssueSummary;
import net.thucydides.plugins.jira.service.JIRAConfiguration;
import net.thucydides.plugins.jira.service.SystemPropertiesJIRAConfiguration;
import org.apache.commons.lang3.StringUtils;
import org.json.JSONException;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import static net.thucydides.plugins.jira.requirements.JIRARequirementsConfiguration.JIRA_CUSTOM_FIELD;
import static net.thucydides.plugins.jira.requirements.JIRARequirementsConfiguration.JIRA_CUSTOM_NARRATIVE_FIELD;
import static net.thucydides.plugins.jira.requirements.JIRARequirementsConfiguration.JIRA_MAX_THREADS;
/**
* Integrate Thucydides reports with requirements, epics and stories in a JIRA server.
*/
public class JIRARequirementsProvider implements RequirementsTagProvider {
private List requirements = null;
private final JerseyJiraClient jiraClient;
private final String projectKey;
private final EnvironmentVariables environmentVariables;
private final String EPIC_LINK = "Epic Link";
private final org.slf4j.Logger logger = LoggerFactory.getLogger(JIRARequirementsProvider.class);
private final ListeningExecutorService executorService;
private final AtomicInteger queueSize = new AtomicInteger(0);
static int DEFAULT_MAX_THREADS = 4;
public JIRARequirementsProvider() {
this(new SystemPropertiesJIRAConfiguration(Injectors.getInjector().getProvider(EnvironmentVariables.class).get() ),
Injectors.getInjector().getProvider(EnvironmentVariables.class).get() );
}
public JIRARequirementsProvider(JIRAConfiguration jiraConfiguration) {
this(jiraConfiguration, Injectors.getInjector().getProvider(EnvironmentVariables.class).get() );
}
private int getMaxJobs() {
return environmentVariables.getPropertyAsInteger(JIRA_MAX_THREADS.getName(),DEFAULT_MAX_THREADS);
}
public JIRARequirementsProvider(JIRAConfiguration jiraConfiguration, EnvironmentVariables environmentVariables) {
logConnectionDetailsFor(jiraConfiguration);
projectKey = jiraConfiguration.getProject();
this.environmentVariables = environmentVariables;
jiraClient = new ConfigurableJiraClient(jiraConfiguration.getJiraUrl(),
jiraConfiguration.getJiraUser(),
jiraConfiguration.getJiraPassword(),
projectKey).usingCustomFields(customFieldsDefinedIn(environmentVariables));
executorService = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(getMaxJobs()));
}
private List definedCustomFields() {
List customFields = Lists.newArrayList();
int customFieldIndex = 1;
while (addCustomFieldIfDefined(environmentVariables, customFields, customFieldNumber(customFieldIndex++))) ;
return customFields;
}
private List customFieldsDefinedIn(EnvironmentVariables environmentVariables) {
List customFields = Lists.newArrayList();
addCustomFieldIfDefined(environmentVariables, customFields,
JIRARequirementsConfiguration.JIRA_CUSTOM_NARRATIVE_FIELD.getName());
customFields.addAll(definedCustomFields());
return customFields;
}
private String customFieldNumber(int customFieldIndex) {
return JIRA_CUSTOM_FIELD.getName() + "." + customFieldIndex;
}
private boolean addCustomFieldIfDefined(EnvironmentVariables environmentVariables,
List customFields,
String customField) {
String customFieldName = environmentVariables.getProperty(customField);
if (StringUtils.isNotEmpty(customFieldName)) {
customFields.add(customFieldName);
return true;
}
return false;
}
private void logConnectionDetailsFor(JIRAConfiguration jiraConfiguration) {
logger.debug("JIRA URL: {0}", jiraConfiguration.getJiraUrl());
logger.debug("JIRA project: {0}", jiraConfiguration.getProject());
logger.debug("JIRA user: {0}", jiraConfiguration.getJiraUser());
}
private String getProjectKey() {
return projectKey;
}
@Override
public List getRequirements() {
requirements = persisted(requirements);
if ((requirements == null) && providerActivated()) {
List rootRequirementIssues;
logger.info("Loading root requirements: " + rootRequirementsJQL());
try {
rootRequirementIssues = jiraClient.findByJQL(rootRequirementsJQL());
} catch (JSONException e) {
logger.info("No root requirements found (JQL = " + rootRequirementsJQL(), e);
rootRequirementIssues = Lists.newArrayList();
}
logger.debug("Loading root requirements done: " + rootRequirementIssues.size());
requirements = Collections.synchronizedList(new ArrayList());
for (final IssueSummary issueSummary : rootRequirementIssues) {
final ListenableFuture future = executorService.submit(new Callable() {
@Override
public IssueSummary call() throws Exception {
return issueSummary;
}
});
future.addListener(new Runnable() {
@Override
public void run() {
try {
queueSize.incrementAndGet();
Requirement requirement = requirementFrom(future.get());
List childRequirements = findChildrenFor(requirement, 0);
requirements.add(requirement.withChildren(childRequirements));
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}, MoreExecutors.sameThreadExecutor());
future.addListener(new Runnable() {
@Override
public void run() {
queueSize.decrementAndGet();
}
}, executorService);
}
waitTillEmpty(queueSize);
requirements = addParentsTo(requirements);
persist(requirements);
}
return requirements;
}
private List persisted(List requirements) {
if (requirements != null) {
return requirements;
}
return null;
}
private void persist(List requirements) {
}
private boolean providerActivated() {
return environmentVariables.getPropertyAsBoolean("thucydides.providers.jira-requirements-provider", true);
}
private List addParentsTo(List requirements) {
return addParentsTo(requirements, null);
}
private final List NO_REQUIREMENTS = ImmutableList.of();
private List addParentsTo(List requirements, String parent) {
List augmentedRequirements = Lists.newArrayList();
for(Requirement requirement : requirements) {
List children = requirement.hasChildren()
? addParentsTo(requirement.getChildren(),requirement.getName()) : NO_REQUIREMENTS;
augmentedRequirements.add(requirement.withParent(parent).withChildren(children));
}
return augmentedRequirements;
}
private Requirement requirementFrom(IssueSummary issue) {
Requirement baseRequirement = Requirement.named(issue.getSummary())
.withOptionalCardNumber(issue.getKey())
.withType(issue.getType())
.withNarrative(narativeTextFrom(issue))
.withReleaseVersions(issue.getFixVersions());
for (String fieldName : definedCustomFields()) {
if (issue.customField(fieldName).isPresent()) {
String value = issue.customField(fieldName).get().asString();
String renderedValue = issue.getRendered().customField(fieldName).get();
baseRequirement = baseRequirement.withCustomField(fieldName).setTo(value, renderedValue);
}
}
return baseRequirement;
}
private String narativeTextFrom(IssueSummary issue) {
Optional customFieldName = Optional.fromNullable(environmentVariables.getProperty(JIRA_CUSTOM_NARRATIVE_FIELD.getName()));
if (customFieldName.isPresent()) {
return customFieldNameFor(issue, customFieldName.get()).or(issue.getRendered().getDescription());
} else {
return issue.getRendered().getDescription();
}
}
private Optional customFieldNameFor(IssueSummary issue, String customFieldName) {
if (issue.customField(customFieldName).isPresent()) {
return Optional.of(issue.customField(customFieldName).get().asString());
} else {
return Optional.absent();
}
}
private List findChildrenFor(Requirement parent, final int level) {
List children = null;
try {
logger.info("Loading child requirements for: " + parent.getName());
children = jiraClient.findByJQL(childIssuesJQL(parent, level));
logger.info("Loading child requirements for " + parent.getName() + " done: " + children.size());
} catch (JSONException e) {
logger.warn("No children found for requirement " + parent, e);
return NO_REQUIREMENTS;
}
final List childRequirements = Collections.synchronizedList(new ArrayList());
for(IssueSummary childIssue : children) {
Requirement childRequirement = requirementFrom(childIssue);
if (moreRequirements(level)) {
List grandChildren = findChildrenFor(childRequirement, level + 1);
childRequirement = childRequirement.withChildren(grandChildren);
}
childRequirements.add(childRequirement);
}
return childRequirements;
}
private void waitTillEmpty(AtomicInteger counter) {
while (counter.get() > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
}
}
}
private String childIssuesJQL(Requirement parent, int level) {
String linkType = getRequirementsLinks().get(level);
if (linkType.equals(EPIC_LINK)) {
return "'" + getRequirementsLinks().get(level) + "' = " + parent.getCardNumber();
} else {
return "issue in linkedIssues(" + parent.getCardNumber() + ",\"" + linkType + "\")";
}
}
private boolean moreRequirements(int level) {
return level < getRequirementsLinks().size() - 1;
}
//////////////////////////////////////
private String rootRequirementsJQL() {
return "issuetype = " + getRootIssueType() + " and project=" + getProjectKey();
}
private String getRootIssueType() {
return environmentVariables.getProperty(JIRARequirementsConfiguration.JIRA_ROOT_ISSUE_TYPE, "epic");
}
@Override
public Optional getParentRequirementOf(TestOutcome testOutcome) {
logger.debug("Find parent requirement in JIRA for " + testOutcome.getTitle());
List issueKeys = testOutcome.getIssueKeys();
if (!issueKeys.isEmpty() && providerActivated()) {
try {
Optional parentIssue = jiraClient.findByKey(issueKeys.get(0));
if (parentIssue.isPresent()) {
logger.debug("Parent found: " + parentIssue.get());
return Optional.of(requirementFrom(parentIssue.get()));
} else {
return Optional.absent();
}
} catch (JSONException e) {
if (noSuchIssue(e)) {
return Optional.absent();
} else {
throw new IllegalArgumentException(e);
}
}
} else {
return Optional.absent();
}
}
private boolean noSuchIssue(JSONException e) {
return e.getMessage().contains("error 400");
}
@Override
public Optional getRequirementFor(TestTag testTag) {
for (Requirement requirement : getFlattenedRequirements()) {
if (requirement.getType().equals(testTag.getType()) && requirement.getName().equals(testTag.getName())) {
return Optional.of(requirement);
}
}
return Optional.absent();
}
@Override
public Set getTagsFor(TestOutcome testOutcome) {
List issues = testOutcome.getIssueKeys();
Set tags = Sets.newHashSet();
for (String issue : issues) {
tags.addAll(tagsFromIssue(issue));
}
return ImmutableSet.copyOf(tags);
}
private Collection extends TestTag> tagsFromIssue(String issueKey) {
if (providerActivated()) {
IssueTagReader tagReader = new IssueTagReader(jiraClient, getFlattenedRequirements(), projectKey);
return tagReader.addIssueTags(issueKey)
.addRequirementTags(issueKey)
.addVersionTags(issueKey).getTags();
} else {
return ImmutableList.of();
}
}
private List getFlattenedRequirements() {
return getFlattenedRequirements(getRequirements());
}
private List getFlattenedRequirements(List someRequirements) {
List flattenedRequirements = Lists.newArrayList();
for (Requirement requirement : someRequirements) {
flattenedRequirements.add(requirement);
flattenedRequirements.addAll(getFlattenedRequirements(requirement.getChildren()));
}
return flattenedRequirements;
}
public List getRequirementsLinks() {
String requirementLinks = environmentVariables.getProperty(JIRARequirementsConfiguration.JIRA_REQUIREMENT_LINKS.getName(),
"Epic Link");
return Splitter.on(",").trimResults().omitEmptyStrings().splitToList(requirementLinks);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy