From 89c88b127da20f9636f4b8e3700b2da78be36900 Mon Sep 17 00:00:00 2001 From: Christian Kurz Date: Wed, 16 Oct 2024 18:34:53 +0200 Subject: [PATCH 1/3] assignments outgoing --- .../gropius/sync/github/IssueMutation.graphql | 30 +++++- .../kotlin/gropius/sync/github/GithubSync.kt | 58 ++++++++++ .../kotlin/gropius/sync/github/IssuePile.kt | 2 +- .../main/kotlin/gropius/sync/jira/JiraSync.kt | 13 +++ .../main/kotlin/gropius/sync/AbstractSync.kt | 101 ++++++++++++++++++ 5 files changed, 202 insertions(+), 2 deletions(-) diff --git a/sync-github/src/main/graphql/gropius/sync/github/IssueMutation.graphql b/sync-github/src/main/graphql/gropius/sync/github/IssueMutation.graphql index cc501070..5c575d99 100644 --- a/sync-github/src/main/graphql/gropius/sync/github/IssueMutation.graphql +++ b/sync-github/src/main/graphql/gropius/sync/github/IssueMutation.graphql @@ -85,4 +85,32 @@ mutation MutateCreateIssue($id: ID!, $title: String!, $body: String!) { ...IssueDataExtensive } } -} \ No newline at end of file +} +mutation MutateAssignUser($id: ID!, $user: ID!) { + addAssigneesToAssignable(input:{assignableId: $id, assigneeIds: [$user]}){ + assignable { + ...on Issue { + timelineItems(itemTypes: [ASSIGNED_EVENT], last: 1) { + nodes { + ...TimelineItemData + } + } + } + } + clientMutationId + } +} +mutation MutateUnassignUser($id: ID!, $user: ID!) { + removeAssigneesFromAssignable(input:{assignableId: $id, assigneeIds: [$user]}){ + assignable { + ...on Issue { + timelineItems(itemTypes: [UNASSIGNED_EVENT], last: 1) { + nodes { + ...TimelineItemData + } + } + } + } + clientMutationId + } +} diff --git a/sync-github/src/main/kotlin/gropius/sync/github/GithubSync.kt b/sync-github/src/main/kotlin/gropius/sync/github/GithubSync.kt index b719eb11..5d11daf6 100644 --- a/sync-github/src/main/kotlin/gropius/sync/github/GithubSync.kt +++ b/sync-github/src/main/kotlin/gropius/sync/github/GithubSync.kt @@ -3,17 +3,22 @@ package gropius.sync.github import gropius.model.architecture.IMSProject import gropius.model.issue.Issue import gropius.model.issue.Label +import gropius.model.issue.timeline.Assignment import gropius.model.issue.timeline.IssueComment import gropius.model.template.IMSTemplate import gropius.model.template.IssueState +import gropius.model.user.GropiusUser +import gropius.model.user.IMSUser import gropius.model.user.User import gropius.sync.* import gropius.sync.github.config.IMSConfigManager import gropius.sync.github.config.IMSProjectConfig import gropius.sync.github.generated.* import gropius.sync.github.generated.MutateAddLabelMutation.Data.AddLabelsToLabelable.Labelable.Companion.asIssue +import gropius.sync.github.generated.MutateAssignUserMutation.Data.AddAssigneesToAssignable.Assignable.Companion.asIssue import gropius.sync.github.generated.MutateCreateCommentMutation.Data.AddComment.CommentEdge.Node.Companion.asIssueTimelineItems import gropius.sync.github.generated.MutateRemoveLabelMutation.Data.RemoveLabelsFromLabelable.Labelable.Companion.asIssue +import gropius.sync.github.generated.MutateUnassignUserMutation.Data.RemoveAssigneesFromAssignable.Assignable.Companion.asIssue import gropius.sync.github.generated.fragment.TimelineItemData.Companion.asNode import org.slf4j.LoggerFactory import org.springframework.stereotype.Component @@ -172,6 +177,59 @@ final class GithubSync( return null } + override suspend fun syncSingleAssigned( + imsProject: IMSProject, issueId: String, assignment: Assignment, users: List + ): TimelineItemConversionInformation? { + val assignedUser = assignment.user().value + val imsUsers = + if (assignedUser as? IMSUser != null) listOf(assignedUser) else if (assignedUser as? GropiusUser != null) assignedUser.imsUsers() + .filter { it.ims().value == imsProject.ims().value } else emptyList() + val ids = imsUsers.map { + it.templatedFields["github_id"]!! + } + if (ids.isEmpty()) { + return null + } + val response = githubDataService.mutation( + imsProject, users, MutateAssignUserMutation(issueId, ids.first()), gropiusUserList(users) + ).second + val item = + response.data?.addAssigneesToAssignable?.assignable?.asIssue()?.timelineItems?.nodes?.lastOrNull()?.asNode() + if (item != null) { + return TODOTimelineItemConversionInformation(imsProject.rawId!!, item.id) + } + logger.error("${response.data} ${response.errors}") + //TODO("ERROR HANDLING") + return null + } + + override suspend fun syncSingleUnassigned( + imsProject: IMSProject, issueId: String, assignment: Assignment, users: List + ): TimelineItemConversionInformation? { + val assignedUser = assignment.user().value + val imsUsers = + if (assignedUser as? IMSUser != null) listOf(assignedUser) else if (assignedUser as? GropiusUser != null) assignedUser.imsUsers() + .filter { it.ims().value == imsProject.ims().value } else emptyList() + val ids = imsUsers.map { + it.templatedFields["github_id"]!! + } + if (ids.isEmpty()) { + return null + } + val response = githubDataService.mutation( + imsProject, users, MutateUnassignUserMutation(issueId, ids.first()), gropiusUserList(users) + ).second + val item = + response.data?.removeAssigneesFromAssignable?.assignable?.asIssue()?.timelineItems?.nodes?.lastOrNull() + ?.asNode() + if (item != null) { + return TODOTimelineItemConversionInformation(imsProject.rawId!!, item.id) + } + logger.error("${response.data} ${response.errors}") + //TODO("ERROR HANDLING") + return null + } + override suspend fun syncAddedLabel( imsProject: IMSProject, issueId: String, label: Label, users: List ): TimelineItemConversionInformation? { diff --git a/sync-github/src/main/kotlin/gropius/sync/github/IssuePile.kt b/sync-github/src/main/kotlin/gropius/sync/github/IssuePile.kt index 56494c47..ce5f6655 100644 --- a/sync-github/src/main/kotlin/gropius/sync/github/IssuePile.kt +++ b/sync-github/src/main/kotlin/gropius/sync/github/IssuePile.kt @@ -540,7 +540,7 @@ class UnassignedTimelineItem( gropiusId ) else RemovedAssignmentEvent(createdAt, createdAt) val opposite = issue.timelineItems().filterIsInstance().sortedBy { it.createdAt } - .lastOrNull { it.user().value.username == user } + .lastOrNull { it.user().value.username == user } // TODO: Catch multiple if ((event == null) || (opposite == null)) { return listOf() to convInfo; } diff --git a/sync-jira/src/main/kotlin/gropius/sync/jira/JiraSync.kt b/sync-jira/src/main/kotlin/gropius/sync/jira/JiraSync.kt index 9397124e..904c181f 100644 --- a/sync-jira/src/main/kotlin/gropius/sync/jira/JiraSync.kt +++ b/sync-jira/src/main/kotlin/gropius/sync/jira/JiraSync.kt @@ -3,6 +3,7 @@ package gropius.sync.jira import gropius.model.architecture.IMSProject import gropius.model.issue.Issue import gropius.model.issue.Label +import gropius.model.issue.timeline.Assignment import gropius.model.issue.timeline.IssueComment import gropius.model.template.IMSTemplate import gropius.model.template.IssueState @@ -301,6 +302,18 @@ final class JiraSync( return issueDataService.findByImsProject(imsProject.rawId!!) } + override suspend fun syncSingleAssigned( + imsProject: IMSProject, issueId: String, assignment: Assignment, users: List + ): TimelineItemConversionInformation? { + TODO() + } + + override suspend fun syncSingleUnassigned( + imsProject: IMSProject, issueId: String, assignment: Assignment, users: List + ): TimelineItemConversionInformation? { + TODO() + } + override suspend fun syncComment( imsProject: IMSProject, issueId: String, issueComment: IssueComment, users: List ): TimelineItemConversionInformation? { diff --git a/sync/src/main/kotlin/gropius/sync/AbstractSync.kt b/sync/src/main/kotlin/gropius/sync/AbstractSync.kt index 79779413..9fb0ac11 100644 --- a/sync/src/main/kotlin/gropius/sync/AbstractSync.kt +++ b/sync/src/main/kotlin/gropius/sync/AbstractSync.kt @@ -174,6 +174,30 @@ abstract class AbstractSync( imsProject: IMSProject, issueId: String, label: Label, users: List ): TimelineItemConversionInformation? + /** + * Incorporate an added assignment + * @param imsProject IMS project to sync + * @param issueId GitHub ID of the issue + * @param assignment Assignment to sync + * @param users List of users involved in this timeline item, sorted with most relevant first + * @return Conversion information + */ + abstract suspend fun syncSingleAssigned( + imsProject: IMSProject, issueId: String, assignment: Assignment, users: List + ): TimelineItemConversionInformation? + + /** + * Incorporate a removed assignment + * @param imsProject IMS project to sync + * @param issueId GitHub ID of the issue + * @param assignment Assignment to sync + * @param users List of users involved in this timeline item, sorted with most relevant first + * @return Conversion information + */ + abstract suspend fun syncSingleUnassigned( + imsProject: IMSProject, issueId: String, assignment: Assignment, users: List + ): TimelineItemConversionInformation? + /** * Create an issue on the IMS * @param imsProject IMS project to sync @@ -882,10 +906,87 @@ abstract class AbstractSync( * @param timeline Timeline of the issue * @param imsProject IMS project to sync * @param issueInfo Issue to sync + * @param label Label to sync + * @param virtualIDs mapping for timeline items that are geerated with generated ids and do not exist in the database */ private suspend fun syncOutgoingAssignments( timeline: List, imsProject: IMSProject, issueInfo: IssueConversionInformation ) { + val labelStateMap = this.labelStateMap(imsProject) + val stateLabelMap = labelStateMap.map { it.value to it.key }.toMap() + val virtualIDs = mutableMapOf() + + val modifiedTimeline = + timeline.filterIsInstance() + timeline.filterIsInstance() + val groups = modifiedTimeline.groupBy { + when (it) { + is Assignment -> it + is RemovedAssignmentEvent -> it.removedAssignment().value!! + else -> throw IllegalStateException("Kotlin Generator Defective") + } + } + for ((assignment, relevantTimeline) in groups) { + syncOutgoingSingleAssignment( + relevantTimeline.sortedBy { it.createdAt }, imsProject, issueInfo, assignment, virtualIDs + ) + } + } + + /** + * Sync Outgoing Assignments + * @param timeline Timeline of the issue + * @param imsProject IMS project to sync + * @param issueInfo Issue to sync + */ + private suspend fun syncOutgoingSingleAssignment( + relevantTimeline: List, + imsProject: IMSProject, + issueInfo: IssueConversionInformation, + assignment: Assignment, + virtualIDs: Map + ) { + var labelIsSynced = false + val finalBlock = findFinalTypeBlock(relevantTimeline) + for (item in finalBlock) { + val relevantEvent = collectedSyncInfo.timelineItemConversionInformationService.findByImsProjectAndGropiusId( + imsProject.rawId!!, item.rawId ?: virtualIDs[item]!! + ) + if (relevantEvent?.githubId != null) { + labelIsSynced = true + } + } + if (!labelIsSynced) { + if (shouldSyncType( + imsProject, finalBlock, relevantTimeline, true, virtualIDs + ) + ) { + val conversionInformation = syncSingleUnassigned(imsProject, + issueInfo.githubId, + assignment!!, + finalBlock.map { it.lastModifiedBy().value }) + if (conversionInformation != null) { + conversionInformation.gropiusId = finalBlock.map { it.rawId ?: virtualIDs[it]!! }.first() + collectedSyncInfo.timelineItemConversionInformationService.save( + conversionInformation + ).awaitSingle() + } + } + if (shouldSyncType( + imsProject, finalBlock, relevantTimeline, false, virtualIDs + ) + ) { + val conversionInformation = syncSingleAssigned(imsProject, + issueInfo.githubId, + assignment!!, + finalBlock.map { it.lastModifiedBy().value }) + if (conversionInformation != null) { + conversionInformation.gropiusId = finalBlock.map { it.rawId ?: virtualIDs[it]!! }.first() + collectedSyncInfo.timelineItemConversionInformationService.save( + conversionInformation + ).awaitSingle() + } + } + } } /** From e651d7ba7007aed5a73f473d649f2b987df91802 Mon Sep 17 00:00:00 2001 From: Christian Kurz Date: Wed, 16 Oct 2024 19:01:23 +0200 Subject: [PATCH 2/3] jira assignee --- .../main/kotlin/gropius/sync/jira/JiraSync.kt | 65 +++++++++++++++++-- 1 file changed, 59 insertions(+), 6 deletions(-) diff --git a/sync-jira/src/main/kotlin/gropius/sync/jira/JiraSync.kt b/sync-jira/src/main/kotlin/gropius/sync/jira/JiraSync.kt index 904c181f..3a0140a5 100644 --- a/sync-jira/src/main/kotlin/gropius/sync/jira/JiraSync.kt +++ b/sync-jira/src/main/kotlin/gropius/sync/jira/JiraSync.kt @@ -7,6 +7,8 @@ import gropius.model.issue.timeline.Assignment import gropius.model.issue.timeline.IssueComment import gropius.model.template.IMSTemplate import gropius.model.template.IssueState +import gropius.model.user.GropiusUser +import gropius.model.user.IMSUser import gropius.model.user.User import gropius.sync.* import gropius.sync.jira.config.IMSConfig @@ -18,10 +20,7 @@ import io.ktor.client.plugins.* import io.ktor.client.statement.* import io.ktor.http.* import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.* import org.slf4j.LoggerFactory import org.springframework.stereotype.Component import java.time.OffsetDateTime @@ -305,13 +304,67 @@ final class JiraSync( override suspend fun syncSingleAssigned( imsProject: IMSProject, issueId: String, assignment: Assignment, users: List ): TimelineItemConversionInformation? { - TODO() + val assignedUser = assignment.user().value + val imsUsers = + if (assignedUser as? IMSUser != null) listOf(assignedUser) else if (assignedUser as? GropiusUser != null) assignedUser.imsUsers() + .filter { it.ims().value == imsProject.ims().value } else emptyList() + val ids = imsUsers.map { it.username } + if (ids.isEmpty()) { + return null + } + val response = jiraDataService.request( + imsProject, users, HttpMethod.Put, gropiusUserList(users), JsonObject( + mapOf( + "fields" to JsonObject( + mapOf( + "assignee" to JsonObject( + mapOf( + "name" to JsonPrimitive(ids.first()) + ) + ) + ) + ) + ) + ) + ) { + appendPathSegments("issue") + appendPathSegments(issueId) + parameters.append("returnIssue", "true") + parameters.append("expand", "names,schema,editmeta,changelog") + + } + val changelogEntry = response.second.body().changelog.histories.lastOrNull() + return JiraTimelineItemConversionInformation( + imsProject.rawId!!, + if (changelogEntry?.items?.singleOrNull()?.field == "assignee") changelogEntry.id else "" + ) } override suspend fun syncSingleUnassigned( imsProject: IMSProject, issueId: String, assignment: Assignment, users: List ): TimelineItemConversionInformation? { - TODO() + val response = jiraDataService.request( + imsProject, users, HttpMethod.Put, gropiusUserList(users), JsonObject( + mapOf( + "fields" to JsonObject( + mapOf( + "assignee" to JsonNull + ) + ) + ) + ) + ) { + appendPathSegments("issue") + appendPathSegments(issueId) + parameters.append("returnIssue", "true") + parameters.append("expand", "names,schema,editmeta,changelog") + + } + val changelogEntry = response.second.body().changelog.histories.lastOrNull() + return JiraTimelineItemConversionInformation( + imsProject.rawId!!, + if (changelogEntry?.items?.singleOrNull()?.field == "assignee") changelogEntry.id else "" + ) } override suspend fun syncComment( From 648df4128aff07441d6e95bcf5275d6f658008cb Mon Sep 17 00:00:00 2001 From: Christian Kurz Date: Wed, 16 Oct 2024 21:40:47 +0200 Subject: [PATCH 3/3] rename label remains & dokka --- sync/src/main/kotlin/gropius/sync/AbstractSync.kt | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/sync/src/main/kotlin/gropius/sync/AbstractSync.kt b/sync/src/main/kotlin/gropius/sync/AbstractSync.kt index 9fb0ac11..3700339f 100644 --- a/sync/src/main/kotlin/gropius/sync/AbstractSync.kt +++ b/sync/src/main/kotlin/gropius/sync/AbstractSync.kt @@ -906,14 +906,10 @@ abstract class AbstractSync( * @param timeline Timeline of the issue * @param imsProject IMS project to sync * @param issueInfo Issue to sync - * @param label Label to sync - * @param virtualIDs mapping for timeline items that are geerated with generated ids and do not exist in the database */ private suspend fun syncOutgoingAssignments( timeline: List, imsProject: IMSProject, issueInfo: IssueConversionInformation ) { - val labelStateMap = this.labelStateMap(imsProject) - val stateLabelMap = labelStateMap.map { it.value to it.key }.toMap() val virtualIDs = mutableMapOf() val modifiedTimeline = @@ -934,9 +930,11 @@ abstract class AbstractSync( /** * Sync Outgoing Assignments - * @param timeline Timeline of the issue + * @param relevantTimeline Timeline of the issue * @param imsProject IMS project to sync * @param issueInfo Issue to sync + * @param assignment Assignment to sync + * @param virtualIDs mapping for timeline items that are geerated with generated ids and do not exist in the database */ private suspend fun syncOutgoingSingleAssignment( relevantTimeline: List, @@ -945,17 +943,17 @@ abstract class AbstractSync( assignment: Assignment, virtualIDs: Map ) { - var labelIsSynced = false + var assignmentIsSynced = false val finalBlock = findFinalTypeBlock(relevantTimeline) for (item in finalBlock) { val relevantEvent = collectedSyncInfo.timelineItemConversionInformationService.findByImsProjectAndGropiusId( imsProject.rawId!!, item.rawId ?: virtualIDs[item]!! ) if (relevantEvent?.githubId != null) { - labelIsSynced = true + assignmentIsSynced = true } } - if (!labelIsSynced) { + if (!assignmentIsSynced) { if (shouldSyncType( imsProject, finalBlock, relevantTimeline, true, virtualIDs )