Skip to content

Commit

Permalink
Merge 78389eb into 89c3fab
Browse files Browse the repository at this point in the history
  • Loading branch information
wwelling committed Sep 25, 2020
2 parents 89c3fab + 78389eb commit 8848951
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 144 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import org.apache.log4j.Logger;
import org.kohsuke.github.GHIssue;
import org.kohsuke.github.GHIssueState;
import org.kohsuke.github.GHLabel;
import org.kohsuke.github.GHOrganization;
import org.kohsuke.github.GHProject;
import org.kohsuke.github.GHProjectCard;
import org.kohsuke.github.GHRepository;
import org.kohsuke.github.GHUser;
import org.kohsuke.github.GitHub;
Expand All @@ -29,6 +32,7 @@
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

import edu.tamu.app.cache.model.Card;
import edu.tamu.app.cache.model.Member;
import edu.tamu.app.cache.model.RemoteProject;
import edu.tamu.app.model.ManagementService;
Expand Down Expand Up @@ -56,8 +60,6 @@ public abstract class AbstractGitHubService extends MappingRemoteProjectManagerB

protected final RestTemplate restTemplate;

protected GHLabel label;

protected AbstractGitHubService(final ManagementService managementService) throws IOException {
this.managementService = managementService;
ghBuilder = new GitHubBuilder();
Expand Down Expand Up @@ -123,6 +125,7 @@ protected RestTemplate getRestTemplate() {
protected RemoteProject buildRemoteProject(final GHRepository repo, final List<GHLabel> labels) throws IOException {
final String scopeId = String.valueOf(repo.getId());
final String name = repo.getName();

final long requestCount = getPrimaryWorkItemCount(REQUEST_LABEL, repo, labels);
final long issueCount = getPrimaryWorkItemCount(ISSUE_LABEL, repo, labels);
final long featureCount = getPrimaryWorkItemCount(FEATURE_LABEL, repo, labels);
Expand All @@ -133,29 +136,22 @@ protected RemoteProject buildRemoteProject(final GHRepository repo, final List<G

protected long getPrimaryWorkItemCount(final String type, final GHRepository repo, final List<GHLabel> labels)
throws IOException {
label = getLabelByName(labels, type);
if (label == null) {
final Optional<GHLabel> label = getLabelByName(labels, type);
if (!label.isPresent()) {
return 0;
}
return repo.listIssues(GHIssueState.OPEN)
.asList()
.stream()
.filter(this::cardIsLabelType)
return repo.listIssues(GHIssueState.OPEN).asList().stream()
.filter(card -> cardIsLabelType(card, label.get()))
.count();
}

protected GHLabel getLabelByName(final List<GHLabel> labels, final String name) {
GHLabel returnValue = null;
final Optional<GHLabel> match = labels.stream()
protected Optional<GHLabel> getLabelByName(final List<GHLabel> labels, final String name) {
return labels.stream()
.filter(label -> label.getName().equals(name))
.findFirst();
if (match.isPresent()) {
returnValue = match.get();
}
return returnValue;
}

protected boolean cardIsLabelType(GHIssue card) {
protected boolean cardIsLabelType(GHIssue card, GHLabel label) {
try {
Collection<GHLabel> labels = card.getLabels();
if (label.getName().equals(ISSUE_LABEL) && isAnIssue(card)) {
Expand Down Expand Up @@ -183,23 +179,13 @@ protected boolean hasLabelByName(final Collection<GHLabel> labels, final String

protected String getCardType(final GHIssue content) throws IOException {
final List<GHLabel> labels = (List<GHLabel>) content.getLabels();
final GHLabel label = labels.stream()
.filter(label1 -> label1.getName().equals(DEFECT_LABEL))
.findFirst()
.orElseGet(() -> labels.stream()
.filter(label2 -> label2.getName().equals(FEATURE_LABEL))
.findFirst()
.orElseGet(() -> labels.stream()
.filter(label3 -> label3.getName().equals(ISSUE_LABEL))
.findFirst()
.orElseGet(() -> labels.stream()
.filter(label4 -> label4.getName().equals(REQUEST_LABEL))
.findFirst()
.orElse(null)
)
)
);
return label == null ? null : label.getName();
final Optional<GHLabel> label = labels.stream()
.filter(l -> l.getName().equals(DEFECT_LABEL) ||
l.getName().equals(FEATURE_LABEL) ||
l.getName().equals(ISSUE_LABEL) ||
l.getName().equals(REQUEST_LABEL)
).findFirst();
return label.isPresent() ? label.get().getName() : null;
}

protected Member getMember(final GHUser user) throws IOException {
Expand Down Expand Up @@ -248,6 +234,33 @@ protected void cacheMember(final String id, final Member member) {
members.put(id, member);
}

String toProductName(GHProject project) {
return String.format("%s/%s", ORGANIZATION, project.getName());
}

Card toCard(GHProjectCard card, GHIssue issue) throws IOException {
String type = getCardType(issue);
String status = card.getColumn().getName();
// TODO: Figure out how we want to handle sizes
String estimate = null;
return new Card(
String.valueOf(card.getId()),
String.valueOf(issue.getNumber()),
mapCardType(type),
issue.getTitle(),
issue.getBody(),
mapStatus(status),
mapEstimate(estimate),
getAssignees(issue)
);
}

private List<Member> getAssignees(GHIssue issue) {
return issue.getAssignees().stream()
.map(user -> exceptionHandlerWrapper(user, u -> getMember(u)))
.collect(Collectors.toList());
}

@FunctionalInterface
public interface ExceptionHandler<T, R, E extends Exception> {
R apply(T t) throws E;
Expand Down
119 changes: 43 additions & 76 deletions src/main/java/edu/tamu/app/service/manager/GitHubMilestoneService.java
Original file line number Diff line number Diff line change
@@ -1,26 +1,22 @@
package edu.tamu.app.service.manager;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.kohsuke.github.GHIssue;
import org.kohsuke.github.GHMilestone;
import org.apache.commons.lang3.tuple.Pair;
import org.kohsuke.github.GHMilestoneState;
import org.kohsuke.github.GHOrganization;
import org.kohsuke.github.GHProject;
import org.kohsuke.github.GHProject.ProjectStateFilter;
import org.kohsuke.github.GHProjectCard;
import org.kohsuke.github.GHProjectColumn;
import org.kohsuke.github.GHRepository;
import org.kohsuke.github.GHUser;
import org.springframework.beans.BeanUtils;

import edu.tamu.app.cache.model.Card;
import edu.tamu.app.cache.model.Member;
import edu.tamu.app.cache.model.Sprint;
import edu.tamu.app.model.ManagementService;

Expand All @@ -33,84 +29,55 @@ public GitHubMilestoneService(final ManagementService managementService) throws
@Override
public List<Sprint> getActiveSprintsByScopeId(final String scopeId) throws Exception {
logger.info("Fetching active sprints for remote project with scope id " + scopeId);
List<Sprint> activeSprints = new ArrayList<Sprint>();
GHRepository repo = github.getRepositoryById(scopeId);
List<GHProject> projects = repo.listProjects(ProjectStateFilter.OPEN).asList();
for (GHProject project : projects) {
String sprintId = String.valueOf(project.getId());
String projectName = repo.getName();
Map<String, List<Card>> partitionedCards = getCards(project);
for (Entry<String, List<Card>> partition : partitionedCards.entrySet()) {
activeSprints.add(new Sprint(sprintId, partition.getKey(), projectName, partition.getValue()));
}
}
return activeSprints;
String productName = repo.getName();
AtomicInteger count = new AtomicInteger(1);
return repo.listProjects(ProjectStateFilter.OPEN).asList().stream()
.flatMap(project -> getActiveSprintsForProject(project, productName, count.getAndIncrement()))
.collect(Collectors.toList());
}

@Override
public List<Sprint> getAdditionalActiveSprints() throws Exception {
GHOrganization organization = github.getOrganization(ORGANIZATION);
List<GHProject> projects = organization.listProjects(ProjectStateFilter.OPEN).asList();
List<Sprint> sprints = new ArrayList<Sprint>();
for (GHProject project : projects) {
String sprintId = String.valueOf(project.getId());
Map<String, List<Card>> partitionedCards = getCards(project);
String productName = String.format("%s - %s", organization.getName(), project.getName());
int count = 0;
for (Entry<String, List<Card>> partition : partitionedCards.entrySet()) {
sprints.add(new Sprint(sprintId + "-" + count, partition.getKey(), productName, partition.getValue()));
count++;
}
}
return sprints;
AtomicInteger count = new AtomicInteger(1);
return organization.listProjects(ProjectStateFilter.OPEN).asList().stream()
.flatMap(project -> getActiveSprintsForProject(project, toProductName(project), count.getAndIncrement()))
.collect(Collectors.toList());
}

private Stream<Sprint> getActiveSprintsForProject(GHProject project, String product, int count) {
String sprintId = String.format("%s-%s", project.getId(), count);
return exceptionHandlerWrapper(project, p -> getCards(p).entrySet()).stream()
.map(e -> new Sprint(sprintId, e.getKey(), product, e.getValue()));
}

private Map<String, List<Card>> getCards(GHProject project) throws IOException {
Map<String, List<Card>> cardsByMilestone = new HashMap<>();
for (GHProjectColumn column : project.listColumns().asList()) {
List<GHProjectCard> projectCards = column.listCards().asList();
Map<Long, GHIssue> cardContents = new HashMap<>();
for (GHProjectCard card : projectCards) {
cardContents.put(card.getId(), card.getContent());
}
Map<GHMilestone, List<GHProjectCard>> partitionedCards = projectCards.stream()
// Card without contents is a note
.filter(c -> cardContents.get(c.getId()) != null)
// Card without a milestone is not on the sprint
.filter(c -> cardContents.get(c.getId()).getMilestone() != null)
// Remove cards with a closed milestone
.filter(c -> cardContents.get(c.getId()).getMilestone().getState().equals(GHMilestoneState.OPEN))
// Remove cards that don't have "sprint" in the milestone title
.filter(c -> cardContents.get(c.getId()).getMilestone().getTitle().toUpperCase().contains(SPRINT))
.collect(Collectors.groupingBy(c -> cardContents.get(c.getId()).getMilestone()));
return project.listColumns().asList().stream()
.flatMap(column -> exceptionHandlerWrapper(column, c -> c.listCards().asList().stream()))
.map(card -> Pair.of(card, exceptionHandlerWrapper(card, c -> c.getContent())))
// Card without contents is a note
.filter(p -> Objects.nonNull(p.getValue()))
// Card without a milestone is not on the sprint
.filter(p -> Objects.nonNull(p.getValue().getMilestone()))
// Remove cards with a closed milestone
.filter(p -> p.getValue().getMilestone().getState().equals(GHMilestoneState.OPEN))
// Remove cards that don't have "sprint" in the milestone title
.filter(p -> p.getValue().getMilestone().getTitle().toUpperCase().contains(SPRINT))
.map(p -> new SprintCard(p.getValue().getMilestone().getTitle(), exceptionHandlerWrapper(p, i -> toCard(i.getKey(), i.getValue()))))
.collect(Collectors.groupingBy(c -> c.getSprint(), Collectors.toList()));
}

for (Entry<GHMilestone, List<GHProjectCard>> partition : partitionedCards.entrySet()) {
List<Card> cards = new ArrayList<>();
for (GHProjectCard card : partition.getValue()) {
GHIssue content = cardContents.get(card.getId());
String id = String.valueOf(card.getId());
String name = content.getTitle();
String number = String.valueOf(content.getNumber());
String type = getCardType(content);
String description = content.getBody();
String status = card.getColumn().getName();
// TODO: Figure out how we want to handle sizes
String estimate = null;
List<Member> assignees = new ArrayList<Member>();
for (GHUser user : content.getAssignees()) {
assignees.add(getMember(user));
}
cards.add(new Card(id, number, mapCardType(type), name, description, mapStatus(status), mapEstimate(estimate), assignees));
}
String title = partition.getKey().getTitle();
if (cardsByMilestone.containsKey(title)) {
cardsByMilestone.get(title).addAll(cards);
} else {
cardsByMilestone.put(title, cards);
}
}
private class SprintCard extends Card {
private static final long serialVersionUID = 1632772205053913423L;
private final String sprint;
public SprintCard(String sprint, Card card) {
this.sprint = sprint;
BeanUtils.copyProperties(card, this);
}
public String getSprint() {
return sprint;
}
return cardsByMilestone;
}

}
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
package edu.tamu.app.service.manager;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import org.kohsuke.github.GHIssue;
import org.kohsuke.github.GHOrganization;
import org.apache.commons.lang3.tuple.Pair;
import org.kohsuke.github.GHProject;
import org.kohsuke.github.GHProject.ProjectStateFilter;
import org.kohsuke.github.GHProjectCard;
import org.kohsuke.github.GHRepository;

import edu.tamu.app.cache.model.Card;
Expand All @@ -26,46 +24,43 @@ public GitHubProjectService(final ManagementService managementService) throws IO
public List<Sprint> getActiveSprintsByScopeId(final String scopeId) throws Exception {
logger.info("Fetching active sprints for remote project with scope id " + scopeId);
GHRepository repo = github.getRepositoryById(scopeId);
String projectName = repo.getName();
return repo.listProjects(ProjectStateFilter.OPEN)
.asList().stream()
.filter(p -> p.getName().toUpperCase().contains(SPRINT))
.map(p -> new Sprint(String.valueOf(p.getId()), p.getName(), projectName, getCards(p)))
String productName = repo.getName();
return repo.listProjects(ProjectStateFilter.OPEN).asList().stream()
.filter(this::isSprintProject)
.map(p -> toSprint(p, productName))
.collect(Collectors.toList());
}

@Override
public List<Sprint> getAdditionalActiveSprints() throws Exception {
GHOrganization organization = github.getOrganization(ORGANIZATION);
return organization.listProjects(ProjectStateFilter.OPEN).asList().stream()
.filter(p -> p.getName().toUpperCase().contains(SPRINT))
.map(p -> new Sprint(
String.valueOf(p.getId()),
String.format("%s - %s", ORGANIZATION, p.getName()),
ORGANIZATION,
getCards(p)
))
return github.getOrganization(ORGANIZATION)
.listProjects(ProjectStateFilter.OPEN).asList().stream()
.filter(this::isSprintProject)
.map(p -> toSprint(p, ORGANIZATION))
.collect(Collectors.toList());
}

private boolean isSprintProject(GHProject project) {
return project.getName().toUpperCase().contains(SPRINT);
}

private Sprint toSprint(GHProject project, String productName) {
return new Sprint(
String.valueOf(project.getId()),
toProductName(project),
productName,
getCards(project)
);
}

private List<Card> getCards(GHProject project) {
return exceptionHandlerWrapper(project, i -> i.listColumns().asList().stream())
.flatMap(c -> exceptionHandlerWrapper(c, i -> i.listCards().asList().stream()))
.collect(HashMap<GHProjectCard, GHIssue>::new, (m, c) -> m.put(c, exceptionHandlerWrapper(c, i -> i.getContent())), HashMap<GHProjectCard, GHIssue>::putAll)
.entrySet().stream()
.filter(e -> e.getValue() != null)
.map(e -> new Card(
String.valueOf(e.getKey().getId()),
String.valueOf(e.getValue().getNumber()),
mapCardType(exceptionHandlerWrapper(e.getValue(), i -> getCardType(i))),
e.getValue().getTitle(),
e.getValue().getBody(),
mapStatus(exceptionHandlerWrapper(e.getKey(), i -> i.getColumn().getName())),
mapEstimate(null),
e.getValue().getAssignees().stream()
.map(a -> exceptionHandlerWrapper(a, i -> getMember(i)))
.collect(Collectors.toList())
))
.map(c -> Pair.of(c, exceptionHandlerWrapper(c, i -> i.getContent())))
// Card without contents is a note
.filter(e -> Objects.nonNull(e.getValue()))
.map(e -> exceptionHandlerWrapper(e, i -> toCard(i.getKey(), i.getValue())))
.collect(Collectors.toList());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,6 @@ public void testGetGitHubInstanceByToken() throws IOException {
public void testGetCardsWithNote() throws Exception {
when(TEST_CARD1.getContent()).thenReturn(null);
List<Sprint> sprints = gitHubProjectService.getAdditionalActiveSprints();
assertEquals("Didn't get expected number of cards", 4, sprints.get(0).getCards().size());
assertEquals("Didn't get expected number of cards", 5, sprints.get(0).getCards().size());
}
}
}

0 comments on commit 8848951

Please sign in to comment.