Skip to content

Commit

Permalink
feat(telemetry): allow to disable telemetry through settings INTELLIJ…
Browse files Browse the repository at this point in the history
…-12 (#8)

Co-authored-by: Anna Henningsen <[email protected]>
  • Loading branch information
kmruiz and addaleax authored Jun 12, 2024
1 parent 0cb18c0 commit 0625244
Show file tree
Hide file tree
Showing 23 changed files with 695 additions and 95 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/quality-check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ jobs:
report_paths: '**/build/test-results/test/TEST-*.xml'
- name: Generate Coverage Report
run: |
./gradlew --quiet --console=plain "jacocoTestReport"
./gradlew "jacocoTestReport" $(./gradlew "jacocoTestReport" --dry-run | awk '/^:/ { print "-x" $1 }' | sed '$ d')
- uses: actions/upload-artifact@v4
name: Upload Functional Test Coverage
with:
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,6 @@ replay_pid*
# This file is generated at compile time.
packages/jetbrains-plugin/src/main/resources/build.properties
/**/video
/**/.DS_Store
/**/.DS_Store

FAKE_BROWSER_OUTPUT
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ MongoDB plugin for IntelliJ IDEA.
## [Unreleased]

### Added
* [INTELLIJ-12](https://jira.mongodb.org/browse/INTELLIJ-11): Notify users about telemetry, and allow them to disable it.
* [INTELLIJ-11](https://jira.mongodb.org/browse/INTELLIJ-11): Flush pending analytics events before closing the IDE.

### Changed
Expand Down
2 changes: 2 additions & 0 deletions gradle/diktat.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,6 @@
- name: FILE_NAME_INCORRECT # File name does not need to match class name inside
enabled: false
- name: LOCAL_VARIABLE_EARLY_DECLARATION # Allow declaring variables at the beginning of a function if they are mutable
enabled: false
- name: USE_DATA_CLASS # Do not force to use data classes, some intellij components won't work if they are data classes
enabled: false
Original file line number Diff line number Diff line change
@@ -1,13 +1,44 @@
/**
* These classes implement the balloon that shows the first time that the plugin is activated.
*/

package com.mongodb.jbplugin

import com.intellij.ide.BrowserUtil
import com.intellij.notification.NotificationGroupManager
import com.intellij.notification.NotificationType
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.options.ShowSettingsUtil
import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.project.Project
import com.intellij.openapi.startup.StartupActivity
import com.mongodb.jbplugin.i18n.TelemetryMessages
import com.mongodb.jbplugin.observability.probe.PluginActivatedProbe
import com.mongodb.jbplugin.settings.PluginSettingsConfigurable
import com.mongodb.jbplugin.settings.useSettings
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

/**
* Class that represents the link that opens the settings page for MongoDB.
*/
class OpenMongoDbPluginSettingsAction : AnAction(TelemetryMessages.message("action.disable-telemetry")) {
override fun actionPerformed(event: AnActionEvent) {
ShowSettingsUtil.getInstance().showSettingsDialog(event.project, PluginSettingsConfigurable::class.java)
}
}

/**
* Class that represents the link that opens the privacy policy page
*/
class OpenPrivacyPolicyPage : AnAction(TelemetryMessages.message("action.view-privacy-policy")) {
override fun actionPerformed(event: AnActionEvent) {
BrowserUtil.browse(TelemetryMessages.message("settings.telemetry-privacy-policy"))
}
}

/**
* This notifies that the plugin has been activated.
*
Expand All @@ -18,6 +49,23 @@ class ActivatePluginPostStartupActivity(private val cs: CoroutineScope) : Startu
cs.launch {
val pluginActivated = ApplicationManager.getApplication().getService(PluginActivatedProbe::class.java)
pluginActivated.pluginActivated()

val settings = useSettings()
if (!settings.hasTelemetryOptOutputNotificationBeenShown) {
NotificationGroupManager.getInstance()
.getNotificationGroup("com.mongodb.jbplugin.notifications.Telemetry")
.createNotification(
TelemetryMessages.message("notification.title"),
TelemetryMessages.message("notification.message"),
NotificationType.INFORMATION,
)
.setImportant(true)
.addAction(OpenPrivacyPolicyPage())
.addAction(OpenMongoDbPluginSettingsAction())
.notify(project)

settings.hasTelemetryOptOutputNotificationBeenShown = true
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.mongodb.jbplugin.i18n

import com.intellij.DynamicBundle
import org.jetbrains.annotations.Nls
import org.jetbrains.annotations.NonNls
import org.jetbrains.annotations.PropertyKey

object SettingsMessages {
@NonNls
private const val BUNDLE = "messages.SettingsBundle"
private val instance = DynamicBundle(SettingsMessages::class.java, BUNDLE)

fun message(
key:
@PropertyKey(resourceBundle = BUNDLE)
String,
vararg params: Any,
): @Nls String = instance.getMessage(key, *params)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.mongodb.jbplugin.i18n

import com.intellij.DynamicBundle
import org.jetbrains.annotations.Nls
import org.jetbrains.annotations.NonNls
import org.jetbrains.annotations.PropertyKey

object TelemetryMessages {
@NonNls
private const val BUNDLE = "messages.TelemetryBundle"
private val instance = DynamicBundle(TelemetryMessages::class.java, BUNDLE)

fun message(
key:
@PropertyKey(resourceBundle = BUNDLE)
String,
vararg params: Any,
): @Nls String = instance.getMessage(key, *params)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.intellij.openapi.components.Service
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.diagnostic.logger
import com.mongodb.jbplugin.meta.BuildInformation
import com.mongodb.jbplugin.settings.useSettings
import com.segment.analytics.Analytics
import com.segment.analytics.messages.IdentifyMessage
import com.segment.analytics.messages.TrackMessage
Expand All @@ -18,48 +19,61 @@ private val logger: Logger = logger<TelemetryService>()
*/
@Service
internal class TelemetryService : AppLifecycleListener {
internal var analytics: Analytics = Analytics
.builder(BuildInformation.segmentApiKey)
.build()
internal var analytics: Analytics =
Analytics
.builder(BuildInformation.segmentApiKey)
.build()

init {
ApplicationManager.getApplication()
.messageBus
.connect()
.subscribe(
AppLifecycleListener.TOPIC,
this
this,
)
}

fun sendEvent(event: TelemetryEvent) {
val runtimeInformationService = ApplicationManager.getApplication().getService(
RuntimeInformationService::class.java
)
val runtimeInfo = runtimeInformationService.get()
if (!useSettings().isTelemetryEnabled) {
return
}

val message = when (event) {
is TelemetryEvent.PluginActivated -> IdentifyMessage.builder().userId(runtimeInfo.userId)
else ->
TrackMessage.builder(event.name).userId(runtimeInfo.userId)
.properties(event.properties.entries.associate {
it.key.publicName to it.value
})
val runtimeInformationService =
ApplicationManager.getApplication().getService(
RuntimeInformationService::class.java,
)
val runtimeInfo = runtimeInformationService.get()

}
val message =
when (event) {
is TelemetryEvent.PluginActivated -> IdentifyMessage.builder().userId(runtimeInfo.userId)
else ->
TrackMessage.builder(event.name).userId(runtimeInfo.userId)
.properties(
event.properties.entries.associate {
it.key.publicName to it.value
},
)
}

analytics.enqueue(message)
}

override fun appWillBeClosed(isRestart: Boolean) {
val telemetryEnabled = useSettings().isTelemetryEnabled
val logMessage = ApplicationManager.getApplication().getService(LogMessage::class.java)
logger.info(
logMessage.message("Flushing Segment analytics because the IDE is closing.")
logMessage.message("Shutting down Segment analytics because the IDE is closing.")
.put("isRestart", isRestart)
.build()
.put("telemetryEnabled", telemetryEnabled)
.build(),
)

analytics.flush()
if (telemetryEnabled) {
analytics.flush()
}

analytics.shutdown()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* Settings state for the plugin. These classes are responsible for the persistence of the plugin
* settings.
*/

package com.mongodb.jbplugin.settings

import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.*
import java.io.Serializable

/**
* The state component represents the persisting state. Don't use directly, this is only necessary
* for the state to be persisted. Use PluginSettings instead.
*
* @see PluginSettings
*/
@Service
@State(
name = "com.mongodb.jbplugin.settings.PluginSettings",
storages = [Storage(value = "MongoDBPluginSettings.xml")],
)
class PluginSettingsStateComponent : SimplePersistentStateComponent<PluginSettings>(PluginSettings())

/**
* The settings themselves. They are tracked, so any change on the settings properties will be eventually
* persisted by IntelliJ. To access the settings, use the useSettings provider.
*
* @see useSettings
*/
class PluginSettings : BaseState(), Serializable {
var isTelemetryEnabled by property(true)
var hasTelemetryOptOutputNotificationBeenShown by property(false)
}

/**
* Function that provides a reference to the current PluginSettings.
*
* @see PluginSettings
*
* @return
*/
fun useSettings(): PluginSettings =
ApplicationManager.getApplication().getService(
PluginSettingsStateComponent::class.java,
).state
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* These classes represent the settings modal.
*/

package com.mongodb.jbplugin.settings

import com.intellij.ide.BrowserUtil
import com.intellij.openapi.options.Configurable
import com.intellij.ui.components.JBCheckBox
import com.intellij.util.ui.FormBuilder
import com.mongodb.jbplugin.i18n.SettingsMessages
import com.mongodb.jbplugin.i18n.TelemetryMessages
import javax.swing.JButton
import javax.swing.JComponent
import javax.swing.JPanel

/**
* This class represents a section in the settings modal. The UI will be implemented by
* PluginSettingsComponent.
*/
class PluginSettingsConfigurable : Configurable {
private lateinit var settingsComponent: PluginSettingsComponent

override fun createComponent(): JComponent {
settingsComponent = PluginSettingsComponent()
return settingsComponent.root
}

override fun isModified(): Boolean {
val savedSettings = useSettings()
return settingsComponent.isTelemetryEnabledCheckBox.isSelected != savedSettings.isTelemetryEnabled
}

override fun apply() {
val savedSettings =
useSettings().apply {
isTelemetryEnabled = settingsComponent.isTelemetryEnabledCheckBox.isSelected
}
}

override fun reset() {
val savedSettings = useSettings()
settingsComponent.isTelemetryEnabledCheckBox.isSelected = savedSettings.isTelemetryEnabled
}

override fun getDisplayName() = SettingsMessages.message("settings.display-name")
}

/**
* The panel that is shown in the settings section for MongoDB.
*/
private class PluginSettingsComponent {
val root: JPanel
val isTelemetryEnabledCheckBox = JBCheckBox(TelemetryMessages.message("settings.telemetry-collection-checkbox"))
val privacyPolicyButton = JButton(TelemetryMessages.message("action.view-privacy-policy"))

init {
privacyPolicyButton.addActionListener {
BrowserUtil.browse(TelemetryMessages.message("settings.telemetry-privacy-policy"))
}

root =
FormBuilder.createFormBuilder()
.addComponent(isTelemetryEnabledCheckBox)
.addTooltip(TelemetryMessages.message("settings.telemetry-collection-tooltip"))
.addComponent(privacyPolicyButton)
.addComponentFillVertically(JPanel(), 0)
.panel

root.accessibleContext.accessibleName = "MongoDB Settings"
isTelemetryEnabledCheckBox.accessibleContext.accessibleName = "MongoDB Enable Telemetry"
}
}
10 changes: 10 additions & 0 deletions packages/jetbrains-plugin/src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@

<extensions defaultExtensionNs="com.intellij">
<postStartupActivity implementation="com.mongodb.jbplugin.ActivatePluginPostStartupActivity"/>
<applicationConfigurable
parentId="tools"
instance="com.mongodb.jbplugin.settings.PluginSettingsConfigurable"
id="com.mongodb.jbplugin.settings.PluginSettingsConfigurable"
displayName="MongoDB"/>
<notificationGroup id="com.mongodb.jbplugin.notifications.Telemetry"
displayType="BALLOON"
bundle="messages.TelemetryBundle"
key="notification.group.name"
/>
</extensions>
<applicationListeners>
</applicationListeners>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
settings.display-name=MongoDB
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
notification.group.name=MongoDB telemetry
notification.title=MongoDB plugin telemetry
notification.message=Anonymous telemetry is enabled by default because it helps us improve the plugin.
action.disable-telemetry=Disable Telemetry
action.view-privacy-policy=View Privacy Policy
settings.telemetry-collection-tooltip=Allow the collection of anonymous diagnostics and usage telemetry data to help improve the product.
settings.telemetry-collection-checkbox=Enable telemetry
settings.telemetry-privacy-policy=https://www.mongodb.com/legal/privacy/privacy-policy
Loading

0 comments on commit 0625244

Please sign in to comment.