From 7f7d576bb8b74e6906fbf3b8dad8c717fa6ca73b Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Wed, 10 Sep 2025 15:35:23 +0100 Subject: [PATCH 1/2] Tag support requests when app passwords are enabled for Jetpack sites --- .../IsAppPasswordsSupportedForJetpackSite.kt | 16 ++++++++++ .../zendesk/ZendeskTicketRepository.kt | 31 +++++++++++++------ 2 files changed, 38 insertions(+), 9 deletions(-) create mode 100644 WooCommerce/src/main/kotlin/com/woocommerce/android/applicationpasswords/IsAppPasswordsSupportedForJetpackSite.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/applicationpasswords/IsAppPasswordsSupportedForJetpackSite.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/applicationpasswords/IsAppPasswordsSupportedForJetpackSite.kt new file mode 100644 index 000000000000..01172c8ee58d --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/applicationpasswords/IsAppPasswordsSupportedForJetpackSite.kt @@ -0,0 +1,16 @@ +package com.woocommerce.android.applicationpasswords + +import org.wordpress.android.fluxc.model.SiteModel +import org.wordpress.android.fluxc.network.rest.wpapi.applicationpasswords.ApplicationPasswordsConfiguration +import org.wordpress.android.fluxc.network.rest.wpapi.applicationpasswords.JetpackApplicationPasswordsSupport +import javax.inject.Inject + +class IsAppPasswordsSupportedForJetpackSite @Inject constructor( + private val applicationPasswordsConfiguration: ApplicationPasswordsConfiguration, + private val jetpackApplicationPasswordsSupport: JetpackApplicationPasswordsSupport +) { + suspend operator fun invoke(site: SiteModel): Boolean { + return applicationPasswordsConfiguration.isEnabledForJetpackAccess() && + jetpackApplicationPasswordsSupport.supportsAppPasswords(site) + } +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/support/zendesk/ZendeskTicketRepository.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/support/zendesk/ZendeskTicketRepository.kt index b2225db549e9..bd3406270a17 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/support/zendesk/ZendeskTicketRepository.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/support/zendesk/ZendeskTicketRepository.kt @@ -2,6 +2,7 @@ package com.woocommerce.android.support.zendesk import android.content.Context import android.os.Parcelable +import com.woocommerce.android.applicationpasswords.IsAppPasswordsSupportedForJetpackSite import com.woocommerce.android.extensions.formatResult import com.woocommerce.android.support.help.HelpOrigin import com.woocommerce.android.support.zendesk.RequestConstants.requestCreationIdentityNotSetErrorMessage @@ -35,7 +36,8 @@ class ZendeskTicketRepository @Inject constructor( private val siteStore: SiteStore, private val dispatchers: CoroutineDispatchers, private val wooLog: WooLog, - private val ssrFetcher: WCSSRModelCachingFetcher + private val ssrFetcher: WCSSRModelCachingFetcher, + private val isAppPasswordsSupportedForJetpackSite: IsAppPasswordsSupportedForJetpackSite ) { /** * This function creates a new customer Support Request through the Zendesk API Providers. @@ -140,16 +142,21 @@ class ZendeskTicketRepository @Inject constructor( * This is a helper function which returns a set of pre-defined tags depending on some conditions. It accepts a list of * custom tags to be added for special cases. */ - private fun buildZendeskTags( + private suspend fun buildZendeskTags( selectedSite: SiteModel?, allSites: List?, origin: HelpOrigin, extraTags: List ): List { val tags = ArrayList() - if (selectedSite?.connectionType == SiteConnectionType.ApplicationPasswords) { - tags.add(ZendeskTags.applicationPasswordAuthenticated) + selectedSite?.let { + if (selectedSite.connectionType == SiteConnectionType.ApplicationPasswords) { + tags.add(ZendeskTags.applicationPasswordAuthenticated) + } else if (isAppPasswordsSupportedForJetpackSite(it)) { + tags.add(ZendeskTags.jetpackSiteUsingAppPasswords) + } } + allSites?.let { it -> // Add wpcom tag if at least one site is WordPress.com site if (it.any { it.isWPCom }) { @@ -181,14 +188,16 @@ sealed class TicketType( val tags: List = emptyList(), val excludedTags: List = emptyList() ) : Parcelable { - @Parcelize object MobileApp : TicketType( + @Parcelize + object MobileApp : TicketType( form = TicketCustomField.wooMobileFormID, categoryName = ZendeskConstants.mobileAppCategory, subcategoryName = ZendeskConstants.mobileSubcategoryValue, tags = listOf(ZendeskTags.mobileApp) ) - @Parcelize object InPersonPayments : TicketType( + @Parcelize + object InPersonPayments : TicketType( form = TicketCustomField.wooMobileFormID, categoryName = ZendeskConstants.mobileAppCategory, subcategoryName = ZendeskConstants.mobileSubcategoryValue, @@ -198,7 +207,8 @@ sealed class TicketType( ) ) - @Parcelize object Payments : TicketType( + @Parcelize + object Payments : TicketType( form = TicketCustomField.wooFormID, categoryName = ZendeskConstants.supportCategory, subcategoryName = ZendeskConstants.paymentsSubcategoryValue, @@ -212,7 +222,8 @@ sealed class TicketType( excludedTags = listOf(ZendeskTags.jetpackTag) ) - @Parcelize object WooPlugin : TicketType( + @Parcelize + object WooPlugin : TicketType( form = TicketCustomField.wooFormID, categoryName = ZendeskConstants.supportCategory, subcategoryName = "", @@ -224,7 +235,8 @@ sealed class TicketType( excludedTags = listOf(ZendeskTags.jetpackTag) ) - @Parcelize object OtherPlugins : TicketType( + @Parcelize + object OtherPlugins : TicketType( form = TicketCustomField.wooFormID, categoryName = ZendeskConstants.supportCategory, subcategoryName = ZendeskConstants.storeSubcategoryValue, @@ -267,6 +279,7 @@ object TicketCustomField { object ZendeskTags { const val applicationPasswordAuthenticated = "application_password_authenticated" + const val jetpackSiteUsingAppPasswords = "jetpack_site_using_app_passwords" const val mobileApp = "mobile_app" const val woocommerceCore = "woocommerce_core" const val paymentsProduct = "woocommerce_payments" From c2b95b771c3b158d4b603a74ea3961551b8e5b76 Mon Sep 17 00:00:00 2001 From: Hicham Boushaba Date: Wed, 10 Sep 2025 15:58:04 +0100 Subject: [PATCH 2/2] Add a test case --- .../support/ZendeskTicketRepositoryTest.kt | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/support/ZendeskTicketRepositoryTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/support/ZendeskTicketRepositoryTest.kt index 7833de020f2a..8a716ea61968 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/support/ZendeskTicketRepositoryTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/support/ZendeskTicketRepositoryTest.kt @@ -1,5 +1,6 @@ package com.woocommerce.android.support +import com.woocommerce.android.applicationpasswords.IsAppPasswordsSupportedForJetpackSite import com.woocommerce.android.support.help.HelpOrigin import com.woocommerce.android.support.zendesk.TicketCustomField import com.woocommerce.android.support.zendesk.TicketType @@ -25,6 +26,7 @@ import org.junit.Test import org.mockito.kotlin.any import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.doReturn +import org.mockito.kotlin.given import org.mockito.kotlin.mock import org.mockito.kotlin.stub import org.mockito.kotlin.verify @@ -46,7 +48,10 @@ internal class ZendeskTicketRepositoryTest : BaseUnitTest() { private lateinit var requestProvider: RequestProvider private lateinit var envDataSource: ZendeskEnvironmentDataSource private lateinit var siteStore: SiteStore - private val ssrFetcher: WCSSRModelCachingFetcher = mock() + private val ssrFetcher: WCSSRModelCachingFetcher = mock { + onBlocking { load(any()) } doReturn WooResult(model = null) + } + private val isAppPasswordsSupportedForJetpackSite: IsAppPasswordsSupportedForJetpackSite = mock() @Before fun setup() { @@ -693,6 +698,36 @@ internal class ZendeskTicketRepositoryTest : BaseUnitTest() { } } + @Test + fun `given jetpack site supports app passwords, when creating the request, then include corresponding tag`() = + testBlocking { + val site = SiteModel().apply { + origin = SiteModel.ORIGIN_WPCOM_REST + setIsJetpackConnected(true) + } + given(isAppPasswordsSupportedForJetpackSite.invoke(site)).willReturn(true) + + val captor = argumentCaptor() + createSUT() + + // when + sut.createRequest( + context = mock(), + origin = HelpOrigin.LOGIN_HELP_NOTIFICATION, + ticketType = TicketType.MobileApp, + selectedSite = site, + subject = "subject", + description = "description", + extraTags = emptyList(), + siteAddress = "siteAddress" + ).first() + + // then + verify(requestProvider).createRequest(captor.capture(), any()) + val tags = captor.firstValue.tags + assertThat(tags).contains(ZendeskTags.jetpackSiteUsingAppPasswords) + } + private fun createSUT() { sut = ZendeskTicketRepository( zendeskSettings = zendeskSettings, @@ -700,7 +735,8 @@ internal class ZendeskTicketRepositoryTest : BaseUnitTest() { siteStore = siteStore, dispatchers = coroutinesTestRule.testDispatchers, mock(), - ssrFetcher + ssrFetcher, + isAppPasswordsSupportedForJetpackSite = isAppPasswordsSupportedForJetpackSite ) }