Skip to content

Commit 77e4165

Browse files
committed
Unarchive then archive version
1 parent b8c3d57 commit 77e4165

8 files changed

Lines changed: 118 additions & 6 deletions

File tree

docs/ApiToken.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Classic scopes:
99
- `read:jira-work`
1010
- `write:jira-work`
1111
- `read:jira-user`
12+
- `manage:jira-project`
1213

1314
## Cloud ID
1415

@@ -103,4 +104,9 @@ Scopes (classic): `read:jira-work`
103104

104105
### [POST /issue/{issueIdOrKey}/transitions](https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-issues/#api-rest-api-2-issue-issueidorkey-transitions-post)
105106

106-
Scopes (classic): `write:jira-work`
107+
Scopes (classic): `write:jira-work`
108+
109+
110+
### [PUT /version/{id}](https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-project-versions/#api-rest-api-2-version-id-put)
111+
112+
Scopes (classic): `manage:jira-project`

src/main/kotlin/io/github/mojira/arisa/apiclient/JiraClient.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import io.github.mojira.arisa.apiclient.models.Project
1616
import io.github.mojira.arisa.apiclient.models.SearchResults
1717
import io.github.mojira.arisa.apiclient.models.Transitions
1818
import io.github.mojira.arisa.apiclient.models.User
19+
import io.github.mojira.arisa.apiclient.models.Version
1920
import io.github.mojira.arisa.apiclient.models.Visibility
2021
import io.github.mojira.arisa.apiclient.requestModels.AddCommentBody
2122
import io.github.mojira.arisa.apiclient.requestModels.CreateIssueLinkBody
@@ -24,6 +25,7 @@ import io.github.mojira.arisa.apiclient.requestModels.JiraSearchRequest
2425
import io.github.mojira.arisa.apiclient.requestModels.TransitionIssueBody
2526
import io.github.mojira.arisa.apiclient.requestModels.UpdateCommentBody
2627
import io.github.mojira.arisa.apiclient.requestModels.UpdateCommentQueryParams
28+
import io.github.mojira.arisa.apiclient.requestModels.UpdateVersionBody
2729
import io.github.mojira.arisa.log
2830
import kotlinx.serialization.json.Json
2931
import okhttp3.HttpUrl.Companion.toHttpUrl
@@ -149,6 +151,12 @@ interface JiraApi {
149151
@Path("issueIdOrKey") issueIdOrKey: String,
150152
@Body body: TransitionIssueBody
151153
): Call<Unit>
154+
155+
@PUT("version/{id}")
156+
fun updateVersion(
157+
@Path("id") versionId: String,
158+
@Body body: UpdateVersionBody
159+
): Call<Version>
152160
}
153161

154162
/**
@@ -335,4 +343,8 @@ class JiraClient(
335343
fun performTransition(issueIdOrKey: String, body: TransitionIssueBody) {
336344
jiraApi.performTransition(issueIdOrKey, body).executeOrThrow()
337345
}
346+
347+
fun updateVersion(versionId: String, body: UpdateVersionBody): Version {
348+
return jiraApi.updateVersion(versionId, body).executeOrThrow()
349+
}
338350
}

src/main/kotlin/io/github/mojira/arisa/domain/IssueUpdateContext.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,9 @@ data class IssueUpdateContext(
1919
var transitionName: String? = null,
2020
val otherOperations: MutableList<() -> Either<Throwable, Unit>> =
2121
emptyList<() -> Either<Throwable, Unit>>().toMutableList(),
22+
val preOperations: MutableList<() -> Either<Throwable, Unit>> =
23+
emptyList<() -> Either<Throwable, Unit>>().toMutableList(),
24+
val postOperations: MutableList<() -> Either<Throwable, Unit>> =
25+
emptyList<() -> Either<Throwable, Unit>>().toMutableList(),
2226
var triggeredBy: String? = null
2327
)

src/main/kotlin/io/github/mojira/arisa/domain/cloud/CloudIssue.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ data class CloudIssue(
5252
// val updateLinked: (Double) -> Unit,
5353
val setPrivate: () -> Unit,
5454
val addAffectedVersionById: (id: String) -> Unit,
55+
val unarchiveVersion: (id: String) -> Unit,
56+
val archiveVersion: (id: String) -> Unit,
5557
// val addAffectedVersion: (version: Version) -> Unit,
5658
// val removeAffectedVersion: (version: Version) -> Unit,
5759
// val createLink: (type: String, key: String, outwards: Boolean) -> Unit,

src/main/kotlin/io/github/mojira/arisa/infrastructure/jira/Functions.kt

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import io.github.mojira.arisa.apiclient.models.Visibility
1414
import io.github.mojira.arisa.apiclient.requestModels.EditIssueBody
1515
import io.github.mojira.arisa.apiclient.requestModels.TransitionIssueBody
1616
import io.github.mojira.arisa.apiclient.requestModels.UpdateCommentBody
17+
import io.github.mojira.arisa.apiclient.requestModels.UpdateVersionBody
1718
import io.github.mojira.arisa.domain.IssueUpdateContext
1819
import io.github.mojira.arisa.infrastructure.CommentCache
1920
import io.github.mojira.arisa.jiraClient
@@ -185,7 +186,18 @@ fun updateDescription(context: Lazy<IssueUpdateContext>, description: String) {
185186
}
186187

187188
fun applyIssueChanges(context: IssueUpdateContext): Either<FailedModuleResponse, ModuleResponse> {
188-
val functions = context.otherOperations.toMutableList()
189+
val functions = mutableListOf<() -> Either<Throwable, Unit>>()
190+
191+
// Build execution list in reverse order for transitions/updates/edits
192+
// since they're added at index 0
193+
194+
// Step 6: Add postOperations last (will run after everything)
195+
functions.addAll(context.postOperations)
196+
197+
// Step 5: Add otherOperations (will run after edits)
198+
functions.addAll(0, context.otherOperations)
199+
200+
// Step 4: Add edits (will run after updates)
189201
if (context.hasEdits) {
190202
functions.add(
191203
0,
@@ -194,6 +206,8 @@ fun applyIssueChanges(context: IssueUpdateContext): Either<FailedModuleResponse,
194206
.partially1(context.edit)
195207
)
196208
}
209+
210+
// Step 3: Add updates (will run after transitions)
197211
if (context.hasUpdates) {
198212
functions.add(
199213
0,
@@ -203,6 +217,8 @@ fun applyIssueChanges(context: IssueUpdateContext): Either<FailedModuleResponse,
203217
.partially1("Update issue")
204218
)
205219
}
220+
221+
// Step 2: Add transitions (will run after preOperations)
206222
if (context.transitionName != null) {
207223
functions.add(
208224
0,
@@ -212,6 +228,10 @@ fun applyIssueChanges(context: IssueUpdateContext): Either<FailedModuleResponse,
212228
.partially1(context.transitionName!!)
213229
)
214230
}
231+
232+
// Step 1: Add preOperations first (will run FIRST)
233+
functions.addAll(0, context.preOperations)
234+
215235
return tryRunAll(functions, context)
216236
}
217237

@@ -438,6 +458,58 @@ fun deleteLink(context: Lazy<IssueUpdateContext>, link: MojiraIssueLink) {
438458
}
439459
}
440460

461+
fun unarchiveVersion(context: Lazy<IssueUpdateContext>, versionId: String) {
462+
context.value.preOperations.add {
463+
runBlocking {
464+
Either.catch {
465+
withContext(Dispatchers.IO) {
466+
try {
467+
context.value.jiraClient.updateVersion(
468+
versionId,
469+
UpdateVersionBody(archived = false)
470+
)
471+
} catch (e: ClientErrorException) {
472+
if (e.code == HttpStatus.SC_NOT_FOUND ||
473+
e.code >= HttpStatus.SC_INTERNAL_SERVER_ERROR
474+
) {
475+
log.warn("Failed to unarchive version $versionId")
476+
} else {
477+
throw e
478+
}
479+
}
480+
}
481+
Unit
482+
}
483+
}
484+
}
485+
}
486+
487+
fun archiveVersion(context: Lazy<IssueUpdateContext>, versionId: String) {
488+
context.value.postOperations.add {
489+
runBlocking {
490+
Either.catch {
491+
withContext(Dispatchers.IO) {
492+
try {
493+
context.value.jiraClient.updateVersion(
494+
versionId,
495+
UpdateVersionBody(archived = true)
496+
)
497+
} catch (e: ClientErrorException) {
498+
if (e.code == HttpStatus.SC_NOT_FOUND ||
499+
e.code >= HttpStatus.SC_INTERNAL_SERVER_ERROR
500+
) {
501+
log.warn("Failed to archive version $versionId")
502+
} else {
503+
throw e
504+
}
505+
}
506+
}
507+
Unit
508+
}
509+
}
510+
}
511+
}
512+
441513
fun updateCommentBody(context: Lazy<IssueUpdateContext>, comment: MojiraComment, body: String) {
442514
context.value.otherOperations.add {
443515
runBlocking {

src/main/kotlin/io/github/mojira/arisa/infrastructure/jira/Mappers.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ fun MojiraIssue.toDomain(
135135
setPrivate = ::updateSecurity.partially1(context)
136136
.partially1(project.getSecurityLevelId(config)),
137137
::addAffectedVersionById.partially1(context),
138+
::unarchiveVersion.partially1(context),
139+
::archiveVersion.partially1(context),
138140
// { version -> addAffectedVersionById(context, version.id) },
139141
// { version -> removeAffectedVersionById(context, version.id) },
140142
// ::createLink.partially1(context).partially1(::getOtherUpdateContext.partially1(jiraClient)),

src/main/kotlin/io/github/mojira/arisa/modules/commands/AddVersionCommand.kt

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,21 @@ class AddVersionCommand {
77
if (issue.affectedVersions.any { it.name == version }) {
88
throw CommandExceptions.VERSION_ALREADY_AFFECTED.create(version)
99
}
10-
if (issue.project.versions.none { it.name == version }) {
11-
throw CommandExceptions.NO_SUCH_VERSION.create(version)
10+
val projectVersion = issue.project.versions.firstOrNull { it.name == version }
11+
?: throw CommandExceptions.NO_SUCH_VERSION.create(version)
12+
13+
val id = projectVersion.id
14+
15+
// If version is archived, we need to unarchive it, add it, then re-archive
16+
if (projectVersion.archived) {
17+
issue.unarchiveVersion(id)
18+
issue.addAffectedVersionById(id)
19+
issue.archiveVersion(id)
20+
} else {
21+
// Normal case: just add the version
22+
issue.addAffectedVersionById(id)
1223
}
13-
val id = issue.project.versions.first { it.name == version }.id
14-
issue.addAffectedVersionById(id)
24+
1525
return 1
1626
}
1727
}

src/test/kotlin/io/github/mojira/arisa/utils/MockCloudDomain.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ fun mockCloudIssue(
3939
addRawRestrictedComment: (body: String, restriction: String) -> Unit = { _, _ -> },
4040
addRawBotComment: (rawMessage: String) -> Unit = { },
4141
addAffectedVersionById: (id: String) -> Unit = { },
42+
unarchiveVersion: (id: String) -> Unit = { },
43+
archiveVersion: (id: String) -> Unit = { },
4244
addAttachmentFromFile: (file: File, cleanupCallback: () -> Unit) -> Unit = { _, cleanupCallback -> cleanupCallback() }
4345
) = CloudIssue(
4446
key = key,
@@ -63,6 +65,8 @@ fun mockCloudIssue(
6365
addRawRestrictedComment = addRawRestrictedComment,
6466
addRawBotComment = addRawBotComment,
6567
addAffectedVersionById = addAffectedVersionById,
68+
unarchiveVersion = unarchiveVersion,
69+
archiveVersion = archiveVersion,
6670
addAttachmentFromFile = addAttachmentFromFile
6771
)
6872

0 commit comments

Comments
 (0)