feat(kanban): add board transfer models, endpoints and auto-nudge dispatcher (#1137)
This commit is contained in:
@@ -92,3 +92,17 @@ data class DeleteBoardResponse(
|
|||||||
val result: JsonElement? = null,
|
val result: JsonElement? = null,
|
||||||
val current: String? = null,
|
val current: String? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ExportBoardBody(
|
||||||
|
val output: String = "",
|
||||||
|
val attachments: Boolean = true,
|
||||||
|
val logs: Boolean = false,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ImportBoardBody(
|
||||||
|
val archive: String,
|
||||||
|
val slug: String? = null,
|
||||||
|
val switch: Boolean = false,
|
||||||
|
)
|
||||||
|
|||||||
@@ -252,4 +252,6 @@ data class TaskEstimate(
|
|||||||
val complexity: String? = null,
|
val complexity: String? = null,
|
||||||
val rationale: String? = null,
|
val rationale: String? = null,
|
||||||
val model: String? = null,
|
val model: String? = null,
|
||||||
)
|
) {
|
||||||
|
val tokens: Int? get() = estTokens
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package com.m57.hermescontrol.data.remote
|
|||||||
|
|
||||||
import com.m57.hermescontrol.data.model.AttachmentUploadResponse
|
import com.m57.hermescontrol.data.model.AttachmentUploadResponse
|
||||||
import com.m57.hermescontrol.data.model.AutoDescribeResponse
|
import com.m57.hermescontrol.data.model.AutoDescribeResponse
|
||||||
|
import com.m57.hermescontrol.data.model.BoardExportResult
|
||||||
|
import com.m57.hermescontrol.data.model.BoardImportResult
|
||||||
import com.m57.hermescontrol.data.model.BulkTasksBody
|
import com.m57.hermescontrol.data.model.BulkTasksBody
|
||||||
import com.m57.hermescontrol.data.model.BulkTasksResponse
|
import com.m57.hermescontrol.data.model.BulkTasksResponse
|
||||||
import com.m57.hermescontrol.data.model.CreateBoardBody
|
import com.m57.hermescontrol.data.model.CreateBoardBody
|
||||||
@@ -10,6 +12,8 @@ import com.m57.hermescontrol.data.model.CreateTaskBody
|
|||||||
import com.m57.hermescontrol.data.model.CreateTaskResponse
|
import com.m57.hermescontrol.data.model.CreateTaskResponse
|
||||||
import com.m57.hermescontrol.data.model.DeleteBoardResponse
|
import com.m57.hermescontrol.data.model.DeleteBoardResponse
|
||||||
import com.m57.hermescontrol.data.model.DispatchResult
|
import com.m57.hermescontrol.data.model.DispatchResult
|
||||||
|
import com.m57.hermescontrol.data.model.ExportBoardBody
|
||||||
|
import com.m57.hermescontrol.data.model.ImportBoardBody
|
||||||
import com.m57.hermescontrol.data.model.KanbanBoardResponse
|
import com.m57.hermescontrol.data.model.KanbanBoardResponse
|
||||||
import com.m57.hermescontrol.data.model.KanbanBoardsResponse
|
import com.m57.hermescontrol.data.model.KanbanBoardsResponse
|
||||||
import com.m57.hermescontrol.data.model.KanbanProfilesResponse
|
import com.m57.hermescontrol.data.model.KanbanProfilesResponse
|
||||||
@@ -77,6 +81,17 @@ interface KanbanApiService {
|
|||||||
@Path("slug") slug: String,
|
@Path("slug") slug: String,
|
||||||
): Response<Unit>
|
): Response<Unit>
|
||||||
|
|
||||||
|
@POST("api/plugins/kanban/boards/{slug}/export")
|
||||||
|
suspend fun exportBoard(
|
||||||
|
@Path("slug") slug: String,
|
||||||
|
@Body body: ExportBoardBody = ExportBoardBody(),
|
||||||
|
): Response<BoardExportResult>
|
||||||
|
|
||||||
|
@POST("api/plugins/kanban/boards/import")
|
||||||
|
suspend fun importBoard(
|
||||||
|
@Body body: ImportBoardBody,
|
||||||
|
): Response<BoardImportResult>
|
||||||
|
|
||||||
@GET("api/plugins/kanban/tasks/{id}")
|
@GET("api/plugins/kanban/tasks/{id}")
|
||||||
suspend fun getTask(
|
suspend fun getTask(
|
||||||
@Path("id") taskId: String,
|
@Path("id") taskId: String,
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.m57.hermescontrol.data.repository
|
||||||
|
|
||||||
|
import com.m57.hermescontrol.data.remote.KanbanApiService
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
interface KanbanDispatcherNudger {
|
||||||
|
fun scheduleNudge(board: String?)
|
||||||
|
}
|
||||||
|
|
||||||
|
class DefaultKanbanDispatcherNudger(
|
||||||
|
private val apiProvider: () -> KanbanApiService,
|
||||||
|
private val scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()),
|
||||||
|
private val debounceMs: Long = 400L,
|
||||||
|
) : KanbanDispatcherNudger {
|
||||||
|
private val lock = Any()
|
||||||
|
private val boardJobs = mutableMapOf<String?, Job>()
|
||||||
|
|
||||||
|
override fun scheduleNudge(board: String?) {
|
||||||
|
synchronized(lock) {
|
||||||
|
boardJobs[board]?.cancel()
|
||||||
|
boardJobs[board] =
|
||||||
|
scope.launch {
|
||||||
|
delay(debounceMs)
|
||||||
|
runCatching {
|
||||||
|
apiProvider().nudgeDispatcher(board)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@ package com.m57.hermescontrol.data.repository
|
|||||||
|
|
||||||
import com.m57.hermescontrol.data.model.AttachmentUploadResponse
|
import com.m57.hermescontrol.data.model.AttachmentUploadResponse
|
||||||
import com.m57.hermescontrol.data.model.AutoDescribeResponse
|
import com.m57.hermescontrol.data.model.AutoDescribeResponse
|
||||||
|
import com.m57.hermescontrol.data.model.BoardExportResult
|
||||||
|
import com.m57.hermescontrol.data.model.BoardImportResult
|
||||||
import com.m57.hermescontrol.data.model.BulkTasksBody
|
import com.m57.hermescontrol.data.model.BulkTasksBody
|
||||||
import com.m57.hermescontrol.data.model.BulkTasksResponse
|
import com.m57.hermescontrol.data.model.BulkTasksResponse
|
||||||
import com.m57.hermescontrol.data.model.CreateBoardBody
|
import com.m57.hermescontrol.data.model.CreateBoardBody
|
||||||
@@ -10,6 +12,8 @@ import com.m57.hermescontrol.data.model.CreateTaskBody
|
|||||||
import com.m57.hermescontrol.data.model.CreateTaskResponse
|
import com.m57.hermescontrol.data.model.CreateTaskResponse
|
||||||
import com.m57.hermescontrol.data.model.DeleteBoardResponse
|
import com.m57.hermescontrol.data.model.DeleteBoardResponse
|
||||||
import com.m57.hermescontrol.data.model.DispatchResult
|
import com.m57.hermescontrol.data.model.DispatchResult
|
||||||
|
import com.m57.hermescontrol.data.model.ExportBoardBody
|
||||||
|
import com.m57.hermescontrol.data.model.ImportBoardBody
|
||||||
import com.m57.hermescontrol.data.model.KanbanBoardResponse
|
import com.m57.hermescontrol.data.model.KanbanBoardResponse
|
||||||
import com.m57.hermescontrol.data.model.KanbanBoardsResponse
|
import com.m57.hermescontrol.data.model.KanbanBoardsResponse
|
||||||
import com.m57.hermescontrol.data.model.KanbanProfilesResponse
|
import com.m57.hermescontrol.data.model.KanbanProfilesResponse
|
||||||
@@ -58,6 +62,13 @@ interface KanbanRepository {
|
|||||||
delete: Boolean = false,
|
delete: Boolean = false,
|
||||||
): NetworkResult<DeleteBoardResponse>
|
): NetworkResult<DeleteBoardResponse>
|
||||||
|
|
||||||
|
suspend fun exportBoard(
|
||||||
|
slug: String,
|
||||||
|
body: ExportBoardBody = ExportBoardBody(),
|
||||||
|
): NetworkResult<BoardExportResult>
|
||||||
|
|
||||||
|
suspend fun importBoard(body: ImportBoardBody): NetworkResult<BoardImportResult>
|
||||||
|
|
||||||
suspend fun getTask(
|
suspend fun getTask(
|
||||||
taskId: String,
|
taskId: String,
|
||||||
board: String?,
|
board: String?,
|
||||||
@@ -151,6 +162,7 @@ interface KanbanRepository {
|
|||||||
class KanbanRepositoryImpl(
|
class KanbanRepositoryImpl(
|
||||||
private val apiProvider: () -> KanbanApiService = { ApiClient.kanbanApi },
|
private val apiProvider: () -> KanbanApiService = { ApiClient.kanbanApi },
|
||||||
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
|
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
|
||||||
|
private val nudger: KanbanDispatcherNudger = DefaultKanbanDispatcherNudger(apiProvider),
|
||||||
) : KanbanRepository {
|
) : KanbanRepository {
|
||||||
override suspend fun getBoards(includeArchived: Boolean): NetworkResult<KanbanBoardsResponse> =
|
override suspend fun getBoards(includeArchived: Boolean): NetworkResult<KanbanBoardsResponse> =
|
||||||
withContext(ioDispatcher) {
|
withContext(ioDispatcher) {
|
||||||
@@ -187,6 +199,19 @@ class KanbanRepositoryImpl(
|
|||||||
safeApiCall { apiProvider().deleteBoard(slug, delete) }
|
safeApiCall { apiProvider().deleteBoard(slug, delete) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun exportBoard(
|
||||||
|
slug: String,
|
||||||
|
body: ExportBoardBody,
|
||||||
|
): NetworkResult<BoardExportResult> =
|
||||||
|
withContext(ioDispatcher) {
|
||||||
|
safeApiCall { apiProvider().exportBoard(slug, body) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun importBoard(body: ImportBoardBody): NetworkResult<BoardImportResult> =
|
||||||
|
withContext(ioDispatcher) {
|
||||||
|
safeApiCall { apiProvider().importBoard(body) }
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun getTask(
|
override suspend fun getTask(
|
||||||
taskId: String,
|
taskId: String,
|
||||||
board: String?,
|
board: String?,
|
||||||
@@ -200,7 +225,11 @@ class KanbanRepositoryImpl(
|
|||||||
body: CreateTaskBody,
|
body: CreateTaskBody,
|
||||||
): NetworkResult<CreateTaskResponse> =
|
): NetworkResult<CreateTaskResponse> =
|
||||||
withContext(ioDispatcher) {
|
withContext(ioDispatcher) {
|
||||||
safeApiCall { apiProvider().createTask(board = board, body = body) }
|
val result = safeApiCall { apiProvider().createTask(board = board, body = body) }
|
||||||
|
if (result is NetworkResult.Success) {
|
||||||
|
nudger.scheduleNudge(board)
|
||||||
|
}
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun updateTask(
|
override suspend fun updateTask(
|
||||||
@@ -209,7 +238,11 @@ class KanbanRepositoryImpl(
|
|||||||
body: UpdateTaskBody,
|
body: UpdateTaskBody,
|
||||||
): NetworkResult<UpdateTaskResponse> =
|
): NetworkResult<UpdateTaskResponse> =
|
||||||
withContext(ioDispatcher) {
|
withContext(ioDispatcher) {
|
||||||
safeApiCall { apiProvider().updateTask(taskId = taskId, board = board, body = body) }
|
val result = safeApiCall { apiProvider().updateTask(taskId = taskId, board = board, body = body) }
|
||||||
|
if (result is NetworkResult.Success) {
|
||||||
|
nudger.scheduleNudge(board)
|
||||||
|
}
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun deleteTask(
|
override suspend fun deleteTask(
|
||||||
@@ -217,7 +250,11 @@ class KanbanRepositoryImpl(
|
|||||||
board: String?,
|
board: String?,
|
||||||
): NetworkResult<Unit> =
|
): NetworkResult<Unit> =
|
||||||
withContext(ioDispatcher) {
|
withContext(ioDispatcher) {
|
||||||
safeApiCall { apiProvider().deleteTask(taskId = taskId, board = board) }
|
val result = safeApiCall { apiProvider().deleteTask(taskId = taskId, board = board) }
|
||||||
|
if (result is NetworkResult.Success) {
|
||||||
|
nudger.scheduleNudge(board)
|
||||||
|
}
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun bulkTasks(
|
override suspend fun bulkTasks(
|
||||||
@@ -225,7 +262,11 @@ class KanbanRepositoryImpl(
|
|||||||
body: BulkTasksBody,
|
body: BulkTasksBody,
|
||||||
): NetworkResult<BulkTasksResponse> =
|
): NetworkResult<BulkTasksResponse> =
|
||||||
withContext(ioDispatcher) {
|
withContext(ioDispatcher) {
|
||||||
safeApiCall { apiProvider().bulkTasks(board = board, body = body) }
|
val result = safeApiCall { apiProvider().bulkTasks(board = board, body = body) }
|
||||||
|
if (result is NetworkResult.Success) {
|
||||||
|
nudger.scheduleNudge(board)
|
||||||
|
}
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun addComment(
|
override suspend fun addComment(
|
||||||
@@ -250,13 +291,18 @@ class KanbanRepositoryImpl(
|
|||||||
reclaimFirst: Boolean,
|
reclaimFirst: Boolean,
|
||||||
): NetworkResult<ReassignTaskResponse> =
|
): NetworkResult<ReassignTaskResponse> =
|
||||||
withContext(ioDispatcher) {
|
withContext(ioDispatcher) {
|
||||||
safeApiCall {
|
val result =
|
||||||
apiProvider().reassignTask(
|
safeApiCall {
|
||||||
taskId = taskId,
|
apiProvider().reassignTask(
|
||||||
board = board,
|
taskId = taskId,
|
||||||
body = ReassignTaskBody(profile = profile, reclaimFirst = reclaimFirst),
|
board = board,
|
||||||
)
|
body = ReassignTaskBody(profile = profile, reclaimFirst = reclaimFirst),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (result is NetworkResult.Success) {
|
||||||
|
nudger.scheduleNudge(board)
|
||||||
}
|
}
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun reclaimTask(
|
override suspend fun reclaimTask(
|
||||||
@@ -265,13 +311,18 @@ class KanbanRepositoryImpl(
|
|||||||
reason: String?,
|
reason: String?,
|
||||||
): NetworkResult<ReclaimTaskResponse> =
|
): NetworkResult<ReclaimTaskResponse> =
|
||||||
withContext(ioDispatcher) {
|
withContext(ioDispatcher) {
|
||||||
safeApiCall {
|
val result =
|
||||||
apiProvider().reclaimTask(
|
safeApiCall {
|
||||||
taskId = taskId,
|
apiProvider().reclaimTask(
|
||||||
board = board,
|
taskId = taskId,
|
||||||
body = ReclaimTaskBody(reason = reason),
|
board = board,
|
||||||
)
|
body = ReclaimTaskBody(reason = reason),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (result is NetworkResult.Success) {
|
||||||
|
nudger.scheduleNudge(board)
|
||||||
}
|
}
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getTaskLog(
|
override suspend fun getTaskLog(
|
||||||
|
|||||||
Reference in New Issue
Block a user