diff --git a/platform/platform-api/resources/messages/IdeBundle.properties b/platform/platform-api/resources/messages/IdeBundle.properties
index ab35cf1d43d4..48917154cfd4 100644
--- a/platform/platform-api/resources/messages/IdeBundle.properties
+++ b/platform/platform-api/resources/messages/IdeBundle.properties
@@ -2738,6 +2738,7 @@ gdpr.ai.data.collection.consent.additional.notice.2=\
Even if this setting is disabled, the AI Assistant plugin will send the data essential for this feature to large language model providers \
and models hosted on JetBrains servers. If you work on a project where you don't want to share your data, you can disable the plugin.
gdpr.consent.externally.disabled.warning=This option is disabled externally
+gdpr.consent.trace.requires.ai.assistant=This option requires the AI Assistant plugin
gdpr.data.sharing.title.comment.text=Help shape the future of {0} products. By sharing your data and usage statistics, you allow us to better understand how you use our tools and how we can improve them. Learn more here.
gdpr.data.collection.consent.setting.enabled.warning.text=Data collection has been enabled by your organization administrator.
gdpr.data.collection.consent.setting.disabled.warning.text=Data collection has been disabled by your organization administrator.
diff --git a/platform/platform-impl/src/com/intellij/ide/gdpr/ConsentOptionsProviderImpl.java b/platform/platform-impl/src/com/intellij/ide/gdpr/ConsentOptionsProviderImpl.java
index 96700f5d3153..a7c16e4d75a5 100644
--- a/platform/platform-impl/src/com/intellij/ide/gdpr/ConsentOptionsProviderImpl.java
+++ b/platform/platform-impl/src/com/intellij/ide/gdpr/ConsentOptionsProviderImpl.java
@@ -63,8 +63,11 @@ final class ConsentOptionsProviderImpl implements ConsentOptionsProvider {
return false;
}
AiDataCollectionExternalSettings settings = AiDataCollectionExternalSettings.findSettingsImplementedByAiAssistant();
+ if (settings == null) {
+ return false; // AIA plugin is required for TRACE data collection
+ }
boolean isAllowed = isTraceDataCollectionAllowedByMetadata(metadata);
- boolean isDisabled = settings != null && settings.isForciblyDisabled();
+ boolean isDisabled = settings.isForciblyDisabled();
return isAllowed && !isDisabled;
}
diff --git a/platform/platform-impl/src/com/intellij/ide/gdpr/ui/consents/AiDataCollectionExternalSettings.kt b/platform/platform-impl/src/com/intellij/ide/gdpr/ui/consents/AiDataCollectionExternalSettings.kt
index 256e59c05d4a..3d43343ff098 100644
--- a/platform/platform-impl/src/com/intellij/ide/gdpr/ui/consents/AiDataCollectionExternalSettings.kt
+++ b/platform/platform-impl/src/com/intellij/ide/gdpr/ui/consents/AiDataCollectionExternalSettings.kt
@@ -2,8 +2,11 @@
package com.intellij.ide.gdpr.ui.consents
import com.intellij.internal.statistic.utils.getPluginInfo
+import com.intellij.openapi.Disposable
import com.intellij.openapi.extensions.ExtensionPointName
+import com.intellij.openapi.util.Disposer
import org.jetbrains.annotations.ApiStatus
+import org.jetbrains.annotations.TestOnly
@ApiStatus.Internal
interface AiDataCollectionExternalSettings {
@@ -13,8 +16,18 @@ interface AiDataCollectionExternalSettings {
private const val AI_ASSISTANT_PLUGIN_ID = "com.intellij.ml.llm"
+ private var testOverride: AiDataCollectionExternalSettings? = null
+
+ @TestOnly
+ @JvmStatic
+ fun overrideForTest(settings: AiDataCollectionExternalSettings, parentDisposable: Disposable) {
+ testOverride = settings
+ Disposer.register(parentDisposable) { testOverride = null }
+ }
+
@JvmStatic
fun findSettingsImplementedByAiAssistant(): AiDataCollectionExternalSettings? {
+ testOverride?.let { return it }
return EP_NAME.findFirstSafe {
val pluginInfo = getPluginInfo(it.javaClass)
pluginInfo.isDevelopedByJetBrains() && pluginInfo.id == AI_ASSISTANT_PLUGIN_ID
diff --git a/platform/platform-impl/src/com/intellij/ide/gdpr/ui/consents/TraceDataCollectionConsentUI.kt b/platform/platform-impl/src/com/intellij/ide/gdpr/ui/consents/TraceDataCollectionConsentUI.kt
index f6f0530b1e17..4cbf42c137c0 100644
--- a/platform/platform-impl/src/com/intellij/ide/gdpr/ui/consents/TraceDataCollectionConsentUI.kt
+++ b/platform/platform-impl/src/com/intellij/ide/gdpr/ui/consents/TraceDataCollectionConsentUI.kt
@@ -22,15 +22,17 @@ internal class TraceDataCollectionConsentUI(
if (LOG.isDebugEnabled) {
LOG.debug("AiDataCollectionExternalSettings: $externalSettings")
}
- if (externalSettings != null) {
- val isForciblyDisabled = externalSettings.isForciblyDisabled()
- if (LOG.isDebugEnabled) {
- LOG.debug("AiDataCollectionExternalSettings: isForciblyDisabled: ${isForciblyDisabled}")
- }
- if (isForciblyDisabled) {
- return ConsentForcedState.ExternallyDisabled(externalSettings.getForciblyDisabledDescription()
- ?: IdeBundle.message("gdpr.consent.externally.disabled.warning"))
- }
+ if (externalSettings == null) {
+ // AIA plugin is required for TRACE data collection
+ return ConsentForcedState.ExternallyDisabled(IdeBundle.message("gdpr.consent.trace.requires.ai.assistant"))
+ }
+ val isForciblyDisabled = externalSettings.isForciblyDisabled()
+ if (LOG.isDebugEnabled) {
+ LOG.debug("AiDataCollectionExternalSettings: isForciblyDisabled: $isForciblyDisabled")
+ }
+ if (isForciblyDisabled) {
+ return ConsentForcedState.ExternallyDisabled(externalSettings.getForciblyDisabledDescription()
+ ?: IdeBundle.message("gdpr.consent.externally.disabled.warning"))
}
val dataCollectionAgreement = DataCollectionAgreement.getInstance()
return when (dataCollectionAgreement) {
diff --git a/platform/platform-tests/BUILD.bazel b/platform/platform-tests/BUILD.bazel
index 27d86f3c27ae..ab0eb4099c19 100644
--- a/platform/platform-tests/BUILD.bazel
+++ b/platform/platform-tests/BUILD.bazel
@@ -189,6 +189,7 @@ jvm_library(
"//platform/platform-tests/eel-helper",
"//platform/platform-impl/ui",
"//platform/eel-provider",
+ "//libraries/mockito",
"@lib//:io-mockk",
"@lib//:io-mockk-jvm",
"//platform/bookmarks",
diff --git a/platform/platform-tests/intellij.platform.tests.iml b/platform/platform-tests/intellij.platform.tests.iml
index 906fff80ed9f..d1b67f225161 100644
--- a/platform/platform-tests/intellij.platform.tests.iml
+++ b/platform/platform-tests/intellij.platform.tests.iml
@@ -138,6 +138,7 @@
+
diff --git a/platform/platform-tests/testSrc/com/intellij/ide/gdpr/DataCollectionConsentUiTest.java b/platform/platform-tests/testSrc/com/intellij/ide/gdpr/DataCollectionConsentUiTest.java
index 48eeae2bd3c3..ab9a5d4cb0ce 100644
--- a/platform/platform-tests/testSrc/com/intellij/ide/gdpr/DataCollectionConsentUiTest.java
+++ b/platform/platform-tests/testSrc/com/intellij/ide/gdpr/DataCollectionConsentUiTest.java
@@ -1,10 +1,13 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ide.gdpr;
+import com.intellij.ide.IdeBundle;
+import com.intellij.ide.gdpr.ui.consents.AiDataCollectionExternalSettings;
import com.intellij.ide.gdpr.ui.consents.ConsentForcedState;
import com.intellij.ide.gdpr.ui.consents.ConsentUi;
import com.intellij.testFramework.fixtures.BasePlatformTestCase;
import com.intellij.ui.LicensingFacade;
+import org.mockito.Mockito;
public class DataCollectionConsentUiTest extends BasePlatformTestCase {
private static final String CONSENT_ID_USAGE_STATS = "rsch.send.usage.stat";
@@ -66,6 +69,10 @@ public class DataCollectionConsentUiTest extends BasePlatformTestCase {
false, "en");
ConsentUi ui = ConsentSettingsUi.getConsentUi(trace);
+ AiDataCollectionExternalSettings mockSettings = Mockito.mock(AiDataCollectionExternalSettings.class);
+ Mockito.doReturn(false).when(mockSettings).isForciblyDisabled();
+ AiDataCollectionExternalSettings.overrideForTest(mockSettings, getTestRootDisposable());
+
setupLicensingFacade('X');
assertNull(ui.getForcedState());
@@ -81,4 +88,35 @@ public class DataCollectionConsentUiTest extends BasePlatformTestCase {
assertInstanceOf(state, ConsentForcedState.AlwaysEnabled.class);
assertEquals("Data collection has been enabled by your organization administrator.", state.getDescription());
}
-}
\ No newline at end of file
+
+ public void testTraceConsentForcedDisabledWhenAiaPluginAbsent() {
+ Consent trace = new Consent(CONSENT_ID_TRACE_DATA_COM_COLLECTION, Version.fromString("1.0"), "Send detailed code-related data", "text", false,
+ false, "en");
+ ConsentUi ui = ConsentSettingsUi.getConsentUi(trace);
+
+ for (char agreementChar : new char[]{'X', 'N', 'Y'}) {
+ setupLicensingFacade(agreementChar);
+ var state = ui.getForcedState();
+ assertNotNull("TRACE consent must be force-disabled when AIA plugin is absent (agreement='" + agreementChar + "')", state);
+ assertInstanceOf(state, ConsentForcedState.ExternallyDisabled.class);
+ assertEquals(IdeBundle.message("gdpr.consent.trace.requires.ai.assistant"), state.getDescription());
+ }
+ }
+
+ public void testTraceConsentForcedDisabledWhenAiaForciblyDisabled() {
+ Consent trace = new Consent(CONSENT_ID_TRACE_DATA_COM_COLLECTION, Version.fromString("1.0"), "Send detailed code-related data", "text", false,
+ false, "en");
+ ConsentUi ui = ConsentSettingsUi.getConsentUi(trace);
+
+ AiDataCollectionExternalSettings mockSettings = Mockito.mock(AiDataCollectionExternalSettings.class);
+ Mockito.doReturn(true).when(mockSettings).isForciblyDisabled();
+ Mockito.doReturn("Disabled by organization").when(mockSettings).getForciblyDisabledDescription();
+ AiDataCollectionExternalSettings.overrideForTest(mockSettings, getTestRootDisposable());
+
+ setupLicensingFacade('X');
+ var state = ui.getForcedState();
+ assertNotNull(state);
+ assertInstanceOf(state, ConsentForcedState.ExternallyDisabled.class);
+ assertEquals("Disabled by organization", state.getDescription());
+ }
+}