feature[TW18355]: Add REST calls to relate worklfows to incorporated releases

Change-Id: Ibfa26b8171e33993d259ddf20d32115e7e158e57
Signed-off-by: kenn.r.luecke <kenn.r.luecke@boeing.com>
diff --git a/plugins/org.eclipse.osee.ats.api/src/org/eclipse/osee/ats/api/data/AtsArtifactTypes.java b/plugins/org.eclipse.osee.ats.api/src/org/eclipse/osee/ats/api/data/AtsArtifactTypes.java
index 6f077ac..d9babb0 100644
--- a/plugins/org.eclipse.osee.ats.api/src/org/eclipse/osee/ats/api/data/AtsArtifactTypes.java
+++ b/plugins/org.eclipse.osee.ats.api/src/org/eclipse/osee/ats/api/data/AtsArtifactTypes.java
@@ -157,7 +157,7 @@
       .zeroOrOne(ReviewFormalType, ReviewFormalType.Formal)
       .any(Role)
       .zeroOrOne(VerificationCodeInspection));
-   ArtifactTypeToken ReleaseArtifact = ats.add(ats.artifactType(61L, "ats.Release Artifact", true, Artifact)
+   ArtifactTypeToken ReleaseArtifact = ats.add(ats.artifactType(61L, "ats.Release Artifact", false, Artifact)
       .zeroOrOne(Released));
    ArtifactTypeToken Task = ats.add(ats.artifactType(74L, "Task", false, AbstractWorkflowArtifact)
       .zeroOrOne(RelatedToState)
diff --git a/plugins/org.eclipse.osee.ats.api/src/org/eclipse/osee/ats/api/data/AtsRelationTypes.java b/plugins/org.eclipse.osee.ats.api/src/org/eclipse/osee/ats/api/data/AtsRelationTypes.java
index 3263448..b63d4a4 100644
--- a/plugins/org.eclipse.osee.ats.api/src/org/eclipse/osee/ats/api/data/AtsRelationTypes.java
+++ b/plugins/org.eclipse.osee.ats.api/src/org/eclipse/osee/ats/api/data/AtsRelationTypes.java
@@ -29,6 +29,7 @@
 import static org.eclipse.osee.ats.api.data.AtsArtifactTypes.InsertionActivity;
 import static org.eclipse.osee.ats.api.data.AtsArtifactTypes.Program;
 import static org.eclipse.osee.ats.api.data.AtsArtifactTypes.Project;
+import static org.eclipse.osee.ats.api.data.AtsArtifactTypes.ReleaseArtifact;
 import static org.eclipse.osee.ats.api.data.AtsArtifactTypes.Task;
 import static org.eclipse.osee.ats.api.data.AtsArtifactTypes.TeamDefinition;
 import static org.eclipse.osee.ats.api.data.AtsArtifactTypes.TeamWorkflow;
@@ -201,6 +202,10 @@
    RelationTypeSide TeamWorkflowToFoundInVersion_TeamWorkflow = RelationTypeSide.create(TeamWorkflowToFoundInVersion, SIDE_A);
    RelationTypeSide TeamWorkflowToFoundInVersion_Version = RelationTypeSide.create(TeamWorkflowToFoundInVersion, SIDE_B);
 
+   RelationTypeToken TeamWorkflowToRelease = ats.add(2531996123289575340L, "TeamWorkflowToRelease", MANY_TO_MANY, UNORDERED, TeamWorkflow, "Team Workflow", ReleaseArtifact, "Release Artifact");
+   RelationTypeSide TeamWorkflowToRelease_TeamWorkflow = RelationTypeSide.create(TeamWorkflowToRelease, SIDE_A);
+   RelationTypeSide TeamWorkflowToRelease_Release = RelationTypeSide.create(TeamWorkflowToRelease, SIDE_B);
+
    RelationTypeToken TeamWorkflowToReview = ats.add(2305843009213694321L, "TeamWorkflowToReview", MANY_TO_MANY, UNORDERED, TeamWorkflow, "Team Workflow", AbstractReview, "Review");
    RelationTypeSide TeamWorkflowToReview_TeamWorkflow = RelationTypeSide.create(TeamWorkflowToReview, SIDE_A);
    RelationTypeSide TeamWorkflowToReview_Review = RelationTypeSide.create(TeamWorkflowToReview, SIDE_B);
diff --git a/plugins/org.eclipse.osee.ats.api/src/org/eclipse/osee/ats/api/workflow/AtsTeamWfEndpointApi.java b/plugins/org.eclipse.osee.ats.api/src/org/eclipse/osee/ats/api/workflow/AtsTeamWfEndpointApi.java
index 7ab6f44..76243f9 100644
--- a/plugins/org.eclipse.osee.ats.api/src/org/eclipse/osee/ats/api/workflow/AtsTeamWfEndpointApi.java
+++ b/plugins/org.eclipse.osee.ats.api/src/org/eclipse/osee/ats/api/workflow/AtsTeamWfEndpointApi.java
@@ -60,4 +60,11 @@
    @Path("{id}/goal")
    @Produces({MediaType.APPLICATION_JSON})
    List<IAtsGoal> getGoals(@PathParam("id") String id);
+
+   @PUT
+   @Path("build/{build}")
+   @Consumes(MediaType.APPLICATION_JSON)
+   @Produces(MediaType.APPLICATION_JSON)
+   XResultData setReleases(@PathParam("build") String build, @HeaderParam(OSEE_ACCOUNT_ID) UserId userId, List<String> changeIds);
+
 }
\ No newline at end of file
diff --git a/plugins/org.eclipse.osee.ats.rest/src/org/eclipse/osee/ats/rest/internal/workitem/AtsTeamWfEndpointImpl.java b/plugins/org.eclipse.osee.ats.rest/src/org/eclipse/osee/ats/rest/internal/workitem/AtsTeamWfEndpointImpl.java
index 6a9b342..ba828d4 100644
--- a/plugins/org.eclipse.osee.ats.rest/src/org/eclipse/osee/ats/rest/internal/workitem/AtsTeamWfEndpointImpl.java
+++ b/plugins/org.eclipse.osee.ats.rest/src/org/eclipse/osee/ats/rest/internal/workitem/AtsTeamWfEndpointImpl.java
@@ -32,11 +32,13 @@
 import org.eclipse.osee.ats.api.IAtsWorkItem;
 import org.eclipse.osee.ats.api.ai.IAtsActionableItem;
 import org.eclipse.osee.ats.api.config.TeamDefinition;
+import org.eclipse.osee.ats.api.data.AtsArtifactTypes;
 import org.eclipse.osee.ats.api.data.AtsRelationTypes;
 import org.eclipse.osee.ats.api.team.IAtsTeamDefinition;
 import org.eclipse.osee.ats.api.user.AtsUser;
 import org.eclipse.osee.ats.api.util.IAtsChangeSet;
 import org.eclipse.osee.ats.api.version.IAtsVersion;
+import org.eclipse.osee.ats.api.workdef.IRelationResolver;
 import org.eclipse.osee.ats.api.workflow.AtsTeamWfEndpointApi;
 import org.eclipse.osee.ats.api.workflow.IAtsGoal;
 import org.eclipse.osee.ats.api.workflow.IAtsTeamWorkflow;
@@ -48,6 +50,7 @@
 import org.eclipse.osee.framework.core.enums.CoreAttributeTypes;
 import org.eclipse.osee.framework.core.model.change.ChangeItem;
 import org.eclipse.osee.framework.jdk.core.result.XResultData;
+import org.eclipse.osee.framework.jdk.core.util.Lib;
 
 /**
  * @author Donald G. Dunne
@@ -180,4 +183,66 @@
       }
       return goalList;
    }
-}
+
+   @Override
+   @PUT
+   @Path("build/{build}")
+   @Consumes(MediaType.APPLICATION_JSON)
+   @Produces(MediaType.APPLICATION_JSON)
+   public XResultData setReleases(@PathParam("build") String build, @HeaderParam(OSEE_ACCOUNT_ID) UserId userId, List<String> changeIds) {
+      XResultData rd = new XResultData();
+      try {
+         rd.setTitle("Add Workflow to Release Relations");
+
+         Collection<ArtifactToken> allWorkflows =
+            atsApi.getQueryService().createQuery(AtsArtifactTypes.TeamWorkflow).andAttr(CoreAttributeTypes.GitChangeId,
+               changeIds).getArtifacts();
+         AtsUser asUser = atsApi.getUserService().getUserByAccountId(userId);
+         if (asUser.isInvalid()) {
+            rd.errorf("%s is an invalid ATS user.", userId);
+            return rd;
+         }
+
+         IAtsChangeSet changes = atsApi.createChangeSet("Add Build Incorporation(s)", asUser);
+         for (IAtsWorkItem workItem : atsApi.getWorkItemService().getWorkItems(allWorkflows)) {
+            if (!workItem.isTeamWorkflow()) {
+               rd.errorf("%s is not a valid team workflow id.", workItem.toStringWithId());
+               return rd;
+            }
+
+            Set<String> distinctChangeIds = new HashSet<String>(
+               atsApi.getAttributeResolver().getAttributesToStringList(workItem, CoreAttributeTypes.GitChangeId));
+            boolean isBuildValid = false;
+            for (String changeId : changeIds) {
+               if (distinctChangeIds.contains(changeId)) {
+                  isBuildValid = true;
+                  break;
+               }
+            }
+            Collection<ArtifactToken> release =
+               atsApi.getQueryService().createQuery(AtsArtifactTypes.ReleaseArtifact).andName(build).getArtifacts();
+            if (isBuildValid) {
+               if (release.size() > 1) {
+                  rd.errorf("%s has multiple releases", build);
+                  return rd;
+               }
+               if (release.isEmpty()) {
+                  rd.errorf("%s has no elements", build);
+                  return rd;
+               }
+               IRelationResolver relationResolver = atsApi.getRelationResolver();
+               if (!relationResolver.areRelated(workItem.getArtifactId(),
+                  AtsRelationTypes.TeamWorkflowToRelease_Release, release.stream().findFirst().get())) {
+                  changes.relate(workItem, AtsRelationTypes.TeamWorkflowToRelease_Release,
+                     release.stream().findFirst().get());
+               }
+            }
+         }
+         changes.executeIfNeeded();
+      } catch (Exception Ex) {
+         rd.errorf("Exception %s", Lib.exceptionToString(Ex));
+      }
+
+      return rd;
+   }
+}
\ No newline at end of file
diff --git a/plugins/org.eclipse.osee.define.api/src/org/eclipse/osee/define/api/GitEndpoint.java b/plugins/org.eclipse.osee.define.api/src/org/eclipse/osee/define/api/GitEndpoint.java
index 700423d..1efca5b 100644
--- a/plugins/org.eclipse.osee.define.api/src/org/eclipse/osee/define/api/GitEndpoint.java
+++ b/plugins/org.eclipse.osee.define.api/src/org/eclipse/osee/define/api/GitEndpoint.java
@@ -14,7 +14,9 @@
 package org.eclipse.osee.define.api;
 
 import static org.eclipse.osee.framework.core.data.OseeClient.OSEE_ACCOUNT_ID;
+import java.util.List;
 import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
 import javax.ws.rs.HeaderParam;
 import javax.ws.rs.POST;
 import javax.ws.rs.Path;
@@ -39,9 +41,15 @@
    ArtifactId trackGitBranch(@PathParam("branch") BranchId branch, @HeaderParam(OSEE_ACCOUNT_ID) UserId account, @QueryParam("git-branch") String gitBranchName, @QueryParam("clone") boolean clone, String gitRepoUrl);
 
    @POST
-   @Path("{branch}/repo/{repository-name}")
+   @Path("{branch}/repo/{repositoryName}")
    @Consumes(MediaType.TEXT_PLAIN)
    @Produces(MediaType.APPLICATION_JSON)
-   ArtifactId updateGitTrackingBranch(@PathParam("branch") BranchId branch, @PathParam("repository-name") String repositoryName, @HeaderParam(OSEE_ACCOUNT_ID) UserId account, @QueryParam("fetch") boolean fetch, String gitBranchName);
+   ArtifactId updateGitTrackingBranch(@PathParam("branch") BranchId branch, @PathParam("repositoryName") String repositoryName, @HeaderParam(OSEE_ACCOUNT_ID) UserId account, @QueryParam("fetch") boolean fetch, String gitBranchName);
+
+   @GET
+   @Path("{branch}/repo/{repositoryName}/changeId/tags")
+   @Consumes(MediaType.TEXT_PLAIN)
+   @Produces(MediaType.APPLICATION_JSON)
+   List<String> getChangeIdBetweenTags(@PathParam("branch") BranchId branch, @PathParam("repositoryName") String repositoryName, @QueryParam("startTag") String startTag, @QueryParam("endTag") String endTag);
 
 }
\ No newline at end of file
diff --git a/plugins/org.eclipse.osee.define.api/src/org/eclipse/osee/define/api/GitOperations.java b/plugins/org.eclipse.osee.define.api/src/org/eclipse/osee/define/api/GitOperations.java
index f9e8095..2b42f51 100644
--- a/plugins/org.eclipse.osee.define.api/src/org/eclipse/osee/define/api/GitOperations.java
+++ b/plugins/org.eclipse.osee.define.api/src/org/eclipse/osee/define/api/GitOperations.java
@@ -13,6 +13,7 @@
 
 package org.eclipse.osee.define.api;
 
+import java.util.List;
 import org.eclipse.osee.framework.core.data.ArtifactId;
 import org.eclipse.osee.framework.core.data.ArtifactToken;
 import org.eclipse.osee.framework.core.data.BranchId;
@@ -39,4 +40,7 @@
    void fetch(ArtifactReadable repoArtifact, String password);
 
    ArtifactToken getCommitArtifactId(BranchId branch, String changeId);
+
+   List<String> getChangeIdBetweenTags(BranchId branch, ArtifactReadable repoArtifact, String startTag, String endTag);
+
 }
\ No newline at end of file
diff --git a/plugins/org.eclipse.osee.define.rest/src/org/eclipse/osee/define/rest/GitEndpointImpl.java b/plugins/org.eclipse.osee.define.rest/src/org/eclipse/osee/define/rest/GitEndpointImpl.java
index 7cdc6ce..6d2a79d 100644
--- a/plugins/org.eclipse.osee.define.rest/src/org/eclipse/osee/define/rest/GitEndpointImpl.java
+++ b/plugins/org.eclipse.osee.define.rest/src/org/eclipse/osee/define/rest/GitEndpointImpl.java
@@ -13,6 +13,7 @@
 
 package org.eclipse.osee.define.rest;
 
+import java.util.List;
 import org.eclipse.osee.activity.api.ActivityLog;
 import org.eclipse.osee.define.api.DefineApi;
 import org.eclipse.osee.define.api.GitEndpoint;
@@ -46,4 +47,10 @@
       return gitOps.updateGitTrackingBranch(branch, gitOps.getRepoArtifact(branch, repositoryName), account,
          gitBranchName, fetch, null, false);
    }
+
+   @Override
+   public List<String> getChangeIdBetweenTags(BranchId branch, String repositoryName, String startTag, String endTag) {
+      return gitOps.getChangeIdBetweenTags(branch, gitOps.getRepoArtifact(branch, repositoryName), startTag, endTag);
+   }
+
 }
\ No newline at end of file
diff --git a/plugins/org.eclipse.osee.define.rest/src/org/eclipse/osee/define/rest/GitOperationsImpl.java b/plugins/org.eclipse.osee.define.rest/src/org/eclipse/osee/define/rest/GitOperationsImpl.java
index 0abed8f..11eba79 100644
--- a/plugins/org.eclipse.osee.define.rest/src/org/eclipse/osee/define/rest/GitOperationsImpl.java
+++ b/plugins/org.eclipse.osee.define.rest/src/org/eclipse/osee/define/rest/GitOperationsImpl.java
@@ -52,6 +52,7 @@
 import org.eclipse.jgit.lib.ConfigConstants;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevSort;
@@ -158,7 +159,7 @@
    private void fetch(Repository localRepo, String passphrase) {
       try (Git git = new Git(localRepo)) {
 
-         FetchCommand fetchCommand = git.fetch().setCheckFetchedObjects(true).setTagOpt(TagOpt.NO_TAGS);
+         FetchCommand fetchCommand = git.fetch().setCheckFetchedObjects(true).setTagOpt(TagOpt.FETCH_TAGS);
 
          configurateAuthentication(localRepo, fetchCommand, passphrase);
 
@@ -268,6 +269,41 @@
       return repoArtifact;
    }
 
+   @Override
+   public List<String> getChangeIdBetweenTags(BranchId branch, ArtifactReadable repoArtifact, String startTag, String endTag) {
+
+      Repository jgitRepo = getLocalRepoReference(repoArtifact.getSoleAttributeValue(FileSystemPath));
+      /* fetch second arg (passPhrase) provide a key or password to enter repo. Here we have no pass phrase. */
+      fetch(jgitRepo, "");
+      try (Git git = new Git(jgitRepo)) {
+
+         Ref tag1 = git.getRepository().exactRef("refs/tags/" + startTag);
+         Ref tag2 = git.getRepository().exactRef("refs/tags/" + endTag);
+         Iterable<RevCommit> commits = git.log().addRange(tag1.getPeeledObjectId(), tag2.getPeeledObjectId()).call();
+
+         // parse through commits to get specific tags with specific commits and change ids
+         List<String> changeIdList = new ArrayList<>();
+         for (RevCommit revCommit : commits) {
+            if (revCommit.getShortMessage() != "") {
+               String commitSHA = revCommit.getId().name();
+
+               if (changeIdMatcher.reset(revCommit.getFullMessage()).find()) {
+                  String changeId = changeIdMatcher.group(1);
+                  changeIdList.add(changeId);
+               } else {
+                  changeIdList.add(commitSHA);
+               }
+
+            }
+         }
+
+         return changeIdList;
+
+      } catch (Exception ex) {
+         throw OseeCoreException.wrap(ex);
+      }
+   }
+
    public ArtifactReadable clone(String gitRepoUrl, BranchId branch, UserId account, String gitBranchName, boolean clone, String passphrase) {
       String serverDataLocation = systemPrefs.getValue(OseeClient.OSEE_APPLICATION_SERVER_DATA);
       String repoName = gitRepoUrl.substring(gitRepoUrl.lastIndexOf('/') + 1).replaceAll("\\.git$", "");
@@ -366,26 +402,24 @@
       }
    }
 
-   private ArtifactId parseGitCommit(ObjectReader objectReader, DiffFormatter df, ArtifactReadable repoArtifact, RevCommit revCommit, BranchId branch, UserId account, HistoryImportStrategy importStrategy) {
+   private ArtifactId createCommitArtifact(RevCommit revCommit, TransactionBuilder tx, BranchId branch) {
+
+      String commitSHA = revCommit.getId().name();
+
+      String commitId;
+      if (changeIdMatcher.reset(revCommit.getFullMessage()).find()) {
+         String changeId = changeIdMatcher.group(1);
+         commitId = changeId;
+
+      } else {
+         commitId = commitSHA;
+      }
+
       try {
-         TransactionBuilder tx = importStrategy.getTransactionBuilder(orcsApi, branch, account);
-
-         String commitSHA = revCommit.getId().name();
-
-         String commitId;
-         if (changeIdMatcher.reset(revCommit.getFullMessage()).find()) {
-            String changeId = changeIdMatcher.group(1);
-            commitId = changeId;
-
-            if (importStrategy.hasChangeIdAlredyImported(changeId)) {
-               return ArtifactId.SENTINEL;
-            }
-         } else {
-            commitId = commitSHA;
-         }
-
+         return queryFactory.fromBranch(branch).andIsOfType(CoreArtifactTypes.GitCommit).andNameEquals(
+            revCommit.getShortMessage()).asArtifact();
+      } catch (Exception ex) {
          ArtifactId commitArtifact = tx.createArtifact(GitCommit, revCommit.getShortMessage());
-
          tx.setSoleAttributeValue(commitArtifact, CoreAttributeTypes.GitCommitSha, commitSHA);
          tx.setSoleAttributeValue(commitArtifact, CoreAttributeTypes.UserArtifactId, SystemUser.OseeSystem); //TODO: this must convert author to the corresponding user artifact
          tx.setSoleAttributeValue(commitArtifact, CoreAttributeTypes.GitCommitAuthorDate,
@@ -393,9 +427,18 @@
          tx.setSoleAttributeValue(commitArtifact, CoreAttributeTypes.GitCommitMessage, revCommit.getFullMessage());
 
          tx.setSoleAttributeValue(commitArtifact, GitChangeId, commitId);
+         return commitArtifact;
 
-         importFileChanges(objectReader, df, repoArtifact, revCommit, commitSHA, commitArtifact, branch, tx,
-            importStrategy);
+      }
+   }
+
+   private ArtifactId parseGitCommit(ObjectReader objectReader, DiffFormatter df, ArtifactReadable repoArtifact, RevCommit revCommit, BranchId branch, UserId account, HistoryImportStrategy importStrategy) {
+      try {
+         TransactionBuilder tx = importStrategy.getTransactionBuilder(orcsApi, branch, account);
+
+         ArtifactId commitArtifact = createCommitArtifact(revCommit, tx, branch);
+         importFileChanges(objectReader, df, repoArtifact, revCommit, revCommit.getId().name(), commitArtifact, branch,
+            tx, importStrategy);
 
          importStrategy.finishGitCommit(tx);
          return commitArtifact;
diff --git a/plugins/org.eclipse.osee.framework.core/src/org/eclipse/osee/framework/core/enums/CoreAttributeTypes.java b/plugins/org.eclipse.osee.framework.core/src/org/eclipse/osee/framework/core/enums/CoreAttributeTypes.java
index 190c4d9..c02ead6 100644
--- a/plugins/org.eclipse.osee.framework.core/src/org/eclipse/osee/framework/core/enums/CoreAttributeTypes.java
+++ b/plugins/org.eclipse.osee.framework.core/src/org/eclipse/osee/framework/core/enums/CoreAttributeTypes.java
@@ -100,6 +100,7 @@
    FunctionalGroupingAttributeType FunctionalGrouping = osee.createEnum(new FunctionalGroupingAttributeType());
    AttributeTypeString GeneralStringData = osee.createStringNoTag(1152921504606847096L, "General String Data", MediaType.TEXT_PLAIN, "");
    GfeCfeAttributeType GfeCfe = osee.createEnum(new GfeCfeAttributeType());
+   AttributeTypeString GitBuildId = osee.createString(1714059195608838442L, "Git Build-Id", MediaType.TEXT_PLAIN, "Build-Id embedded in Git commit message that is intended to be immutable even during rebase and amending the commit");
    AttributeTypeString GitChangeId = osee.createString(1152921504606847702L, "Git Change-Id", MediaType.TEXT_PLAIN, "Change-Id embedded in Git commit message that is intended to be immutable even during rebase and amending the commit");
    AttributeTypeDate GitCommitAuthorDate = osee.createDate(1152921504606847704L, "Git Commit Author Date", MediaType.TEXT_PLAIN, "when this commit was originally made");
    AttributeTypeString GitCommitMessage = osee.createString(1152921504606847705L, "Git Commit Message", MediaType.TEXT_PLAIN, "Full message minus Change-Id");