#5 - Create release tickets and release versions for Jira-tracked projects.

Creates jira release version for a train iteration if not present. Creates release tickets in Jira and GitHub if not present. Added pre-release verification that the release ticket is present and all other tickets are closed. Allow singular ticket and release version creation. Add integration tests using WireMock. Add command to self-assign release tickets.

Added CLI commands:
* jira releasetickets
* jira create releasetickets
* jira self-assign releasetickets
* jira create releaseversions
* github tickets
* github releasetickets
* github create releaseversions
* github create releasetickets

Original pull request: #14.
This commit is contained in:
Mark Paluch
2016-03-07 15:31:16 +01:00
committed by Oliver Gierke
parent ce01cabdf3
commit 7f1ae27d3e
47 changed files with 3463 additions and 260 deletions

View File

@@ -3,6 +3,7 @@ git.username=
git.author=
git.email=
git.password=
github.api.url=https://api.github.com
# Maven
maven.mavenHome=
@@ -12,6 +13,11 @@ deployment.repository-prefix=
deployment.password=
deployment.api-key=
# Jira
jira.username=
jira.password=
jira.url=https://jira.spring.io
# GPG
deployment.gpg.keyname=
deployment.gpg.password=

View File

@@ -103,6 +103,19 @@
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock</artifactId>
<version>1.58</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0</version>
</dependency>
</dependencies>
<build>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014 the original author or authors.
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,14 +16,14 @@
package org.springframework.data.release.cli;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.data.release.CliComponent;
import org.springframework.data.release.jira.Changelog;
import org.springframework.data.release.jira.Credentials;
import org.springframework.data.release.jira.IssueTracker;
import org.springframework.data.release.jira.JiraConnector;
import org.springframework.data.release.jira.Tickets;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.TrainIteration;
@@ -36,13 +36,13 @@ import org.springframework.util.StringUtils;
/**
* @author Oliver Gierke
* @author Mark Paluch
*/
@CliComponent
public class IssueTrackerCommands implements CommandMarker {
private final PluginRegistry<IssueTracker, Project> tracker;
private final JiraConnector jira;
private final Credentials credentials;
/**
* @param tracker must not be {@literal null}.
@@ -50,20 +50,15 @@ public class IssueTrackerCommands implements CommandMarker {
* @param environment must not be {@literal null}.
*/
@Autowired
public IssueTrackerCommands(PluginRegistry<IssueTracker, Project> tracker, JiraConnector jira,
Environment environment) {
String username = environment.getProperty("jira.username", (String) null);
String password = environment.getProperty("jira.password", (String) null);
public IssueTrackerCommands(PluginRegistry<IssueTracker, Project> tracker, JiraConnector jira) {
this.tracker = tracker;
this.jira = jira;
this.credentials = StringUtils.hasText(username) ? new Credentials(username, password) : null;
}
@CliCommand("jira evict")
public void jiraEvict() {
jira.reset();
@CliCommand("tracker evict")
public void evict() {
StreamSupport.stream(tracker.spliterator(), false).forEach(IssueTracker::reset);
}
@CliCommand(value = "jira tickets")
@@ -71,11 +66,52 @@ public class IssueTrackerCommands implements CommandMarker {
@CliOption(key = "for-current-user", specifiedDefaultValue = "true",
unspecifiedDefaultValue = "false") boolean forCurrentUser) {
if (forCurrentUser && credentials == null) {
return "No authentication specified! Use 'jira authenticate' first!";
}
Tickets tickets = StreamSupport.stream(tracker.spliterator(), false). //
flatMap(issueTracker -> issueTracker. //
getTicketsFor(iteration, forCurrentUser).stream())
. //
collect(Tickets.toTicketsCollector());
return tickets.toString();
}
return jira.getTicketsFor(iteration, forCurrentUser ? credentials : null).toString();
@CliCommand(value = "tracker releasetickets")
public String releaseTickets(@CliOption(key = "", mandatory = true) TrainIteration iteration) {
Tickets tickets = StreamSupport.stream(tracker.spliterator(), false). //
flatMap(issueTracker -> issueTracker.getTicketsFor(iteration).stream()). //
collect(Tickets.toTicketsCollector());
return tickets.getReleaseTickets(iteration).toString();
}
@CliCommand(value = "jira self-assign releasetickets")
public String jiraSelfAssignReleaseTickets(@CliOption(key = "", mandatory = true) TrainIteration iteration) {
Tickets releaseTickets = jira.getTicketsFor(iteration).getReleaseTickets(iteration);
releaseTickets.forEach(ticket -> jira.assignTicketToMe(ticket));
return releaseTickets(iteration);
}
@CliCommand(value = "tracker create releaseversions")
public void jiraCreateReleaseVersions(@CliOption(key = "", mandatory = true) TrainIteration iteration) {
iteration.forEach(this::createReleaseVersion);
}
@CliCommand(value = "tracker create releasetickets")
public String createReleaseTickets(@CliOption(key = "", mandatory = true) TrainIteration iteration) {
iteration.forEach(this::createReleaseVersion);
evict();
return releaseTickets(iteration);
}
private void createReleaseVersion(ModuleIteration moduleIteration) {
getPluginFor(moduleIteration).createReleaseVersion(moduleIteration);
}
private IssueTracker getPluginFor(ModuleIteration moduleIteration) {
return tracker.getPluginFor(moduleIteration.getProject());
}
@CliCommand("tracker changelog")

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015 the original author or authors.
* Copyright 2015-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@ import javax.annotation.PostConstruct;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.data.release.utils.HttpBasicCredentials;
import org.springframework.stereotype.Component;
@@ -32,22 +33,26 @@ import org.springframework.util.Assert;
* Configurable properties for Git.
*
* @author Oliver Gierke
* @author Mark Paluch
*/
@Data
@Component
@ConfigurationProperties(prefix = "git")
public class GitProperties {
private @Getter(AccessLevel.PRIVATE) String username, password;
private String author, email;
private @Getter(AccessLevel.PRIVATE) String password;
private String username, author, email;
@Value("${github.api.url}") private String githubApiBaseUrl;
@PostConstruct
public void init() {
Assert.hasText(username, "No GitHub username (git.username) configured!");
Assert.hasText(username, "No GitHub password (git.password) configured!");
Assert.hasText(username, "No Git author (git.author) configured!");
Assert.hasText(username, "No Git email (git.email) configured!");
Assert.hasText(password, "No GitHub password (git.password) configured!");
Assert.hasText(author, "No Git author (git.author) configured!");
Assert.hasText(email, "No Git email (git.email) configured!");
Assert.hasText(githubApiBaseUrl, "No GitHub API base url configured!");
}
/**

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.release.jira;
import lombok.Data;
/**
* Value object for a created Jira issue.
*
* @author Mark Paluch
*/
@Data
public class CreatedJiraIssue {
private String id;
private String key;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014 the original author or authors.
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,25 +15,77 @@
*/
package org.springframework.data.release.jira;
import lombok.Data;
import org.springframework.data.release.model.ModuleIteration;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Value;
/**
* @author Oliver Gierke
* @author Mark Paluch
*/
@Data
@Value
class GitHubIssue {
private String number;
private String title;
private String state;
private Object milestone;
@JsonCreator
public GitHubIssue(@JsonProperty("number") String number, @JsonProperty("title") String title,
@JsonProperty("state") String state, @JsonProperty("milestone") Object milestone) {
this.number = number;
this.title = title;
this.state = state;
this.milestone = milestone;
}
public GitHubIssue(String title, Milestone milestone) {
this.title = title;
this.milestone = milestone.getNumber();
this.number = null;
this.state = null;
}
public String getId() {
if (number == null) {
return null;
}
return "#".concat(number);
}
public boolean isReleaseTicket(ModuleIteration module) {
return title.contains("Release") && title.contains(module.getShortVersionString());
/**
* @author Oliver Gierke
* @author Mark Paluch
*/
@Value
static class Milestone {
private final String title;
private final Long number;
private final String description;
@JsonCreator
public Milestone(@JsonProperty("title") String title, @JsonProperty("number") Long number,
@JsonProperty("description") String description) {
this.title = title;
this.number = number;
this.description = description;
}
public Milestone(String title, String description) {
this.number = null;
this.title = title;
this.description = description;
}
public boolean matchesIteration(ModuleIteration moduleIteration) {
return title.contains(moduleIteration.getShortVersionString());
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014 the original author or authors.
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,8 +15,6 @@
*/
package org.springframework.data.release.jira;
import lombok.RequiredArgsConstructor;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
@@ -24,7 +22,7 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.Optional;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
@@ -45,21 +43,28 @@ import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.util.Assert;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestOperations;
import org.springframework.web.util.UriTemplate;
import lombok.RequiredArgsConstructor;
/**
* @author Oliver Gierke
* @author Mark Paluch
*/
@RequiredArgsConstructor
class GitHubIssueTracker implements IssueTracker {
private static final String MILESTONE_URI = "https://api.github.com/repos/spring-projects/{repoName}/milestones?state={state}";
private static final String ISSUES_BY_MILESTONE_URI_TEMPLATE = "https://api.github.com/repos/spring-projects/{repoName}/issues?milestone={id}&state=all";
private static final String ISSUE_BY_ID_URI_TEMPLATE = "https://api.github.com/repos/spring-projects/{repoName}/issues/{id}";
private static final String MILESTONE_URI = "{githubBaseUrl}/repos/spring-projects/{repoName}/milestones?state={state}";
private static final String ISSUES_BY_MILESTONE_AND_ASSIGNEE_URI_TEMPLATE = "{githubBaseUrl}/repos/spring-projects/{repoName}/issues?milestone={id}&state=all&assignee={assignee}";
private static final String ISSUES_BY_MILESTONE_URI_TEMPLATE = "{githubBaseUrl}/repos/spring-projects/{repoName}/issues?milestone={id}&state=all";
private static final String MILESTONES_URI_TEMPLATE = "{githubBaseUrl}/repos/spring-projects/{repoName}/milestones";
private static final String ISSUE_BY_ID_URI_TEMPLATE = "{githubBaseUrl}/repos/spring-projects/{repoName}/issues/{id}";
private static final String ISSUES_URI_TEMPLATE = "{githubBaseUrl}/repos/spring-projects/{repoName}/issues";
private static final ParameterizedTypeReference<List<GitHubMilestone>> MILESTONES_TYPE = new ParameterizedTypeReference<List<GitHubMilestone>>() {};
private static final ParameterizedTypeReference<List<GitHubIssue.Milestone>> MILESTONES_TYPE = new ParameterizedTypeReference<List<GitHubIssue.Milestone>>() {};
private static final ParameterizedTypeReference<List<GitHubIssue>> ISSUES_TYPE = new ParameterizedTypeReference<List<GitHubIssue>>() {};
private static final ParameterizedTypeReference<GitHubIssue> ISSUE_TYPE = new ParameterizedTypeReference<GitHubIssue>() {};
@@ -72,7 +77,7 @@ class GitHubIssueTracker implements IssueTracker {
* @see org.springframework.data.release.jira.JiraConnector#flushTickets()
*/
@Override
@CacheEvict(value = "tickets", allEntries = true)
@CacheEvict(value = { "tickets", "release-tickets", "milestone" }, allEntries = true)
public void reset() {
}
@@ -84,20 +89,10 @@ class GitHubIssueTracker implements IssueTracker {
@Override
@Cacheable("release-tickets")
public Ticket getReleaseTicketFor(ModuleIteration module) {
return getIssuesFor(module).stream().//
filter(issue -> issue.isReleaseTicket(module)).//
findFirst().//
map(issue -> toTicket(issue)).//
orElseThrow(
() -> new IllegalArgumentException(String.format("Could not find a release ticket for %s!", module)));
return getTicketsFor(module).getReleaseTicket(module);
}
private Ticket toTicket(GitHubIssue issue) {
return new Ticket(issue.getId(), issue.getTitle(), new GithubTicketStatus(issue.getState()));
}
/**
/*
* (non-Javadoc)
*
* @see IssueTracker#findTickets(Project, Collection)
@@ -110,13 +105,13 @@ class GitHubIssueTracker implements IssueTracker {
List<Ticket> tickets = new ArrayList<>();
for (String ticketId : ticketIds) {
Map<String, Object> parameters = new HashMap<>();
Map<String, Object> parameters = newUrlTemplateVariables();
parameters.put("repoName", repositoryName);
parameters.put("id", ticketId);
try {
GitHubIssue gitHubIssue = operations.exchange(ISSUE_BY_ID_URI_TEMPLATE, HttpMethod.GET,
new HttpEntity<>(getAuthenticationHeaders()), ISSUE_TYPE, parameters).getBody();
new HttpEntity<>(newUserScopedHttpHeaders()), ISSUE_TYPE, parameters).getBody();
tickets.add(toTicket(gitHubIssue));
} catch (HttpStatusCodeException e) {
@@ -136,15 +131,15 @@ class GitHubIssueTracker implements IssueTracker {
*/
@Override
@Cacheable("changelogs")
public Changelog getChangelogFor(ModuleIteration module) {
public Changelog getChangelogFor(ModuleIteration moduleIteration) {
List<Ticket> tickets = getIssuesFor(module).stream().//
Tickets tickets = getIssuesFor(moduleIteration, false).stream().//
map(issue -> toTicket(issue)).//
collect(Collectors.toList());
collect(Tickets.toTicketsCollector());
logger.log(module, "Created changelog with %s entries.", tickets.size());
logger.log(moduleIteration, "Created changelog with %s entries.", tickets.getOverallTotal());
return new Changelog(module, new Tickets(tickets));
return new Changelog(moduleIteration, tickets);
}
/*
@@ -156,53 +151,159 @@ class GitHubIssueTracker implements IssueTracker {
return project.uses(Tracker.GITHUB);
}
private List<GitHubIssue> getIssuesFor(ModuleIteration module) {
@Cacheable("tickets")
public Tickets getTicketsFor(ModuleIteration iteration) {
return getTicketsFor(iteration, false);
}
String repositoryName = GitProject.of(module.getProject()).getRepositoryName();
@Override
public Tickets getTicketsFor(TrainIteration iteration) {
return getTicketsFor(iteration, false);
}
GitHubMilestone milestone = findMilestone(module, repositoryName);
/*
* (non-Javadoc)
* @see org.springframework.data.release.jira.GitHubIssueConnector#getTicketsFor(org.springframework.data.release.model.TrainIteration, boolean)
*/
@Override
public Tickets getTicketsFor(TrainIteration trainIteration, boolean forCurrentUser) {
Map<String, Object> parameters = new HashMap<>();
if (forCurrentUser) {
logger.log(trainIteration, "Retrieving tickets (for user %s)…", properties.getUsername());
} else {
logger.log(trainIteration, "Retrieving tickets…");
}
Tickets tickets = trainIteration.stream(). //
filter(moduleIteration -> supports(moduleIteration.getProject())). //
flatMap(moduleIteration -> getTicketsFor(moduleIteration, forCurrentUser).stream()). //
collect(Tickets.toTicketsCollector());
return tickets;
}
@Override
public void createReleaseVersion(ModuleIteration moduleIteration) {
Assert.notNull(moduleIteration, "ModuleIteration must not be null.");
String repositoryName = GitProject.of(moduleIteration.getProject()).getRepositoryName();
Optional<GitHubIssue.Milestone> milestone = findMilestone(moduleIteration, repositoryName);
if (milestone.isPresent()) {
return;
}
GithubMilestone githubMilestone = new GithubMilestone(moduleIteration);
logger.log(moduleIteration, "Creating GitHub milestone %s", githubMilestone);
HttpHeaders httpHeaders = newUserScopedHttpHeaders();
Map<String, Object> parameters = newUrlTemplateVariables();
parameters.put("repoName", repositoryName);
operations.exchange(MILESTONES_URI_TEMPLATE, HttpMethod.POST,
new HttpEntity<Object>(githubMilestone.toMilestone(), httpHeaders), GitHubIssue.Milestone.class, parameters);
}
@Override
public void createReleaseTicket(ModuleIteration moduleIteration) {
Assert.notNull(moduleIteration, "ModuleIteration must not be null.");
HttpHeaders httpHeaders = newUserScopedHttpHeaders();
Tickets tickets = getTicketsFor(moduleIteration);
if (tickets.hasReleaseTicket(moduleIteration)) {
return;
}
String repositoryName = GitProject.of(moduleIteration.getProject()).getRepositoryName();
Map<String, Object> parameters = newUrlTemplateVariables();
parameters.put("repoName", repositoryName);
GitHubIssue.Milestone milestone = getMilestone(moduleIteration, repositoryName);
logger.log(moduleIteration, "Creating release ticket…");
GitHubIssue gitHubIssue = new GitHubIssue(Tracker.releaseTicketSummary(moduleIteration), milestone);
operations.exchange(ISSUES_URI_TEMPLATE, HttpMethod.POST, new HttpEntity<Object>(gitHubIssue, httpHeaders),
GitHubIssue.class, parameters).getBody();
}
private Tickets getTicketsFor(ModuleIteration moduleIteration, boolean forCurrentUser) {
List<GitHubIssue> issues = getIssuesFor(moduleIteration, forCurrentUser);
return issues.stream().map(this::toTicket).collect(Tickets.toTicketsCollector());
}
private List<GitHubIssue> getIssuesFor(ModuleIteration moduleIteration, boolean forCurrentUser) {
String repositoryName = GitProject.of(moduleIteration.getProject()).getRepositoryName();
GitHubIssue.Milestone milestone = getMilestone(moduleIteration, repositoryName);
Map<String, Object> parameters = newUrlTemplateVariables();
parameters.put("repoName", repositoryName);
parameters.put("id", milestone.getNumber());
if (forCurrentUser) {
parameters.put("assignee", properties.getUsername());
return operations.exchange(ISSUES_BY_MILESTONE_AND_ASSIGNEE_URI_TEMPLATE, HttpMethod.GET,
new HttpEntity<>(newUserScopedHttpHeaders()), ISSUES_TYPE, parameters).getBody();
}
return operations.exchange(ISSUES_BY_MILESTONE_URI_TEMPLATE, HttpMethod.GET,
new HttpEntity<>(getAuthenticationHeaders()), ISSUES_TYPE, parameters).getBody();
new HttpEntity<>(newUserScopedHttpHeaders()), ISSUES_TYPE, parameters).getBody();
}
private GitHubMilestone findMilestone(ModuleIteration module, String repositoryName) {
private GitHubIssue.Milestone getMilestone(ModuleIteration moduleIteration, String repositoryName) {
Optional<GitHubIssue.Milestone> milestone = findMilestone(moduleIteration, repositoryName);
return milestone
.orElseThrow(() -> new IllegalStateException(String.format("No milestone for %s found containing %s!", //
moduleIteration.getProject().getFullName(), //
moduleIteration.getShortVersionString())));
}
@Cacheable("milestone")
protected Optional<GitHubIssue.Milestone> findMilestone(ModuleIteration moduleIteration, String repositoryName) {
for (String state : Arrays.asList("close", "open")) {
Map<String, Object> parameters = new HashMap<>();
Map<String, Object> parameters = newUrlTemplateVariables();
parameters.put("repoName", repositoryName);
parameters.put("state", state);
URI milestoneUri = new UriTemplate(MILESTONE_URI).expand(parameters);
logger.log(module, "Looking up milestone from %s…", milestoneUri);
logger.log(moduleIteration, "Looking up milestone from %s…", milestoneUri);
List<GitHubMilestone> exchange = operations.exchange(MILESTONE_URI, HttpMethod.GET,
new HttpEntity<>(getAuthenticationHeaders()), MILESTONES_TYPE, parameters).getBody();
List<GitHubIssue.Milestone> milestones = operations.exchange(MILESTONE_URI, HttpMethod.GET,
new HttpEntity<>(newUserScopedHttpHeaders()), MILESTONES_TYPE, parameters).getBody();
GitHubMilestone milestone = null;
Optional<GitHubIssue.Milestone> milestone = milestones.stream(). //
filter(m -> m.matchesIteration(moduleIteration)). //
findFirst(). //
map(m -> {
logger.log(moduleIteration, "Found milestone %s.", m);
return m;
});
for (GitHubMilestone candidate : exchange) {
if (candidate.getTitle().contains(module.getShortVersionString())) {
milestone = candidate;
}
}
if (milestone != null) {
logger.log(module, "Found milestone %s.", milestone);
if (milestone.isPresent()) {
return milestone;
}
}
throw new IllegalStateException(String.format("No milestone found containing %s!", module.getShortVersionString()));
return Optional.empty();
}
private HttpHeaders getAuthenticationHeaders() {
private HttpHeaders newUserScopedHttpHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", properties.getHttpCredentials().toString());
@@ -210,6 +311,16 @@ class GitHubIssueTracker implements IssueTracker {
return headers;
}
private Map<String, Object> newUrlTemplateVariables() {
Map<String, Object> parameters = new HashMap<>();
parameters.put("githubBaseUrl", properties.getGithubApiBaseUrl());
return parameters;
}
private Ticket toTicket(GitHubIssue issue) {
return new Ticket(issue.getId(), issue.getTitle(), new GithubTicketStatus(issue.getState()));
}
public static void main(String[] args) {
try (ConfigurableApplicationContext context = new ClassPathXmlApplicationContext(

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014 the original author or authors.
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,14 +15,32 @@
*/
package org.springframework.data.release.jira;
import lombok.Data;
import org.springframework.data.release.model.ModuleIteration;
import lombok.Value;
/**
* @author Oliver Gierke
* @author Mark Paluch
*/
@Data
class GitHubMilestone {
@Value
class GithubMilestone {
private String title;
private Long number;
private ModuleIteration module;
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return module.getMediumVersionString();
}
public String getDescription() {
return module.getTrain().getName() + " " + module.getIteration().getName();
}
public GitHubIssue.Milestone toMilestone() {
return new GitHubIssue.Milestone(toString(), getDescription());
}
}

View File

@@ -17,12 +17,16 @@ package org.springframework.data.release.jira;
import java.util.Collection;
import org.springframework.data.release.model.Iteration;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.Train;
import org.springframework.data.release.model.TrainIteration;
import org.springframework.plugin.core.Plugin;
/**
* @author Oliver Gierke
* @author Mark Paluch
*/
public interface IssueTracker extends Plugin<Project> {
@@ -31,6 +35,23 @@ public interface IssueTracker extends Plugin<Project> {
*/
void reset();
/**
* Returns all {@link Tickets} for the given {@link Train} and {@link Iteration}.
*
* @param iteration must not be {@literal null}.
* @return
*/
Tickets getTicketsFor(TrainIteration iteration);
/**
* Returns all {@link Tickets} for the given {@link Train} and {@link Iteration}.
*
* @param iteration must not be {@literal null}.
* @param forCurrentUser
* @return
*/
Tickets getTicketsFor(TrainIteration iteration, boolean forCurrentUser);
/**
* Returns the {@link Ticket} that tracks modifications in the context of a release.
*
@@ -49,5 +70,21 @@ public interface IssueTracker extends Plugin<Project> {
*/
Collection<Ticket> findTickets(Project project, Collection<String> ticketIds);
/**
* Creates a release version if release version is missing.
*
* @param moduleIteration must not be {@literal null}.
* @param credentials must not be {@literal null}.
*/
void createReleaseVersion(ModuleIteration moduleIteration);
/**
* Create release ticket if release ticket is missing.
*
* @param iteration must not be {@literal null}.
* @param credentials must not be {@literal null}.
*/
void createReleaseTicket(ModuleIteration moduleIteration);
Changelog getChangelogFor(ModuleIteration iteration);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014 the original author or authors.
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.data.release.jira;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
@@ -32,11 +33,13 @@ import org.springframework.plugin.core.OrderAwarePluginRegistry;
import org.springframework.plugin.core.PluginRegistry;
import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* @author Oliver Gierke
* @author Mark Paluch
*/
@Configuration
@EnableCaching
@@ -57,6 +60,7 @@ class IssueTrackerConfiguration {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.setSerializationInclusion(Include.NON_NULL);
return mapper;
}
@@ -77,8 +81,8 @@ class IssueTrackerConfiguration {
}
@Bean
public Jira jira(Logger logger) {
return new Jira(restTemplate(), logger);
public Jira jira(Logger logger, JiraProperties jiraProperties) {
return new Jira(restTemplate(), logger, jiraProperties);
}
@Bean

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,18 +22,21 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.SpringApplication;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.data.release.Application;
import org.springframework.data.release.jira.JiraIssue.Component;
import org.springframework.data.release.jira.JiraIssue.Fields;
import org.springframework.data.release.model.Iteration;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.ProjectKey;
import org.springframework.data.release.model.ReleaseTrains;
import org.springframework.data.release.model.Tracker;
import org.springframework.data.release.model.TrainIteration;
@@ -41,46 +44,57 @@ import org.springframework.data.release.utils.Logger;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.util.Assert;
import org.springframework.web.client.RestOperations;
import org.springframework.web.util.UriTemplate;
import lombok.RequiredArgsConstructor;
/**
* @author Oliver Gierke
* @author Mark Paluch
*/
@RequiredArgsConstructor
class Jira implements JiraConnector {
private static final String JIRA_HOST = "https://jira.spring.io";
private static final String BASE_URI = "/rest/api/2";
private static final String SEARCH_TEMPLATE = JIRA_HOST + BASE_URI
+ "/search?jql={jql}&fields={fields}&startAt={startAt}";
private static final String BASE_URI = "{jiraBaseUrl}/rest/api/2";
private static final String CREATE_ISSUES_TEMPLATE = BASE_URI + "/issue";
private static final String UPDATE_ISSUE_TEMPLATE = BASE_URI + "/issue/{ticketId}";
private static final String PROJECT_VERSIONS_TEMPLATE = BASE_URI + "/project/{project}/version?startAt={startAt}";
private static final String PROJECT_COMPONENTS_TEMPLATE = BASE_URI + "/project/{project}/components";
private static final String VERSIONS_TEMPLATE = BASE_URI + "/version";
private static final String SEARCH_TEMPLATE = BASE_URI + "/search?jql={jql}&fields={fields}&startAt={startAt}";
public static final String INFRASTRUCTURE_COMPONENT_NAME = "Infrastructure";
private final RestOperations operations;
private final Logger logger;
/*
private final JiraProperties jiraProperties;
/*
* (non-Javadoc)
* @see org.springframework.data.release.jira.JiraConnector#flushTickets()
* @see org.springframework.data.release.jira.JiraConnector#reset()
*/
@Override
@CacheEvict(value = "tickets", allEntries = true)
@CacheEvict(value = { "release-ticket", "tickets", "changelogs", "release-version" }, allEntries = true)
public void reset() {}
@Cacheable("release-tickets")
public Ticket getReleaseTicketFor(ModuleIteration iteration) {
/*
* (non-Javadoc)
* @see org.springframework.data.release.jira.IssueTracker#getReleaseTicketFor(org.springframework.data.release.model.ModuleIteration)
*/
@Override
@Cacheable("release-ticket")
public Ticket getReleaseTicketFor(ModuleIteration moduleIteration) {
JqlQuery query = JqlQuery.from(iteration).and("summary ~ \"Release\"");
JqlQuery query = JqlQuery.from(moduleIteration)
.and(String.format("summary ~ \"%s\"", Tracker.releaseTicketSummary(moduleIteration)));
Map<String, Object> parameters = new HashMap<>();
parameters.put("jql", query);
parameters.put("fields", "summary,status,resolution");
parameters.put("startAt", 0);
JiraIssues issues = operations.exchange(SEARCH_TEMPLATE, HttpMethod.GET, null, JiraIssues.class, parameters)
.getBody();
JiraIssues issues = getJiraIssues(query, new HttpHeaders(), 0);
if (issues.getIssues().isEmpty()) {
throw new IllegalStateException(String.format("Did not find a release ticket for %s!", iteration));
throw new IllegalArgumentException(String.format("Did not find a release ticket for %s!", moduleIteration));
}
JiraIssue issue = issues.getIssues().get(0);
@@ -88,9 +102,9 @@ class Jira implements JiraConnector {
return toTicket(issue);
}
/**
/*
* (non-Javadoc)
*
*
* @see org.springframework.data.release.jira.IssueTracker#findTickets(Project, Collection)
*/
@Override
@@ -103,7 +117,7 @@ class Jira implements JiraConnector {
JqlQuery query = JqlQuery.from(ticketIds).and(" resolution is not EMPTY");
Map<String, Object> parameters = new HashMap<>();
Map<String, Object> parameters = newUrlTemplateVariables();
parameters.put("jql", query);
parameters.put("fields", "summary,status,resolution");
parameters.put("startAt", 0);
@@ -116,57 +130,330 @@ class Jira implements JiraConnector {
collect(Collectors.toList());
}
/*
* (non-Javadoc)
* @see org.springframework.data.release.jira.JiraConnector#getTicketsFor(org.springframework.data.release.model.Train, org.springframework.data.release.model.Iteration, org.springframework.data.release.jira.Credentials)
*/
@Override
@Cacheable("tickets")
public Tickets getTicketsFor(TrainIteration iteration, Credentials credentials) {
@Override
public Tickets getTicketsFor(TrainIteration iteration) {
return getTicketsFor(iteration, false);
}
JqlQuery query = JqlQuery.from(iteration);
@Cacheable("tickets")
@Override
public Tickets getTicketsFor(TrainIteration trainIteration, boolean forCurrentUser) {
JqlQuery query = JqlQuery
.from(trainIteration.stream().filter(moduleIteration -> supports(moduleIteration.getProject())));
HttpHeaders headers = newUserScopedHttpHeaders();
HttpHeaders headers = new HttpHeaders();
int startAt = 0;
List<Ticket> tickets = new ArrayList<>();
JiraIssues issues = null;
if (credentials != null) {
if (forCurrentUser) {
query = query.and("assignee = currentUser()");
headers.set("Authorization", String.format("Basic %s", credentials.asBase64()));
logger.log(iteration, "Retrieving tickets (for user %s)…", credentials.getUsername());
logger.log(trainIteration, "Retrieving tickets (for user %s)…", jiraProperties.getUsername());
} else {
logger.log(iteration, "Retrieving tickets…");
logger.log(trainIteration, "Retrieving tickets…");
}
query = query.orderBy("updatedDate DESC");
do {
JiraIssues issues = execute(trainIteration.toString(), query, headers, jiraIssues -> {
jiraIssues.stream().//
filter(jiraIssue -> !jiraIssue.wasBackportedFrom(trainIteration.getTrain())). //
forEach(jiraIssue -> tickets.add(toTicket(jiraIssue)));
});
Map<String, Object> parameters = new HashMap<>();
parameters.put("jql", query);
parameters.put("fields", "summary,status,resolution,fixVersions");
parameters.put("startAt", startAt);
return new Tickets(tickets, issues.getTotal());
}
issues = operations
.exchange(SEARCH_TEMPLATE, HttpMethod.GET, new HttpEntity<>(headers), JiraIssues.class, parameters).getBody();
@Cacheable("tickets")
public Tickets getTicketsFor(ModuleIteration moduleIteration) {
logger.log(iteration, "Got tickets %s to %s of %s.", startAt, issues.getNextStartAt(), issues.getTotal());
JqlQuery query = JqlQuery.from(moduleIteration);
for (JiraIssue issue : issues) {
if (!issue.wasBackportedFrom(iteration.getTrain())) {
tickets.add(toTicket(issue));
}
HttpHeaders headers = new HttpHeaders();
List<Ticket> tickets = new ArrayList<>();
logger.log(moduleIteration, "Retrieving tickets…");
query = query.orderBy("updatedDate DESC");
JiraIssues issues = execute(moduleIteration.toString(), query, headers, jiraIssues -> {
jiraIssues.stream().//
filter(jiraIssue -> !jiraIssue.wasBackportedFrom(moduleIteration.getTrain())). //
forEach(jiraIssue -> tickets.add(toTicket(jiraIssue)));
});
return new Tickets(tickets, issues.getTotal());
}
/*
* (non-Javadoc)
* @see org.springframework.data.release.jira.JiraConnector#createReleaseVersion(org.springframework.data.release.model.ModuleIteration, org.springframework.data.release.jira.Credentials)
*/
@Override
public void createReleaseVersion(ModuleIteration moduleIteration) {
Assert.notNull(moduleIteration, "ModuleIteration must not be null.");
Map<String, Object> parameters = newUrlTemplateVariables();
HttpHeaders httpHeaders = newUserScopedHttpHeaders();
Optional<JiraReleaseVersion> versionsForModuleIteration = findJiraReleaseVersion(moduleIteration);
if (versionsForModuleIteration.isPresent()) {
return;
}
JiraVersion jiraVersion = new JiraVersion(moduleIteration);
logger.log(moduleIteration, "Creating Jira release version %s", jiraVersion);
JiraReleaseVersion jiraReleaseVersion = new JiraReleaseVersion(moduleIteration, jiraVersion);
operations.exchange(VERSIONS_TEMPLATE, HttpMethod.POST, new HttpEntity<Object>(jiraReleaseVersion, httpHeaders),
JiraReleaseVersion.class, parameters).getBody();
}
/*
* (non-Javadoc)
* @see org.springframework.data.release.jira.JiraConnector#findJiraReleaseVersion(org.springframework.data.release.model.ModuleIteration)
*/
@Cacheable("release-version")
public Optional<JiraReleaseVersion> findJiraReleaseVersion(ModuleIteration moduleIteration) {
JiraVersion jiraVersion = new JiraVersion(moduleIteration);
HttpHeaders httpHeaders = new HttpHeaders();
List<JiraReleaseVersion> versionsForModuleIteration = new ArrayList<>();
getReleaseVersions(moduleIteration.toString(), moduleIteration.getProjectKey(), httpHeaders, releaseVersions -> {
releaseVersions.stream(). //
filter(jiraReleaseVersion -> jiraReleaseVersion.hasSameNameAs(jiraVersion)). //
findFirst(). //
ifPresent(jiraReleaseVersion -> versionsForModuleIteration.add(jiraReleaseVersion));
});
return versionsForModuleIteration.stream().findFirst();
}
@Override
public void createReleaseTicket(ModuleIteration moduleIteration) {
Assert.notNull(moduleIteration, "ModuleIteration must not be null.");
HttpHeaders httpHeaders = newUserScopedHttpHeaders();
Map<String, Object> parameters = newUrlTemplateVariables();
Tickets tickets = getTicketsFor(moduleIteration);
if (tickets.hasReleaseTicket(moduleIteration)) {
return;
}
Optional<JiraReleaseVersion> jiraReleaseVersion = findJiraReleaseVersion(moduleIteration);
jiraReleaseVersion.orElseThrow(
() -> new IllegalStateException(String.format("Did not find a release version for %s", moduleIteration)));
JiraComponents jiraComponents = getJiraComponents(moduleIteration.getProjectKey());
logger.log(moduleIteration, "Creating release ticket…");
JiraIssue jiraIssue = prepareJiraIssueToCreate(moduleIteration, jiraComponents);
operations.exchange(CREATE_ISSUES_TEMPLATE, HttpMethod.POST, new HttpEntity<Object>(jiraIssue, httpHeaders),
CreatedJiraIssue.class, parameters).getBody();
}
@Override
public void assignTicketToMe(Ticket ticket) {
Assert.notNull(ticket, "Ticket must not be null.");
HttpHeaders httpHeaders = newUserScopedHttpHeaders();
Map<String, Object> parameters = newUrlTemplateVariables();
parameters.put("ticketId", ticket.getId());
JiraIssue jiraIssue = JiraIssue.create().assignTo(jiraProperties.getCredentials());
operations.exchange(UPDATE_ISSUE_TEMPLATE, HttpMethod.POST, new HttpEntity<Object>(jiraIssue, httpHeaders),
String.class, parameters).getBody();
}
private JiraIssue prepareJiraIssueToCreate(ModuleIteration moduleIteration, JiraComponents jiraComponents) {
JiraIssue jiraIssue = JiraIssue.createTask();
jiraIssue.project(moduleIteration.getProjectKey()).summary(Tracker.releaseTicketSummary(moduleIteration))
.fixVersion(moduleIteration);
Fields fields = jiraIssue.getFields();
Optional<JiraComponent> component = jiraComponents.findComponent(INFRASTRUCTURE_COMPONENT_NAME);
component.ifPresent(
jiraComponent -> fields.setComponents(Collections.singletonList(Component.from(jiraComponent.getName()))));
return jiraIssue;
}
/*
* (non-Javadoc)
* @see org.springframework.data.release.jira.JiraConnector#verifyBeforeRelease(org.springframework.data.release.model.Train, org.springframework.data.release.model.Iteration)
*/
@Override
public void verifyBeforeRelease(TrainIteration trainIteration) {
// for each module
for (ModuleIteration moduleIteration : trainIteration) {
Tickets tickets = getTicketsFor(moduleIteration);
Ticket releaseTicket = tickets.getReleaseTicket(moduleIteration);
if (releaseTicket.isResolved()) {
throw new IllegalStateException(
String.format("Release ticket %s for %s is resolved", releaseTicket, moduleIteration));
}
Tickets issueTickets = tickets.getIssueTickets(moduleIteration);
List<Ticket> unresolvedTickets = issueTickets.stream().//
filter(ticket -> !ticket.isResolved()).//
collect(Collectors.toList());
if (!unresolvedTickets.isEmpty()) {
throw new IllegalStateException(
String.format("Unresolved tickets for %s: %s", moduleIteration, unresolvedTickets));
}
}
}
/*
*
* (non-Javadoc)
* @see org.springframework.data.release.jira.JiraConnector#closeIteration(org.springframework.data.release.model.Train, org.springframework.data.release.model.Iteration)
*/
@Override
public void closeIteration(TrainIteration iteration) {
// for each module
// - close all tickets
// -- make sure only one ticket is open
// -- resolve open ticket
// -- close tickets
// - mark version as releases
// - if no next version exists, create
}
/*
* (non-Javadoc)
* @see org.springframework.data.release.jira.JiraConnector#getChangelogFor(org.springframework.data.release.model.Module, org.springframework.data.release.model.Iteration)
*/
@Override
@Cacheable("changelogs")
public Changelog getChangelogFor(ModuleIteration moduleIteration) {
Map<String, Object> parameters = newUrlTemplateVariables();
parameters.put("jql", JqlQuery.from(moduleIteration));
parameters.put("fields", "summary,status,resolution,fixVersions");
parameters.put("startAt", 0);
URI searchUri = new UriTemplate(SEARCH_TEMPLATE).expand(parameters);
logger.log(moduleIteration, "Looking up JIRA issues from %s…", searchUri);
JiraIssues issues = operations.getForObject(searchUri, JiraIssues.class);
Tickets tickets = issues.stream().map(this::toTicket).collect(Tickets.toTicketsCollector());
logger.log(moduleIteration, "Created changelog with %s entries.", tickets.getOverallTotal());
return new Changelog(moduleIteration, tickets);
}
/*
* (non-Javadoc)
* @see org.springframework.plugin.core.Plugin#supports(java.lang.Object)
*/
@Override
public boolean supports(Project project) {
return project.uses(Tracker.JIRA);
}
@Cacheable("jira-components")
protected JiraComponents getJiraComponents(ProjectKey projectKey) {
HttpHeaders headers = new HttpHeaders();
Map<String, Object> parameters = newUrlTemplateVariables();
parameters.put("project", projectKey.getKey());
List<JiraComponent> components = operations.exchange(PROJECT_COMPONENTS_TEMPLATE, HttpMethod.GET,
new HttpEntity<>(headers), new ParameterizedTypeReference<List<JiraComponent>>() {}, parameters).getBody();
return JiraComponents.of(components);
}
private JiraIssues execute(String context, JqlQuery query, HttpHeaders headers, JiraIssuesCallback callback) {
JiraIssues issues;
int startAt = 0;
do {
issues = getJiraIssues(query, headers, startAt);
logger.log(context, "Got tickets %s to %s of %s.", startAt, issues.getNextStartAt(), issues.getTotal());
callback.doWithJiraIssues(issues);
startAt = issues.getNextStartAt();
} while (issues.hasMoreResults());
return new Tickets(Collections.unmodifiableList(tickets), issues.getTotal());
return issues;
}
private JiraIssues getJiraIssues(JqlQuery query, HttpHeaders headers, int startAt) {
JiraIssues issues;
Map<String, Object> parameters = newUrlTemplateVariables();
parameters.put("jql", query);
parameters.put("fields", "summary,status,resolution,fixVersions");
parameters.put("startAt", startAt);
issues = operations
.exchange(SEARCH_TEMPLATE, HttpMethod.GET, new HttpEntity<>(headers), JiraIssues.class, parameters).getBody();
return issues;
}
private JiraReleaseVersions getReleaseVersions(String context, ProjectKey projectKey, HttpHeaders headers,
JiraReleaseVersionsCallback callback) {
JiraReleaseVersions releaseVersions;
int startAt = 0;
do {
releaseVersions = getJiraReleaseVersions(projectKey, headers, startAt);
logger.log(context, "Got release versions %s to %s of %s.", startAt, releaseVersions.getNextStartAt(),
releaseVersions.getTotal());
callback.doWithJiraReleaseVersions(releaseVersions);
startAt = releaseVersions.getNextStartAt();
} while (releaseVersions.hasMoreResults());
return releaseVersions;
}
private JiraReleaseVersions getJiraReleaseVersions(ProjectKey projectKey, HttpHeaders headers, int startAt) {
Map<String, Object> parameters = newUrlTemplateVariables();
parameters.put("project", projectKey.getKey());
parameters.put("fields", "summary,status,resolution,fixVersions");
parameters.put("startAt", startAt);
return operations.exchange(PROJECT_VERSIONS_TEMPLATE, HttpMethod.GET, new HttpEntity<>(headers),
JiraReleaseVersions.class, parameters).getBody();
}
private Ticket toTicket(JiraIssue issue) {
@@ -187,73 +474,16 @@ class Jira implements JiraConnector {
return new Ticket(issue.getKey(), fields.getSummary(), jiraTicketStatus);
}
/*
* (non-Javadoc)
* @see org.springframework.data.release.jira.JiraConnector#verifyBeforeRelease(org.springframework.data.release.model.Train, org.springframework.data.release.model.Iteration)
*/
@Override
public void verifyBeforeRelease(TrainIteration iteration) {
// for each module
// - make sure only one ticket is open
}
/*
/**
* Returns new {@link HttpHeaders} with authentication headers.
*
* (non-Javadoc)
* @see org.springframework.data.release.jira.JiraConnector#closeIteration(org.springframework.data.release.model.Train, org.springframework.data.release.model.Iteration, org.springframework.data.release.jira.Credentials)
* @return
*/
@Override
public void closeIteration(TrainIteration iteration, Credentials credentials) {
private HttpHeaders newUserScopedHttpHeaders() {
// for each module
// - close all tickets
// -- make sure only one ticket is open
// -- resolve open ticket
// -- close tickets
// - mark version as releases
// - if no next version exists, create
}
/*
* (non-Javadoc)
* @see org.springframework.data.release.jira.JiraConnector#getChangelogFor(org.springframework.data.release.model.Module, org.springframework.data.release.model.Iteration)
*/
@Override
@Cacheable("changelogs")
public Changelog getChangelogFor(ModuleIteration module) {
Map<String, Object> parameters = new HashMap<>();
parameters.put("jql", JqlQuery.from(module));
parameters.put("fields", "summary,status,resolution,fixVersions");
parameters.put("startAt", 0);
URI searchUri = new UriTemplate(SEARCH_TEMPLATE).expand(parameters);
logger.log(module, "Looking up JIRA issues from %s…", searchUri);
JiraIssues issues = operations.getForObject(searchUri, JiraIssues.class);
List<Ticket> tickets = new ArrayList<>();
for (JiraIssue issue : issues) {
tickets.add(toTicket(issue));
}
logger.log(module, "Created changelog with %s entries.", tickets.size());
return new Changelog(module, new Tickets(tickets, tickets.size()));
}
/*
* (non-Javadoc)
* @see org.springframework.plugin.core.Plugin#supports(java.lang.Object)
*/
@Override
public boolean supports(Project project) {
return project.uses(Tracker.JIRA);
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", String.format("Basic %s", jiraProperties.getCredentials().asBase64()));
return headers;
}
public static void main(String[] args) {
@@ -270,4 +500,26 @@ class Jira implements JiraConnector {
System.out.println(tracker.getReleaseTicketFor(module));
}
}
private Map<String, Object> newUrlTemplateVariables() {
Map<String, Object> parameters = new HashMap<>();
parameters.put("jiraBaseUrl", jiraProperties.getUrl());
return parameters;
}
/**
* Callback for {@link JiraIssues}.
*/
interface JiraIssuesCallback {
void doWithJiraIssues(JiraIssues jiraIssues);
}
/**
* Callback for {@link JiraReleaseVersions}.
*/
interface JiraReleaseVersionsCallback {
void doWithJiraReleaseVersions(JiraReleaseVersions releaseVersions);
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.release.jira;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Value;
/*
* Value object to bind REST responses to.
*
* @author Mark Paluch
*/
@Value
class JiraComponent {
private String id;
private String name;
@JsonCreator
public JiraComponent(@JsonProperty("id") String id, @JsonProperty("name") String name) {
this.id = id;
this.name = name;
}
public boolean hasName(String name) {
return this.name.equals(name);
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.release.jira;
import java.util.Collection;
import java.util.Iterator;
import java.util.Optional;
import org.springframework.util.Assert;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NonNull;
/*
* Value object to represent a list of {@link JiraComponent}s.
* @author Mark Paluch
*/
@Data
@AllArgsConstructor(access = AccessLevel.PRIVATE)
class JiraComponents implements Iterable<JiraComponent> {
@NonNull private Collection<JiraComponent> jiraComponents;
/**
* Try to find a component by its {@code name}.
*
* @param name
* @return
*/
public Optional<JiraComponent> findComponent(String name) {
return jiraComponents.stream().filter(jiraComponent -> jiraComponent.hasName(name)).findFirst();
}
public static JiraComponents of(Collection<JiraComponent> jiraComponents) {
Assert.notNull(jiraComponents, "JiraComponents must not be null!");
return new JiraComponents(jiraComponents);
}
@Override
public Iterator<JiraComponent> iterator() {
return jiraComponents.iterator();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2014 the original author or authors.
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,27 +15,41 @@
*/
package org.springframework.data.release.jira;
import org.springframework.data.release.model.Iteration;
import org.springframework.data.release.model.Train;
import java.util.Optional;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.TrainIteration;
/**
* @author Oliver Gierke
* @author Mark Paluch
*/
public interface JiraConnector extends IssueTracker {
void reset();
/**
* Returns all {@link Tickets} for the given {@link Train} and {@link Iteration}.
* Verifies the state of all {@link Tickets} before releasing. In particular: Check whether the release ticket exists,
* check whether all other issue tickets are in a resolved state.
*
* @param iteration must not be {@literal null}.
* @param credentials may be {@literal null}.
* @return
* @param iteration
*/
Tickets getTicketsFor(TrainIteration iteration, Credentials credentials);
void verifyBeforeRelease(TrainIteration iteration);
void closeIteration(TrainIteration iteration, Credentials credentials);
void closeIteration(TrainIteration iteration);
/**
* Lookup a Jira release version.
*
* @param moduleIteration must not be {@literal null}.
* @return
*/
Optional<JiraReleaseVersion> findJiraReleaseVersion(ModuleIteration moduleIteration);
/**
* Assigns the ticket to the current user.
*
* @param ticket must not be {@literal null}.
* @param credentials must not be {@literal null}.
*/
void assignTicketToMe(Ticket ticket);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014 the original author or authors.
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,18 +15,24 @@
*/
package org.springframework.data.release.jira;
import java.util.Collections;
import java.util.List;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.ProjectKey;
import org.springframework.data.release.model.Tracker;
import org.springframework.data.release.model.Train;
import org.springframework.util.Assert;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.release.model.Train;
/**
* Value object to bind REST responses to.
*
*
* @author Oliver Gierke
* @author Mark Paluch
*/
@Data
class JiraIssue {
@@ -37,23 +43,154 @@ class JiraIssue {
/**
* Returns whether the ticket is only fixed in the given {@link Train}. {@literal false} indicates the {@link Ticket}
* has been resolved for
*
*
* @param train
* @return
*/
public boolean wasBackportedFrom(Train train) {
List<FixVersions> fixVersions = fields.getFixVersions();
return !(fixVersions.size() == 1 && fixVersions.get(0).name.contains(train.getName()));
return fixVersions != null && wasBackportedFrom(train, fixVersions);
}
private boolean wasBackportedFrom(Train train, List<FixVersions> fixVersions) {
return fixVersions.size() > 1 && isPresent(train, fixVersions);
}
private boolean isPresent(Train train, List<FixVersions> fixVersions) {
return fixVersions.stream().//
filter(fixVersion -> fixVersion.isTrain(train)).//
findFirst().//
isPresent();
}
/**
* Returns whether the ticket is a release ticket.
*
* @param moduleIteration
* @return
*/
public boolean isReleaseTicket(ModuleIteration moduleIteration) {
List<FixVersions> fixVersions = fields.getFixVersions();
return fixVersions.size() == 1 && isPresent(moduleIteration.getTrain(), fixVersions)
&& fields.hasSummary(Tracker.releaseTicketSummary(moduleIteration));
}
/**
* Creates a new Jira issue value object.
*
* @return
*/
public static JiraIssue create() {
JiraIssue jiraIssue = new JiraIssue();
jiraIssue.setFields(new Fields());
return jiraIssue;
}
/**
* Creates a new Jira issue value object with a issue type of {@link IssueType#TASK}.
*
* @return
*/
public static JiraIssue createTask() {
JiraIssue jiraIssue = create();
jiraIssue.getFields().setIssuetype(IssueType.TASK);
return jiraIssue;
}
/**
* Assign to the user specified in {@link Credentials}.
*
* @param credentials must not be {@literal null}.
* @return
*/
public JiraIssue assignTo(Credentials credentials) {
return assignTo(credentials.getUsername());
}
/**
* Assign the ticket to {@code username}.
*
* @param username must not be empty and not {@literal null}.
* @return
*/
public JiraIssue assignTo(String username) {
Assert.hasText(username, "Username must not be empty!");
getFields().setAssignee(JiraUser.from(username));
return this;
}
/**
* Set the project to {@code projectKey}.
*
* @param projectKey must not be {@literal null}.
* @return
*/
public JiraIssue project(ProjectKey projectKey) {
getFields().setProject(new Project(projectKey));
return this;
}
/**
* Set the issue summary.
*
* @param summary must not be empty and not {@literal null}.
*/
public JiraIssue summary(String summary) {
Assert.hasText(summary, "Summary must not be empty!");
getFields().setSummary(summary);
return this;
}
/**
* Set the fix version.
*
* @param moduleIteration must not be {@literal null}.
* @return
*/
public JiraIssue fixVersion(ModuleIteration moduleIteration) {
Assert.notNull(moduleIteration, "ModuleIteration must not be null!");
getFields().setFixVersions(Collections.singletonList(new JiraVersion(moduleIteration).toFixVersions()));
return this;
}
@Data
static class Fields {
Project project;
IssueType issuetype;
String summary;
List<FixVersions> fixVersions;
List<Component> components;
Status status;
Resolution resolution;
JiraUser assignee;
public boolean hasSummary(String other) {
return summary.equals(other);
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
static class Component {
String id;
String name;
public static Component from(String name) {
Assert.hasText(name, "Name must not be empty!");
return new Component(null, name);
}
}
@Data
@@ -61,6 +198,27 @@ class JiraIssue {
@AllArgsConstructor
static class FixVersions {
String id;
String name;
/**
* Returns whether this {@link FixVersions} matches the given {@link Train}.
*
* @param train
* @return
*/
public boolean isTrain(Train train) {
return name.contains(train.getName());
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
static class IssueType {
public final static IssueType TASK = new IssueType("Task");
String name;
}
@@ -73,6 +231,18 @@ class JiraIssue {
StatusCategory statusCategory;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
static class Project {
String key;
public Project(ProjectKey projectKey) {
this.key = projectKey.getKey();
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@@ -88,4 +258,17 @@ class JiraIssue {
String key;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
static class JiraUser {
String name;
String displayName;
static JiraUser from(String username) {
return new JiraUser(username, null);
}
}
}

View File

@@ -18,13 +18,15 @@ package org.springframework.data.release.jira;
import java.util.Iterator;
import java.util.List;
import org.springframework.data.release.Streamable;
import lombok.Data;
/**
* @author Oliver Gierke
*/
@Data
class JiraIssues implements Iterable<JiraIssue> {
class JiraIssues implements Streamable<JiraIssue> {
private int startAt;
private int maxResults;

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.release.jira;
import javax.annotation.PostConstruct;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import lombok.AccessLevel;
import lombok.Data;
import lombok.Getter;
/**
* Configurable properties for Git.
*
* @author Mark Paluch
*/
@Data
@Component
@ConfigurationProperties(prefix = "jira")
class JiraProperties {
private @Getter(AccessLevel.PRIVATE) String password;
private String username, url;
@PostConstruct
public void init() {
Assert.hasText(username, "No Jira username (jira.username) configured!");
Assert.hasText(password, "No Jira password (jira.password) configured!");
Assert.hasText(url, "No Jira url (jira.url) configured!");
}
/**
* Returns the {@link Credentials} to be used.
*
* @return
*/
public Credentials getCredentials() {
return new Credentials(username, password);
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.release.jira;
import lombok.Data;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.util.Assert;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Value object to bind REST responses to.
*
* @author Mark Paluch
*/
@Data
public class JiraReleaseVersion {
private String id;
private String name;
private String project;
private String description;
@JsonCreator
public JiraReleaseVersion(@JsonProperty("id") String id, @JsonProperty("name") String name,
@JsonProperty("string") String project, @JsonProperty("description") String description) {
this.id = id;
this.name = name;
this.project = project;
this.description = description;
}
public JiraReleaseVersion(ModuleIteration moduleIteration, JiraVersion jiraVersion) {
Assert.notNull(moduleIteration, "ModuleIteration must not be null.");
Assert.notNull(jiraVersion, "JiraVersion must not be null.");
project = moduleIteration.getProjectKey().getKey();
name = jiraVersion.toString();
description = jiraVersion.getDescription();
}
public boolean hasSameNameAs(JiraVersion jiraVersion) {
return getName().equals(jiraVersion.toString());
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.release.jira;
import java.util.Iterator;
import java.util.List;
import org.springframework.data.release.Streamable;
import lombok.Data;
/**
* Value object to bind REST responses to.
*
* @author Mark Paluch
*/
@Data
class JiraReleaseVersions implements Streamable<JiraReleaseVersion> {
private int startAt;
private int maxResults;
private int total;
private List<JiraReleaseVersion> values;
public int getNextStartAt() {
return startAt + values.size();
}
public boolean hasMoreResults() {
return startAt + values.size() < total;
}
/*
* (non-Javadoc)
* @see java.lang.Iterable#iterator()
*/
@Override
public Iterator<JiraReleaseVersion> iterator() {
return values.iterator();
}
}

View File

@@ -15,10 +15,11 @@
*/
package org.springframework.data.release.jira;
import lombok.Value;
import org.springframework.data.release.jira.JiraIssue.FixVersions;
import org.springframework.data.release.model.ModuleIteration;
import lombok.Value;
/**
* @author Oliver Gierke
*/
@@ -35,4 +36,12 @@ class JiraVersion {
public String toString() {
return module.getMediumVersionString();
}
public String getDescription() {
return module.getTrain().getName() + " " + module.getIteration().getName();
}
public FixVersions toFixVersions() {
return new FixVersions(null, toString());
}
}

View File

@@ -15,27 +15,25 @@
*/
package org.springframework.data.release.jira;
import static org.springframework.data.release.model.Projects.*;
import lombok.Value;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.StringJoiner;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.TrainIteration;
import org.springframework.util.StringUtils;
import lombok.Value;
/**
* @author Oliver Gierke
* @author Mark Paluch
*/
@Value
class JqlQuery {
private static final String PROJECT_VERSION_TEMPLATE = "project = %s AND fixVersion = \"%s\"";
private static final String PROJECT_VERSION_TEMPLATE = "(project = %s AND fixVersion = \"%s\" )";
private static final String ISSUE_KEY_IN_TEMPLATE = "issueKey in (%s)";
private final String query;
@@ -62,15 +60,14 @@ class JqlQuery {
return new JqlQuery(String.format(ISSUE_KEY_IN_TEMPLATE, joinedTicketIds));
}
public static JqlQuery from(TrainIteration iteration) {
public static JqlQuery from(Stream<ModuleIteration> stream) {
List<String> parts = new ArrayList<>();
for (ModuleIteration module : iteration.getModulesExcept(BUILD)) {
JiraVersion version = new JiraVersion(module);
parts.add(String.format(PROJECT_VERSION_TEMPLATE, module.getProjectKey(), version));
}
stream.forEach(moduleIteration -> {
JiraVersion version = new JiraVersion(moduleIteration);
parts.add(String.format(PROJECT_VERSION_TEMPLATE, moduleIteration.getProjectKey(), version));
});
return new JqlQuery(StringUtils.collectionToDelimitedString(parts, " OR "));
}

View File

@@ -15,6 +15,10 @@
*/
package org.springframework.data.release.jira;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Tracker;
import org.springframework.data.release.model.TrainIteration;
import lombok.Value;
/**
@@ -37,4 +41,16 @@ public class Ticket {
public String toString() {
return String.format("%13s - %s", id, summary);
}
public boolean isResolved() {
return ticketStatus.isResolved();
}
public boolean isReleaseTicketFor(ModuleIteration moduleIteration) {
return summary.equals(Tracker.releaseTicketSummary(moduleIteration));
}
public boolean isReleaseTicketFor(TrainIteration iteration) {
return iteration.stream().filter(this::isReleaseTicketFor).findFirst().isPresent();
}
}

View File

@@ -15,10 +15,6 @@
*/
package org.springframework.data.release.jira;
import lombok.EqualsAndHashCode;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
@@ -29,6 +25,10 @@ import org.springframework.data.release.Streamable;
import org.springframework.data.release.git.Branch;
import org.springframework.util.Assert;
import lombok.EqualsAndHashCode;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
/**
* Value object to represent a collection of {@link Branch}es with assigned tickets.
*

View File

@@ -15,31 +15,45 @@
*/
package org.springframework.data.release.jira;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Stream;
import org.springframework.data.release.Streamable;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.TrainIteration;
import org.springframework.util.StringUtils;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import org.springframework.util.StringUtils;
/**
* Value object to represent a list of {@link Ticket}s.
*
*
* @author Oliver Gierke
* @author Mark Paluch
*/
@Value
@RequiredArgsConstructor
public class Tickets implements Iterable<Ticket> {
public class Tickets implements Streamable<Ticket> {
private final List<Ticket> tickets;
private final int overallTotal;
public Tickets(List<Ticket> tickets) {
this(tickets, tickets.size());
this(Collections.unmodifiableList(tickets), tickets.size());
}
/*
/*
* (non-Javadoc)
* @see java.lang.Iterable#iterator()
*/
@@ -48,6 +62,37 @@ public class Tickets implements Iterable<Ticket> {
return tickets.iterator();
}
public boolean hasReleaseTicket(ModuleIteration moduleIteration) {
return findReleaseTicket(moduleIteration).isPresent();
}
public Ticket getReleaseTicket(ModuleIteration moduleIteration) {
Optional<Ticket> releaseTicket = findReleaseTicket(moduleIteration);
return releaseTicket.orElseThrow(
() -> new IllegalStateException(String.format("Did not find a release ticket for %s!", moduleIteration)));
}
public Tickets getIssueTickets(ModuleIteration moduleIteration) {
return tickets.stream(). //
filter(ticket -> !ticket.isReleaseTicketFor(moduleIteration)).//
collect(toTicketsCollector());
}
public Tickets getReleaseTickets(TrainIteration iteration) {
return stream().//
filter(ticket -> ticket.isReleaseTicketFor(iteration)).//
collect(toTicketsCollector());
}
private Optional<Ticket> findReleaseTicket(ModuleIteration moduleIteration) {
return stream().//
filter(ticket -> ticket.isReleaseTicketFor(moduleIteration)).//
findFirst();
}
@Override
public String toString() {
@@ -58,4 +103,43 @@ public class Tickets implements Iterable<Ticket> {
return builder.toString();
}
/**
* Returns a new collector to toTicketsCollector {@link Ticket} as {@link Tickets} using the {@link Stream} API.
*
* @return
*/
public static Collector<? super Ticket, ?, Tickets> toTicketsCollector() {
return new Collector<Ticket, List<Ticket>, Tickets>() {
@Override
public Supplier<List<Ticket>> supplier() {
return ArrayList::new;
}
@Override
public BiConsumer<List<Ticket>, Ticket> accumulator() {
return List::add;
}
@Override
public BinaryOperator<List<Ticket>> combiner() {
return (left, right) -> {
left.addAll(right);
return left;
};
}
@Override
public Function<List<Ticket>, Tickets> finisher() {
return tickets -> new Tickets(tickets);
}
@Override
public Set<Characteristics> characteristics() {
return Collections.emptySet();
}
};
}
}

View File

@@ -21,6 +21,7 @@ import lombok.RequiredArgsConstructor;
/**
* @author Oliver Gierke
* @author Mark Paluch
*/
@Getter
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
@@ -30,4 +31,8 @@ public enum Tracker {
GITHUB("((#)?\\d+)");
private final String ticketPattern;
public static final String releaseTicketSummary(ModuleIteration moduleIteration) {
return "Release " + moduleIteration.getMediumVersionString();
}
}

View File

@@ -16,28 +16,223 @@
package org.springframework.data.release.jira;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.junit.Assert.assertThat;
import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.Collection;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.AbstractIntegrationTests;
import org.springframework.data.release.model.Iteration;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Projects;
import org.springframework.data.release.model.ReleaseTrains;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder;
import com.github.tomakehurst.wiremock.common.ClasspathFileSource;
import com.github.tomakehurst.wiremock.junit.WireMockRule;
/**
* Integration Tests for {@link GitHubIssueTracker} using a local {@link WireMockRule} server.
*
* @author Mark Paluch
*/
public class GitHubIssueTrackerIntegrationTests extends AbstractIntegrationTests {
@Autowired GitHubConnector gitHubIssueTracker;
public static final String ISSUES_URI = "/repos/spring-projects/spring-data-build/issues";
public static final String MILESTONES_URI = "/repos/spring-projects/spring-data-build/milestones";
public static final ModuleIteration BUILD_HOPPER_RC1 = ReleaseTrains.HOPPER.getModuleIteration(Iteration.RC1,
"Build");
@Rule public WireMockRule mockService = new WireMockRule(
wireMockConfig().port(8888).fileSource(new ClasspathFileSource("integration/github")));
@Rule public ExpectedException expectedException = ExpectedException.none();
@Autowired IssueTracker github;
@Before
public void before() throws Exception {
github.reset();
}
/**
* @see #5
*/
@Test
public void getTickets() throws Exception {
public void findTicketsByTicketIds() throws Exception {
mockGetIssueWith("issue.json", 233);
Collection<Ticket> tickets = github.findTickets(Projects.BUILD, Arrays.asList("233"));
assertThat(tickets, hasSize(1));
}
/**
* @see #5
*/
@Test
public void ignoresUnknownTicketsByTicketId() throws Exception {
Collection<Ticket> tickets = github.findTickets(Projects.BUILD, Arrays.asList("123"));
assertThat(tickets, hasSize(0));
}
/**
* @see #5
*/
@Test
public void emptyResultWithEmptyTicketIds() throws Exception {
Collection<Ticket> tickets = github.findTickets(Projects.COMMONS, Arrays.asList());
assertThat(tickets, hasSize(0));
}
/**
* @see #5
*/
@Test
public void getReleaseTicketForReturnsTheReleaseTicket() throws Exception {
mockGetMilestonesWith("milestones.json");
mockGetIssuesWith("issues.json");
Ticket releaseTicket = github.getReleaseTicketFor(BUILD_HOPPER_RC1);
assertThat(releaseTicket.getId(), is(Matchers.equalTo("#233")));
}
/**
* @see #5
*/
@Test
public void noReleaseTicketFound() throws Exception {
expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("No milestone for Spring Data Build found containing 1.8 RC1!");
mockGetMilestonesWith("emptyMilestones.json");
github.getReleaseTicketFor(BUILD_HOPPER_RC1);
fail("Missing IllegalStateException");
}
/**
* @see #5
*/
@Test
public void createReleaseVersionShouldCreateAVersion() throws Exception {
mockGetMilestonesWith("emptyMilestones.json");
mockCreateMilestoneWith("milestone.json");
github.createReleaseVersion(BUILD_HOPPER_RC1);
verify(postRequestedFor(urlPathMatching(MILESTONES_URI))
.withRequestBody(equalToJson("{\"title\":\"1.8 RC1 (Hopper)\", \"description\":\"Hopper RC1\"}")));
}
/**
* @see #5
*/
@Test
public void createReleaseVersionShouldFindExistingReleaseVersion() throws Exception {
mockGetMilestonesWith("milestones.json");
github.createReleaseVersion(BUILD_HOPPER_RC1);
verify(0, postRequestedFor(urlPathMatching(MILESTONES_URI)));
}
/**
* @see #5
*/
@Test
public void createReleaseTicketShouldCreateReleaseTicket() throws Exception {
mockGetMilestonesWith("milestones.json");
mockGetIssuesWith("emptyIssues.json");
mockCreateIssueWith("issue.json");
github.createReleaseTicket(BUILD_HOPPER_RC1);
verify(postRequestedFor(urlPathMatching(ISSUES_URI))
.withRequestBody(equalToJson("{\"title\":\"Release 1.8 RC1 (Hopper)\",\"milestone\":45}")));
}
/**
* @see #5
*/
@Test
public void createReleaseTicketShouldFailWithNoReleaseVersion() throws Exception {
expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("No milestone for Spring Data Build found containing 1.8 RC1!");
ModuleIteration moduleIteration = ReleaseTrains.HOPPER.getModuleIteration(Iteration.RC1, "Build");
mockGetIssuesWith("emptyIssues.json");
mockGetMilestonesWith("emptyMilestones.json");
github.createReleaseTicket(moduleIteration);
fail("Missing IllegalStateException");
}
/**
* @see #5
*/
@Test
public void createReleaseTicketShouldFindExistingTicket() throws Exception {
mockGetMilestonesWith("milestones.json");
mockGetIssuesWith("issues.json");
github.createReleaseTicket(BUILD_HOPPER_RC1);
verify(0, postRequestedFor(urlPathMatching(MILESTONES_URI)));
}
private void mockGetIssueWith(String fromClassPath, int issueId) {
mockService.stubFor(get(urlPathMatching(ISSUES_URI + "/" + issueId)).//
willReturn(json(fromClassPath)));
}
private void mockGetIssuesWith(String fromClassPath) {
mockService.stubFor(get(urlPathMatching(ISSUES_URI)).//
willReturn(json(fromClassPath)));
}
private void mockCreateIssueWith(String fromClassPath) {
mockService.stubFor(post(urlPathMatching(ISSUES_URI)).//
willReturn(json(fromClassPath)));
}
private void mockGetMilestonesWith(String fromClassPath) {
mockService.stubFor(get(urlPathMatching(MILESTONES_URI)).//
willReturn(json(fromClassPath)));
}
private void mockCreateMilestoneWith(String fromClassPath) {
mockService.stubFor(post(urlPathMatching(MILESTONES_URI)).//
willReturn(json(fromClassPath)));
}
private ResponseDefinitionBuilder json(String fromClassPathFile) {
return aResponse().//
withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).//
withBodyFile(fromClassPathFile);
Collection<Ticket> tickets = gitHubIssueTracker.findTickets(Projects.BUILD, Arrays.asList("1", "2", "-1"));
assertThat(tickets, hasSize(2));
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.release.jira;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.data.release.model.Iteration;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.ReleaseTrains;
/**
* Unit tests for {@link GithubMilestone}.
*
* @author Mark Paluch
*/
public class GithubMilestoneUnitTests {
@Test
public void rendersGithubGaVersionCorrectly() {
assertIterationVersion(Iteration.M1, "1.8 M1 (Dijkstra)");
assertIterationVersion(Iteration.RC1, "1.8 RC1 (Dijkstra)");
assertIterationVersion(Iteration.GA, "1.8 GA (Dijkstra)");
assertIterationVersion(Iteration.SR1, "1.8.1 (Dijkstra SR1)");
assertIterationVersion(Iteration.SR2, "1.8.2 (Dijkstra SR2)");
assertIterationVersion(Iteration.SR3, "1.8.3 (Dijkstra SR3)");
assertIterationVersion(Iteration.SR4, "1.8.4 (Dijkstra SR4)");
}
@Test
public void usesCustomModuleIterationStartVersion() {
ModuleIteration module = ReleaseTrains.DIJKSTRA.getModuleIteration(Iteration.M1, "Elasticsearch");
GithubMilestone version = new GithubMilestone(module);
assertThat(version.toString(), is("1.0 M1 (Dijkstra)"));
}
@Test
public void doesNotUseCustomIterationOnNonFirstiterations() {
ModuleIteration module = ReleaseTrains.DIJKSTRA.getModuleIteration(Iteration.RC1, "Elasticsearch");
GithubMilestone version = new GithubMilestone(module);
assertThat(version.toString(), is("1.0 RC1 (Dijkstra)"));
}
@Test
public void rendersDescriptionCorrectly() {
ModuleIteration module = ReleaseTrains.DIJKSTRA.getModuleIteration(Iteration.M1, "Elasticsearch");
GithubMilestone version = new GithubMilestone(module);
assertThat(version.getDescription(), is("Dijkstra M2"));
}
private void assertIterationVersion(Iteration iteration, String expected) {
ModuleIteration module = ReleaseTrains.DIJKSTRA.getModuleIteration(iteration, "Commons");
GithubMilestone version = new GithubMilestone(module);
assertThat(version.toString(), is(expected));
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.release.jira;
import static org.hamcrest.core.Is.*;
import static org.junit.Assert.*;
import java.util.Collections;
import org.junit.Test;
/**
* Unit tests for {@link JiraComponents}.
*
* @author Mark Paluch
*/
public class JiraComponentsUnitTests {
/**
* @see #5
*/
@Test
public void returnsComponentByName() throws Exception {
JiraComponent fooComponent = new JiraComponent("123", "foo");
JiraComponents jiraComponents = JiraComponents.of(Collections.singleton(fooComponent));
assertThat(jiraComponents.findComponent("foo").isPresent(), is(true));
}
/**
* @see #5
*/
@Test
public void returnsEmptyIfComponentMissing() throws Exception {
JiraComponent fooComponent = new JiraComponent("123", "foo");
JiraComponents jiraComponents = JiraComponents.of(Collections.singleton(fooComponent));
assertThat(jiraComponents.findComponent("baz").isPresent(), is(false));
}
/**
* @see #5
*/
@Test(expected = IllegalArgumentException.class)
public void failsOnNullArgumentConstruction() throws Exception {
JiraComponents.of(null);
fail("Missing IllegalArgumentException");
}
}

View File

@@ -16,42 +16,292 @@
package org.springframework.data.release.jira;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.Assert.assertThat;
import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.Collection;
import java.util.Optional;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.AbstractIntegrationTests;
import org.springframework.data.release.model.Iteration;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.ProjectKey;
import org.springframework.data.release.model.Projects;
import org.springframework.data.release.model.ReleaseTrains;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder;
import com.github.tomakehurst.wiremock.common.ClasspathFileSource;
import com.github.tomakehurst.wiremock.junit.WireMockRule;
/**
* Integration Tests for {@link Jira} using a local {@link WireMockRule} server.
*
* @author Mark Paluch
*/
public class JiraIntegrationTests extends AbstractIntegrationTests {
public static final String CREATE_ISSUE_URI = "/rest/api/2/issue";
public static final String CREATE_VERSION_URI = "/rest/api/2/version";
public static final String SEARCH_URI = "/rest/api/2/search";
public static final String PROJECT_VERSION_URI = "/rest/api/2/project/%s/version";
public static final String PROJECT_COMPONENTS_URI = "/rest/api/2/project/%s/components";
public static final ModuleIteration REST_HOPPER_RC1 = ReleaseTrains.HOPPER.getModuleIteration(Iteration.RC1, "REST");
@Rule public WireMockRule mockService = new WireMockRule(
wireMockConfig().port(8888).fileSource(new ClasspathFileSource("integration/jira")));
@Rule public ExpectedException expectedException = ExpectedException.none();
@Autowired JiraConnector jira;
@Before
public void before() throws Exception {
jira.reset();
}
/**
* @see #5
*/
@Test
public void findResolvedTicketsByTicketIds() throws Exception {
mockSearchWith("DATAREDIS-1andDATAJPA-1.json");
Collection<Ticket> tickets = jira.findTickets(Projects.COMMONS, Arrays.asList("DATAREDIS-1", "DATAJPA-1"));
assertThat(tickets, hasSize(2));
}
/**
* @see #5
*/
@Test
public void ignoresUnknownTicketsByTicketId() throws Exception {
mockSearchWith("emptyTickets.json");
Collection<Ticket> tickets = jira.findTickets(Projects.COMMONS, Arrays.asList("XYZ-1", "UNKOWN-1"));
assertThat(tickets, hasSize(0));
}
/**
* @see #5
*/
@Test
public void emptyResultWithEmptyTicketIds() throws Exception {
Collection<Ticket> tickets = jira.findTickets(Projects.COMMONS, Arrays.asList());
assertThat(tickets, hasSize(0));
}
/**
* @see #5
*/
@Test
public void getReleaseTicketForReturnsTheReleaseTicket() throws Exception {
mockSearchWith("releaseTickets.json");
Ticket releaseTicket = jira.getReleaseTicketFor(REST_HOPPER_RC1);
assertThat(releaseTicket.getId(), is(Matchers.equalTo("DATAREST-782")));
}
/**
* @see #5
*/
@Test
public void noReleaseTicketFound() throws Exception {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Did not find a release ticket for Spring Data REST 2.5 RC1");
mockSearchWith("emptyTickets.json");
jira.getReleaseTicketFor(REST_HOPPER_RC1);
fail("Missing IllegalStateException");
}
/**
* @see #5
*/
@Test
public void getReleaseVersion() throws Exception {
mockGetProjectVersionsWith("releaseVersions.json", REST_HOPPER_RC1.getProjectKey());
Optional<JiraReleaseVersion> optional = jira.findJiraReleaseVersion(REST_HOPPER_RC1);
assertThat(optional.isPresent(), is(true));
assertThat(optional.get().getName(), is(Matchers.equalTo("2.5 RC1 (Hopper)")));
}
/**
* @see #5
*/
@Test
public void createReleaseVersionShouldCreateAVersion() throws Exception {
mockGetProjectVersionsWith("emptyReleaseVersions.json", REST_HOPPER_RC1.getProjectKey());
mockCreateVersionWith("versionCreated.json");
jira.createReleaseVersion(REST_HOPPER_RC1);
verify(postRequestedFor(urlPathMatching(CREATE_VERSION_URI)).withRequestBody(
equalToJson("{\"name\":\"2.5 RC1 (Hopper)\",\"project\":\"DATAREST\",\"description\":\"Hopper RC1\"}")));
}
/**
* @see #5
*/
@Test
public void createReleaseVersionShouldFindExistingReleaseVersion() throws Exception {
ModuleIteration moduleIteration = ReleaseTrains.HOPPER.getModuleIteration(Iteration.RC1, "REST");
mockGetProjectVersionsWith("releaseVersions.json", moduleIteration.getProjectKey());
jira.createReleaseVersion(moduleIteration);
verify(0, postRequestedFor(urlPathMatching(CREATE_VERSION_URI)));
}
/**
* @see #5
*/
@Test
public void createReleaseTicketShouldCreateReleaseTicket() throws Exception {
ModuleIteration moduleIteration = ReleaseTrains.HOPPER.getModuleIteration(Iteration.RC1, "REST");
mockGetProjectVersionsWith("releaseVersions.json", moduleIteration.getProjectKey());
mockGetProjectComponentsWith("projectComponents.json", moduleIteration.getProjectKey());
mockSearchWith("emptyTickets.json");
prepareCreateIssueAndReturn("issueCreated.json");
jira.createReleaseTicket(moduleIteration);
verify(postRequestedFor(urlPathMatching(CREATE_ISSUE_URI))
.withRequestBody(equalToJson("{\"fields\":{\"project\":{\"key\":\"DATAREST\"},"
+ "\"issuetype\":{\"name\":\"Task\"},\"summary\":\"Release 2.5 RC1 (Hopper)\","
+ "\"fixVersions\":[{\"name\":\"2.5 RC1 (Hopper)\"}],"
+ "\"components\":[{\"name\":\"Infrastructure\"}]}}")));
}
/**
* @see #5
*/
@Test
public void createReleaseTicketShouldCreateReleaseTicketWithoutComponent() throws Exception {
ModuleIteration moduleIteration = ReleaseTrains.HOPPER.getModuleIteration(Iteration.RC1, "REST");
mockGetProjectVersionsWith("releaseVersions.json", moduleIteration.getProjectKey());
mockGetProjectComponentsWith("emptyProjectComponents.json", moduleIteration.getProjectKey());
mockSearchWith("emptyTickets.json");
prepareCreateIssueAndReturn("issueCreated.json");
jira.createReleaseTicket(moduleIteration);
verify(postRequestedFor(urlPathMatching(CREATE_ISSUE_URI))
.withRequestBody(equalToJson("{\"fields\":{\"project\":{\"key\":\"DATAREST\"},"
+ "\"issuetype\":{\"name\":\"Task\"},\"summary\":\"Release 2.5 RC1 (Hopper)\","
+ "\"fixVersions\":[{\"name\":\"2.5 RC1 (Hopper)\"}]}}")));
}
/**
* @see #5
*/
@Test
public void createReleaseTicketShouldFailWithNoReleaseVersion() throws Exception {
expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("Did not find a release version for Spring Data REST 2.5 RC1");
ModuleIteration moduleIteration = ReleaseTrains.HOPPER.getModuleIteration(Iteration.RC1, "REST");
mockSearchWith("emptyTickets.json");
mockGetProjectVersionsWith("emptyReleaseVersions.json", moduleIteration.getProjectKey());
jira.createReleaseTicket(moduleIteration);
fail("Missing IllegalStateException");
}
/**
* @see #5
*/
@Test
public void createReleaseTicketShouldFindExistingTicket() throws Exception {
ModuleIteration moduleIteration = ReleaseTrains.HOPPER.getModuleIteration(Iteration.RC1, "REST");
mockGetProjectVersionsWith("releaseVersions.json", moduleIteration.getProjectKey());
mockGetProjectComponentsWith("projectComponents.json", moduleIteration.getProjectKey());
mockSearchWith("releaseTickets.json");
jira.createReleaseTicket(moduleIteration);
verify(0, postRequestedFor(urlPathMatching(CREATE_ISSUE_URI)));
}
/**
* @see #5
*/
@Test
public void assignTicketToMe() throws Exception {
mockService.stubFor(post(urlPathMatching("/rest/api/2/issue/DATAREDIS-99999")).//
willReturn(aResponse().withStatus(204)));
jira.assignTicketToMe(new Ticket("DATAREDIS-99999", "", null));
verify(postRequestedFor(urlPathMatching("/rest/api/2/issue/DATAREDIS-99999"))
.withRequestBody(equalToJson("{\"fields\":{\"assignee\":{\"name\":\"dummy\"}}}")));
}
private void mockSearchWith(String fromClassPath) {
mockService.stubFor(get(urlPathMatching(SEARCH_URI)).//
willReturn(json(fromClassPath)));
}
private void mockGetProjectVersionsWith(String fromClassPath, ProjectKey projectKey) {
mockService.stubFor(get(urlPathMatching(String.format(PROJECT_VERSION_URI, projectKey))).//
willReturn(json(fromClassPath)));
}
private void mockGetProjectComponentsWith(String fromClassPath, ProjectKey projectKey) {
mockService.stubFor(get(urlPathMatching(String.format(PROJECT_COMPONENTS_URI, projectKey))).//
willReturn(json(fromClassPath)));
}
private void prepareCreateIssueAndReturn(String fromClassPath) {
mockService.stubFor(post(urlPathMatching(CREATE_ISSUE_URI)).//
willReturn(json(fromClassPath)));
}
private void mockCreateVersionWith(String fromClassPath) {
mockService.stubFor(post(urlPathMatching(CREATE_VERSION_URI)).//
willReturn(json(fromClassPath)));
}
private ResponseDefinitionBuilder json(String fromClassPathFile) {
return aResponse().//
withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).//
withBodyFile(fromClassPathFile);
}
}

View File

@@ -25,7 +25,7 @@ import org.springframework.data.release.model.ReleaseTrains;
/**
* Unit tests for {@link JiraVersion}.
*
*
* @author Oliver Gierke
*/
public class JiraVersionUnitTests {
@@ -49,7 +49,7 @@ public class JiraVersionUnitTests {
ModuleIteration module = ReleaseTrains.DIJKSTRA.getModuleIteration(Iteration.M1, "Elasticsearch");
JiraVersion version = new JiraVersion(module);
assertThat(version.toString(), is("1.0 M2 (Dijkstra)"));
assertThat(version.toString(), is("1.0 M1 (Dijkstra)"));
}
@Test
@@ -61,6 +61,15 @@ public class JiraVersionUnitTests {
assertThat(version.toString(), is("1.0 RC1 (Dijkstra)"));
}
@Test
public void rendersDescriptionCorrectly() {
ModuleIteration module = ReleaseTrains.DIJKSTRA.getModuleIteration(Iteration.M1, "Elasticsearch");
JiraVersion version = new JiraVersion(module);
assertThat(version.getDescription(), is("Dijkstra M2"));
}
private void assertIterationVersion(Iteration iteration, String expected) {
ModuleIteration module = ReleaseTrains.DIJKSTRA.getModuleIteration(iteration, "Commons");

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.release.jira;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.springframework.test.util.AssertionErrors.fail;
import java.util.Collections;
import org.junit.Test;
import org.springframework.data.release.model.Iteration;
import org.springframework.data.release.model.ReleaseTrains;
/**
* Unit tests for {@link Tickets}.
*
* @author Mark Paluch
*/
public class TicketsUnitTests {
@Test
public void hasReleaseTicketShouldReturnTrue() throws Exception {
Ticket ticket = new Ticket("1234", "Release 1.10 GA (Hopper)", new JiraTicketStatus(false, "", ""));
Tickets tickets = new Tickets(Collections.singletonList(ticket));
boolean result = tickets.hasReleaseTicket(ReleaseTrains.HOPPER.getModuleIteration(Iteration.GA, "JPA"));
assertThat(result, is(true));
}
@Test
public void hasReleaseTickeForTicketWithoutTrainNameShouldReturnFalse() throws Exception {
Ticket ticket = new Ticket("1234", "Release 1.10 GA", new JiraTicketStatus(false, "", ""));
Tickets tickets = new Tickets(Collections.singletonList(ticket));
boolean result = tickets.hasReleaseTicket(ReleaseTrains.HOPPER.getModuleIteration(Iteration.GA, "JPA"));
assertThat(result, is(false));
}
@Test
public void getReleaseTicketReturnsReleaseTicket() throws Exception {
Ticket ticket = new Ticket("1234", "Release 1.10 GA (Hopper)", new JiraTicketStatus(false, "", ""));
Tickets tickets = new Tickets(Collections.singletonList(ticket));
Ticket releaseTicket = tickets.getReleaseTicket(ReleaseTrains.HOPPER.getModuleIteration(Iteration.GA, "JPA"));
assertThat(releaseTicket, is(ticket));
}
@Test(expected = IllegalStateException.class)
public void getReleaseTicketThrowsExceptionWithoutAReleaseTicket() throws Exception {
Ticket ticket = new Ticket("1234", "Release 1.10 GA", new JiraTicketStatus(false, "", ""));
Tickets tickets = new Tickets(Collections.singletonList(ticket));
tickets.getReleaseTicket(ReleaseTrains.HOPPER.getModuleIteration(Iteration.GA, "JPA"));
fail("Missing IllegalStateException");
}
@Test
public void getResolvedReleaseTicket() throws Exception {
Ticket ticket = new Ticket("1234", "Release 1.10 GA (Hopper)", new JiraTicketStatus(true, "", ""));
Tickets tickets = new Tickets(Collections.singletonList(ticket));
Ticket releaseTicket = tickets.getReleaseTicket(ReleaseTrains.HOPPER.getModuleIteration(Iteration.GA, "JPA"));
assertThat(releaseTicket, is(ticket));
}
@Test
public void getReleaseTicketsReturnsReleaseTickets() throws Exception {
Ticket ticket = new Ticket("1234", "Release 1.10 GA (Hopper)", new JiraTicketStatus(false, "", ""));
Tickets tickets = new Tickets(Collections.singletonList(ticket));
Tickets result = tickets
.getReleaseTickets(ReleaseTrains.HOPPER.getModuleIteration(Iteration.GA, "JPA").getTrainIteration());
assertThat(result.getTickets().contains(ticket), is(true));
}
}

View File

@@ -5,3 +5,8 @@ logging.level.org.springframework.web.client=TRACE
# Deployment
deployment.repository-prefix=test-
jira.username=dummy
jira.password=dummy
jira.url=http://localhost:8888
github.api.url=http://localhost:8888

View File

@@ -0,0 +1,75 @@
{
"url": "https://api.github.com/repos/spring-projects/spring-data-build/issues/233",
"repository_url": "https://api.github.com/repos/spring-projects/spring-data-build",
"labels_url": "https://api.github.com/repos/spring-projects/spring-data-build/issues/233/labels{/name}",
"comments_url": "https://api.github.com/repos/spring-projects/spring-data-build/issues/233/comments",
"events_url": "https://api.github.com/repos/spring-projects/spring-data-build/issues/233/events",
"html_url": "https://github.com/spring-projects/spring-data-build/issues/233",
"id": 141655399,
"number": 233,
"title": "Release 1.8 RC1 (Hopper)",
"user": {
"login": "olivergierke",
"id": 128577,
"avatar_url": "https://avatars.githubusercontent.com/u/128577?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/olivergierke",
"html_url": "https://github.com/olivergierke",
"followers_url": "https://api.github.com/users/olivergierke/followers",
"following_url": "https://api.github.com/users/olivergierke/following{/other_user}",
"gists_url": "https://api.github.com/users/olivergierke/gists{/gist_id}",
"starred_url": "https://api.github.com/users/olivergierke/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/olivergierke/subscriptions",
"organizations_url": "https://api.github.com/users/olivergierke/orgs",
"repos_url": "https://api.github.com/users/olivergierke/repos",
"events_url": "https://api.github.com/users/olivergierke/events{/privacy}",
"received_events_url": "https://api.github.com/users/olivergierke/received_events",
"type": "User",
"site_admin": false
},
"labels": [
],
"state": "open",
"locked": false,
"assignee": null,
"milestone": {
"url": "https://api.github.com/repos/spring-projects/spring-data-build/milestones/45",
"html_url": "https://github.com/spring-projects/spring-data-build/milestones/1.8%20RC1%20(Hopper)",
"labels_url": "https://api.github.com/repos/spring-projects/spring-data-build/milestones/45/labels",
"id": 1579988,
"number": 45,
"title": "1.8 RC1 (Hopper)",
"description": "",
"creator": {
"login": "olivergierke",
"id": 128577,
"avatar_url": "https://avatars.githubusercontent.com/u/128577?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/olivergierke",
"html_url": "https://github.com/olivergierke",
"followers_url": "https://api.github.com/users/olivergierke/followers",
"following_url": "https://api.github.com/users/olivergierke/following{/other_user}",
"gists_url": "https://api.github.com/users/olivergierke/gists{/gist_id}",
"starred_url": "https://api.github.com/users/olivergierke/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/olivergierke/subscriptions",
"organizations_url": "https://api.github.com/users/olivergierke/orgs",
"repos_url": "https://api.github.com/users/olivergierke/repos",
"events_url": "https://api.github.com/users/olivergierke/events{/privacy}",
"received_events_url": "https://api.github.com/users/olivergierke/received_events",
"type": "User",
"site_admin": false
},
"open_issues": 1,
"closed_issues": 4,
"state": "open",
"created_at": "2016-02-12T16:05:35Z",
"updated_at": "2016-03-17T17:46:03Z",
"due_on": null,
"closed_at": null
},
"comments": 0,
"created_at": "2016-03-17T17:43:09Z",
"updated_at": "2016-03-17T17:43:09Z",
"closed_at": null,
"body": ""
}

View File

@@ -0,0 +1,436 @@
[
{
"url": "https://api.github.com/repos/spring-projects/spring-data-build/issues/233",
"repository_url": "https://api.github.com/repos/spring-projects/spring-data-build",
"labels_url": "https://api.github.com/repos/spring-projects/spring-data-build/issues/233/labels{/name}",
"comments_url": "https://api.github.com/repos/spring-projects/spring-data-build/issues/233/comments",
"events_url": "https://api.github.com/repos/spring-projects/spring-data-build/issues/233/events",
"html_url": "https://github.com/spring-projects/spring-data-build/issues/233",
"id": 141655399,
"number": 233,
"title": "Release 1.8 RC1 (Hopper)",
"user": {
"login": "olivergierke",
"id": 128577,
"avatar_url": "https://avatars.githubusercontent.com/u/128577?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/olivergierke",
"html_url": "https://github.com/olivergierke",
"followers_url": "https://api.github.com/users/olivergierke/followers",
"following_url": "https://api.github.com/users/olivergierke/following{/other_user}",
"gists_url": "https://api.github.com/users/olivergierke/gists{/gist_id}",
"starred_url": "https://api.github.com/users/olivergierke/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/olivergierke/subscriptions",
"organizations_url": "https://api.github.com/users/olivergierke/orgs",
"repos_url": "https://api.github.com/users/olivergierke/repos",
"events_url": "https://api.github.com/users/olivergierke/events{/privacy}",
"received_events_url": "https://api.github.com/users/olivergierke/received_events",
"type": "User",
"site_admin": false
},
"labels": [
],
"state": "open",
"locked": false,
"assignee": null,
"milestone": {
"url": "https://api.github.com/repos/spring-projects/spring-data-build/milestones/45",
"html_url": "https://github.com/spring-projects/spring-data-build/milestones/1.8%20RC1%20(Hopper)",
"labels_url": "https://api.github.com/repos/spring-projects/spring-data-build/milestones/45/labels",
"id": 1579988,
"number": 45,
"title": "1.8 RC1 (Hopper)",
"description": "",
"creator": {
"login": "olivergierke",
"id": 128577,
"avatar_url": "https://avatars.githubusercontent.com/u/128577?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/olivergierke",
"html_url": "https://github.com/olivergierke",
"followers_url": "https://api.github.com/users/olivergierke/followers",
"following_url": "https://api.github.com/users/olivergierke/following{/other_user}",
"gists_url": "https://api.github.com/users/olivergierke/gists{/gist_id}",
"starred_url": "https://api.github.com/users/olivergierke/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/olivergierke/subscriptions",
"organizations_url": "https://api.github.com/users/olivergierke/orgs",
"repos_url": "https://api.github.com/users/olivergierke/repos",
"events_url": "https://api.github.com/users/olivergierke/events{/privacy}",
"received_events_url": "https://api.github.com/users/olivergierke/received_events",
"type": "User",
"site_admin": false
},
"open_issues": 1,
"closed_issues": 4,
"state": "open",
"created_at": "2016-02-12T16:05:35Z",
"updated_at": "2016-03-17T17:46:03Z",
"due_on": null,
"closed_at": null
},
"comments": 0,
"created_at": "2016-03-17T17:43:09Z",
"updated_at": "2016-03-17T17:43:09Z",
"closed_at": null,
"body": ""
},
{
"url": "https://api.github.com/repos/spring-projects/spring-data-build/issues/232",
"repository_url": "https://api.github.com/repos/spring-projects/spring-data-build",
"labels_url": "https://api.github.com/repos/spring-projects/spring-data-build/issues/232/labels{/name}",
"comments_url": "https://api.github.com/repos/spring-projects/spring-data-build/issues/232/comments",
"events_url": "https://api.github.com/repos/spring-projects/spring-data-build/issues/232/events",
"html_url": "https://github.com/spring-projects/spring-data-build/issues/232",
"id": 139529287,
"number": 232,
"title": "Remove spring41-next build profile.",
"user": {
"login": "christophstrobl",
"id": 2317257,
"avatar_url": "https://avatars.githubusercontent.com/u/2317257?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/christophstrobl",
"html_url": "https://github.com/christophstrobl",
"followers_url": "https://api.github.com/users/christophstrobl/followers",
"following_url": "https://api.github.com/users/christophstrobl/following{/other_user}",
"gists_url": "https://api.github.com/users/christophstrobl/gists{/gist_id}",
"starred_url": "https://api.github.com/users/christophstrobl/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/christophstrobl/subscriptions",
"organizations_url": "https://api.github.com/users/christophstrobl/orgs",
"repos_url": "https://api.github.com/users/christophstrobl/repos",
"events_url": "https://api.github.com/users/christophstrobl/events{/privacy}",
"received_events_url": "https://api.github.com/users/christophstrobl/received_events",
"type": "User",
"site_admin": false
},
"labels": [
],
"state": "closed",
"locked": false,
"assignee": null,
"milestone": {
"url": "https://api.github.com/repos/spring-projects/spring-data-build/milestones/45",
"html_url": "https://github.com/spring-projects/spring-data-build/milestones/1.8%20RC1%20(Hopper)",
"labels_url": "https://api.github.com/repos/spring-projects/spring-data-build/milestones/45/labels",
"id": 1579988,
"number": 45,
"title": "1.8 RC1 (Hopper)",
"description": "",
"creator": {
"login": "olivergierke",
"id": 128577,
"avatar_url": "https://avatars.githubusercontent.com/u/128577?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/olivergierke",
"html_url": "https://github.com/olivergierke",
"followers_url": "https://api.github.com/users/olivergierke/followers",
"following_url": "https://api.github.com/users/olivergierke/following{/other_user}",
"gists_url": "https://api.github.com/users/olivergierke/gists{/gist_id}",
"starred_url": "https://api.github.com/users/olivergierke/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/olivergierke/subscriptions",
"organizations_url": "https://api.github.com/users/olivergierke/orgs",
"repos_url": "https://api.github.com/users/olivergierke/repos",
"events_url": "https://api.github.com/users/olivergierke/events{/privacy}",
"received_events_url": "https://api.github.com/users/olivergierke/received_events",
"type": "User",
"site_admin": false
},
"open_issues": 1,
"closed_issues": 4,
"state": "open",
"created_at": "2016-02-12T16:05:35Z",
"updated_at": "2016-03-17T17:46:03Z",
"due_on": null,
"closed_at": null
},
"comments": 0,
"created_at": "2016-03-09T09:56:42Z",
"updated_at": "2016-03-17T17:46:03Z",
"closed_at": "2016-03-17T17:46:03Z",
"body": "With the baseline upgrade to Spring 4.2 the `spring41-next` profile has become obsolete."
},
{
"url": "https://api.github.com/repos/spring-projects/spring-data-build/issues/231",
"repository_url": "https://api.github.com/repos/spring-projects/spring-data-build",
"labels_url": "https://api.github.com/repos/spring-projects/spring-data-build/issues/231/labels{/name}",
"comments_url": "https://api.github.com/repos/spring-projects/spring-data-build/issues/231/comments",
"events_url": "https://api.github.com/repos/spring-projects/spring-data-build/issues/231/events",
"html_url": "https://github.com/spring-projects/spring-data-build/issues/231",
"id": 137830920,
"number": 231,
"title": "Upgrade to latest Querydsl",
"user": {
"login": "olivergierke",
"id": 128577,
"avatar_url": "https://avatars.githubusercontent.com/u/128577?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/olivergierke",
"html_url": "https://github.com/olivergierke",
"followers_url": "https://api.github.com/users/olivergierke/followers",
"following_url": "https://api.github.com/users/olivergierke/following{/other_user}",
"gists_url": "https://api.github.com/users/olivergierke/gists{/gist_id}",
"starred_url": "https://api.github.com/users/olivergierke/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/olivergierke/subscriptions",
"organizations_url": "https://api.github.com/users/olivergierke/orgs",
"repos_url": "https://api.github.com/users/olivergierke/repos",
"events_url": "https://api.github.com/users/olivergierke/events{/privacy}",
"received_events_url": "https://api.github.com/users/olivergierke/received_events",
"type": "User",
"site_admin": false
},
"labels": [
],
"state": "closed",
"locked": false,
"assignee": {
"login": "olivergierke",
"id": 128577,
"avatar_url": "https://avatars.githubusercontent.com/u/128577?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/olivergierke",
"html_url": "https://github.com/olivergierke",
"followers_url": "https://api.github.com/users/olivergierke/followers",
"following_url": "https://api.github.com/users/olivergierke/following{/other_user}",
"gists_url": "https://api.github.com/users/olivergierke/gists{/gist_id}",
"starred_url": "https://api.github.com/users/olivergierke/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/olivergierke/subscriptions",
"organizations_url": "https://api.github.com/users/olivergierke/orgs",
"repos_url": "https://api.github.com/users/olivergierke/repos",
"events_url": "https://api.github.com/users/olivergierke/events{/privacy}",
"received_events_url": "https://api.github.com/users/olivergierke/received_events",
"type": "User",
"site_admin": false
},
"milestone": {
"url": "https://api.github.com/repos/spring-projects/spring-data-build/milestones/45",
"html_url": "https://github.com/spring-projects/spring-data-build/milestones/1.8%20RC1%20(Hopper)",
"labels_url": "https://api.github.com/repos/spring-projects/spring-data-build/milestones/45/labels",
"id": 1579988,
"number": 45,
"title": "1.8 RC1 (Hopper)",
"description": "",
"creator": {
"login": "olivergierke",
"id": 128577,
"avatar_url": "https://avatars.githubusercontent.com/u/128577?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/olivergierke",
"html_url": "https://github.com/olivergierke",
"followers_url": "https://api.github.com/users/olivergierke/followers",
"following_url": "https://api.github.com/users/olivergierke/following{/other_user}",
"gists_url": "https://api.github.com/users/olivergierke/gists{/gist_id}",
"starred_url": "https://api.github.com/users/olivergierke/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/olivergierke/subscriptions",
"organizations_url": "https://api.github.com/users/olivergierke/orgs",
"repos_url": "https://api.github.com/users/olivergierke/repos",
"events_url": "https://api.github.com/users/olivergierke/events{/privacy}",
"received_events_url": "https://api.github.com/users/olivergierke/received_events",
"type": "User",
"site_admin": false
},
"open_issues": 1,
"closed_issues": 4,
"state": "open",
"created_at": "2016-02-12T16:05:35Z",
"updated_at": "2016-03-17T17:46:03Z",
"due_on": null,
"closed_at": null
},
"comments": 0,
"created_at": "2016-03-02T10:11:20Z",
"updated_at": "2016-03-02T10:14:24Z",
"closed_at": "2016-03-02T10:14:24Z",
"body": ""
},
{
"url": "https://api.github.com/repos/spring-projects/spring-data-build/issues/230",
"repository_url": "https://api.github.com/repos/spring-projects/spring-data-build",
"labels_url": "https://api.github.com/repos/spring-projects/spring-data-build/issues/230/labels{/name}",
"comments_url": "https://api.github.com/repos/spring-projects/spring-data-build/issues/230/comments",
"events_url": "https://api.github.com/repos/spring-projects/spring-data-build/issues/230/events",
"html_url": "https://github.com/spring-projects/spring-data-build/issues/230",
"id": 137829896,
"number": 230,
"title": "Upgrade to Spring 4.2",
"user": {
"login": "olivergierke",
"id": 128577,
"avatar_url": "https://avatars.githubusercontent.com/u/128577?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/olivergierke",
"html_url": "https://github.com/olivergierke",
"followers_url": "https://api.github.com/users/olivergierke/followers",
"following_url": "https://api.github.com/users/olivergierke/following{/other_user}",
"gists_url": "https://api.github.com/users/olivergierke/gists{/gist_id}",
"starred_url": "https://api.github.com/users/olivergierke/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/olivergierke/subscriptions",
"organizations_url": "https://api.github.com/users/olivergierke/orgs",
"repos_url": "https://api.github.com/users/olivergierke/repos",
"events_url": "https://api.github.com/users/olivergierke/events{/privacy}",
"received_events_url": "https://api.github.com/users/olivergierke/received_events",
"type": "User",
"site_admin": false
},
"labels": [
],
"state": "closed",
"locked": false,
"assignee": {
"login": "olivergierke",
"id": 128577,
"avatar_url": "https://avatars.githubusercontent.com/u/128577?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/olivergierke",
"html_url": "https://github.com/olivergierke",
"followers_url": "https://api.github.com/users/olivergierke/followers",
"following_url": "https://api.github.com/users/olivergierke/following{/other_user}",
"gists_url": "https://api.github.com/users/olivergierke/gists{/gist_id}",
"starred_url": "https://api.github.com/users/olivergierke/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/olivergierke/subscriptions",
"organizations_url": "https://api.github.com/users/olivergierke/orgs",
"repos_url": "https://api.github.com/users/olivergierke/repos",
"events_url": "https://api.github.com/users/olivergierke/events{/privacy}",
"received_events_url": "https://api.github.com/users/olivergierke/received_events",
"type": "User",
"site_admin": false
},
"milestone": {
"url": "https://api.github.com/repos/spring-projects/spring-data-build/milestones/45",
"html_url": "https://github.com/spring-projects/spring-data-build/milestones/1.8%20RC1%20(Hopper)",
"labels_url": "https://api.github.com/repos/spring-projects/spring-data-build/milestones/45/labels",
"id": 1579988,
"number": 45,
"title": "1.8 RC1 (Hopper)",
"description": "",
"creator": {
"login": "olivergierke",
"id": 128577,
"avatar_url": "https://avatars.githubusercontent.com/u/128577?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/olivergierke",
"html_url": "https://github.com/olivergierke",
"followers_url": "https://api.github.com/users/olivergierke/followers",
"following_url": "https://api.github.com/users/olivergierke/following{/other_user}",
"gists_url": "https://api.github.com/users/olivergierke/gists{/gist_id}",
"starred_url": "https://api.github.com/users/olivergierke/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/olivergierke/subscriptions",
"organizations_url": "https://api.github.com/users/olivergierke/orgs",
"repos_url": "https://api.github.com/users/olivergierke/repos",
"events_url": "https://api.github.com/users/olivergierke/events{/privacy}",
"received_events_url": "https://api.github.com/users/olivergierke/received_events",
"type": "User",
"site_admin": false
},
"open_issues": 1,
"closed_issues": 4,
"state": "open",
"created_at": "2016-02-12T16:05:35Z",
"updated_at": "2016-03-17T17:46:03Z",
"due_on": null,
"closed_at": null
},
"comments": 0,
"created_at": "2016-03-02T10:05:48Z",
"updated_at": "2016-03-02T10:14:29Z",
"closed_at": "2016-03-02T10:14:29Z",
"body": ""
},
{
"url": "https://api.github.com/repos/spring-projects/spring-data-build/issues/227",
"repository_url": "https://api.github.com/repos/spring-projects/spring-data-build",
"labels_url": "https://api.github.com/repos/spring-projects/spring-data-build/issues/227/labels{/name}",
"comments_url": "https://api.github.com/repos/spring-projects/spring-data-build/issues/227/comments",
"events_url": "https://api.github.com/repos/spring-projects/spring-data-build/issues/227/events",
"html_url": "https://github.com/spring-projects/spring-data-build/issues/227",
"id": 134905897,
"number": 227,
"title": "Downgrade Jackson baseline to 2.6",
"user": {
"login": "olivergierke",
"id": 128577,
"avatar_url": "https://avatars.githubusercontent.com/u/128577?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/olivergierke",
"html_url": "https://github.com/olivergierke",
"followers_url": "https://api.github.com/users/olivergierke/followers",
"following_url": "https://api.github.com/users/olivergierke/following{/other_user}",
"gists_url": "https://api.github.com/users/olivergierke/gists{/gist_id}",
"starred_url": "https://api.github.com/users/olivergierke/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/olivergierke/subscriptions",
"organizations_url": "https://api.github.com/users/olivergierke/orgs",
"repos_url": "https://api.github.com/users/olivergierke/repos",
"events_url": "https://api.github.com/users/olivergierke/events{/privacy}",
"received_events_url": "https://api.github.com/users/olivergierke/received_events",
"type": "User",
"site_admin": false
},
"labels": [
],
"state": "closed",
"locked": false,
"assignee": {
"login": "olivergierke",
"id": 128577,
"avatar_url": "https://avatars.githubusercontent.com/u/128577?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/olivergierke",
"html_url": "https://github.com/olivergierke",
"followers_url": "https://api.github.com/users/olivergierke/followers",
"following_url": "https://api.github.com/users/olivergierke/following{/other_user}",
"gists_url": "https://api.github.com/users/olivergierke/gists{/gist_id}",
"starred_url": "https://api.github.com/users/olivergierke/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/olivergierke/subscriptions",
"organizations_url": "https://api.github.com/users/olivergierke/orgs",
"repos_url": "https://api.github.com/users/olivergierke/repos",
"events_url": "https://api.github.com/users/olivergierke/events{/privacy}",
"received_events_url": "https://api.github.com/users/olivergierke/received_events",
"type": "User",
"site_admin": false
},
"milestone": {
"url": "https://api.github.com/repos/spring-projects/spring-data-build/milestones/45",
"html_url": "https://github.com/spring-projects/spring-data-build/milestones/1.8%20RC1%20(Hopper)",
"labels_url": "https://api.github.com/repos/spring-projects/spring-data-build/milestones/45/labels",
"id": 1579988,
"number": 45,
"title": "1.8 RC1 (Hopper)",
"description": "",
"creator": {
"login": "olivergierke",
"id": 128577,
"avatar_url": "https://avatars.githubusercontent.com/u/128577?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/olivergierke",
"html_url": "https://github.com/olivergierke",
"followers_url": "https://api.github.com/users/olivergierke/followers",
"following_url": "https://api.github.com/users/olivergierke/following{/other_user}",
"gists_url": "https://api.github.com/users/olivergierke/gists{/gist_id}",
"starred_url": "https://api.github.com/users/olivergierke/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/olivergierke/subscriptions",
"organizations_url": "https://api.github.com/users/olivergierke/orgs",
"repos_url": "https://api.github.com/users/olivergierke/repos",
"events_url": "https://api.github.com/users/olivergierke/events{/privacy}",
"received_events_url": "https://api.github.com/users/olivergierke/received_events",
"type": "User",
"site_admin": false
},
"open_issues": 1,
"closed_issues": 4,
"state": "open",
"created_at": "2016-02-12T16:05:35Z",
"updated_at": "2016-03-17T17:46:03Z",
"due_on": null,
"closed_at": null
},
"comments": 0,
"created_at": "2016-02-19T16:21:22Z",
"updated_at": "2016-02-19T16:37:54Z",
"closed_at": "2016-02-19T16:37:54Z",
"body": "The Jackson 2.7 release causes significant regressions in a variety of places and it seems proper fixes in Spring Framework will only be available in the 4.2 line. That's good reason to stay on Jackson 2.6 for Hopper. Users requiring Jackson 2.7 features would have to use at least Spring 4.2, 4.3 even preferred.\r\n\r\nhttps://jira.spring.io/browse/SPR-13853"
}
]

View File

@@ -0,0 +1,35 @@
{
"url": "https://api.github.com/repos/spring-projects/spring-data-build/milestones/45",
"html_url": "https://github.com/spring-projects/spring-data-build/milestones/1.8%20RC1%20(Hopper)",
"labels_url": "https://api.github.com/repos/spring-projects/spring-data-build/milestones/45/labels",
"id": 1579988,
"number": 45,
"title": "1.8 RC1 (Hopper)",
"description": "",
"creator": {
"login": "olivergierke",
"id": 128577,
"avatar_url": "https://avatars.githubusercontent.com/u/128577?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/olivergierke",
"html_url": "https://github.com/olivergierke",
"followers_url": "https://api.github.com/users/olivergierke/followers",
"following_url": "https://api.github.com/users/olivergierke/following{/other_user}",
"gists_url": "https://api.github.com/users/olivergierke/gists{/gist_id}",
"starred_url": "https://api.github.com/users/olivergierke/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/olivergierke/subscriptions",
"organizations_url": "https://api.github.com/users/olivergierke/orgs",
"repos_url": "https://api.github.com/users/olivergierke/repos",
"events_url": "https://api.github.com/users/olivergierke/events{/privacy}",
"received_events_url": "https://api.github.com/users/olivergierke/received_events",
"type": "User",
"site_admin": false
},
"open_issues": 1,
"closed_issues": 4,
"state": "open",
"created_at": "2016-02-12T16:05:35Z",
"updated_at": "2016-03-17T17:46:03Z",
"due_on": null,
"closed_at": null
}

View File

@@ -0,0 +1,72 @@
[
{
"url": "https://api.github.com/repos/spring-projects/spring-data-build/milestones/44",
"html_url": "https://github.com/spring-projects/spring-data-build/milestones/1.7.4%20(Gosling%20SR4)",
"labels_url": "https://api.github.com/repos/spring-projects/spring-data-build/milestones/44/labels",
"id": 1488963,
"number": 44,
"title": "1.7.4 (Gosling SR4)",
"description": "",
"creator": {
"login": "olivergierke",
"id": 128577,
"avatar_url": "https://avatars.githubusercontent.com/u/128577?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/olivergierke",
"html_url": "https://github.com/olivergierke",
"followers_url": "https://api.github.com/users/olivergierke/followers",
"following_url": "https://api.github.com/users/olivergierke/following{/other_user}",
"gists_url": "https://api.github.com/users/olivergierke/gists{/gist_id}",
"starred_url": "https://api.github.com/users/olivergierke/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/olivergierke/subscriptions",
"organizations_url": "https://api.github.com/users/olivergierke/orgs",
"repos_url": "https://api.github.com/users/olivergierke/repos",
"events_url": "https://api.github.com/users/olivergierke/events{/privacy}",
"received_events_url": "https://api.github.com/users/olivergierke/received_events",
"type": "User",
"site_admin": false
},
"open_issues": 1,
"closed_issues": 2,
"state": "open",
"created_at": "2016-01-07T07:19:58Z",
"updated_at": "2016-02-11T13:51:38Z",
"due_on": null,
"closed_at": null
},
{
"url": "https://api.github.com/repos/spring-projects/spring-data-build/milestones/45",
"html_url": "https://github.com/spring-projects/spring-data-build/milestones/1.8%20RC1%20(Hopper)",
"labels_url": "https://api.github.com/repos/spring-projects/spring-data-build/milestones/45/labels",
"id": 1579988,
"number": 45,
"title": "1.8 RC1 (Hopper)",
"description": "",
"creator": {
"login": "olivergierke",
"id": 128577,
"avatar_url": "https://avatars.githubusercontent.com/u/128577?v=3",
"gravatar_id": "",
"url": "https://api.github.com/users/olivergierke",
"html_url": "https://github.com/olivergierke",
"followers_url": "https://api.github.com/users/olivergierke/followers",
"following_url": "https://api.github.com/users/olivergierke/following{/other_user}",
"gists_url": "https://api.github.com/users/olivergierke/gists{/gist_id}",
"starred_url": "https://api.github.com/users/olivergierke/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/olivergierke/subscriptions",
"organizations_url": "https://api.github.com/users/olivergierke/orgs",
"repos_url": "https://api.github.com/users/olivergierke/repos",
"events_url": "https://api.github.com/users/olivergierke/events{/privacy}",
"received_events_url": "https://api.github.com/users/olivergierke/received_events",
"type": "User",
"site_admin": false
},
"open_issues": 1,
"closed_issues": 4,
"state": "open",
"created_at": "2016-02-12T16:05:35Z",
"updated_at": "2016-03-17T17:46:03Z",
"due_on": null,
"closed_at": null
}
]

View File

@@ -0,0 +1,66 @@
{
"expand": "schema,names",
"startAt": 0,
"maxResults": 50,
"total": 2,
"issues": [
{
"expand": "operations,editmeta,changelog,transitions,renderedFields",
"id": "35482",
"self": "https://jira.spring.io/rest/api/2/issue/35482",
"key": "DATAREDIS-1",
"fields": {
"summary": "Connections obtained from pool are never returned",
"resolution": {
"self": "https://jira.spring.io/rest/api/2/resolution/8",
"id": "8",
"description": "",
"name": "Complete"
},
"status": {
"self": "https://jira.spring.io/rest/api/2/status/5",
"description": "A resolution has been taken, and it is awaiting verification by reporter. From here issues are either reopened, or are closed.",
"iconUrl": "https://jira.spring.io/images/icons/statuses/resolved.png",
"name": "Resolved",
"id": "5",
"statusCategory": {
"self": "https://jira.spring.io/rest/api/2/statuscategory/3",
"id": 3,
"key": "done",
"colorName": "green",
"name": "Done"
}
}
}
},
{
"expand": "operations,editmeta,changelog,transitions,renderedFields",
"id": "35060",
"self": "https://jira.spring.io/rest/api/2/issue/35060",
"key": "DATAJPA-1",
"fields": {
"summary": "Move JPA parts of Hades library into Spring JPA",
"resolution": {
"self": "https://jira.spring.io/rest/api/2/resolution/1",
"id": "1",
"description": "A fix for this issue is checked into the tree and tested.",
"name": "Fixed"
},
"status": {
"self": "https://jira.spring.io/rest/api/2/status/6",
"description": "The issue is considered finished, the resolution is correct. Issues which are not closed can be reopened.",
"iconUrl": "https://jira.spring.io/images/icons/statuses/closed.png",
"name": "Closed",
"id": "6",
"statusCategory": {
"self": "https://jira.spring.io/rest/api/2/statuscategory/3",
"id": 3,
"key": "done",
"colorName": "green",
"name": "Done"
}
}
}
}
]
}

View File

@@ -0,0 +1,8 @@
{
"self": "https://jira.spring.io/rest/api/2/project/DATAREST/version?maxResults=50&startAt=0",
"maxResults": 50,
"startAt": 0,
"total": 0,
"isLast": true,
"values": []
}

View File

@@ -0,0 +1,6 @@
{
"startAt": 0,
"maxResults": 50,
"total": 0,
"issues": []
}

View File

@@ -0,0 +1,5 @@
{
"id": "10000",
"key": "DATAREDIS-42",
"self": "http://www.example.com/jira/rest/api/2/issue/10000"
}

View File

@@ -0,0 +1,146 @@
[
{
"self": "https://jira.spring.io/rest/api/2/component/12775",
"id": "12775",
"name": "Infrastructure",
"assigneeType": "PROJECT_DEFAULT",
"assignee": {
"self": "https://jira.spring.io/rest/api/2/user?username=olivergierke",
"key": "olivergierke",
"name": "olivergierke",
"avatarUrls": {
"16x16": "https://secure.gravatar.com/avatar/27320c872264d5db7a38f65137d423db?d=mm&s=16",
"24x24": "https://secure.gravatar.com/avatar/27320c872264d5db7a38f65137d423db?d=mm&s=24",
"32x32": "https://secure.gravatar.com/avatar/27320c872264d5db7a38f65137d423db?d=mm&s=32",
"48x48": "https://secure.gravatar.com/avatar/27320c872264d5db7a38f65137d423db?d=mm&s=48"
},
"displayName": "Oliver Gierke",
"active": true
},
"realAssigneeType": "PROJECT_DEFAULT",
"realAssignee": {
"self": "https://jira.spring.io/rest/api/2/user?username=olivergierke",
"key": "olivergierke",
"name": "olivergierke",
"avatarUrls": {
"16x16": "https://secure.gravatar.com/avatar/27320c872264d5db7a38f65137d423db?d=mm&s=16",
"24x24": "https://secure.gravatar.com/avatar/27320c872264d5db7a38f65137d423db?d=mm&s=24",
"32x32": "https://secure.gravatar.com/avatar/27320c872264d5db7a38f65137d423db?d=mm&s=32",
"48x48": "https://secure.gravatar.com/avatar/27320c872264d5db7a38f65137d423db?d=mm&s=48"
},
"displayName": "Oliver Gierke",
"active": true
},
"isAssigneeTypeValid": true,
"project": "DATAREST",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/component/12748",
"id": "12748",
"name": "Content negotiation",
"assigneeType": "PROJECT_DEFAULT",
"assignee": {
"self": "https://jira.spring.io/rest/api/2/user?username=olivergierke",
"key": "olivergierke",
"name": "olivergierke",
"avatarUrls": {
"16x16": "https://secure.gravatar.com/avatar/27320c872264d5db7a38f65137d423db?d=mm&s=16",
"24x24": "https://secure.gravatar.com/avatar/27320c872264d5db7a38f65137d423db?d=mm&s=24",
"32x32": "https://secure.gravatar.com/avatar/27320c872264d5db7a38f65137d423db?d=mm&s=32",
"48x48": "https://secure.gravatar.com/avatar/27320c872264d5db7a38f65137d423db?d=mm&s=48"
},
"displayName": "Oliver Gierke",
"active": true
},
"realAssigneeType": "PROJECT_DEFAULT",
"realAssignee": {
"self": "https://jira.spring.io/rest/api/2/user?username=olivergierke",
"key": "olivergierke",
"name": "olivergierke",
"avatarUrls": {
"16x16": "https://secure.gravatar.com/avatar/27320c872264d5db7a38f65137d423db?d=mm&s=16",
"24x24": "https://secure.gravatar.com/avatar/27320c872264d5db7a38f65137d423db?d=mm&s=24",
"32x32": "https://secure.gravatar.com/avatar/27320c872264d5db7a38f65137d423db?d=mm&s=32",
"48x48": "https://secure.gravatar.com/avatar/27320c872264d5db7a38f65137d423db?d=mm&s=48"
},
"displayName": "Oliver Gierke",
"active": true
},
"isAssigneeTypeValid": true,
"project": "DATAREST",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/component/12747",
"id": "12747",
"name": "Documentation",
"assigneeType": "PROJECT_DEFAULT",
"assignee": {
"self": "https://jira.spring.io/rest/api/2/user?username=olivergierke",
"key": "olivergierke",
"name": "olivergierke",
"avatarUrls": {
"16x16": "https://secure.gravatar.com/avatar/27320c872264d5db7a38f65137d423db?d=mm&s=16",
"24x24": "https://secure.gravatar.com/avatar/27320c872264d5db7a38f65137d423db?d=mm&s=24",
"32x32": "https://secure.gravatar.com/avatar/27320c872264d5db7a38f65137d423db?d=mm&s=32",
"48x48": "https://secure.gravatar.com/avatar/27320c872264d5db7a38f65137d423db?d=mm&s=48"
},
"displayName": "Oliver Gierke",
"active": true
},
"realAssigneeType": "PROJECT_DEFAULT",
"realAssignee": {
"self": "https://jira.spring.io/rest/api/2/user?username=olivergierke",
"key": "olivergierke",
"name": "olivergierke",
"avatarUrls": {
"16x16": "https://secure.gravatar.com/avatar/27320c872264d5db7a38f65137d423db?d=mm&s=16",
"24x24": "https://secure.gravatar.com/avatar/27320c872264d5db7a38f65137d423db?d=mm&s=24",
"32x32": "https://secure.gravatar.com/avatar/27320c872264d5db7a38f65137d423db?d=mm&s=32",
"48x48": "https://secure.gravatar.com/avatar/27320c872264d5db7a38f65137d423db?d=mm&s=48"
},
"displayName": "Oliver Gierke",
"active": true
},
"isAssigneeTypeValid": true,
"project": "DATAREST",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/component/12746",
"id": "12746",
"name": "Repositories",
"assigneeType": "PROJECT_DEFAULT",
"assignee": {
"self": "https://jira.spring.io/rest/api/2/user?username=olivergierke",
"key": "olivergierke",
"name": "olivergierke",
"avatarUrls": {
"16x16": "https://secure.gravatar.com/avatar/27320c872264d5db7a38f65137d423db?d=mm&s=16",
"24x24": "https://secure.gravatar.com/avatar/27320c872264d5db7a38f65137d423db?d=mm&s=24",
"32x32": "https://secure.gravatar.com/avatar/27320c872264d5db7a38f65137d423db?d=mm&s=32",
"48x48": "https://secure.gravatar.com/avatar/27320c872264d5db7a38f65137d423db?d=mm&s=48"
},
"displayName": "Oliver Gierke",
"active": true
},
"realAssigneeType": "PROJECT_DEFAULT",
"realAssignee": {
"self": "https://jira.spring.io/rest/api/2/user?username=olivergierke",
"key": "olivergierke",
"name": "olivergierke",
"avatarUrls": {
"16x16": "https://secure.gravatar.com/avatar/27320c872264d5db7a38f65137d423db?d=mm&s=16",
"24x24": "https://secure.gravatar.com/avatar/27320c872264d5db7a38f65137d423db?d=mm&s=24",
"32x32": "https://secure.gravatar.com/avatar/27320c872264d5db7a38f65137d423db?d=mm&s=32",
"48x48": "https://secure.gravatar.com/avatar/27320c872264d5db7a38f65137d423db?d=mm&s=48"
},
"displayName": "Oliver Gierke",
"active": true
},
"isAssigneeTypeValid": true,
"project": "DATAREST",
"projectId": 10901
}
]

View File

@@ -0,0 +1,37 @@
{
"expand": "names,schema",
"startAt": 0,
"maxResults": 50,
"total": 1,
"issues": [
{
"expand": "operations,editmeta,changelog,transitions,renderedFields",
"id": "67908",
"self": "https://jira.spring.io/rest/api/2/issue/67908",
"key": "DATAREST-782",
"fields": {
"summary": "Release 2.5 RC1 (Hopper)",
"resolution": {
"self": "https://jira.spring.io/rest/api/2/resolution/1",
"id": "1",
"description": "A fix for this issue is checked into the tree and tested.",
"name": "Fixed"
},
"status": {
"self": "https://jira.spring.io/rest/api/2/status/6",
"description": "The issue is considered finished, the resolution is correct. Issues which are not closed can be reopened.",
"iconUrl": "https://jira.spring.io/images/icons/statuses/closed.png",
"name": "Closed",
"id": "6",
"statusCategory": {
"self": "https://jira.spring.io/rest/api/2/statuscategory/3",
"id": 3,
"key": "done",
"colorName": "green",
"name": "Done"
}
}
}
}
]
}

View File

@@ -0,0 +1,446 @@
{
"self": "https://jira.spring.io/rest/api/2/project/DATAREST/version?maxResults=50&startAt=0",
"maxResults": 50,
"startAt": 0,
"total": 44,
"isLast": true,
"values": [
{
"self": "https://jira.spring.io/rest/api/2/version/12843",
"id": "12843",
"name": "1.0 - M1",
"archived": true,
"released": true,
"releaseDate": "2012-04-24",
"userReleaseDate": "24/Apr/12",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/12844",
"id": "12844",
"name": "1.0 - M2",
"archived": true,
"released": true,
"releaseDate": "2012-05-16",
"userReleaseDate": "16/May/12",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/12845",
"id": "12845",
"name": "1.0.0.RC1",
"archived": true,
"released": true,
"releaseDate": "2012-06-26",
"userReleaseDate": "26/Jun/12",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/13203",
"id": "13203",
"name": "1.0.0.RC2",
"archived": true,
"released": true,
"releaseDate": "2012-07-31",
"userReleaseDate": "31/Jul/12",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/13598",
"id": "13598",
"name": "1.0.0.RC3",
"archived": true,
"released": true,
"releaseDate": "2012-09-14",
"userReleaseDate": "14/Sep/12",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/12846",
"id": "12846",
"name": "1.0.0 GA",
"archived": true,
"released": true,
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/14009",
"id": "14009",
"name": "1.1.0.M1",
"archived": true,
"released": true,
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/13003",
"id": "13003",
"name": "2.0 M1 (Codd)",
"archived": true,
"released": true,
"releaseDate": "2013-11-21",
"userReleaseDate": "21/Nov/13",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/14259",
"id": "14259",
"name": "2.0 RC1 (Codd)",
"archived": true,
"released": true,
"releaseDate": "2014-01-29",
"userReleaseDate": "29/Jan/14",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/14260",
"id": "14260",
"name": "2.0 GA (Codd)",
"archived": true,
"released": true,
"releaseDate": "2014-02-24",
"userReleaseDate": "24/Feb/14",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/14384",
"id": "14384",
"name": "2.0.1 (Codd SR1)",
"archived": true,
"released": true,
"releaseDate": "2014-03-13",
"userReleaseDate": "13/Mar/14",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/14385",
"id": "14385",
"name": "2.1 M1 (Dijkstra)",
"archived": true,
"released": true,
"releaseDate": "2014-04-01",
"userReleaseDate": "01/Apr/14",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/14412",
"id": "14412",
"name": "2.0.2 (Codd SR2)",
"archived": true,
"released": true,
"releaseDate": "2014-04-15",
"userReleaseDate": "15/Apr/14",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/14386",
"id": "14386",
"name": "2.1 RC1 (Dijkstra)",
"archived": true,
"released": true,
"releaseDate": "2014-05-02",
"userReleaseDate": "02/May/14",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/14520",
"id": "14520",
"name": "2.0.3 (Codd SR3)",
"archived": true,
"released": true,
"releaseDate": "2014-06-18",
"userReleaseDate": "18/Jun/14",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/14387",
"id": "14387",
"name": "2.1 GA (Dijkstra)",
"archived": true,
"released": true,
"releaseDate": "2014-05-20",
"userReleaseDate": "20/May/14",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/14607",
"id": "14607",
"name": "2.1.1 (Dijkstra SR1)",
"archived": true,
"released": true,
"releaseDate": "2014-06-30",
"userReleaseDate": "30/Jun/14",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/14614",
"id": "14614",
"name": "2.2 M1 (Evans)",
"archived": true,
"released": true,
"releaseDate": "2014-07-10",
"userReleaseDate": "10/Jul/14",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/14656",
"id": "14656",
"name": "2.1.2 (Dijkstra SR2)",
"archived": true,
"released": true,
"releaseDate": "2014-07-28",
"userReleaseDate": "28/Jul/14",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/14675",
"id": "14675",
"name": "2.2 RC1 (Evans)",
"archived": true,
"released": true,
"releaseDate": "2014-08-13",
"userReleaseDate": "13/Aug/14",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/14695",
"id": "14695",
"name": "2.1.4 (Dijkstra SR4)",
"archived": true,
"released": true,
"releaseDate": "2014-08-27",
"userReleaseDate": "27/Aug/14",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/14710",
"id": "14710",
"name": "2.2 GA (Evans)",
"archived": true,
"released": true,
"releaseDate": "2014-09-05",
"userReleaseDate": "05/Sep/14",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/14731",
"id": "14731",
"name": "2.2.1 (Evans SR1)",
"archived": true,
"released": true,
"releaseDate": "2014-10-31",
"userReleaseDate": "31/Oct/14",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/14732",
"id": "14732",
"name": "2.3 M1 (Fowler)",
"archived": true,
"released": true,
"releaseDate": "2014-12-01",
"userReleaseDate": "01/Dec/14",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/14721",
"id": "14721",
"name": "2.1.5 (Dijkstra SR5)",
"archived": true,
"released": true,
"releaseDate": "2015-01-27",
"userReleaseDate": "27/Jan/15",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/14819",
"id": "14819",
"name": "2.2.2 (Evans SR2)",
"archived": true,
"released": true,
"releaseDate": "2015-01-28",
"userReleaseDate": "28/Jan/15",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/14852",
"id": "14852",
"name": "2.3 RC1 (Fowler)",
"archived": true,
"released": true,
"releaseDate": "2015-03-05",
"userReleaseDate": "05/Mar/15",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/14949",
"id": "14949",
"name": "2.3 GA (Fowler)",
"archived": true,
"released": true,
"releaseDate": "2015-03-23",
"userReleaseDate": "23/Mar/15",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/14981",
"id": "14981",
"name": "2.4 M1 (Gosling)",
"archived": true,
"released": true,
"releaseDate": "2015-06-02",
"userReleaseDate": "02/Jun/15",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/14970",
"id": "14970",
"name": "2.3.1 (Fowler SR1)",
"archived": true,
"released": true,
"releaseDate": "2015-07-01",
"userReleaseDate": "01/Jul/15",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/14925",
"id": "14925",
"name": "2.2.3 (Evans SR3)",
"archived": true,
"released": true,
"releaseDate": "2015-07-01",
"userReleaseDate": "01/Jul/15",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/14912",
"id": "14912",
"name": "2.1.6 (Dijkstra SR6)",
"archived": true,
"released": true,
"releaseDate": "2015-07-01",
"userReleaseDate": "01/Jul/15",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/15240",
"id": "15240",
"name": "2.3.2 (Fowler SR2)",
"archived": false,
"released": true,
"releaseDate": "2015-07-28",
"userReleaseDate": "28/Jul/15",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/15205",
"id": "15205",
"name": "2.4 RC1 (Gosling)",
"archived": true,
"released": true,
"releaseDate": "2015-08-04",
"userReleaseDate": "04/Aug/15",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/15279",
"id": "15279",
"name": "2.4 GA (Gosling)",
"archived": true,
"released": true,
"releaseDate": "2015-09-01",
"userReleaseDate": "01/Sep/15",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/15348",
"id": "15348",
"description": "Evans SR4",
"name": "2.2.4 (Evans SR4)",
"archived": false,
"released": true,
"releaseDate": "2015-10-14",
"userReleaseDate": "14/Oct/15",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/15310",
"id": "15310",
"description": "Gosling SR1",
"name": "2.4.1 (Gosling SR1)",
"archived": true,
"released": true,
"releaseDate": "2015-11-15",
"userReleaseDate": "15/Nov/15",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/15416",
"id": "15416",
"description": "Gosling SR2",
"name": "2.4.2 (Gosling SR2)",
"archived": false,
"released": true,
"releaseDate": "2015-12-18",
"userReleaseDate": "18/Dec/15",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/15311",
"id": "15311",
"description": "Hopper M1",
"name": "2.5 M1 (Hopper)",
"archived": false,
"released": true,
"releaseDate": "2016-02-12",
"userReleaseDate": "12/Feb/16",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/15455",
"id": "15455",
"description": "Gosling SR4",
"name": "2.4.4 (Gosling SR4)",
"archived": false,
"released": true,
"releaseDate": "2016-02-23",
"userReleaseDate": "23/Feb/16",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/15475",
"id": "15475",
"description": "Hopper RC1",
"name": "2.5 RC1 (Hopper)",
"archived": false,
"released": true,
"releaseDate": "2016-03-18",
"userReleaseDate": "18/Mar/16",
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/15260",
"id": "15260",
"description": "Fowler SR3",
"name": "2.3.3 (Fowler SR3)",
"archived": false,
"released": false,
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/15512",
"id": "15512",
"name": "2.4.5 (Gosling SR5)",
"archived": false,
"released": false,
"projectId": 10901
},
{
"self": "https://jira.spring.io/rest/api/2/version/15513",
"id": "15513",
"name": "2.5 GA (Hopper)",
"archived": false,
"released": false,
"projectId": 10901
}
]
}

View File

@@ -0,0 +1,8 @@
{
"description": "An excellent version",
"name": "2.5 RC1 (Hopper)",
"archived": false,
"released": false,
"project": "DATAREST",
"projectId": 10000
}