Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion pj-core/src/main/java/com/g2forge/project/core/Server.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,29 @@ public class Server implements IFieldConfig {

protected final Integer sprintOffset;

protected final Map<Integer, Integer> sprintMap;

@Singular
protected final Map<String, String> users;

protected final UserPrimaryKey userPrimaryKey;

protected final UserPrimaryKey userPrimaryKey;
protected final JiraAPI api;

public UserPrimaryKey getUserPrimaryKey() {
return userPrimaryKey == null ? UserPrimaryKey.NAME : userPrimaryKey;
}

public Integer modifySprint(Integer sprint) {
// No matter how things are configured, this is the correct result
if (sprint == null) return null;

if ((getSprintOffset() != null) && (getSprintMap() != null)) throw new IllegalArgumentException("Both offset and map are non-null, please specify either a map from sprint numbers to IDs, or an offset!");

if (getSprintMap() != null) {
final Integer retVal = getSprintMap().get(sprint);
if (retVal == null) throw new IllegalArgumentException(String.format("Sprint number %1$d was not mapped, please update your sprint number to ID map!", sprint));
return retVal;
} else return sprint + getSprintOffset();
}
}
32 changes: 18 additions & 14 deletions pj-create/src/main/java/com/g2forge/project/plan/create/Create.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
import com.g2forge.alexandria.command.exit.IExit;
import com.g2forge.alexandria.command.invocation.CommandInvocation;
import com.g2forge.alexandria.java.core.error.HError;
import com.g2forge.alexandria.java.function.ISupplier;
import com.g2forge.alexandria.java.function.cache.FixedCachingSupplier;
import com.g2forge.alexandria.java.io.dataaccess.IDataSource;
import com.g2forge.alexandria.java.io.dataaccess.PathDataSource;
import com.g2forge.alexandria.log.HLog;
Expand Down Expand Up @@ -165,7 +167,7 @@ protected static class LinkType {

protected static Changes computeChanges(Server server, CreateConfig config) {
final SprintConfig sprintWithDefault = config.getSprintConfig() == null ? SprintConfig.getDEFAULT() : config.getSprintConfig().fallback(SprintConfig.getDEFAULT());
final SprintConfig sprintWithOffset = ((server == null) || (server.getSprintOffset() == null)) ? sprintWithDefault : sprintWithDefault.toBuilder().offset(sprintWithDefault.getOffset() + server.getSprintOffset()).build();
final SprintConfig sprintWithOffset = (server == null) ? sprintWithDefault : sprintWithDefault.toBuilder().offset(server.modifySprint(sprintWithDefault.getOffset())).build();

final Changes.ChangesBuilder retVal = Changes.builder();
final Set<String> disabledSummaries = config.getDisabledIssues().stream().map(CreateIssue::getSummary).collect(Collectors.toSet());
Expand Down Expand Up @@ -249,13 +251,6 @@ protected Map<String, String> implementChanges(Server server, Changes changes) t
final boolean dryrun = ProjectCreateFlag.DRYRUN.getAccessor().get();
HLog.getLogControl().setLogLevel(Level.INFO);
try (final ExtendedJiraRestClient client = JiraAPI.createFromPropertyInput(server == null ? null : server.getApi(), null).connect(true)) {
final Map<String, LinkType> linkTypes = new HashMap<>();
for (IssuelinksType linkType : client.getMetadataClient().getIssueLinkTypes().get()) {
linkTypes.put(linkType.getName(), new LinkType(linkType.getName(), false));
linkTypes.put(linkType.getInward(), new LinkType(linkType.getName(), true));
linkTypes.put(linkType.getOutward(), new LinkType(linkType.getName(), false));
}

final IssueRestClient issueClient = client.getIssueClient();
final Map<String, String> issues = new LinkedHashMap<>();
for (CreateIssue issue : changes.getIssues()) {
Expand Down Expand Up @@ -324,12 +319,21 @@ protected Map<String, String> implementChanges(Server server, Changes changes) t
}
}

if (!dryrun) for (LinkIssuesInput link : changes.getLinks()) {
final LinkType linkType = linkTypes.get(link.getLinkType());
final String from = issues.get(link.getFromIssueKey());
final String to = issues.getOrDefault(link.getToIssueKey(), link.getToIssueKey());
// TODO: Handle it when an issue we're linking wasn't created
issueClient.linkIssue(new LinkIssuesInput(linkType.isReverse() ? to : from, linkType.isReverse() ? from : to, linkType.getName(), link.getComment())).get();
if (!dryrun && !changes.getLinks().isEmpty()) {
final Map<String, LinkType> linkTypes = new HashMap<>();
for (IssuelinksType linkType : client.getMetadataClient().getIssueLinkTypes().get()) {
linkTypes.put(linkType.getName(), new LinkType(linkType.getName(), false));
linkTypes.put(linkType.getInward(), new LinkType(linkType.getName(), true));
linkTypes.put(linkType.getOutward(), new LinkType(linkType.getName(), false));
}

for (LinkIssuesInput link : changes.getLinks()) {
final LinkType linkType = linkTypes.get(link.getLinkType());
final String from = issues.get(link.getFromIssueKey());
final String to = issues.getOrDefault(link.getToIssueKey(), link.getToIssueKey());
// TODO: Handle it when an issue we're linking wasn't created
issueClient.linkIssue(new LinkIssuesInput(linkType.isReverse() ? to : from, linkType.isReverse() ? from : to, linkType.getName(), link.getComment())).get();
}
}

return issues;
Expand Down
31 changes: 21 additions & 10 deletions pj-report/src/main/java/com/g2forge/project/report/Billing.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

import com.atlassian.jira.rest.client.api.IssueRestClient;
import com.atlassian.jira.rest.client.api.domain.BasicComponent;
import com.atlassian.jira.rest.client.api.domain.BasicUser;
import com.atlassian.jira.rest.client.api.domain.ChangelogGroup;
import com.atlassian.jira.rest.client.api.domain.ChangelogItem;
import com.atlassian.jira.rest.client.api.domain.Comment;
Expand Down Expand Up @@ -181,14 +182,17 @@ public static void main(String[] args) throws Throwable {

protected final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy/MM/dd");

protected List<Change> computeChanges(ExtendedJiraRestClient client, Server server, Request request, IFunction1<User, String> userToFriendly, String issueKey, ZonedDateTime start, ZonedDateTime end) throws InterruptedException, ExecutionException {
protected final DateTimeFormatter DATE_FORMAT_FILENAME = DateTimeFormatter.ofPattern("yyyy-MM-dd");

protected List<Change> computeChanges(ExtendedJiraRestClient client, Server server, Request request, IFunction1<BasicUser, String> userToFriendly, String issueKey, ZonedDateTime start, ZonedDateTime end) throws InterruptedException, ExecutionException {
final Issue issue = client.getIssueClient().getIssue(issueKey, HCollection.asList(IssueRestClient.Expandos.CHANGELOG)).get();
final List<ChangelogGroup> changelog = new ArrayList<>(HCollection.asListIterable(issue.getChangelog()));
for (Comment comment : issue.getComments()) {
final String body = comment.getBody();
final List<StatusAdjustment> adjustments = StatusAdjustment.parse(body);
if (!adjustments.isEmpty()) {
final ZoneId zone = request.getZone(comment.getAuthor().getName());

final ZoneId zone = request.getZone(userToFriendly.apply(comment.getAuthor()));
for (StatusAdjustment adjustment : adjustments) {
final ZonedDateTime when = adjustment.getWhen().atZone(zone);
changelog.add(new ChangelogGroup(comment.getAuthor(), convert(when), HCollection.asList(new ChangelogItem(FieldType.JIRA, KnownField.Status.getName(), adjustment.getFrom(), adjustment.getFrom(), adjustment.getTo(), adjustment.getTo()))));
Expand All @@ -208,11 +212,12 @@ protected List<Change> computeChanges(ExtendedJiraRestClient client, Server serv
return Change.toChanges(changelog, start, end, userToFriendly.apply(issue.getAssignee()), issue.getStatus().getName(), users);
}

protected List<Issue> findRelevantIssues(ExtendedJiraRestClient client, String jql, Collection<? extends String> users, LocalDate start, LocalDate end) throws InterruptedException, ExecutionException {
protected List<Issue> findRelevantIssues(ExtendedJiraRestClient client, String jql, Collection<? extends String> users, Map<String, String> userMap, LocalDate start, LocalDate end) throws InterruptedException, ExecutionException {
final List<Issue> retVal = new ArrayList<>();
for (String user : users) {
log.info("Finding issues for {}", user);
final String compositeJQL = String.format("issuekey IN updatedBy(%1$s, \"%2$s\", \"%3$s\")", user, start.format(DATE_FORMAT), end.format(DATE_FORMAT)) + ((jql == null) ? "" : (" AND " + jql));
final String username = (userMap == null) ? user : userMap.getOrDefault(user, user);
final String compositeJQL = String.format("issuekey IN updatedBy(%1$s, \"%2$s\", \"%3$s\")", username, start.format(DATE_FORMAT), end.format(DATE_FORMAT)) + ((jql == null) ? "" : (" AND " + jql));
final int desiredMax = 500;
int base = 0;
while (true) {
Expand All @@ -228,7 +233,7 @@ protected List<Issue> findRelevantIssues(ExtendedJiraRestClient client, String j
return retVal;
}

protected List<Change> examineIssue(final ExtendedJiraRestClient client, Server server, Request request, IPredicate1<String> isStatusBillable, IPredicate1<Object> isComponentBillable, IFunction1<User, String> userToFriendly, Issue issue, Bill.BillBuilder billBuilder) throws InterruptedException, ExecutionException {
protected List<Change> examineIssue(final ExtendedJiraRestClient client, Server server, Request request, IPredicate1<String> isStatusBillable, IPredicate1<Object> isComponentBillable, IFunction1<BasicUser, String> userToFriendly, Issue issue, Bill.BillBuilder billBuilder) throws InterruptedException, ExecutionException {
log.info("Examining {}", issue.getKey());
final Set<String> billableComponents = HCollection.asListIterable(issue.getComponents()).stream().map(BasicComponent::getName).distinct().filter(isComponentBillable).collect(Collectors.toSet());
if (billableComponents.isEmpty()) return null;
Expand Down Expand Up @@ -257,7 +262,7 @@ public IExit invoke(CommandInvocation<?, InputStream, PrintStream> invocation) t
final IPredicate1<Object> isComponentBillable = HMatch.createPredicate(true, request.getBillableComponents());

final Map<String, String> userReverseMap = server.getUsers().entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));
final IFunction1<User, String> userToFriendly = user -> {
final IFunction1<BasicUser, String> userToFriendly = user -> {
final String primaryKey = server.getUserPrimaryKey().getValue(user);
return userReverseMap.getOrDefault(primaryKey, primaryKey);
};
Expand All @@ -269,7 +274,7 @@ public IExit invoke(CommandInvocation<?, InputStream, PrintStream> invocation) t
final Map<String, Issue> issues;
final Map<String, List<Change>> changes = new TreeMap<>();
{
final List<Issue> relevantIssues = findRelevantIssues(client, request.getJql(), request.getUsers().keySet(), request.getStart(), request.getEnd());
final List<Issue> relevantIssues = findRelevantIssues(client, request.getJql(), request.getUsers().keySet(), server.getUsers(), request.getStart(), request.getEnd());
issues = relevantIssues.stream().collect(Collectors.toMap(Issue::getKey, IFunction1.identity(), (i0, i1) -> i0));
}
log.info("Found: {}", issues.keySet().stream().collect(HCollector.joiningHuman()));
Expand Down Expand Up @@ -315,9 +320,15 @@ public IExit invoke(CommandInvocation<?, InputStream, PrintStream> invocation) t
billLines.add(new BillLine(component, assignees, issue, summary, hours, ranges.toString().strip(), link));
}
}
final Path outputFile = Filename.replaceExtension(arguments.getRequest(), "csv");
log.info("Writing bill to {}", outputFile);
BillLine.getMapper().write(billLines, outputFile);

{

final Path outputDirectory = arguments.getRequest().getParent();
final String filename = Filename.fromPath(arguments.getRequest()).getPrefix().toString() + " (" + DATE_FORMAT_FILENAME.format(request.getStart()) + " to " + DATE_FORMAT_FILENAME.format(request.getEnd()) + ").csv";
final Path outputFile = outputDirectory.resolve(filename);
log.info("Writing bill to {}", outputFile);
BillLine.getMapper().write(billLines, outputFile);
}

log.info("Bill by user");
for (String user : bill.getUsers()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ public class Request {
protected final LocalDate end;

public ZoneId getZone(String user) {
return user == null ? ZoneId.systemDefault() : getUsers().get(user).getZone();
if (user == null) return ZoneId.systemDefault();
final WorkingHours workingHours = getUsers().get(user);
if (workingHours == null) throw new IllegalArgumentException("No working hours specified for user " + user);
return workingHours.getZone();
}
}
Loading