mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[gitlab] IJPL-82619: implement file uploading
* `GitLabProject` now has `GitLabProjectFileUploader` to upload files via Gitlab api * added the uploader to AbstractGitLabNoteEditingViewModel GitOrigin-RevId: 90fdb310d87a3dfbc9c8953f3ed9846dde06e9c1
This commit is contained in:
committed by
intellij-monorepo-bot
parent
744d7e56b8
commit
18f683ca1b
@@ -549,6 +549,7 @@ f:com.intellij.collaboration.api.json.JsonHttpApiHelperKt
|
||||
- getProject():com.intellij.openapi.project.Project
|
||||
- f:getState():kotlinx.coroutines.flow.StateFlow
|
||||
- f:getText():kotlinx.coroutines.flow.MutableStateFlow
|
||||
- pf:launchTask(kotlin.jvm.functions.Function1):V
|
||||
- f:requestFocus():V
|
||||
- pf:submit(kotlin.jvm.functions.Function2):V
|
||||
*:com.intellij.collaboration.ui.codereview.comment.CodeReviewTextEditingViewModel
|
||||
|
||||
+11
@@ -53,6 +53,17 @@ abstract class CodeReviewSubmittableTextViewModelBase(
|
||||
}
|
||||
}
|
||||
|
||||
protected fun launchTask(task: suspend () -> Unit) {
|
||||
taskLauncher.launch {
|
||||
_state.value = ComputedResult.loading()
|
||||
try {
|
||||
task()
|
||||
} finally {
|
||||
_state.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected fun submit(submitter: suspend (String) -> Unit) {
|
||||
taskLauncher.launch {
|
||||
val newText = text.first()
|
||||
|
||||
@@ -215,6 +215,15 @@ snippet.create.error.some-empty-contents.tooltip=Empty files are excluded from s
|
||||
action.GitLab.Review.Upload.File.text=Upload File\u2026
|
||||
action.GitLab.Review.Upload.File.description=Upload a file to GitLab
|
||||
|
||||
## Progress and notification after clicking 'Ok' in a dialog about uploading a file
|
||||
upload.file.action.progress=Uploading file\u2026
|
||||
upload.file.action.error.title=Uploading file failed
|
||||
|
||||
## Upload file dialog messages
|
||||
upload.file.dialog.title=Upload File
|
||||
upload.file.dialog.file.not.found=File not found
|
||||
upload.file.dialog.not.readable=File is not readable
|
||||
|
||||
### Share Project in dialog
|
||||
action.GitLab.Share.text=Share Project on GitLab
|
||||
# suppress inspection "UnusedProperty" (should be used by VCS Toolbar in New UI)
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.plugins.gitlab.api.dto
|
||||
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import org.jetbrains.plugins.gitlab.api.SinceGitLab
|
||||
|
||||
/**
|
||||
* Uploaded file data.
|
||||
*
|
||||
* @see <a href="https://docs.gitlab.com/api/project_markdown_uploads/">Markdown uploads API</a>
|
||||
*/
|
||||
@SinceGitLab("15.10")
|
||||
class GitLabUploadRestDTO(
|
||||
@SinceGitLab("17.3") val id: String,
|
||||
val alt: @NlsSafe String,
|
||||
val url: @NlsSafe String,
|
||||
@SinceGitLab("17.1") val fullPath: @NlsSafe String,
|
||||
val markdown: @NlsSafe String,
|
||||
)
|
||||
+19
@@ -20,8 +20,11 @@ import org.jetbrains.plugins.gitlab.api.request.*
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.api.dto.GitLabMergeRequestDTO
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.api.request.loadMergeRequest
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.api.request.mergeRequestSetReviewers
|
||||
import org.jetbrains.plugins.gitlab.upload.markdownUploadFile
|
||||
import org.jetbrains.plugins.gitlab.util.GitLabProjectMapping
|
||||
import org.jetbrains.plugins.gitlab.util.GitLabRegistry
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
private val LOG = logger<GitLabProject>()
|
||||
@@ -52,6 +55,9 @@ interface GitLabProject {
|
||||
suspend fun adjustReviewers(mrIid: String, reviewers: List<GitLabUserDTO>): GitLabMergeRequestDTO
|
||||
|
||||
fun reloadData()
|
||||
|
||||
suspend fun uploadFile(path: Path): String
|
||||
fun canUploadFile(): Boolean
|
||||
}
|
||||
|
||||
@CodeReviewDomainEntity
|
||||
@@ -153,6 +159,19 @@ class GitLabLazyProject(
|
||||
_dataReloadSignal.tryEmit(Unit)
|
||||
}
|
||||
|
||||
override suspend fun uploadFile(path: Path): String {
|
||||
val uploadRestDTO = withContext(cs.coroutineContext + Dispatchers.IO) {
|
||||
Files.newInputStream(path).use {
|
||||
api.rest.markdownUploadFile(projectCoordinates, path).body()
|
||||
}
|
||||
}
|
||||
return uploadRestDTO.markdown
|
||||
}
|
||||
|
||||
override fun canUploadFile(): Boolean {
|
||||
return glMetadata != null && glMetadata.version >= GitLabVersion(15, 10)
|
||||
}
|
||||
|
||||
private suspend fun getAllowsMultipleAssigneesPropertyFromNamespacePlan() = try {
|
||||
api.rest.getProjectNamespace(projectMapping.repository.projectPath.owner).body()?.plan?.let {
|
||||
it != GitLabPlan.FREE
|
||||
|
||||
+3
-3
@@ -72,7 +72,7 @@ fun GitLabMergeRequestDiscussionsViewModels.NewDiscussionPosition.mapToLocation(
|
||||
internal class GitLabMergeRequestDiscussionsViewModelsImpl(
|
||||
private val project: Project,
|
||||
parentCs: CoroutineScope,
|
||||
projectData: GitLabProject,
|
||||
private val projectData: GitLabProject,
|
||||
private val currentUser: GitLabUserDTO,
|
||||
private val mergeRequest: GitLabMergeRequest,
|
||||
htmlConverter: GitLabMarkdownToHtmlConverter,
|
||||
@@ -93,7 +93,7 @@ internal class GitLabMergeRequestDiscussionsViewModelsImpl(
|
||||
.transformConsecutiveSuccesses {
|
||||
mapFiltered { it.discussionId == null }
|
||||
.mapStatefulToStateful {
|
||||
GitLabMergeRequestStandaloneDraftNoteViewModelBase(project, this, it, mergeRequest, htmlConverter)
|
||||
GitLabMergeRequestStandaloneDraftNoteViewModelBase(project, this, it, mergeRequest, projectData, htmlConverter)
|
||||
}
|
||||
}
|
||||
.stateInNow(cs, ComputedResult.loading())
|
||||
@@ -149,7 +149,7 @@ internal class GitLabMergeRequestDiscussionsViewModelsImpl(
|
||||
override fun requestNewDiscussion(position: GitLabMergeRequestDiscussionsViewModels.NewDiscussionPosition, focus: Boolean) {
|
||||
_newDiscussions.updateAndGet { currentNewDiscussions ->
|
||||
if (!currentNewDiscussions.containsKey(position) && mergeRequest.canAddNotes) {
|
||||
val vm = GitLabNoteEditingViewModel.forNewDiffNote(cs, project, mergeRequest, currentUser, position.position).apply {
|
||||
val vm = GitLabNoteEditingViewModel.forNewDiffNote(cs, project, projectData, mergeRequest, currentUser, position.position).apply {
|
||||
onDoneIn(cs) {
|
||||
cancelNewDiscussion(position)
|
||||
}
|
||||
|
||||
+1
-1
@@ -100,7 +100,7 @@ class GitLabMergeRequestTimelineDiscussionViewModelImpl(
|
||||
override val canCreateReplies: StateFlow<Boolean> = discussion.canAddNotes.stateIn(cs, SharingStarted.Eagerly, false)
|
||||
override val replyVm: StateFlow<NewGitLabNoteViewModel?> =
|
||||
discussion.canAddNotes.mapScoped { canAddNotes ->
|
||||
if (canAddNotes) GitLabNoteEditingViewModel.forReplyNote(this, project, discussion, currentUser) else null
|
||||
if (canAddNotes) GitLabNoteEditingViewModel.forReplyNote(this, project, projectData, discussion, currentUser) else null
|
||||
}.stateIn(cs, SharingStarted.Eagerly, null)
|
||||
|
||||
override val diffVm: Flow<GitLabDiscussionDiffViewModel?> =
|
||||
|
||||
+2
-1
@@ -107,6 +107,7 @@ sealed interface GitLabMergeRequestTimelineItemViewModel {
|
||||
project: Project,
|
||||
parentCs: CoroutineScope,
|
||||
mr: GitLabMergeRequest,
|
||||
projectData: GitLabProject,
|
||||
note: GitLabMergeRequestNote,
|
||||
htmlConverter: GitLabMarkdownToHtmlConverter
|
||||
) : GitLabMergeRequestTimelineItemViewModel, GitLabNoteViewModel {
|
||||
@@ -119,7 +120,7 @@ sealed interface GitLabMergeRequestTimelineItemViewModel {
|
||||
override val serverUrl: URL = mr.glProject.serverPath.toURL()
|
||||
|
||||
override val actionsVm: GitLabNoteAdminActionsViewModel? =
|
||||
if (note is MutableGitLabNote && note.canAdmin) GitLabNoteAdminActionsViewModelImpl(cs, project, note) else null
|
||||
if (note is MutableGitLabNote && note.canAdmin) GitLabNoteAdminActionsViewModelImpl(cs, project, projectData, note) else null
|
||||
override val reactionsVm: GitLabReactionsViewModel? = null
|
||||
|
||||
override val body: StateFlow<String> = note.body
|
||||
|
||||
+2
-2
@@ -74,7 +74,7 @@ internal class LoadAllGitLabMergeRequestTimelineViewModel(
|
||||
|
||||
override val newNoteVm: NewGitLabNoteViewModel? =
|
||||
if (mergeRequest.canAddNotes) {
|
||||
GitLabNoteEditingViewModel.forNewNote(cs, project, mergeRequest, currentUser).apply {
|
||||
GitLabNoteEditingViewModel.forNewNote(cs, project, projectData, mergeRequest, currentUser).apply {
|
||||
onDoneIn(cs) {
|
||||
text.value = ""
|
||||
}
|
||||
@@ -153,7 +153,7 @@ internal class LoadAllGitLabMergeRequestTimelineViewModel(
|
||||
handleDiffRequests(it.diffVm, _diffRequests::emit)
|
||||
}
|
||||
is GitLabMergeRequestTimelineItem.DraftNote ->
|
||||
GitLabMergeRequestTimelineItemViewModel.DraftNote(project, cs, mergeRequest, item.note, htmlConverter).also {
|
||||
GitLabMergeRequestTimelineItemViewModel.DraftNote(project, cs, mergeRequest, projectData, item.note, htmlConverter).also {
|
||||
handleDiffRequests(it.diffVm, _diffRequests::emit)
|
||||
}
|
||||
}
|
||||
|
||||
+8
-3
@@ -3,8 +3,13 @@ package org.jetbrains.plugins.gitlab.notification
|
||||
|
||||
import com.intellij.notification.impl.NotificationIdsHolder
|
||||
|
||||
|
||||
internal object GitLabNotificationIds {
|
||||
const val GL_NOTIFICATION_UPLOAD_FILE_ERROR = "gitlab.upload.file.action.error"
|
||||
}
|
||||
|
||||
class GitLabNotificationIdsHolder : NotificationIdsHolder {
|
||||
override fun getNotificationIds(): List<String> {
|
||||
return listOf()
|
||||
}
|
||||
override fun getNotificationIds(): List<String> = listOf(
|
||||
GitLabNotificationIds.GL_NOTIFICATION_UPLOAD_FILE_ERROR,
|
||||
)
|
||||
}
|
||||
+4
-4
@@ -3,8 +3,6 @@ package org.jetbrains.plugins.gitlab.ui.comment
|
||||
|
||||
import com.intellij.collaboration.ui.EditableComponentFactory
|
||||
import com.intellij.collaboration.ui.codereview.comment.CodeReviewCommentTextFieldFactory
|
||||
import com.intellij.collaboration.ui.codereview.comment.CodeReviewSubmittableTextViewModel
|
||||
import com.intellij.collaboration.ui.codereview.comment.CodeReviewTextEditingViewModel
|
||||
import com.intellij.collaboration.ui.codereview.comment.CommentInputActionsComponentFactory
|
||||
import com.intellij.collaboration.ui.codereview.comment.createEditActionsConfig
|
||||
import com.intellij.collaboration.ui.codereview.timeline.comment.CommentTextFieldFactory
|
||||
@@ -30,15 +28,17 @@ object GitLabCodeReviewCommentTextFieldFactory {
|
||||
icon: CommentTextFieldFactory.IconConfig? = null,
|
||||
): JComponent {
|
||||
|
||||
val canUploadFile = Registry.`is`("gitlab.merge.requests.file.upload.enabled") && vm.canUploadFile()
|
||||
|
||||
val uploadFileAction = object : AnAction(GitLabBundle.message("action.GitLab.Review.Upload.File.text"),
|
||||
GitLabBundle.message("action.GitLab.Review.Upload.File.description"),
|
||||
AllIcons.Actions.Upload) {
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
TODO("Not yet implemented")
|
||||
vm.uploadFile(null)
|
||||
}
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
e.presentation.isEnabledAndVisible = Registry.`is`("gitlab.merge.requests.file.upload.enabled")
|
||||
e.presentation.isEnabledAndVisible = canUploadFile
|
||||
}
|
||||
|
||||
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
|
||||
|
||||
+3
-1
@@ -11,6 +11,7 @@ import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.jetbrains.plugins.gitlab.api.dto.GitLabUserDTO
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabDiscussion
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabProject
|
||||
|
||||
interface GitLabDiscussionReplyViewModel {
|
||||
|
||||
@@ -24,6 +25,7 @@ class GitLabDiscussionReplyViewModelImpl(
|
||||
parentCs: CoroutineScope,
|
||||
project: Project,
|
||||
currentUser: GitLabUserDTO,
|
||||
projectData: GitLabProject,
|
||||
discussion: GitLabDiscussion
|
||||
) : GitLabDiscussionReplyViewModel {
|
||||
|
||||
@@ -33,7 +35,7 @@ class GitLabDiscussionReplyViewModelImpl(
|
||||
override val newNoteVm: Flow<NewGitLabNoteViewModel?> = isWriting.mapScoped {
|
||||
if (!it) return@mapScoped null
|
||||
val cs = this
|
||||
GitLabNoteEditingViewModel.forReplyNote(cs, project, discussion, currentUser).apply {
|
||||
GitLabNoteEditingViewModel.forReplyNote(cs, project, projectData, discussion, currentUser).apply {
|
||||
onDoneIn(cs) {
|
||||
text.value = ""
|
||||
}
|
||||
|
||||
+3
-2
@@ -64,7 +64,7 @@ internal class GitLabMergeRequestDiscussionViewModelBase(
|
||||
|
||||
override val replyVm: StateFlow<GitLabDiscussionReplyViewModel?> =
|
||||
discussion.canAddNotes.mapScoped { canAddNotes ->
|
||||
if (canAddNotes) GitLabDiscussionReplyViewModelImpl(this, project, currentUser, discussion)
|
||||
if (canAddNotes) GitLabDiscussionReplyViewModelImpl(this, project, currentUser, projectData, discussion)
|
||||
else null
|
||||
}.stateInNow(cs, null)
|
||||
|
||||
@@ -118,6 +118,7 @@ class GitLabMergeRequestStandaloneDraftNoteViewModelBase internal constructor(
|
||||
parentCs: CoroutineScope,
|
||||
note: GitLabMergeRequestDraftNote,
|
||||
mr: GitLabMergeRequest,
|
||||
projectData: GitLabProject,
|
||||
htmlConverter: GitLabMarkdownToHtmlConverter,
|
||||
) : GitLabNoteViewModel {
|
||||
|
||||
@@ -130,7 +131,7 @@ class GitLabMergeRequestStandaloneDraftNoteViewModelBase internal constructor(
|
||||
override val serverUrl: URL = mr.glProject.serverPath.toURL()
|
||||
|
||||
override val actionsVm: GitLabNoteAdminActionsViewModel? =
|
||||
if (note.canAdmin) GitLabNoteAdminActionsViewModelImpl(cs, project, note) else null
|
||||
if (note.canAdmin) GitLabNoteAdminActionsViewModelImpl(cs, project, projectData, note) else null
|
||||
override val reactionsVm: GitLabReactionsViewModel? = null
|
||||
|
||||
override val body: StateFlow<String> = note.body
|
||||
|
||||
+4
-2
@@ -7,6 +7,7 @@ import com.intellij.openapi.project.Project
|
||||
import com.intellij.platform.util.coroutines.childScope
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabProject
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.data.MutableGitLabNote
|
||||
|
||||
interface GitLabNoteAdminActionsViewModel {
|
||||
@@ -41,7 +42,8 @@ private val LOG = logger<GitLabNoteAdminActionsViewModel>()
|
||||
class GitLabNoteAdminActionsViewModelImpl(
|
||||
parentCs: CoroutineScope,
|
||||
private val project: Project,
|
||||
private val note: MutableGitLabNote
|
||||
projectData: GitLabProject,
|
||||
private val note: MutableGitLabNote,
|
||||
) : GitLabNoteAdminActionsViewModel {
|
||||
|
||||
private val cs = parentCs.childScope()
|
||||
@@ -53,7 +55,7 @@ class GitLabNoteAdminActionsViewModelImpl(
|
||||
if (editing) {
|
||||
coroutineScope {
|
||||
val cs = this@coroutineScope
|
||||
val editVm = GitLabNoteEditingViewModel.forExistingNote(cs, project, note) {
|
||||
val editVm = GitLabNoteEditingViewModel.forExistingNote(cs, project, projectData, note) {
|
||||
stopEditing()
|
||||
}
|
||||
editVm.requestFocus()
|
||||
|
||||
+38
-11
@@ -15,6 +15,7 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.annotations.Nls
|
||||
import org.jetbrains.plugins.gitlab.api.dto.GitLabUserDTO
|
||||
@@ -22,12 +23,18 @@ import org.jetbrains.plugins.gitlab.mergerequest.GitLabMergeRequestsPreferences
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabDiscussion
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabMergeRequest
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabMergeRequestNewDiscussionPosition
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabProject
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.data.MutableGitLabNote
|
||||
import org.jetbrains.plugins.gitlab.upload.GitLabUploadFileUtil
|
||||
import org.jetbrains.plugins.gitlab.util.GitLabStatistics
|
||||
import java.nio.file.Path
|
||||
import java.util.*
|
||||
import javax.swing.Action
|
||||
|
||||
interface GitLabCodeReviewSubmittableTextViewModel: CodeReviewSubmittableTextViewModel
|
||||
interface GitLabCodeReviewSubmittableTextViewModel : CodeReviewSubmittableTextViewModel {
|
||||
fun uploadFile(path: Path?)
|
||||
fun canUploadFile(): Boolean
|
||||
}
|
||||
|
||||
interface GitLabNoteEditingViewModel : GitLabCodeReviewSubmittableTextViewModel {
|
||||
suspend fun destroy()
|
||||
@@ -36,53 +43,69 @@ interface GitLabNoteEditingViewModel : GitLabCodeReviewSubmittableTextViewModel
|
||||
internal fun forExistingNote(
|
||||
parentCs: CoroutineScope,
|
||||
project: Project,
|
||||
projectData: GitLabProject,
|
||||
note: MutableGitLabNote,
|
||||
onStopEditing: () -> Unit
|
||||
): GitLabCodeReviewTextEditingViewModel =
|
||||
GitLabNoteEditingViewModelImpl(project, parentCs, note, onStopEditing)
|
||||
GitLabNoteEditingViewModelImpl(project, parentCs, projectData, note, onStopEditing)
|
||||
|
||||
internal fun forNewNote(
|
||||
parentCs: CoroutineScope,
|
||||
project: Project,
|
||||
projectData: GitLabProject,
|
||||
mergeRequest: GitLabMergeRequest,
|
||||
currentUser: GitLabUserDTO
|
||||
): NewGitLabNoteViewModel =
|
||||
NewStandaloneGitLabNoteViewModel(project, parentCs, "", mergeRequest, currentUser)
|
||||
NewStandaloneGitLabNoteViewModel(project, parentCs, "", projectData, mergeRequest, currentUser)
|
||||
|
||||
internal fun forNewDiffNote(
|
||||
parentCs: CoroutineScope,
|
||||
project: Project,
|
||||
projectData: GitLabProject,
|
||||
mergeRequest: GitLabMergeRequest,
|
||||
currentUser: GitLabUserDTO,
|
||||
position: GitLabMergeRequestNewDiscussionPosition
|
||||
): NewGitLabNoteViewModel =
|
||||
NewDiffGitLabNoteViewModel(project, parentCs, "", mergeRequest, currentUser, position)
|
||||
NewDiffGitLabNoteViewModel(project, parentCs, "", projectData, mergeRequest, currentUser, position)
|
||||
|
||||
internal fun forReplyNote(
|
||||
parentCs: CoroutineScope,
|
||||
project: Project,
|
||||
projectData: GitLabProject,
|
||||
discussion: GitLabDiscussion,
|
||||
currentUser: GitLabUserDTO
|
||||
): NewGitLabNoteViewModel =
|
||||
NewReplyGitLabNoteViewModel(project, parentCs, "", discussion, currentUser)
|
||||
NewReplyGitLabNoteViewModel(project, parentCs, "", projectData, discussion, currentUser)
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AbstractGitLabNoteEditingViewModel(
|
||||
override val project: Project,
|
||||
val projectData: GitLabProject,
|
||||
parentCs: CoroutineScope,
|
||||
initialText: String
|
||||
) : CodeReviewSubmittableTextViewModelBase(project, parentCs, initialText), GitLabNoteEditingViewModel {
|
||||
override suspend fun destroy() = cs.cancelAndJoinSilently()
|
||||
|
||||
override fun uploadFile(path: Path?) {
|
||||
launchTask {
|
||||
GitLabUploadFileUtil.uploadFileAndNotify(project, projectData, path)?.let { fileText ->
|
||||
text.update { if (it.isBlank()) fileText else it.trimEnd() + "\n\n" + fileText }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun canUploadFile(): Boolean = projectData.canUploadFile()
|
||||
}
|
||||
|
||||
interface GitLabCodeReviewTextEditingViewModel : GitLabCodeReviewSubmittableTextViewModel, CodeReviewTextEditingViewModel
|
||||
|
||||
private class GitLabNoteEditingViewModelImpl(
|
||||
project: Project, parentCs: CoroutineScope,
|
||||
projectData: GitLabProject,
|
||||
private val note: MutableGitLabNote,
|
||||
private val onStopEditing: () -> Unit,
|
||||
) : AbstractGitLabNoteEditingViewModel(project, parentCs, note.body.value), GitLabCodeReviewTextEditingViewModel {
|
||||
) : AbstractGitLabNoteEditingViewModel(project, projectData, parentCs, note.body.value), GitLabCodeReviewTextEditingViewModel {
|
||||
override fun save() {
|
||||
submit {
|
||||
note.setBody(it)
|
||||
@@ -111,9 +134,10 @@ interface NewGitLabNoteViewModel :
|
||||
private abstract class NewGitLabNoteViewModelBase(
|
||||
project: Project,
|
||||
parentCs: CoroutineScope,
|
||||
projectData: GitLabProject,
|
||||
initialText: String,
|
||||
override val currentUser: GitLabUserDTO
|
||||
) : AbstractGitLabNoteEditingViewModel(project, parentCs, initialText), NewGitLabNoteViewModel {
|
||||
override val currentUser: GitLabUserDTO,
|
||||
) : AbstractGitLabNoteEditingViewModel(project, projectData, parentCs, initialText), NewGitLabNoteViewModel {
|
||||
override val trackingId: String = UUID.randomUUID().toString()
|
||||
|
||||
private val preferences = project.service<GitLabMergeRequestsPreferences>()
|
||||
@@ -145,9 +169,10 @@ private abstract class NewGitLabNoteViewModelBase(
|
||||
private class NewStandaloneGitLabNoteViewModel(project: Project,
|
||||
parentCs: CoroutineScope,
|
||||
initialText: String,
|
||||
projectData: GitLabProject,
|
||||
private val mergeRequest: GitLabMergeRequest,
|
||||
currentUser: GitLabUserDTO)
|
||||
: NewGitLabNoteViewModelBase(project, parentCs, initialText, currentUser) {
|
||||
: NewGitLabNoteViewModelBase(project, parentCs, projectData, initialText, currentUser) {
|
||||
override val canSubmitAsDraft: Boolean = mergeRequest.canAddDraftNotes
|
||||
override suspend fun doSubmit(text: String) = mergeRequest.addNote(text)
|
||||
override suspend fun doSubmitAsDraft(text: String) = mergeRequest.addDraftNote(text)
|
||||
@@ -156,10 +181,11 @@ private class NewStandaloneGitLabNoteViewModel(project: Project,
|
||||
private class NewDiffGitLabNoteViewModel(project: Project,
|
||||
parentCs: CoroutineScope,
|
||||
initialText: String,
|
||||
projectData: GitLabProject,
|
||||
private val mergeRequest: GitLabMergeRequest,
|
||||
currentUser: GitLabUserDTO,
|
||||
private val position: GitLabMergeRequestNewDiscussionPosition)
|
||||
: NewGitLabNoteViewModelBase(project, parentCs, initialText, currentUser) {
|
||||
: NewGitLabNoteViewModelBase(project, parentCs, projectData, initialText, currentUser, ) {
|
||||
override val canSubmitAsDraft: Boolean = mergeRequest.canAddPositionalDraftNotes
|
||||
override suspend fun doSubmit(text: String) = mergeRequest.addNote(position, text)
|
||||
override suspend fun doSubmitAsDraft(text: String) = mergeRequest.addDraftNote(position, text)
|
||||
@@ -168,9 +194,10 @@ private class NewDiffGitLabNoteViewModel(project: Project,
|
||||
private class NewReplyGitLabNoteViewModel(project: Project,
|
||||
parentCs: CoroutineScope,
|
||||
initialText: String,
|
||||
projectData: GitLabProject,
|
||||
private val discussion: GitLabDiscussion,
|
||||
currentUser: GitLabUserDTO)
|
||||
: NewGitLabNoteViewModelBase(project, parentCs, initialText, currentUser) {
|
||||
: NewGitLabNoteViewModelBase(project, parentCs, projectData, initialText, currentUser, ) {
|
||||
override val canSubmitAsDraft: Boolean = discussion.canAddDraftNotes
|
||||
override suspend fun doSubmit(text: String) = discussion.addNote(text)
|
||||
override suspend fun doSubmitAsDraft(text: String) = discussion.addDraftNote(text)
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ class GitLabNoteViewModelImpl(
|
||||
override val serverUrl: URL = projectData.projectMapping.repository.serverPath.toURL()
|
||||
|
||||
override val actionsVm: GitLabNoteAdminActionsViewModel? =
|
||||
if (note is MutableGitLabNote && note.canAdmin) GitLabNoteAdminActionsViewModelImpl(cs, project, note) else null
|
||||
if (note is MutableGitLabNote && note.canAdmin) GitLabNoteAdminActionsViewModelImpl(cs, project, projectData, note) else null
|
||||
override val reactionsVm: GitLabReactionsViewModel? =
|
||||
if (note is GitLabMergeRequestNote && note.canReact) GitLabReactionsViewModelImpl(cs, projectData, note, currentUser) else null
|
||||
|
||||
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.plugins.gitlab.upload
|
||||
|
||||
import com.intellij.openapi.fileChooser.FileChooser
|
||||
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vcs.VcsNotifier
|
||||
import com.intellij.platform.ide.progress.withBackgroundProgress
|
||||
import kotlinx.coroutines.*
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabProject
|
||||
import org.jetbrains.plugins.gitlab.mergerequest.util.localizedMessageOrClassName
|
||||
import org.jetbrains.plugins.gitlab.notification.GitLabNotificationIds.GL_NOTIFICATION_UPLOAD_FILE_ERROR
|
||||
import org.jetbrains.plugins.gitlab.util.GitLabBundle.message
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.AccessDeniedException
|
||||
import java.nio.file.NoSuchFileException
|
||||
|
||||
|
||||
internal object GitLabUploadFileUtil {
|
||||
|
||||
suspend fun uploadFileAndNotify(
|
||||
project: Project,
|
||||
projectData: GitLabProject,
|
||||
path: Path?,
|
||||
): String? {
|
||||
val resultingPath = path ?: showDialog(project) ?: return null
|
||||
|
||||
return withContext(Dispatchers.IO) {
|
||||
uploadFileAndNotify(project) { projectData.uploadFile(resultingPath) }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun uploadFileAndNotify(
|
||||
project: Project,
|
||||
uploader: suspend () -> String,
|
||||
): String? {
|
||||
try {
|
||||
return withBackgroundProgress(project, message("upload.file.action.progress"), true) {
|
||||
uploader()
|
||||
}
|
||||
}
|
||||
catch (e: CancellationException) {
|
||||
throw e
|
||||
}
|
||||
catch (_: NoSuchFileException) {
|
||||
VcsNotifier.getInstance(project).notifyError(GL_NOTIFICATION_UPLOAD_FILE_ERROR, message("upload.file.action.error.title"),
|
||||
message("upload.file.dialog.file.not.found"))
|
||||
}
|
||||
catch (_: AccessDeniedException) {
|
||||
VcsNotifier.getInstance(project).notifyError(GL_NOTIFICATION_UPLOAD_FILE_ERROR, message("upload.file.action.error.title"),
|
||||
message("upload.file.dialog.not.readable"))
|
||||
}
|
||||
catch (e: Exception) {
|
||||
VcsNotifier.getInstance(project).notifyError(GL_NOTIFICATION_UPLOAD_FILE_ERROR, message("upload.file.action.error.title"),
|
||||
e.localizedMessageOrClassName())
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private suspend fun showDialog(project: Project): Path? {
|
||||
return withContext(Dispatchers.Main) {
|
||||
FileChooser.chooseFile(FileChooserDescriptorFactory.singleFile().withTitle(message("upload.file.dialog.title")), project, null)?.toNioPath()
|
||||
}
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.plugins.gitlab.upload
|
||||
|
||||
import com.intellij.collaboration.api.json.loadJsonValue
|
||||
import com.intellij.collaboration.util.resolveRelative
|
||||
import org.jetbrains.plugins.gitlab.api.GitLabApi
|
||||
import org.jetbrains.plugins.gitlab.api.GitLabProjectCoordinates
|
||||
import org.jetbrains.plugins.gitlab.api.SinceGitLab
|
||||
import org.jetbrains.plugins.gitlab.api.dto.GitLabUploadRestDTO
|
||||
import org.jetbrains.plugins.gitlab.api.restApiUri
|
||||
import java.net.http.HttpRequest.BodyPublishers
|
||||
import java.net.http.HttpResponse
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.util.*
|
||||
|
||||
|
||||
@SinceGitLab("15.10")
|
||||
suspend fun GitLabApi.Rest.markdownUploadFile(
|
||||
project: GitLabProjectCoordinates,
|
||||
path: Path,
|
||||
): HttpResponse<out GitLabUploadRestDTO> {
|
||||
val uri = project.restApiUri.resolveRelative("uploads")
|
||||
val filename = path.fileName.toString()
|
||||
|
||||
val mimeType = Files.probeContentType(path) ?: "application/octet-stream"
|
||||
|
||||
val boundary = "FormBoundary" + UUID.randomUUID()
|
||||
val boundaryStart = "--$boundary\r\n" +
|
||||
"Content-Disposition: form-data; name=\"file\"; filename=\"$filename\"\r\n" +
|
||||
"Content-Type: $mimeType\r\n\r\n"
|
||||
val boundaryEnd = "\r\n--$boundary--\r\n"
|
||||
|
||||
val bodyPublisher = BodyPublishers.concat(
|
||||
BodyPublishers.ofString(boundaryStart),
|
||||
BodyPublishers.ofInputStream { Files.newInputStream(path) },
|
||||
BodyPublishers.ofString(boundaryEnd)
|
||||
)
|
||||
|
||||
val httpRequest = request(uri)
|
||||
.POST(bodyPublisher)
|
||||
.header("Content-Type", "multipart/form-data; boundary=$boundary")
|
||||
.build()
|
||||
|
||||
return loadJsonValue(httpRequest)
|
||||
}
|
||||
Reference in New Issue
Block a user