diff --git a/pj-core/.gitignore b/pj-core/.gitignore
new file mode 100644
index 0000000..5572039
--- /dev/null
+++ b/pj-core/.gitignore
@@ -0,0 +1,7 @@
+/.project
+/.classpath
+/.settings/
+/target/
+/bulldozer-temp.json
+/bulldozer-state.json
+/.factorypath
diff --git a/pj-core/pom.xml b/pj-core/pom.xml
new file mode 100644
index 0000000..20b589a
--- /dev/null
+++ b/pj-core/pom.xml
@@ -0,0 +1,28 @@
+
+
+ 4.0.0
+
+ pj-core
+
+
+ com.g2forge.project
+ pj-project
+ 0.0.1-SNAPSHOT
+ ../pj-project/pom.xml
+
+
+
+
+ com.g2forge.gearbox
+ gb-argparse
+ ${gearbox.version}
+
+
+ com.g2forge.gearbox
+ gb-jira
+ ${gearbox.version}
+
+
+
diff --git a/pj-create/pom.xml b/pj-create/pom.xml
index 09dd7be..882f1a8 100644
--- a/pj-create/pom.xml
+++ b/pj-create/pom.xml
@@ -15,14 +15,9 @@
- com.g2forge.gearbox
- gb-argparse
- ${gearbox.version}
-
-
- com.g2forge.gearbox
- gb-jira
- ${gearbox.version}
+ com.g2forge.project
+ pj-core
+ ${project.version}
diff --git a/pj-create/src/main/java/com/g2forge/project/plan/create/Create.java b/pj-create/src/main/java/com/g2forge/project/plan/create/Create.java
index ea7710b..22e9a7e 100644
--- a/pj-create/src/main/java/com/g2forge/project/plan/create/Create.java
+++ b/pj-create/src/main/java/com/g2forge/project/plan/create/Create.java
@@ -44,9 +44,9 @@
import com.g2forge.alexandria.java.type.ref.ITypeRef;
import com.g2forge.alexandria.log.HLog;
import com.g2forge.gearbox.jira.ExtendedJiraRestClient;
-import com.g2forge.gearbox.jira.JIRAServer;
+import com.g2forge.gearbox.jira.JiraAPI;
+import com.g2forge.gearbox.jira.fields.KnownField;
import com.g2forge.project.plan.create.CreateIssue.CreateIssueBuilder;
-import com.g2forge.project.plan.create.field.KnownField;
import com.google.common.base.Objects;
import io.atlassian.util.concurrent.Promise;
@@ -63,7 +63,7 @@
* The INPUTFILE must be a YAML file, which consists of a configuration one field of which is issues. The configuration must specify
* at least a summary for each issue, and optionally more. The fields of the issues in the YAML file are documented below. The configuration can
* also include, at the top level, an entry for any field marked "configurable" whose value will be used for any issue that does not specify a value explicitly.
- * Please see {@link JIRAServer} for information on specifying the Jira server and user account.
+ * Please see {@link JiraAPI} for information on specifying the Jira server and user account.
*
*
* Create issues issue properties and their descriptions
@@ -257,7 +257,7 @@ protected Map getProjectComponents(final ExtendedJiraRes
protected List implementChanges(Server server, Changes changes) throws IOException, URISyntaxException, InterruptedException, ExecutionException {
HLog.getLogControl().setLogLevel(Level.INFO);
- try (final ExtendedJiraRestClient client = JIRAServer.createFromPropertyInput(server == null ? null : server.getApi(), null).connect(true)) {
+ try (final ExtendedJiraRestClient client = JiraAPI.createFromPropertyInput(server == null ? null : server.getApi(), null).connect(true)) {
final Map linkTypes = new HashMap<>();
for (IssuelinksType linkType : client.getMetadataClient().getIssueLinkTypes().get()) {
linkTypes.put(linkType.getName(), new LinkType(linkType.getName(), false));
diff --git a/pj-create/src/main/java/com/g2forge/project/plan/create/Server.java b/pj-create/src/main/java/com/g2forge/project/plan/create/Server.java
index 548af1d..2c9a754 100644
--- a/pj-create/src/main/java/com/g2forge/project/plan/create/Server.java
+++ b/pj-create/src/main/java/com/g2forge/project/plan/create/Server.java
@@ -2,9 +2,10 @@
import java.util.Map;
-import com.g2forge.gearbox.jira.JIRAServer;
-import com.g2forge.project.plan.create.field.Field;
-import com.g2forge.project.plan.create.field.KnownField;
+import com.g2forge.gearbox.jira.JiraAPI;
+import com.g2forge.gearbox.jira.fields.Field;
+import com.g2forge.gearbox.jira.fields.IFieldConfig;
+import com.g2forge.gearbox.jira.fields.KnownField;
import lombok.AllArgsConstructor;
import lombok.Builder;
@@ -14,7 +15,7 @@
@Data
@Builder
@AllArgsConstructor
-public class Server {
+public class Server implements IFieldConfig {
@Singular
protected final Map fields;
@@ -23,5 +24,5 @@ public class Server {
@Singular
protected final Map users;
- protected final JIRAServer api;
+ protected final JiraAPI api;
}
diff --git a/pj-create/src/main/java/com/g2forge/project/plan/create/field/Field.java b/pj-create/src/main/java/com/g2forge/project/plan/create/field/Field.java
deleted file mode 100644
index f683f26..0000000
--- a/pj-create/src/main/java/com/g2forge/project/plan/create/field/Field.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.g2forge.project.plan.create.field;
-
-import lombok.Builder;
-import lombok.Data;
-import lombok.RequiredArgsConstructor;
-
-@Data
-@Builder(toBuilder = true)
-@RequiredArgsConstructor
-public class Field implements IField {
- protected final String name;
-
- protected final String withKey;
-}
diff --git a/pj-create/src/main/java/com/g2forge/project/plan/create/field/IField.java b/pj-create/src/main/java/com/g2forge/project/plan/create/field/IField.java
deleted file mode 100644
index 43b70ca..0000000
--- a/pj-create/src/main/java/com/g2forge/project/plan/create/field/IField.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.g2forge.project.plan.create.field;
-
-import com.atlassian.jira.rest.client.api.domain.input.ComplexIssueInputFieldValue;
-import com.atlassian.jira.rest.client.api.domain.input.FieldInput;
-import com.g2forge.alexandria.java.adt.name.IStringNamed;
-
-public interface IField extends IStringNamed {
- public default FieldInput createFieldInput(Object value) {
- final Object fieldInputValue;
- final String withKey = getWithKey();
- if (withKey == null) fieldInputValue = value;
- else fieldInputValue = ComplexIssueInputFieldValue.with(withKey, value);
- return new FieldInput(getName(), fieldInputValue);
- }
-
- public String getWithKey();
-}
diff --git a/pj-create/src/main/java/com/g2forge/project/plan/create/field/KnownField.java b/pj-create/src/main/java/com/g2forge/project/plan/create/field/KnownField.java
deleted file mode 100644
index 4c9fd1d..0000000
--- a/pj-create/src/main/java/com/g2forge/project/plan/create/field/KnownField.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.g2forge.project.plan.create.field;
-
-import com.atlassian.jira.rest.client.api.domain.IssueFieldId;
-import com.g2forge.project.plan.create.Server;
-
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-
-@RequiredArgsConstructor
-@Getter
-public enum KnownField implements IField {
- Assignee(IssueFieldId.ASSIGNEE_FIELD.id, "name"),
- EpicSummary("customfield_10002", null),
- Parent("customfield_10000", null),
- Sprint("customfield_10004", null),
- Security("security", "name");
-
- protected final String name;
-
- protected final String withKey;
-
- public IField get(Server server) {
- if ((server == null) || (server.getFields() == null)) return this;
- final Field retVal = server.getFields().get(this);
- if (retVal == null) return this;
- return retVal;
- }
-}
diff --git a/pj-plan/pom.xml b/pj-plan/pom.xml
index 701393f..c6a211d 100644
--- a/pj-plan/pom.xml
+++ b/pj-plan/pom.xml
@@ -19,14 +19,9 @@
- com.g2forge.gearbox
- gb-argparse
- ${gearbox.version}
-
-
- com.g2forge.gearbox
- gb-jira
- ${gearbox.version}
+ com.g2forge.project
+ pj-core
+ ${project.version}
diff --git a/pj-plan/src/main/java/com/g2forge/project/plan/Download.java b/pj-plan/src/main/java/com/g2forge/project/plan/Download.java
index 3909d28..dc7ea53 100644
--- a/pj-plan/src/main/java/com/g2forge/project/plan/Download.java
+++ b/pj-plan/src/main/java/com/g2forge/project/plan/Download.java
@@ -22,7 +22,7 @@
import com.g2forge.alexandria.command.invocation.CommandInvocation;
import com.g2forge.alexandria.log.HLog;
import com.g2forge.gearbox.jira.ExtendedJiraRestClient;
-import com.g2forge.gearbox.jira.JIRAServer;
+import com.g2forge.gearbox.jira.JiraAPI;
import lombok.Builder;
import lombok.Data;
@@ -67,7 +67,7 @@ public IExit invoke(CommandInvocation invocation) thro
header.createCell(1).setCellValue("Summary");
}
- try (final ExtendedJiraRestClient client = JIRAServer.load().connect(true)) {
+ try (final ExtendedJiraRestClient client = JiraAPI.load().connect(true)) {
final int max = 500;
int base = 0, rowNum = 1;
while (true) {
diff --git a/pj-report/.gitignore b/pj-report/.gitignore
new file mode 100644
index 0000000..5572039
--- /dev/null
+++ b/pj-report/.gitignore
@@ -0,0 +1,7 @@
+/.project
+/.classpath
+/.settings/
+/target/
+/bulldozer-temp.json
+/bulldozer-state.json
+/.factorypath
diff --git a/pj-report/pom.xml b/pj-report/pom.xml
new file mode 100644
index 0000000..b4e4240
--- /dev/null
+++ b/pj-report/pom.xml
@@ -0,0 +1,23 @@
+
+
+ 4.0.0
+
+ pj-report
+
+
+ com.g2forge.project
+ pj-project
+ 0.0.1-SNAPSHOT
+ ../pj-project/pom.xml
+
+
+
+
+ com.g2forge.project
+ pj-core
+ ${project.version}
+
+
+
diff --git a/pj-report/src/main/java/com/g2forge/project/report/Billing.java b/pj-report/src/main/java/com/g2forge/project/report/Billing.java
new file mode 100644
index 0000000..8c986ce
--- /dev/null
+++ b/pj-report/src/main/java/com/g2forge/project/report/Billing.java
@@ -0,0 +1,89 @@
+package com.g2forge.project.report;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.net.URISyntaxException;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+
+import org.slf4j.event.Level;
+
+import com.atlassian.jira.rest.client.api.IssueRestClient;
+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.Issue;
+import com.g2forge.alexandria.command.command.IStandardCommand;
+import com.g2forge.alexandria.command.exit.IExit;
+import com.g2forge.alexandria.command.invocation.CommandInvocation;
+import com.g2forge.alexandria.java.adt.name.IStringNamed;
+import com.g2forge.alexandria.java.core.helpers.HCollection;
+import com.g2forge.alexandria.log.HLog;
+import com.g2forge.gearbox.argparse.ArgumentParser;
+import com.g2forge.gearbox.jira.ExtendedJiraRestClient;
+import com.g2forge.gearbox.jira.JiraAPI;
+import com.g2forge.gearbox.jira.fields.KnownField;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class Billing implements IStandardCommand {
+ @Data
+ @Builder(toBuilder = true)
+ @AllArgsConstructor
+ protected static class Arguments {
+ protected final String issueKey;
+ }
+
+ public static void main(String[] args) throws Throwable {
+ IStandardCommand.main(args, new Billing());
+ }
+
+ protected void demoLogChanges(final String issueKey) throws InterruptedException, ExecutionException, IOException, URISyntaxException {
+ final Set fields = HCollection.asList(KnownField.Status).stream().map(IStringNamed::getName).collect(Collectors.toSet());
+ try (final ExtendedJiraRestClient client = JiraAPI.load().connect(true)) {
+ final Issue issue = client.getIssueClient().getIssue(issueKey, HCollection.asList(IssueRestClient.Expandos.CHANGELOG)).get();
+ log.info("Created at {}", issue.getCreationDate());
+ for (ChangelogGroup changelogGroup : issue.getChangelog()) {
+ boolean printedGroupLabel = false;
+ for (ChangelogItem changelogItem : changelogGroup.getItems()) {
+ if ((fields == null) || fields.contains(changelogItem.getField())) {
+ if (!printedGroupLabel) {
+ log.info("{} {}", changelogGroup.getCreated(), changelogGroup.getAuthor().getDisplayName());
+ printedGroupLabel = true;
+ }
+ log.info("\t{}: {} -> {}", changelogItem.getField(), changelogItem.getFromString(), changelogItem.getToString());
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public IExit invoke(CommandInvocation invocation) throws Throwable {
+ HLog.getLogControl().setLogLevel(Level.INFO);
+ final Arguments arguments = ArgumentParser.parse(Arguments.class, invocation.getArguments());
+
+ demoLogChanges(arguments.getIssueKey());
+
+ // Progressing: Input - API info, list of users
+ // TODO: Search for all relevant issues (anything updatedBy a relevant user in the given time range https://confluence.atlassian.com/jirasoftwareserver/advanced-searching-functions-reference-939938746.html, might have to search across all users)
+
+ // TODO: I/O - Start time and end time for the report, and the exact time we ran in
+ // TODO: Build a status history for an issue (Limit to the queried time range, Infer initial status from first status change, and create a timestamp of "now" for the end if needed)
+ // TODO: Input - working hours for a person (just start/stop times & days of week for now, add support for exceptions later)
+ // TODO: Input - mapping of issues to accounts (e.g. by epic, by component, etc)
+ // TODO: Construct a per-person timeline
+ // what accounts were they working on at all times (what issues, then group issues by account, two accounts can be double billed, or split)
+ // Reduce issue timeline to "active" statuses, and project those times against working hours
+ // Abstract the projection, so I can add filters/exceptions/days-off later
+ // TODO: Report on any times where a person was not billing to anything, but was working
+ // TODO: Report on any times an issue changed status outside working hours
+
+ return IStandardCommand.SUCCESS;
+ }
+}
diff --git a/pj-report/src/main/java/com/g2forge/project/report/Report.java b/pj-report/src/main/java/com/g2forge/project/report/Report.java
new file mode 100644
index 0000000..9a50fb2
--- /dev/null
+++ b/pj-report/src/main/java/com/g2forge/project/report/Report.java
@@ -0,0 +1,10 @@
+package com.g2forge.project.report;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+
+@Data
+@Builder(toBuilder = true)
+@AllArgsConstructor
+public class Report {}
\ No newline at end of file
diff --git a/pj-report/src/main/java/com/g2forge/project/report/Request.java b/pj-report/src/main/java/com/g2forge/project/report/Request.java
new file mode 100644
index 0000000..a2eac09
--- /dev/null
+++ b/pj-report/src/main/java/com/g2forge/project/report/Request.java
@@ -0,0 +1,20 @@
+package com.g2forge.project.report;
+
+import java.util.List;
+
+import com.g2forge.gearbox.jira.JiraAPI;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.Singular;
+
+@Data
+@Builder(toBuilder = true)
+@AllArgsConstructor
+public class Request {
+ protected final JiraAPI api;
+
+ @Singular
+ protected final List users;
+}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 5ef6f19..1008454 100644
--- a/pom.xml
+++ b/pom.xml
@@ -19,7 +19,9 @@
pj-project
+ pj-core
pj-create
pj-plan
+ pj-report