From d910818fefe2e5cd7266ce51b1b532202f68ea20 Mon Sep 17 00:00:00 2001 From: Christian Kurz Date: Fri, 27 Sep 2024 01:26:05 +0200 Subject: [PATCH 01/14] initial issue type by name --- .../src/main/kotlin/gropius/sync/jira/JiraDataService.kt | 6 +++++- .../src/main/kotlin/gropius/sync/jira/model/IssueData.kt | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/sync-jira/src/main/kotlin/gropius/sync/jira/JiraDataService.kt b/sync-jira/src/main/kotlin/gropius/sync/jira/JiraDataService.kt index 4d9d5aad..6af02ca3 100644 --- a/sync-jira/src/main/kotlin/gropius/sync/jira/JiraDataService.kt +++ b/sync-jira/src/main/kotlin/gropius/sync/jira/JiraDataService.kt @@ -109,9 +109,13 @@ class JiraDataService( * @param imsProject The IMSProject to work with * @return the IssueType */ - suspend fun issueType(imsProject: IMSProject): IssueType { + suspend fun issueType(imsProject: IMSProject, name: String): IssueType { val template = issueTemplate(imsProject) val imsProjectConfig = IMSProjectConfig(helper, imsProject) + val namedType = template.issueTypes().firstOrNull { it.name == name } + if (namedType != null) { + return namedType + } if (imsProjectConfig.defaultType != null) { val type = neoOperations.findById(imsProjectConfig.defaultType) if ((type != null) && (type.partOf().contains(template))) { diff --git a/sync-jira/src/main/kotlin/gropius/sync/jira/model/IssueData.kt b/sync-jira/src/main/kotlin/gropius/sync/jira/model/IssueData.kt index 35ecf263..f49183ee 100644 --- a/sync-jira/src/main/kotlin/gropius/sync/jira/model/IssueData.kt +++ b/sync-jira/src/main/kotlin/gropius/sync/jira/model/IssueData.kt @@ -428,7 +428,8 @@ data class IssueData( issue.state().value = jiraService.issueState(imsProject, null, true) issue.template().value = jiraService.issueTemplate(imsProject) issue.trackables() += jiraService.neoOperations.findAll(Project::class.java).awaitFirst() - issue.type().value = jiraService.issueType(imsProject) + issue.type().value = + jiraService.issueType(imsProject, fields["issuetype"]?.jsonObject?.get("name")?.jsonPrimitive?.content!!) return issue } } From 3e18df9496301ae25d7b0903b1c05ea1dd6c4fca Mon Sep 17 00:00:00 2001 From: Christian Kurz Date: Fri, 27 Sep 2024 01:42:27 +0200 Subject: [PATCH 02/14] fix pipeline error --- sync-jira/src/main/kotlin/gropius/sync/jira/JiraSync.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 885f0a16..89b1b4bd 100644 --- a/sync-jira/src/main/kotlin/gropius/sync/jira/JiraSync.kt +++ b/sync-jira/src/main/kotlin/gropius/sync/jira/JiraSync.kt @@ -129,7 +129,7 @@ final class JiraSync( override suspend fun fetchData(imsProjects: List) { for (imsProject in imsProjects) { jiraDataService.issueTemplate(imsProject) - jiraDataService.issueType(imsProject) + jiraDataService.issueType(imsProject, "") jiraDataService.issueState(imsProject, null, true) jiraDataService.issueState(imsProject, null, false) } From bcf5a8e21d64661cd0661374b4a64a4840dd173c Mon Sep 17 00:00:00 2001 From: Christian Kurz Date: Wed, 9 Oct 2024 17:01:57 +0200 Subject: [PATCH 03/14] outgoing templated fields --- .../kotlin/gropius/sync/github/GithubSync.kt | 11 ++++ .../main/kotlin/gropius/sync/jira/JiraSync.kt | 33 ++++++++++++ .../sync/jira/config/IMSProjectConfig.kt | 5 ++ .../main/kotlin/gropius/sync/AbstractSync.kt | 53 +++++++++++++++++++ 4 files changed, 102 insertions(+) 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..87caa27c 100644 --- a/sync-github/src/main/kotlin/gropius/sync/github/GithubSync.kt +++ b/sync-github/src/main/kotlin/gropius/sync/github/GithubSync.kt @@ -4,6 +4,7 @@ import gropius.model.architecture.IMSProject import gropius.model.issue.Issue import gropius.model.issue.Label import gropius.model.issue.timeline.IssueComment +import gropius.model.issue.timeline.TemplatedFieldChangedEvent import gropius.model.template.IMSTemplate import gropius.model.template.IssueState import gropius.model.user.User @@ -80,6 +81,10 @@ final class GithubSync( return imsProjectConfig.enableOutgoingComments } + override suspend fun isOutgoingTemplatedFieldsEnabled(imsProject: IMSProject): Boolean { + return false + } + override suspend fun isOutgoingTitleChangedEnabled(imsProject: IMSProject): Boolean { val imsProjectConfig = IMSProjectConfig(helper, imsProject) return imsProjectConfig.enableOutgoingTitleChanges @@ -209,6 +214,12 @@ final class GithubSync( return null } + override suspend fun syncTemplatedField( + imsProject: IMSProject, issueId: String, fieldChangedEvent: TemplatedFieldChangedEvent, users: List + ): TimelineItemConversionInformation? { + TODO("Remove this, as soon as the fallback is done in AbstractSync") + } + override suspend fun syncStateChange( imsProject: IMSProject, issueId: String, newState: IssueState, users: List ): TimelineItemConversionInformation? { 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 e50cbfde..a6d87c69 100644 --- a/sync-jira/src/main/kotlin/gropius/sync/jira/JiraSync.kt +++ b/sync-jira/src/main/kotlin/gropius/sync/jira/JiraSync.kt @@ -4,6 +4,7 @@ import gropius.model.architecture.IMSProject import gropius.model.issue.Issue import gropius.model.issue.Label import gropius.model.issue.timeline.IssueComment +import gropius.model.issue.timeline.TemplatedFieldChangedEvent import gropius.model.template.IMSTemplate import gropius.model.template.IssueState import gropius.model.user.User @@ -126,6 +127,11 @@ final class JiraSync( return imsProjectConfig.enableOutgoingState } + override suspend fun isOutgoingTemplatedFieldsEnabled(imsProject: IMSProject): Boolean { + val imsProjectConfig = IMSProjectConfig(helper, imsProject) + return imsProjectConfig.enableOutgoingTemplatedFields + } + override suspend fun fetchData(imsProjects: List) { for (imsProject in imsProjects) { jiraDataService.issueTemplate(imsProject) @@ -348,6 +354,33 @@ final class JiraSync( ) } + override suspend fun syncTemplatedField( + imsProject: IMSProject, issueId: String, fieldChangedEvent: TemplatedFieldChangedEvent, users: List + ): TimelineItemConversionInformation? { + val response = jiraDataService.request( + imsProject, users, HttpMethod.Put, gropiusUserList(users), JsonObject( + mapOf( + "fields" to JsonObject( + mapOf( + fieldChangedEvent.fieldName to JsonPrimitive(fieldChangedEvent.newValue) + ) + ) + ) + ) + ) { + 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 == fieldChangedEvent.fieldName) changelogEntry.id else "" + ) + } + override suspend fun syncStateChange( imsProject: IMSProject, issueId: String, newState: IssueState, users: List ): TimelineItemConversionInformation? { diff --git a/sync-jira/src/main/kotlin/gropius/sync/jira/config/IMSProjectConfig.kt b/sync-jira/src/main/kotlin/gropius/sync/jira/config/IMSProjectConfig.kt index a27406c7..77548009 100644 --- a/sync-jira/src/main/kotlin/gropius/sync/jira/config/IMSProjectConfig.kt +++ b/sync-jira/src/main/kotlin/gropius/sync/jira/config/IMSProjectConfig.kt @@ -21,6 +21,7 @@ data class IMSProjectConfig( val enableOutgoingAssignments: Boolean, val enableOutgoingTitleChanges: Boolean, val enableOutgoingState: Boolean, + val enableOutgoingTemplatedFields: Boolean, val defaultType: String?, val defaultTemplate: String? ) { @@ -40,6 +41,7 @@ data class IMSProjectConfig( enableOutgoingAssignments = helper.parseBoolean(imsProject.templatedFields["enable-outgoing-assignments"]), enableOutgoingTitleChanges = helper.parseBoolean(imsProject.templatedFields["enable-outgoing-title-changes"]), enableOutgoingState = helper.parseBoolean(imsProject.templatedFields["enable-outgoing-state"]), + enableOutgoingTemplatedFields = helper.parseBoolean(imsProject.templatedFields["enable-outgoing-templated-fields"]), defaultType = helper.parseString(imsProject.templatedFields["default-type"]), defaultTemplate = helper.parseString(imsProject.templatedFields["default-template"]) ) @@ -85,6 +87,9 @@ data class IMSProjectConfig( }.toString(), "enable-outgoing-state" to obj { "nullable" to true "type" to "boolean" + }.toString(), "enable-outgoing-templated-fields" to obj { + "nullable" to true + "type" to "boolean" }.toString()) + IMSConfigManager.COMMON_TEMPLATE_FIELDS } } diff --git a/sync/src/main/kotlin/gropius/sync/AbstractSync.kt b/sync/src/main/kotlin/gropius/sync/AbstractSync.kt index c2e48c48..37836408 100644 --- a/sync/src/main/kotlin/gropius/sync/AbstractSync.kt +++ b/sync/src/main/kotlin/gropius/sync/AbstractSync.kt @@ -150,6 +150,18 @@ abstract class AbstractSync( imsProject: IMSProject, issueId: String, newState: IssueState, users: List ): TimelineItemConversionInformation? + /** + * Incorporate a templated field change + * @param imsProject IMS project to sync + * @param issueId GitHub ID of the issue + * @param fieldChangedEvent Event describing the field change + * @param users List of users involved in this timeline item, sorted with most relevant first + * @return Conversion information + */ + abstract suspend fun syncTemplatedField( + imsProject: IMSProject, issueId: String, fieldChangedEvent: TemplatedFieldChangedEvent, users: List + ): TimelineItemConversionInformation? + /** * Incorporate an added label * @param imsProject IMS project to sync @@ -196,6 +208,13 @@ abstract class AbstractSync( */ abstract suspend fun isOutgoingLabelsEnabled(imsProject: IMSProject): Boolean + /** + * Check if Outgoing Sync of TemplatedFields is Enabled + * @param imsProject IMS project to check for + * @return true if and only if outgoing sync of templatedFields is enabled + */ + abstract suspend fun isOutgoingTemplatedFieldsEnabled(imsProject: IMSProject): Boolean + /** * Check if Outgoing Sync of Comments is Enabled * @param imsProject IMS project to check for @@ -660,6 +679,9 @@ abstract class AbstractSync( if (isOutgoingAssignmentsEnabled(imsProject)) { syncOutgoingAssignments(timeline, imsProject, issueInfo) } + if (isOutgoingTemplatedFieldsEnabled(imsProject)) { + syncOutgoingTemplatedFields(timeline, imsProject, issueInfo) + } if (isOutgoingStatesEnabled(imsProject)) { syncOutgoingStateChanges(timeline, imsProject, issueInfo) } @@ -844,6 +866,37 @@ abstract class AbstractSync( } } + /** + * Sync Outgoing TemplatedFields Changes + * @param timeline Timeline of the issue + * @param imsProject IMS project to sync + * @param issueInfo Issue to sync + */ + private suspend fun syncOutgoingTemplatedFields( + timeline: List, imsProject: IMSProject, issueInfo: IssueConversionInformation + ) { + val virtualIDs = mapOf()//For future features + val relevantTimeline = timeline.mapNotNull { it as? TemplatedFieldChangedEvent } + if (relevantTimeline.isEmpty()) return + val finalBlock = findFinalBlock(relevantTimeline) { it.fieldName to it.newValue } + if (finalBlock.none { + collectedSyncInfo.timelineItemConversionInformationService.findByImsProjectAndGropiusId( + imsProject.rawId!!, it.rawId!! + ) != null + }) { + val conversionInformation = syncTemplatedField(imsProject, + issueInfo.githubId, + finalBlock.first(), + finalBlock.map { it.createdBy().value }) + if (conversionInformation != null) { + conversionInformation.gropiusId = finalBlock.map { it.rawId ?: virtualIDs[it]!! }.first() + collectedSyncInfo.timelineItemConversionInformationService.save( + conversionInformation + ).awaitSingle() + } + } + } + /** * Sync Outgoing State Changes * @param timeline Timeline of the issue From f8539974e6efa070a5040e14b31fabfedaa274d5 Mon Sep 17 00:00:00 2001 From: Christian Kurz Date: Thu, 10 Oct 2024 18:01:43 +0200 Subject: [PATCH 04/14] fallback comments --- .../kotlin/gropius/sync/github/GithubSync.kt | 23 +++++--- .../main/kotlin/gropius/sync/jira/JiraSync.kt | 19 ++++++ .../main/kotlin/gropius/sync/AbstractSync.kt | 58 ++++++++++++++----- .../main/kotlin/gropius/sync/TokenManager.kt | 6 +- 4 files changed, 85 insertions(+), 21 deletions(-) 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 87caa27c..55ed236e 100644 --- a/sync-github/src/main/kotlin/gropius/sync/github/GithubSync.kt +++ b/sync-github/src/main/kotlin/gropius/sync/github/GithubSync.kt @@ -4,7 +4,7 @@ import gropius.model.architecture.IMSProject import gropius.model.issue.Issue import gropius.model.issue.Label import gropius.model.issue.timeline.IssueComment -import gropius.model.issue.timeline.TemplatedFieldChangedEvent +import gropius.model.issue.timeline.TimelineItem import gropius.model.template.IMSTemplate import gropius.model.template.IssueState import gropius.model.user.User @@ -177,6 +177,21 @@ final class GithubSync( return null } + override suspend fun syncFallbackComment( + imsProject: IMSProject, issueId: String, comment: String, original: TimelineItem?, users: List + ): TimelineItemConversionInformation? { + val response = githubDataService.mutation( + imsProject, users, MutateCreateCommentMutation(issueId, comment), gropiusUserList(users) + ).second + val item = response.data?.addComment?.commentEdge?.node?.asIssueTimelineItems() + 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? { @@ -214,12 +229,6 @@ final class GithubSync( return null } - override suspend fun syncTemplatedField( - imsProject: IMSProject, issueId: String, fieldChangedEvent: TemplatedFieldChangedEvent, users: List - ): TimelineItemConversionInformation? { - TODO("Remove this, as soon as the fallback is done in AbstractSync") - } - override suspend fun syncStateChange( imsProject: IMSProject, issueId: String, newState: IssueState, users: List ): TimelineItemConversionInformation? { 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 f1b45e3e..29b64cf0 100644 --- a/sync-jira/src/main/kotlin/gropius/sync/jira/JiraSync.kt +++ b/sync-jira/src/main/kotlin/gropius/sync/jira/JiraSync.kt @@ -5,6 +5,7 @@ import gropius.model.issue.Issue import gropius.model.issue.Label import gropius.model.issue.timeline.IssueComment import gropius.model.issue.timeline.TemplatedFieldChangedEvent +import gropius.model.issue.timeline.TimelineItem import gropius.model.template.IMSTemplate import gropius.model.template.IssueState import gropius.model.user.User @@ -328,6 +329,24 @@ final class JiraSync( return JiraTimelineItemConversionInformation(imsProject.rawId!!, iid) } + override suspend fun syncFallbackComment( + imsProject: IMSProject, issueId: String, comment: String, original: TimelineItem?, users: List + ): TimelineItemConversionInformation? { + val response = jiraDataService.request( + imsProject, + users, + HttpMethod.Post, + gropiusUserList(users), + JsonObject(mapOf("body" to JsonPrimitive(comment))) + ) { + appendPathSegments("issue") + appendPathSegments(issueId) + appendPathSegments("comment") + }.second.body() + val iid = response["id"]!!.jsonPrimitive.content + return JiraTimelineItemConversionInformation(imsProject.rawId!!, iid) + } + override suspend fun syncTitleChange( imsProject: IMSProject, issueId: String, newTitle: String, users: List ): TimelineItemConversionInformation? { diff --git a/sync/src/main/kotlin/gropius/sync/AbstractSync.kt b/sync/src/main/kotlin/gropius/sync/AbstractSync.kt index 37836408..a443af10 100644 --- a/sync/src/main/kotlin/gropius/sync/AbstractSync.kt +++ b/sync/src/main/kotlin/gropius/sync/AbstractSync.kt @@ -122,8 +122,22 @@ abstract class AbstractSync( * @param users List of users involved in this timeline item, sorted with most relevant first * @return Conversion information */ - abstract suspend fun syncComment( + open suspend fun syncComment( imsProject: IMSProject, issueId: String, issueComment: IssueComment, users: List + ): TimelineItemConversionInformation? { + return syncFallbackComment(imsProject, issueId, issueComment.body, issueComment, users) + } + + /** + * Incorporate a fallback comment + * @param imsProject IMS project to sync + * @param issueId GitHub ID of the issue + * @param issueComment Comment to sync + * @param users List of users involved in this timeline item, sorted with most relevant first + * @return Conversion information + */ + abstract suspend fun syncFallbackComment( + imsProject: IMSProject, issueId: String, comment: String, original: TimelineItem?, users: List ): TimelineItemConversionInformation? /** @@ -134,9 +148,11 @@ abstract class AbstractSync( * @param users List of users involved in this timeline item, sorted with most relevant first * @return Conversion information */ - abstract suspend fun syncTitleChange( + open suspend fun syncTitleChange( imsProject: IMSProject, issueId: String, newTitle: String, users: List - ): TimelineItemConversionInformation? + ): TimelineItemConversionInformation? { + return syncFallbackComment(imsProject, issueId, "Gropius Title changed to $newTitle", null, users) + } /** * Incorporate a state change @@ -158,9 +174,17 @@ abstract class AbstractSync( * @param users List of users involved in this timeline item, sorted with most relevant first * @return Conversion information */ - abstract suspend fun syncTemplatedField( + open suspend fun syncTemplatedField( imsProject: IMSProject, issueId: String, fieldChangedEvent: TemplatedFieldChangedEvent, users: List - ): TimelineItemConversionInformation? + ): TimelineItemConversionInformation? { + return syncFallbackComment( + imsProject, + issueId, + "Gropius Field ${fieldChangedEvent.fieldName} changed to ${fieldChangedEvent.newValue}", + fieldChangedEvent, + users + ) + } /** * Incorporate an added label @@ -170,9 +194,11 @@ abstract class AbstractSync( * @param users List of users involved in this timeline item, sorted with most relevant first * @return Conversion information */ - abstract suspend fun syncAddedLabel( + open suspend fun syncAddedLabel( imsProject: IMSProject, issueId: String, label: Label, users: List - ): TimelineItemConversionInformation? + ): TimelineItemConversionInformation? { + return syncFallbackComment(imsProject, issueId, "Gropius Label ${label.name} added", null, users) + } /** * Incorporate a removed label @@ -182,9 +208,11 @@ abstract class AbstractSync( * @param users List of users involved in this timeline item, sorted with most relevant first * @return Conversion information */ - abstract suspend fun syncRemovedLabel( + open suspend fun syncRemovedLabel( imsProject: IMSProject, issueId: String, label: Label, users: List - ): TimelineItemConversionInformation? + ): TimelineItemConversionInformation? { + return syncFallbackComment(imsProject, issueId, "Gropius Label ${label.name} removed", null, users) + } /** * Create an issue on the IMS @@ -779,7 +807,8 @@ abstract class AbstractSync( imsProject, finalBlock, relevantTimeline, true, virtualIDs ) ) { - val conversionInformation = syncRemovedLabel(imsProject, + val conversionInformation = syncRemovedLabel( + imsProject, issueInfo.githubId, label!!, finalBlock.map { it.lastModifiedBy().value }) @@ -794,7 +823,8 @@ abstract class AbstractSync( imsProject, finalBlock, relevantTimeline, false, virtualIDs ) ) { - val conversionInformation = syncAddedLabel(imsProject, + val conversionInformation = syncAddedLabel( + imsProject, issueInfo.githubId, label!!, finalBlock.map { it.lastModifiedBy().value }) @@ -853,7 +883,8 @@ abstract class AbstractSync( imsProject.rawId!!, it.rawId!! ) != null }) { - val conversionInformation = syncTitleChange(imsProject, + val conversionInformation = syncTitleChange( + imsProject, issueInfo.githubId, finalBlock.first().newTitle, finalBlock.map { it.createdBy().value }) @@ -884,7 +915,8 @@ abstract class AbstractSync( imsProject.rawId!!, it.rawId!! ) != null }) { - val conversionInformation = syncTemplatedField(imsProject, + val conversionInformation = syncTemplatedField( + imsProject, issueInfo.githubId, finalBlock.first(), finalBlock.map { it.createdBy().value }) diff --git a/sync/src/main/kotlin/gropius/sync/TokenManager.kt b/sync/src/main/kotlin/gropius/sync/TokenManager.kt index 90418513..16e3f7b9 100644 --- a/sync/src/main/kotlin/gropius/sync/TokenManager.kt +++ b/sync/src/main/kotlin/gropius/sync/TokenManager.kt @@ -12,6 +12,7 @@ import io.ktor.client.request.* import io.ktor.client.statement.* import io.ktor.http.* import io.ktor.serialization.kotlinx.json.* +import kotlinx.coroutines.delay import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import org.slf4j.LoggerFactory @@ -235,7 +236,10 @@ abstract class TokenManager( ): Pair { val users = user.map { getPossibleUsersForUser(imsProject.ims().value, it) }.flatten().distinct() logger.info("Expanding ${user.map { "${it::class.simpleName}:${it.rawId}(${it.username})" }} to ${users.map { "${it::class.simpleName}:${it.rawId}(${it.username})" }}") - return executeUntilWorking(imsProject, users, executor, owner) + return executeUntilWorking(imsProject, users, { + delay(1100) + executor(it) + }, owner) } /** From 89c88b127da20f9636f4b8e3700b2da78be36900 Mon Sep 17 00:00:00 2001 From: Christian Kurz Date: Wed, 16 Oct 2024 18:34:53 +0200 Subject: [PATCH 05/14] 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 06/14] 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 07/14] 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 ) From 079a9a48a9e82cebe4894f449931475a539f3551 Mon Sep 17 00:00:00 2001 From: Christian Kurz Date: Wed, 16 Oct 2024 21:47:26 +0200 Subject: [PATCH 08/14] dokka --- sync-jira/src/main/kotlin/gropius/sync/jira/JiraDataService.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/sync-jira/src/main/kotlin/gropius/sync/jira/JiraDataService.kt b/sync-jira/src/main/kotlin/gropius/sync/jira/JiraDataService.kt index d4662486..6e062a02 100644 --- a/sync-jira/src/main/kotlin/gropius/sync/jira/JiraDataService.kt +++ b/sync-jira/src/main/kotlin/gropius/sync/jira/JiraDataService.kt @@ -108,6 +108,7 @@ class JiraDataService( /** * Find and ensure the IMSIssueTemplate in the database * @param imsProject The IMSProject to work with + * @param name name of the type to look for, if known * @return the IssueType */ suspend fun issueType(imsProject: IMSProject, name: String): IssueType { From 62aa13dffbc73e4f6bd19814988eb3b515342a08 Mon Sep 17 00:00:00 2001 From: Christian Kurz Date: Wed, 16 Oct 2024 21:50:53 +0200 Subject: [PATCH 09/14] fixup outdated dokka --- .../main/kotlin/gropius/sync/AbstractSync.kt | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/sync/src/main/kotlin/gropius/sync/AbstractSync.kt b/sync/src/main/kotlin/gropius/sync/AbstractSync.kt index 3dcf9410..0c17ddf6 100644 --- a/sync/src/main/kotlin/gropius/sync/AbstractSync.kt +++ b/sync/src/main/kotlin/gropius/sync/AbstractSync.kt @@ -117,7 +117,7 @@ abstract class AbstractSync( /** * Incorporate a comment * @param imsProject IMS project to sync - * @param issueId GitHub ID of the issue + * @param issueId Remote ID of the issue * @param issueComment Comment to sync * @param users List of users involved in this timeline item, sorted with most relevant first * @return Conversion information @@ -131,8 +131,9 @@ abstract class AbstractSync( /** * Incorporate a fallback comment * @param imsProject IMS project to sync - * @param issueId GitHub ID of the issue - * @param issueComment Comment to sync + * @param issueId Remote ID of the issue + * @param comment Comment to sync + * @param original TimelineItem it fell back from * @param users List of users involved in this timeline item, sorted with most relevant first * @return Conversion information */ @@ -143,7 +144,7 @@ abstract class AbstractSync( /** * Incorporate a title change * @param imsProject IMS project to sync - * @param issueId GitHub ID of the issue + * @param issueId Remote ID of the issue * @param newTitle New title of the issue * @param users List of users involved in this timeline item, sorted with most relevant first * @return Conversion information @@ -157,7 +158,7 @@ abstract class AbstractSync( /** * Incorporate a state change * @param imsProject IMS project to sync - * @param issueId GitHub ID of the issue + * @param issueId Remote ID of the issue * @param newState New state of the issue * @param users List of users involved in this timeline item, sorted with most relevant first * @return Conversion information @@ -169,7 +170,7 @@ abstract class AbstractSync( /** * Incorporate a templated field change * @param imsProject IMS project to sync - * @param issueId GitHub ID of the issue + * @param issueId Remote ID of the issue * @param fieldChangedEvent Event describing the field change * @param users List of users involved in this timeline item, sorted with most relevant first * @return Conversion information @@ -189,7 +190,7 @@ abstract class AbstractSync( /** * Incorporate an added label * @param imsProject IMS project to sync - * @param issueId GitHub ID of the issue + * @param issueId Remote ID of the issue * @param label Label to sync * @param users List of users involved in this timeline item, sorted with most relevant first * @return Conversion information @@ -203,7 +204,7 @@ abstract class AbstractSync( /** * Incorporate a removed label * @param imsProject IMS project to sync - * @param issueId GitHub ID of the issue + * @param issueId Remote ID of the issue * @param label Label to sync * @param users List of users involved in this timeline item, sorted with most relevant first * @return Conversion information @@ -217,7 +218,7 @@ abstract class AbstractSync( /** * Incorporate an added assignment * @param imsProject IMS project to sync - * @param issueId GitHub ID of the issue + * @param issueId Remote 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 @@ -229,7 +230,7 @@ abstract class AbstractSync( /** * Incorporate a removed assignment * @param imsProject IMS project to sync - * @param issueId GitHub ID of the issue + * @param issueId Remote 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 From 4372292e8abc99eb4ca7cd6c6d2f1c5ec6a3e894 Mon Sep 17 00:00:00 2001 From: Christian Kurz Date: Wed, 16 Oct 2024 21:51:42 +0200 Subject: [PATCH 10/14] brackets --- sync/src/main/kotlin/gropius/sync/AbstractSync.kt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/sync/src/main/kotlin/gropius/sync/AbstractSync.kt b/sync/src/main/kotlin/gropius/sync/AbstractSync.kt index 0c17ddf6..c92327d7 100644 --- a/sync/src/main/kotlin/gropius/sync/AbstractSync.kt +++ b/sync/src/main/kotlin/gropius/sync/AbstractSync.kt @@ -901,7 +901,9 @@ abstract class AbstractSync( ) { val virtualIDs = mapOf()//For future features val relevantTimeline = timeline.mapNotNull { it as? TitleChangedEvent } - if (relevantTimeline.isEmpty()) return + if (relevantTimeline.isEmpty()) { + return + } val finalBlock = findFinalBlock(relevantTimeline) { it.newTitle } if (finalBlock.none { collectedSyncInfo.timelineItemConversionInformationService.findByImsProjectAndGropiusId( @@ -933,7 +935,9 @@ abstract class AbstractSync( ) { val virtualIDs = mapOf()//For future features val relevantTimeline = timeline.mapNotNull { it as? TemplatedFieldChangedEvent } - if (relevantTimeline.isEmpty()) return + if (relevantTimeline.isEmpty()) { + return + } val finalBlock = findFinalBlock(relevantTimeline) { it.fieldName to it.newValue } if (finalBlock.none { collectedSyncInfo.timelineItemConversionInformationService.findByImsProjectAndGropiusId( @@ -965,7 +969,9 @@ abstract class AbstractSync( ) { val virtualIDs = mapOf()//For future features val relevantTimeline = timeline.mapNotNull { it as? StateChangedEvent } - if (relevantTimeline.isEmpty()) return + if (relevantTimeline.isEmpty()) { + return + } val finalBlock = findFinalBlock(relevantTimeline) { it.newState().value } logger.debug("finalBlock: $finalBlock in $relevantTimeline being ${relevantTimeline.map { it.newState().value.name }}") if (finalBlock.none { From b7bc985fcf236b4b2c81866b22cefb9d15d0f7f3 Mon Sep 17 00:00:00 2001 From: Christian Kurz Date: Wed, 16 Oct 2024 21:54:31 +0200 Subject: [PATCH 11/14] split syncOutgoingSingleAssignmentBlock --- .../main/kotlin/gropius/sync/AbstractSync.kt | 96 +++++++++++-------- 1 file changed, 58 insertions(+), 38 deletions(-) diff --git a/sync/src/main/kotlin/gropius/sync/AbstractSync.kt b/sync/src/main/kotlin/gropius/sync/AbstractSync.kt index c92327d7..c033f454 100644 --- a/sync/src/main/kotlin/gropius/sync/AbstractSync.kt +++ b/sync/src/main/kotlin/gropius/sync/AbstractSync.kt @@ -832,8 +832,7 @@ abstract class AbstractSync( imsProject, finalBlock, relevantTimeline, true, virtualIDs ) ) { - val conversionInformation = syncRemovedLabel( - imsProject, + val conversionInformation = syncRemovedLabel(imsProject, issueInfo.githubId, label!!, finalBlock.map { it.lastModifiedBy().value }) @@ -848,8 +847,7 @@ abstract class AbstractSync( imsProject, finalBlock, relevantTimeline, false, virtualIDs ) ) { - val conversionInformation = syncAddedLabel( - imsProject, + val conversionInformation = syncAddedLabel(imsProject, issueInfo.githubId, label!!, finalBlock.map { it.lastModifiedBy().value }) @@ -910,8 +908,7 @@ abstract class AbstractSync( imsProject.rawId!!, it.rawId!! ) != null }) { - val conversionInformation = syncTitleChange( - imsProject, + val conversionInformation = syncTitleChange(imsProject, issueInfo.githubId, finalBlock.first().newTitle, finalBlock.map { it.createdBy().value }) @@ -944,8 +941,7 @@ abstract class AbstractSync( imsProject.rawId!!, it.rawId!! ) != null }) { - val conversionInformation = syncTemplatedField( - imsProject, + val conversionInformation = syncTemplatedField(imsProject, issueInfo.githubId, finalBlock.first(), finalBlock.map { it.createdBy().value }) @@ -1020,6 +1016,57 @@ abstract class AbstractSync( } } + /** + * Sync Outgoing Assignments + * @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 + * @param finalBlock Final block relevant for this assignment + */ + private suspend fun syncOutgoingSingleAssignmentBlock( + relevantTimeline: List, + imsProject: IMSProject, + issueInfo: IssueConversionInformation, + assignment: Assignment, + virtualIDs: Map, + finalBlock: List + ) { + 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() + } + } + } + /** * Sync Outgoing Assignments * @param relevantTimeline Timeline of the issue @@ -1046,36 +1093,9 @@ abstract class AbstractSync( } } if (!assignmentIsSynced) { - 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() - } - } + syncOutgoingSingleAssignmentBlock( + relevantTimeline, imsProject, issueInfo, assignment, virtualIDs, finalBlock + ) } } From ab2987ecc8449e1beb9135f6f521c6789ea91c60 Mon Sep 17 00:00:00 2001 From: Christian Kurz Date: Fri, 18 Oct 2024 17:08:02 +0200 Subject: [PATCH 12/14] introduce github_node_id (to be renamed) --- .../main/graphql/gropius/sync/github/CommonData.graphql | 6 ++++++ .../main/kotlin/gropius/sync/github/GithubDataService.kt | 9 ++++++++- .../src/main/kotlin/gropius/sync/github/GithubSync.kt | 4 ++-- .../gropius/sync/github/config/IMSConfigManager.kt | 4 ++++ 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/sync-github/src/main/graphql/gropius/sync/github/CommonData.graphql b/sync-github/src/main/graphql/gropius/sync/github/CommonData.graphql index d106f81d..7f431544 100644 --- a/sync-github/src/main/graphql/gropius/sync/github/CommonData.graphql +++ b/sync-github/src/main/graphql/gropius/sync/github/CommonData.graphql @@ -47,4 +47,10 @@ fragment IssueData on Issue { } query CurrentMetaData { ...MetaData +} +query FindUser($login: String!) { + ...MetaData + user(login: $login) { + ...UserData + } } \ No newline at end of file diff --git a/sync-github/src/main/kotlin/gropius/sync/github/GithubDataService.kt b/sync-github/src/main/kotlin/gropius/sync/github/GithubDataService.kt index ca33d16e..d9f395f9 100644 --- a/sync-github/src/main/kotlin/gropius/sync/github/GithubDataService.kt +++ b/sync-github/src/main/kotlin/gropius/sync/github/GithubDataService.kt @@ -21,6 +21,7 @@ import gropius.sync.github.config.IMSConfig import gropius.sync.github.config.IMSProjectConfig import gropius.sync.github.generated.fragment.LabelData import gropius.sync.github.generated.fragment.UserData +import gropius.sync.github.generated.fragment.UserData.Companion.asNode import gropius.sync.github.generated.fragment.UserData.Companion.asUser import gropius.sync.model.LabelInfo import gropius.sync.repository.LabelInfoRepository @@ -115,6 +116,12 @@ class GithubDataService( val databaseId = userData?.asUser()?.databaseId val encodedAccountId = jsonNodeMapper.jsonNodeToDeterministicString(objectMapper.valueToTree(databaseId ?: 0)) + val encodedUserId = + jsonNodeMapper.jsonNodeToDeterministicString( + objectMapper.valueToTree( + userData?.asNode()?.id ?: "" + ) + ) val username = userData?.login ?: FALLBACK_USER_NAME val ims = neoOperations.findById(imsProject.ims().value.rawId!!)!! if (databaseId != null) { @@ -133,7 +140,7 @@ class GithubDataService( userData?.asUser()?.email, null, username, - mutableMapOf("github_id" to encodedAccountId) + mutableMapOf("github_id" to encodedAccountId, "github_node_id" to encodedUserId) ) imsUser.ims().value = imsProject.ims().value imsUser.template().value = imsUser.ims().value.template().value.imsUserTemplate().value 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 92c7526a..d97763a4 100644 --- a/sync-github/src/main/kotlin/gropius/sync/github/GithubSync.kt +++ b/sync-github/src/main/kotlin/gropius/sync/github/GithubSync.kt @@ -190,7 +190,7 @@ final class GithubSync( 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"]!! + githubDataService.objectMapper.readTree(it.templatedFields["github_node_id"]!!).textValue() } if (ids.isEmpty()) { return null @@ -216,7 +216,7 @@ final class GithubSync( 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"]!! + githubDataService.objectMapper.readTree(it.templatedFields["github_node_id"]!!).textValue() } if (ids.isEmpty()) { return null diff --git a/sync-github/src/main/kotlin/gropius/sync/github/config/IMSConfigManager.kt b/sync-github/src/main/kotlin/gropius/sync/github/config/IMSConfigManager.kt index f6a389dc..d9081ab2 100644 --- a/sync-github/src/main/kotlin/gropius/sync/github/config/IMSConfigManager.kt +++ b/sync-github/src/main/kotlin/gropius/sync/github/config/IMSConfigManager.kt @@ -91,6 +91,10 @@ class IMSConfigManager( "github_id" to obj { "nullable" to true "type" to "int32" + }.toString(), + "github_node_id" to obj { + "nullable" to true + "type" to "string" }.toString() ) } From 7fd25744740edcc535246b7b14ab78498b433557 Mon Sep 17 00:00:00 2001 From: Christian Kurz Date: Fri, 18 Oct 2024 19:44:23 +0200 Subject: [PATCH 13/14] fix assingments --- .../gropius/sync/jira/JiraDataService.kt | 21 ++++-- .../main/kotlin/gropius/sync/jira/JiraSync.kt | 26 ++++--- .../gropius/sync/jira/model/IssueData.kt | 69 ++++++++++++++++++- .../main/kotlin/gropius/sync/IssueCleaner.kt | 10 ++- 4 files changed, 105 insertions(+), 21 deletions(-) diff --git a/sync-jira/src/main/kotlin/gropius/sync/jira/JiraDataService.kt b/sync-jira/src/main/kotlin/gropius/sync/jira/JiraDataService.kt index 6e062a02..1f9f7d6e 100644 --- a/sync-jira/src/main/kotlin/gropius/sync/jira/JiraDataService.kt +++ b/sync-jira/src/main/kotlin/gropius/sync/jira/JiraDataService.kt @@ -14,6 +14,7 @@ import gropius.sync.JsonHelper import gropius.sync.SyncDataService import gropius.sync.jira.config.IMSConfig import gropius.sync.jira.config.IMSProjectConfig +import gropius.sync.jira.model.IssueDataRepository import gropius.util.JsonNodeMapper import io.ktor.client.* import io.ktor.client.call.* @@ -46,6 +47,7 @@ import java.util.* * @param objectMapper Reference for the spring instance of ObjectMapper * @param jsonNodeMapper Reference for the spring instance of JsonNodeMapper * @param gropiusUserRepository Reference for the spring instance of GropiusUserRepository + * @param issueDataRepository Reference for the spring instance of IssueDataRepository */ @Component class JiraDataService( @@ -55,7 +57,8 @@ class JiraDataService( val helper: JsonHelper, val objectMapper: ObjectMapper, val jsonNodeMapper: JsonNodeMapper, - val gropiusUserRepository: GropiusUserRepository + val gropiusUserRepository: GropiusUserRepository, + val issueDataRepository: IssueDataRepository ) : SyncDataService { companion object { @@ -274,8 +277,7 @@ class JiraDataService( ): Optional { val imsConfig = IMSConfig(helper, imsProject.ims().value, imsProject.ims().value.template().value) val cloudId = - token.cloudIds?.filter { URI(it.url + "/rest/api/2") == URI(imsConfig.rootUrl.toString()) }?.map { it.id } - ?.firstOrNull() + token.cloudIds?.filter { URI(it.url) == URI(imsConfig.rootUrl.toString()) }?.map { it.id }?.firstOrNull() if (token.type == "PAT") { try { val res = client.request(imsConfig.rootUrl.toString()) { @@ -294,15 +296,15 @@ class JiraDataService( setBody(body) } } - logger.info("Response Code for request with token token is ${res.status}(${res.status.isSuccess()}): $body is ${res.bodyAsText()}") return if (res.status.isSuccess()) { logger.debug("Response for {} {}", res.request.url, res.bodyAsText()) Optional.of(res) } else { + logger.info("Response Code for request with token token is ${res.status}(${res.status.isSuccess()}): $body is ${res.bodyAsText()}") Optional.empty() } } catch (e: ClientRequestException) { - e.printStackTrace() + logger.warn("Request failed with token $token", e) return Optional.empty() } } else if (cloudId != null) { @@ -324,18 +326,23 @@ class JiraDataService( setBody(body) } } - logger.info("Response Code for request with token token is ${res.status}(${res.status.isSuccess()}): $body is ${res.bodyAsText()}") return if (res.status.isSuccess()) { logger.debug("Response for {} {}", res.request.url, res.bodyAsText()) Optional.of(res) } else { + logger.info("Response Code for request with token token is ${res.status}(${res.status.isSuccess()}): $body is ${res.bodyAsText()}") Optional.empty() } } catch (e: ClientRequestException) { - e.printStackTrace() + logger.warn("Request failed with token $token", e) return Optional.empty() } } else { + logger.error("Invalid value for token type: ${token.type} and cloudId: ${token.cloudIds} with ${ + token.cloudIds?.map { + URI(it.url) to URI(imsConfig.rootUrl.toString()) + } + }") return Optional.empty() } } 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 be21190a..b9418353 100644 --- a/sync-jira/src/main/kotlin/gropius/sync/jira/JiraSync.kt +++ b/sync-jira/src/main/kotlin/gropius/sync/jira/JiraSync.kt @@ -29,6 +29,7 @@ import java.time.OffsetDateTime import java.time.ZoneId import java.time.format.DateTimeFormatter import kotlin.io.encoding.ExperimentalEncodingApi +import kotlin.jvm.optionals.getOrNull /** * Details of the issue status after the transition. @@ -270,13 +271,13 @@ final class JiraSync( val issueList = mutableListOf() val times = mutableListOf() val issueResponse = jiraDataService.tokenManager.executeUntilWorking(imsProject, userList, listOf()) { - val userTimeZone = ZoneId.of( - jiraDataService.sendRequest( - imsProject, HttpMethod.Get, null, { - appendPathSegments("myself") - }, it - ).get().body().timeZone - ) + val requestData = jiraDataService.sendRequest( + imsProject, HttpMethod.Get, null, { + appendPathSegments("myself") + }, it + ).getOrNull() ?: TODO("Timezone bad") + logger.trace("TIME ZONE RESPONSE ${requestData.bodyAsText()}") + val userTimeZone = ZoneId.of(requestData.body().timeZone) var query = "project=${imsProjectConfig.repo} ORDER BY updated ASC" if (lastSuccessfulSync != null) { query = "project=${imsProjectConfig.repo} AND updated > ${ @@ -474,12 +475,21 @@ final class JiraSync( override suspend fun syncTemplatedField( imsProject: IMSProject, issueId: String, fieldChangedEvent: TemplatedFieldChangedEvent, users: List ): TimelineItemConversionInformation? { + val customName = jiraDataService.issueDataRepository.findByImsProject(imsProject.rawId!!).map { + it.names.map { it.value.jsonPrimitive.content to it.key }.toMap()[fieldChangedEvent.fieldName] + }.filterNotNull().firstOrNull() + logger.trace("Writing ${fieldChangedEvent.fieldName} to $customName with ${fieldChangedEvent.newValue}") + if (customName == null) { + return null + } val response = jiraDataService.request( imsProject, users, HttpMethod.Put, gropiusUserList(users), JsonObject( mapOf( "fields" to JsonObject( mapOf( - fieldChangedEvent.fieldName to JsonPrimitive(fieldChangedEvent.newValue) + customName to JsonPrimitive( + jiraDataService.objectMapper.readTree(fieldChangedEvent.newValue).textValue() + ) ) ) ) diff --git a/sync-jira/src/main/kotlin/gropius/sync/jira/model/IssueData.kt b/sync-jira/src/main/kotlin/gropius/sync/jira/model/IssueData.kt index ce790e18..651884eb 100644 --- a/sync-jira/src/main/kotlin/gropius/sync/jira/model/IssueData.kt +++ b/sync-jira/src/main/kotlin/gropius/sync/jira/model/IssueData.kt @@ -2,7 +2,6 @@ package gropius.sync.jira.model import com.fasterxml.jackson.databind.JsonNode import gropius.model.architecture.IMSProject -import gropius.model.architecture.Project import gropius.model.issue.Issue import gropius.model.issue.timeline.* import gropius.sync.IncomingIssue @@ -13,7 +12,6 @@ import gropius.sync.jira.JiraDataService import gropius.util.schema.Schema import gropius.util.schema.Type import jakarta.transaction.Transactional -import kotlinx.coroutines.reactive.awaitFirst import kotlinx.coroutines.reactor.awaitSingle import kotlinx.serialization.json.* import org.bson.* @@ -77,6 +75,10 @@ class JiraTimelineItem(val id: String, val created: String, val author: JsonObje return gropiusLabels( timelineItemConversionInformation, imsProject, service, jiraService ) + } else if (fieldId == "assignee") { + return gropiusAssignment( + timelineItemConversionInformation, imsProject, service, jiraService, issue + ) } if (issue.template().value.templateFieldSpecifications.containsKey(data.field)) { val schema = issue.template().value.templateFieldSpecifications[data.field]!! @@ -218,6 +220,67 @@ class JiraTimelineItem(val id: String, val created: String, val author: JsonObje return listOf(titleChangedEvent) to convInfo; } + /** + * Convert a single assignment change to a Gropius TimelineItem + * @param timelineItemConversionInformation the timeline item conversion information + * @param imsProject the ims project + * @param service the service + * @param jiraService the jira service + * @param issue the issue to work on (sometimes not yet saved or complete) + * @return the pair of timeline items and conversion information + */ + private suspend fun gropiusAssignment( + timelineItemConversionInformation: TimelineItemConversionInformation?, + imsProject: IMSProject, + service: JiraDataService, + jiraService: JiraDataService, + issue: Issue + ): Pair, TimelineItemConversionInformation> { + val convInfo = + timelineItemConversionInformation ?: JiraTimelineItemConversionInformation(imsProject.rawId!!, id); + val timelineId = timelineItemConversionInformation?.gropiusId + val encodedAccountId = service.jsonNodeMapper.jsonNodeToDeterministicString( + service.objectMapper.valueToTree(data.to) + ) + val newUser = if (data.to != null) imsProject.ims().value.users() + .firstOrNull { (it.username == data.to) or (it.templatedFields["jira_id"] == encodedAccountId) } else null + if (newUser != null) { + val titleChangedEvent = (if (timelineId != null) service.neoOperations.findById( + timelineId + ) else null) ?: Assignment( + OffsetDateTime.parse( + created, IssueData.formatter + ).minusNanos(1), OffsetDateTime.parse( + created, IssueData.formatter + ).minusNanos(1) + ) + titleChangedEvent.createdBy().value = jiraService.mapUser(imsProject, author) + titleChangedEvent.lastModifiedBy().value = jiraService.mapUser(imsProject, author) + titleChangedEvent.user().value = newUser + return listOf(titleChangedEvent) to convInfo; + } else if (data.to != null) { + logger.warn("Cannot find user for ${data.to} ${data.toString}") + } + val oldAssignment = + issue.assignments().lastOrNull { it.user().value.username == data.from } ?: issue.assignments().lastOrNull() + if (oldAssignment != null) {//TODO: Replace with robuster logic once handling of multiple timeline items works + val titleChangedEvent = (if (timelineId != null) service.neoOperations.findById( + timelineId + ) else null) ?: RemovedAssignmentEvent( + OffsetDateTime.parse( + created, IssueData.formatter + ).minusNanos(1), OffsetDateTime.parse( + created, IssueData.formatter + ).minusNanos(1) + ) + titleChangedEvent.createdBy().value = jiraService.mapUser(imsProject, author) + titleChangedEvent.lastModifiedBy().value = jiraService.mapUser(imsProject, author) + titleChangedEvent.removedAssignment().value = oldAssignment + return listOf(titleChangedEvent) to convInfo + } + return listOf() to convInfo + } + /** * Convert a single state change to a Gropius StateChangedEvent * @param timelineItemConversionInformation the timeline item conversion information @@ -427,7 +490,7 @@ data class IssueData( issue.body().value.issue().value = issue issue.state().value = jiraService.issueState(imsProject, null, true) issue.template().value = jiraService.issueTemplate(imsProject) - issue.trackables() += jiraService.neoOperations.findAll(Project::class.java).awaitFirst() + issue.trackables() += imsProject.trackable().value issue.type().value = jiraService.issueType(imsProject, fields["issuetype"]?.jsonObject?.get("name")?.jsonPrimitive?.content!!) return issue diff --git a/sync/src/main/kotlin/gropius/sync/IssueCleaner.kt b/sync/src/main/kotlin/gropius/sync/IssueCleaner.kt index 73ff077c..7b30761b 100644 --- a/sync/src/main/kotlin/gropius/sync/IssueCleaner.kt +++ b/sync/src/main/kotlin/gropius/sync/IssueCleaner.kt @@ -49,13 +49,17 @@ class IssueCleaner( if (item is Assignment) { issue.assignments() += item } - if (item is RemovedAssignmentEvent) { - issue.assignments() -= item.removedAssignment().value - } + } + for (item in issue.timelineItems().sortedBy { it.createdAt }) { if (item is AssignmentTypeChangedEvent) { TODO() } } + for (item in issue.timelineItems().sortedBy { it.createdAt }) { + if (item is RemovedAssignmentEvent) { + issue.assignments() -= item.removedAssignment().value + } + } } /** From 2a61cf51213177914dd26c4e59e2280351d38429 Mon Sep 17 00:00:00 2001 From: Christian Kurz Date: Fri, 18 Oct 2024 19:47:33 +0200 Subject: [PATCH 14/14] less title changes --- .../gropius/sync/jira/model/IssueData.kt | 49 ++++++++++--------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/sync-jira/src/main/kotlin/gropius/sync/jira/model/IssueData.kt b/sync-jira/src/main/kotlin/gropius/sync/jira/model/IssueData.kt index 651884eb..8fc51836 100644 --- a/sync-jira/src/main/kotlin/gropius/sync/jira/model/IssueData.kt +++ b/sync-jira/src/main/kotlin/gropius/sync/jira/model/IssueData.kt @@ -204,7 +204,7 @@ class JiraTimelineItem(val id: String, val created: String, val author: JsonObje val convInfo = timelineItemConversionInformation ?: JiraTimelineItemConversionInformation(imsProject.rawId!!, id); val timelineId = timelineItemConversionInformation?.gropiusId - val titleChangedEvent = if (timelineId != null) service.neoOperations.findById( + val stateChangedEvent = if (timelineId != null) service.neoOperations.findById( timelineId )!! else StateChangedEvent( OffsetDateTime.parse( @@ -213,11 +213,11 @@ class JiraTimelineItem(val id: String, val created: String, val author: JsonObje created, IssueData.formatter ).minusNanos(1) ) - titleChangedEvent.createdBy().value = jiraService.mapUser(imsProject, author) - titleChangedEvent.lastModifiedBy().value = jiraService.mapUser(imsProject, author) - titleChangedEvent.oldState().value = jiraService.issueState(imsProject, issue, data.fromString == null) - titleChangedEvent.newState().value = jiraService.issueState(imsProject, issue, data.toString == null) - return listOf(titleChangedEvent) to convInfo; + stateChangedEvent.createdBy().value = jiraService.mapUser(imsProject, author) + stateChangedEvent.lastModifiedBy().value = jiraService.mapUser(imsProject, author) + stateChangedEvent.oldState().value = jiraService.issueState(imsProject, issue, data.fromString == null) + stateChangedEvent.newState().value = jiraService.issueState(imsProject, issue, data.toString == null) + return listOf(stateChangedEvent) to convInfo; } /** @@ -245,7 +245,7 @@ class JiraTimelineItem(val id: String, val created: String, val author: JsonObje val newUser = if (data.to != null) imsProject.ims().value.users() .firstOrNull { (it.username == data.to) or (it.templatedFields["jira_id"] == encodedAccountId) } else null if (newUser != null) { - val titleChangedEvent = (if (timelineId != null) service.neoOperations.findById( + val assignmentEvent = (if (timelineId != null) service.neoOperations.findById( timelineId ) else null) ?: Assignment( OffsetDateTime.parse( @@ -254,29 +254,30 @@ class JiraTimelineItem(val id: String, val created: String, val author: JsonObje created, IssueData.formatter ).minusNanos(1) ) - titleChangedEvent.createdBy().value = jiraService.mapUser(imsProject, author) - titleChangedEvent.lastModifiedBy().value = jiraService.mapUser(imsProject, author) - titleChangedEvent.user().value = newUser - return listOf(titleChangedEvent) to convInfo; + assignmentEvent.createdBy().value = jiraService.mapUser(imsProject, author) + assignmentEvent.lastModifiedBy().value = jiraService.mapUser(imsProject, author) + assignmentEvent.user().value = newUser + return listOf(assignmentEvent) to convInfo; } else if (data.to != null) { logger.warn("Cannot find user for ${data.to} ${data.toString}") } val oldAssignment = issue.assignments().lastOrNull { it.user().value.username == data.from } ?: issue.assignments().lastOrNull() if (oldAssignment != null) {//TODO: Replace with robuster logic once handling of multiple timeline items works - val titleChangedEvent = (if (timelineId != null) service.neoOperations.findById( - timelineId - ) else null) ?: RemovedAssignmentEvent( - OffsetDateTime.parse( - created, IssueData.formatter - ).minusNanos(1), OffsetDateTime.parse( - created, IssueData.formatter - ).minusNanos(1) - ) - titleChangedEvent.createdBy().value = jiraService.mapUser(imsProject, author) - titleChangedEvent.lastModifiedBy().value = jiraService.mapUser(imsProject, author) - titleChangedEvent.removedAssignment().value = oldAssignment - return listOf(titleChangedEvent) to convInfo + val assignmentRemovedEvent = + (if (timelineId != null) service.neoOperations.findById( + timelineId + ) else null) ?: RemovedAssignmentEvent( + OffsetDateTime.parse( + created, IssueData.formatter + ).minusNanos(1), OffsetDateTime.parse( + created, IssueData.formatter + ).minusNanos(1) + ) + assignmentRemovedEvent.createdBy().value = jiraService.mapUser(imsProject, author) + assignmentRemovedEvent.lastModifiedBy().value = jiraService.mapUser(imsProject, author) + assignmentRemovedEvent.removedAssignment().value = oldAssignment + return listOf(assignmentRemovedEvent) to convInfo } return listOf() to convInfo }