Skip to content

Commit

Permalink
Merge pull request #21115 from wordpress-mobile/issue/21105-feedback-…
Browse files Browse the repository at this point in the history
…form-upload-attachments

Feedback form: upload attachments
  • Loading branch information
nbradbury authored Aug 7, 2024
2 parents 490dcf2 + 765f77f commit e4fc976
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,8 @@ class ZendeskHelper(
selectedSite: SiteModel?,
extraTags: List<String>?,
requestDescription: String,
callback: CreateRequestCallback
callback: CreateRequestCallback,
attachmentTokens: List<String> = emptyList()
) {
require(isZendeskEnabled) {
zendeskNeedsToBeEnabledError
Expand All @@ -186,6 +187,7 @@ class ZendeskHelper(
ticketFormId = TicketFieldIds.form
tags = buildZendeskTags(siteStore.sites, selectedSite, origin ?: Origin.UNKNOWN, extraTags)
.plus("DocsBot")
attachments = attachmentTokens
}

Support.INSTANCE.provider()?.requestProvider()?.createRequest(request, object : ZendeskCallback<Request>() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.wordpress.android.support

import com.zendesk.service.ErrorResponse
import com.zendesk.service.ZendeskCallback
import org.wordpress.android.util.AppLog
import org.wordpress.android.util.AppLog.T
import zendesk.support.Support
import zendesk.support.UploadResponse
import java.io.File
import javax.inject.Inject

/**
* https://zendesk.github.io/mobile_sdk_javadocs/supportv2/v301/index.html?zendesk/support/UploadProvider.html
*/
class ZendeskUploadHelper @Inject constructor() {
/**
* Uploads an attachment to Zendesk. Note that the UploadResponse will contain the attachment token.
*/
fun uploadAttachment(
file: File,
mimeType: String,
callback: ZendeskCallback<UploadResponse>,
) {
val uploadProvider = Support.INSTANCE.provider()?.uploadProvider()
if (uploadProvider == null) {
AppLog.e(T.SUPPORT, "Upload provider is null")
return
}
uploadProvider.uploadAttachment(
file.name,
file,
mimeType,
callback
)
}

/**
* Deletes an attachment from Zendesk. This is currently used only during development.
*/
@Suppress("unused")
fun deleteAttachment(token: String) {
val uploadProvider = Support.INSTANCE.provider()?.uploadProvider()
if (uploadProvider == null) {
AppLog.e(T.SUPPORT, "Upload provider is null")
return
}
uploadProvider.deleteAttachment(token, object : ZendeskCallback<Void>() {
override fun onSuccess(result: Void) {
AppLog.i(T.SUPPORT, "Successfully deleted Zendesk attachment")
}

override fun onError(error: ErrorResponse?) {
AppLog.e(T.SUPPORT, "Unable to delete Zendesk attachment")
}
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,3 @@ enum class FeedbackFormAttachmentType {
IMAGE,
VIDEO,
}

/**
* TODO
*
fun FeedbackFormAttachment.toZenDeskAttachment(): SupportNetworkService.ZenDeskSupportTicket.Attachment {
return SupportNetworkService.ZenDeskSupportTicket.Attachment(
file = this.tempFile,
type = this.mimeType
)
}
*/
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import android.net.Uri
import androidx.annotation.StringRes
import androidx.lifecycle.viewModelScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.zendesk.service.ErrorResponse
import com.zendesk.service.ZendeskCallback
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableStateFlow
Expand All @@ -16,26 +18,30 @@ import org.wordpress.android.R
import org.wordpress.android.fluxc.utils.AppLogWrapper
import org.wordpress.android.modules.UI_THREAD
import org.wordpress.android.support.ZendeskHelper
import org.wordpress.android.support.ZendeskUploadHelper
import org.wordpress.android.ui.accounts.HelpActivity
import org.wordpress.android.ui.media.MediaBrowserType
import org.wordpress.android.ui.mysite.SelectedSiteRepository
import org.wordpress.android.ui.photopicker.MediaPickerConstants
import org.wordpress.android.ui.photopicker.MediaPickerLauncher
import org.wordpress.android.util.AppLog
import org.wordpress.android.util.AppLog.T
import org.wordpress.android.util.NetworkUtils
import org.wordpress.android.util.ToastUtilsWrapper
import org.wordpress.android.util.extensions.copyToTempFile
import org.wordpress.android.util.extensions.fileSize
import org.wordpress.android.util.extensions.mimeType
import org.wordpress.android.util.extensions.sizeFmt
import org.wordpress.android.viewmodel.ScopedViewModel
import zendesk.support.UploadResponse
import javax.inject.Inject
import javax.inject.Named

@HiltViewModel
class FeedbackFormViewModel @Inject constructor(
@Named(UI_THREAD) mainDispatcher: CoroutineDispatcher,
private val zendeskHelper: ZendeskHelper,
private val zendeskUploadHelper: ZendeskUploadHelper,
private val selectedSiteRepository: SelectedSiteRepository,
private val appLogWrapper: AppLogWrapper,
private val toastUtilsWrapper: ToastUtilsWrapper,
Expand All @@ -51,6 +57,8 @@ class FeedbackFormViewModel @Inject constructor(
private val _attachments = MutableStateFlow<List<FeedbackFormAttachment>>(emptyList())
val attachments = _attachments.asStateFlow()

private val attachmentTokens = ArrayList<String>()

fun updateMessageText(message: String) {
if (message != _messageText.value) {
_messageText.value = message
Expand All @@ -66,9 +74,29 @@ class FeedbackFormViewModel @Inject constructor(
// identity if it hasn't been previously set
zendeskHelper.createAnonymousIdentityIfNeeded()

// if there are attachments, upload them first then create the feedback request when they're all uploaded.
// using a completion handler isn't ideal but it's done since Zendesk only provides uploading using callbacks.
if (_attachments.value.isNotEmpty()) {
uploadAttachments(
completionHandler = { createZendeskFeedbackRequest(context) }
)
} else {
createZendeskFeedbackRequest(context)
}
}

private fun createZendeskFeedbackRequest(
context: Context,
) {
_isProgressShowing.value = true
createZendeskFeedbackRequest(

zendeskHelper.createRequest(
context = context,
origin = HelpActivity.Origin.FEEDBACK_FORM,
selectedSite = selectedSiteRepository.getSelectedSite(),
extraTags = listOf("in_app_feedback"),
requestDescription = _messageText.value,
attachmentTokens = attachmentTokens,
callback = object : ZendeskHelper.CreateRequestCallback() {
override fun onSuccess() {
_isProgressShowing.value = false
Expand All @@ -82,20 +110,6 @@ class FeedbackFormViewModel @Inject constructor(
})
}

private fun createZendeskFeedbackRequest(
context: Context,
callback: ZendeskHelper.CreateRequestCallback
) {
zendeskHelper.createRequest(
context = context,
origin = HelpActivity.Origin.FEEDBACK_FORM,
selectedSite = selectedSiteRepository.getSelectedSite(),
extraTags = listOf("in_app_feedback"),
requestDescription = _messageText.value,
callback = callback
)
}

fun onCloseClick(context: Context) {
(context as? Activity)?.let { activity ->
if (_messageText.value.isEmpty() && _attachments.value.isEmpty()) {
Expand Down Expand Up @@ -124,7 +138,7 @@ class FeedbackFormViewModel @Inject constructor(
}

private fun onFailure(errorMessage: String? = null) {
appLogWrapper.e(AppLog.T.SUPPORT, "Failed to submit feedback form: $errorMessage")
appLogWrapper.e(T.SUPPORT, "Failed to submit feedback form: $errorMessage")
showToast(R.string.feedback_form_failure)
}

Expand Down Expand Up @@ -208,6 +222,49 @@ class FeedbackFormViewModel @Inject constructor(
}
}

/**
* Uploads the attachments to Zendesk
*/
private fun uploadAttachments(
completionHandler: () -> Unit
) {
attachmentTokens.clear()
var numAttachments = _attachments.value.size

fun decAttachments() {
numAttachments--
if (numAttachments <= 0) {
_isProgressShowing.value = false
completionHandler()
}
}

val callback = object : ZendeskCallback<UploadResponse>() {
override fun onSuccess(result: UploadResponse) {
result.token?.let {
attachmentTokens.add(it)
}
decAttachments()
}

override fun onError(errorResponse: ErrorResponse?) {
AppLog.e(
T.SUPPORT, "Uploading to Zendesk failed with ${errorResponse?.reason}"
)
decAttachments()
}
}

_isProgressShowing.value = true
_attachments.value.forEach { attachment ->
zendeskUploadHelper.uploadAttachment(
file = attachment.tempFile,
mimeType = attachment.mimeType,
callback = callback
)
}
}

companion object {
private const val MAX_SINGLE_ATTACHMENT_SIZE = 50000000
private const val MAX_TOTAL_ATTACHMENT_SIZE = MAX_SINGLE_ATTACHMENT_SIZE * 3
Expand Down

0 comments on commit e4fc976

Please sign in to comment.