From e2c606042c6b77aff69fca57fbd53e82e462da99 Mon Sep 17 00:00:00 2001 From: Vera Petrenkova Date: Fri, 3 Jan 2025 18:22:18 +0000 Subject: [PATCH] IJPL-172387 FUS: Add 'invocationPlace' parameter to dialogs statistic Merge-request: IJ-MR-151127 Merged-by: Vera Petrenkova GitOrigin-RevId: dbe25a3787fd283c2576bdb8ff4f8155a963d8f9 --- platform/ide-core/api-dump-unreviewed.txt | 2 + .../openapi/ui/MessageDialogBuilder.kt | 22 +++++++-- .../openapi/ui/messages/MessagesService.kt | 42 ++++++++++++----- platform/platform-api/api-dump-unreviewed.txt | 3 ++ .../eventLog/FeatureUsageUiEvents.kt | 12 ++--- .../intellij/openapi/ui/DialogWrapper.java | 14 ++++-- .../src/com/intellij/openapi/ui/Messages.java | 19 ++++++++ .../openapi/ui/messages/MessageDialog.java | 32 ++++++++++++- .../platform-impl/api-dump-unreviewed.txt | 2 +- .../fus/ui/DialogInvocationPlaceEP.kt | 16 +++++++ .../fus/ui/DialogInvocationPlacesCollector.kt | 47 +++++++++++++++++++ .../fus/ui/FeatureUsageUiEventsImpl.kt | 37 +++++++++++---- .../project/impl/ProjectManagerImpl.kt | 5 ++ .../ui/messages/AlertMessagesManager.kt | 9 ++-- .../ui/messages/MessagesServiceImpl.java | 9 ++-- .../src/META-INF/LangExtensions.xml | 1 + .../src/META-INF/PlatformExtensionPoints.xml | 2 +- .../src/META-INF/PlatformExtensions.xml | 1 + 18 files changed, 228 insertions(+), 47 deletions(-) create mode 100644 platform/platform-impl/src/com/intellij/internal/statistic/collectors/fus/ui/DialogInvocationPlaceEP.kt create mode 100644 platform/platform-impl/src/com/intellij/internal/statistic/collectors/fus/ui/DialogInvocationPlacesCollector.kt diff --git a/platform/ide-core/api-dump-unreviewed.txt b/platform/ide-core/api-dump-unreviewed.txt index 8d8dccd53f00..d4d5f7d0c4a1 100644 --- a/platform/ide-core/api-dump-unreviewed.txt +++ b/platform/ide-core/api-dump-unreviewed.txt @@ -1851,6 +1851,7 @@ a:com.intellij.openapi.ui.MessageDialogBuilder - pf:getDoNotAskOption():com.intellij.openapi.ui.DoNotAskOption - pf:getHelpId():java.lang.String - pf:getIcon():javax.swing.Icon +- pf:getInvocationPlace():java.lang.String - pf:getMessage():java.lang.String - pf:getNoText():java.lang.String - pf:getParentComponent():java.awt.Component @@ -1865,6 +1866,7 @@ a:com.intellij.openapi.ui.MessageDialogBuilder - pf:setDoNotAskOption(com.intellij.openapi.ui.DoNotAskOption):V - pf:setHelpId(java.lang.String):V - pf:setIcon(javax.swing.Icon):V +- pf:setInvocationPlace(java.lang.String):V - pf:setNoText(java.lang.String):V - pf:setParentComponent(java.awt.Component):V - pf:setProject(com.intellij.openapi.project.Project):V diff --git a/platform/ide-core/src/com/intellij/openapi/ui/MessageDialogBuilder.kt b/platform/ide-core/src/com/intellij/openapi/ui/MessageDialogBuilder.kt index fa5ac319bb40..8519202f0ea0 100644 --- a/platform/ide-core/src/com/intellij/openapi/ui/MessageDialogBuilder.kt +++ b/platform/ide-core/src/com/intellij/openapi/ui/MessageDialogBuilder.kt @@ -23,6 +23,7 @@ sealed class MessageDialogBuilder>(protected val tit protected var icon: Icon? = null protected var doNotAskOption: DoNotAskOption? = null @NonNls protected var helpId: String? = null + @NonNls protected var invocationPlace: String? = null protected abstract fun getThis(): T @@ -85,6 +86,17 @@ sealed class MessageDialogBuilder>(protected val tit return getThis() } + /** + * Please provide invocation place to clarify the purpose of dialog in statistic. + * You should register your invocation place in plugin.xml as extension point + * See also: com.intellij.internal.statistic.collectors.fus.ui.InvocationPlaceEP + */ + @ApiStatus.Internal + fun invocationPlace(invocationPlace: @NonNls String): T { + this.invocationPlace = invocationPlace + return getThis() + } + class YesNo internal constructor(title: String, message: String) : MessageDialogBuilder(title, message) { override fun getThis(): YesNo = this @@ -106,8 +118,8 @@ sealed class MessageDialogBuilder>(protected val tit val yesText = yesText ?: CommonBundle.getYesButtonText() val noText = noText ?: CommonBundle.getNoButtonText() return MessagesService.getInstance().showMessageDialog( - project = project, parentComponent = parentComponent, message = message, title = title, icon = icon, - options = arrayOf(yesText, noText), doNotAskOption = doNotAskOption, helpId = helpId, alwaysUseIdeaUI = true + project = project, parentComponent = parentComponent, message = message, title = title, options = arrayOf(yesText, noText), + icon = icon, doNotAskOption = doNotAskOption, alwaysUseIdeaUI = true, helpId = helpId, invocationPlace = invocationPlace, ) == YES } } @@ -140,7 +152,7 @@ sealed class MessageDialogBuilder>(protected val tit val options = arrayOf(yesText, noText, cancelText) val choice = MessagesService.getInstance().showMessageDialog( project = project, parentComponent = parentComponent, message = message, title = title, options = options, - icon = icon, doNotAskOption = doNotAskOption, helpId = helpId, alwaysUseIdeaUI = true) + icon = icon, doNotAskOption = doNotAskOption, alwaysUseIdeaUI = true, helpId = helpId, invocationPlace = invocationPlace) return when (choice) { 0 -> YES 1 -> NO @@ -179,7 +191,7 @@ sealed class MessageDialogBuilder>(protected val tit val result = MessagesService.getInstance().showMessageDialog( project = project, parentComponent = parentComponent, message = message, title = title, options = options, defaultOptionIndex = defaultOptionIndex, focusedOptionIndex = focusedOptionIndex, - icon = icon, doNotAskOption = doNotAskOption, helpId = helpId, alwaysUseIdeaUI = true) + icon = icon, doNotAskOption = doNotAskOption, alwaysUseIdeaUI = true, helpId = helpId, invocationPlace = invocationPlace) return if (result < 0) null else buttons[result] } } @@ -200,6 +212,6 @@ class OkCancelDialogBuilder internal constructor(title: String, message: String) val noText = noText ?: CommonBundle.getCancelButtonText() return MessagesService.getInstance().showMessageDialog( project = project, parentComponent = parentComponent, message = message, title = title, options = arrayOf(yesText, noText), - icon = icon, doNotAskOption = doNotAskOption, helpId = helpId, alwaysUseIdeaUI = true) == 0 + icon = icon, doNotAskOption = doNotAskOption, alwaysUseIdeaUI = true, helpId = helpId, invocationPlace = invocationPlace) == 0 } } diff --git a/platform/ide-core/src/com/intellij/openapi/ui/messages/MessagesService.kt b/platform/ide-core/src/com/intellij/openapi/ui/messages/MessagesService.kt index a234b118b8e1..6b571333dbed 100644 --- a/platform/ide-core/src/com/intellij/openapi/ui/messages/MessagesService.kt +++ b/platform/ide-core/src/com/intellij/openapi/ui/messages/MessagesService.kt @@ -33,17 +33,37 @@ interface MessagesService { ?: MessagesService::class.java.classLoader.loadClass("com.intellij.ui.messages.MessagesServiceImpl").getDeclaredConstructor().newInstance() as MessagesService } - fun showMessageDialog(project: Project?, - parentComponent: Component? = null, - @DialogMessage message: String?, - @NlsContexts.DialogTitle title: String?, - options: Array, - defaultOptionIndex: Int = 0, - focusedOptionIndex: Int = -1, - icon: Icon?, - doNotAskOption: DoNotAskOption?, - alwaysUseIdeaUI: Boolean = false, - helpId: String? = null): Int + fun showMessageDialog( + project: Project?, + parentComponent: Component? = null, + @DialogMessage message: String?, + @NlsContexts.DialogTitle title: String?, + options: Array, + defaultOptionIndex: Int = 0, + focusedOptionIndex: Int = -1, + icon: Icon?, + doNotAskOption: DoNotAskOption?, + alwaysUseIdeaUI: Boolean = false, + helpId: String? = null + ): Int { + return showMessageDialog(project, parentComponent, message, title, options, defaultOptionIndex, focusedOptionIndex, icon, doNotAskOption, alwaysUseIdeaUI, helpId, null) + } + + fun showMessageDialog( + project: Project?, + parentComponent: Component? = null, + @DialogMessage message: String?, + @NlsContexts.DialogTitle title: String?, + options: Array, + defaultOptionIndex: Int = 0, + focusedOptionIndex: Int = -1, + icon: Icon?, + doNotAskOption: DoNotAskOption?, + alwaysUseIdeaUI: Boolean = false, + helpId: String? = null, + invocationPlace: String? = null + ): Int + fun showMoreInfoMessageDialog(project: Project?, @DialogMessage message: String?, diff --git a/platform/platform-api/api-dump-unreviewed.txt b/platform/platform-api/api-dump-unreviewed.txt index bb362a5944c3..edf0ccda0bbe 100644 --- a/platform/platform-api/api-dump-unreviewed.txt +++ b/platform/platform-api/api-dump-unreviewed.txt @@ -3915,6 +3915,7 @@ Fc:com.intellij.openapi.ui.Messages - s:showDialog(com.intellij.openapi.project.Project,java.lang.String,java.lang.String,java.lang.String[],I,javax.swing.Icon,com.intellij.openapi.ui.DialogWrapper$DoNotAskOption):I - s:showDialog(java.awt.Component,java.lang.String,java.lang.String,java.lang.String[],I,javax.swing.Icon):I - s:showDialog(java.lang.String,java.lang.String,java.lang.String[],I,I,javax.swing.Icon,com.intellij.openapi.ui.DialogWrapper$DoNotAskOption):I +- s:showDialog(java.lang.String,java.lang.String,java.lang.String[],I,I,javax.swing.Icon,com.intellij.openapi.ui.DialogWrapper$DoNotAskOption,java.lang.String):I - s:showDialog(java.lang.String,java.lang.String,java.lang.String[],I,javax.swing.Icon):I - s:showDialog(java.lang.String,java.lang.String,java.lang.String[],I,javax.swing.Icon,com.intellij.openapi.ui.DialogWrapper$DoNotAskOption):I - s:showEditableChooseDialog(java.lang.String,java.lang.String,javax.swing.Icon,java.lang.String[],java.lang.String,com.intellij.openapi.ui.InputValidator):java.lang.String @@ -4345,10 +4346,12 @@ c:com.intellij.openapi.ui.messages.MessageDialog - p:(com.intellij.openapi.project.Project):V - (com.intellij.openapi.project.Project,java.awt.Component,java.lang.String,java.lang.String,java.lang.String[],I,I,javax.swing.Icon,com.intellij.openapi.ui.DoNotAskOption,Z):V - (com.intellij.openapi.project.Project,java.awt.Component,java.lang.String,java.lang.String,java.lang.String[],I,I,javax.swing.Icon,com.intellij.openapi.ui.DoNotAskOption,Z,java.lang.String):V +- (com.intellij.openapi.project.Project,java.awt.Component,java.lang.String,java.lang.String,java.lang.String[],I,I,javax.swing.Icon,com.intellij.openapi.ui.DoNotAskOption,Z,java.lang.String,java.lang.String):V - (com.intellij.openapi.project.Project,java.lang.String,java.lang.String,java.lang.String[],I,javax.swing.Icon,Z):V - (com.intellij.openapi.project.Project,Z):V - (java.lang.String,java.lang.String,java.lang.String[],I,javax.swing.Icon):V - p:_init(java.lang.String,java.lang.String,java.lang.String[],I,I,javax.swing.Icon,com.intellij.openapi.ui.DoNotAskOption,java.lang.String):V +- p:_init(java.lang.String,java.lang.String,java.lang.String[],I,I,javax.swing.Icon,com.intellij.openapi.ui.DoNotAskOption,java.lang.String,java.lang.String):V - p:createActions():javax.swing.Action[] - p:createCenterPanel():javax.swing.JComponent - p:createIconPanel():javax.swing.JPanel diff --git a/platform/platform-api/src/com/intellij/internal/statistic/eventLog/FeatureUsageUiEvents.kt b/platform/platform-api/src/com/intellij/internal/statistic/eventLog/FeatureUsageUiEvents.kt index f39f8be5e429..2bd23f129553 100644 --- a/platform/platform-api/src/com/intellij/internal/statistic/eventLog/FeatureUsageUiEvents.kt +++ b/platform/platform-api/src/com/intellij/internal/statistic/eventLog/FeatureUsageUiEvents.kt @@ -22,11 +22,11 @@ interface FeatureUsageUiEvents { fun logResetConfigurable(configurable: Configurable) - fun logShowDialog(dialogClass: Class<*>) + fun logShowDialog(dialogClass: Class<*>, invocationPlace: String? = null) - fun logCloseDialog(dialogClass: Class<*>, exitCode: Int) + fun logCloseDialog(dialogClass: Class<*>, exitCode: Int, invocationPlace: String? = null) - fun logClickOnHelpDialog(dialogClass: Class<*>) + fun logClickOnHelpDialog(dialogClass: Class<*>, invocationPlace: String? = null) } @ApiStatus.Internal @@ -40,12 +40,12 @@ object EmptyFeatureUsageUiEvents : FeatureUsageUiEvents { override fun logResetConfigurable(configurable: Configurable) { } - override fun logShowDialog(dialogClass: Class<*>) { + override fun logShowDialog(dialogClass: Class<*>, invocationPlace: String?) { } - override fun logCloseDialog(dialogClass: Class<*>, exitCode: Int) { + override fun logCloseDialog(dialogClass: Class<*>, exitCode: Int, invocationPlace: String?) { } - override fun logClickOnHelpDialog(dialogClass: Class<*>) { + override fun logClickOnHelpDialog(dialogClass: Class<*>, invocationPlace: String?) { } } \ No newline at end of file diff --git a/platform/platform-api/src/com/intellij/openapi/ui/DialogWrapper.java b/platform/platform-api/src/com/intellij/openapi/ui/DialogWrapper.java index 649268dee96d..66a46cfa6c51 100644 --- a/platform/platform-api/src/com/intellij/openapi/ui/DialogWrapper.java +++ b/platform/platform-api/src/com/intellij/openapi/ui/DialogWrapper.java @@ -50,8 +50,8 @@ import java.awt.event.*; import java.beans.PropertyChangeListener; import java.lang.reflect.Field; import java.lang.reflect.Modifier; -import java.util.List; import java.util.*; +import java.util.List; import java.util.function.Function; import java.util.stream.IntStream; @@ -197,6 +197,7 @@ public abstract class DialogWrapper { private int myValidationDelay = 300; private boolean myValidationStarted; private boolean myKeepPopupsOpen; + @Nls private @NonNls @Nullable String invocationPlace = null; protected Action myOKAction; protected Action myCancelAction; @@ -1866,20 +1867,20 @@ public abstract class DialogWrapper { private void logCloseDialogEvent(int exitCode) { boolean canRecord = canRecordDialogId(); if (canRecord) { - FeatureUsageUiEventsKt.getUiEventLogger().logCloseDialog(getClass(), exitCode); + FeatureUsageUiEventsKt.getUiEventLogger().logCloseDialog(getClass(), exitCode, invocationPlace); } } private void logShowDialogEvent() { boolean canRecord = canRecordDialogId(); if (canRecord) { - FeatureUsageUiEventsKt.getUiEventLogger().logShowDialog(getClass()); + FeatureUsageUiEventsKt.getUiEventLogger().logShowDialog(getClass(), invocationPlace); } } private void logClickOnHelpDialogEvent() { if (!canRecordDialogId()) return; - FeatureUsageUiEventsKt.getUiEventLogger().logClickOnHelpDialog(getClass()); + FeatureUsageUiEventsKt.getUiEventLogger().logClickOnHelpDialog(getClass(), invocationPlace); } /** @@ -1889,6 +1890,11 @@ public abstract class DialogWrapper { return true; } + @ApiStatus.Internal + public void setInvocationPlace(@Nullable @NonNls String invocationPlace) { + this.invocationPlace = invocationPlace; + } + /** * Base class for dialog wrapper actions that need to ensure that only * one action for the dialog is running. diff --git a/platform/platform-api/src/com/intellij/openapi/ui/Messages.java b/platform/platform-api/src/com/intellij/openapi/ui/Messages.java index 057bd09eeea3..08c8eefc166d 100644 --- a/platform/platform-api/src/com/intellij/openapi/ui/Messages.java +++ b/platform/platform-api/src/com/intellij/openapi/ui/Messages.java @@ -274,6 +274,25 @@ public class Messages { .showMessageDialog(null, null, message, title, options, defaultOptionIndex, focusedOptionIndex, icon, doNotAskOption, false, null); } + /** + * Use this method only if you do not know project or component + * + * @return number of button pressed: from 0 up to options.length-1 inclusive, or -1 for Cancel + * @see #showDialog(Project, String, String, String[], int, Icon, DialogWrapper.DoNotAskOption) + * @see #showDialog(Component, String, String, String[], int, Icon) + */ + public static int showDialog(@DialogMessage String message, + @NotNull @DialogTitle String title, + String @NotNull @NlsContexts.Button [] options, + int defaultOptionIndex, + int focusedOptionIndex, + @Nullable Icon icon, + @Nullable DialogWrapper.DoNotAskOption doNotAskOption, + @Nullable String invocationPlace) { + return MessagesService.getInstance() + .showMessageDialog(null, null, message, title, options, defaultOptionIndex, focusedOptionIndex, icon, doNotAskOption, false, null, invocationPlace); + } + /** * Use this method only if you do not know project or component * diff --git a/platform/platform-api/src/com/intellij/openapi/ui/messages/MessageDialog.java b/platform/platform-api/src/com/intellij/openapi/ui/messages/MessageDialog.java index f839129a31d0..2ddd37c3ebc8 100644 --- a/platform/platform-api/src/com/intellij/openapi/ui/messages/MessageDialog.java +++ b/platform/platform-api/src/com/intellij/openapi/ui/messages/MessageDialog.java @@ -59,9 +59,24 @@ public class MessageDialog extends DialogWrapper { @Nullable Icon icon, @Nullable com.intellij.openapi.ui.DoNotAskOption doNotAskOption, boolean canBeParent, - @Nullable String helpId) { + @Nullable String helpId, + @Nullable String invocationPlace) { super(project, parentComponent, canBeParent, IdeModalityType.IDE); - _init(title, message, options, defaultOptionIndex, focusedOptionIndex, icon, doNotAskOption, helpId); + _init(title, message, options, defaultOptionIndex, focusedOptionIndex, icon, doNotAskOption, helpId, invocationPlace); + } + + public MessageDialog(@Nullable Project project, + @Nullable Component parentComponent, + @NlsContexts.DialogMessage @Nullable String message, + @NlsContexts.DialogTitle String title, + String @NotNull [] options, + int defaultOptionIndex, + int focusedOptionIndex, + @Nullable Icon icon, + @Nullable com.intellij.openapi.ui.DoNotAskOption doNotAskOption, + boolean canBeParent, + @Nullable String helpId) { + this(project, parentComponent, message, title, options, defaultOptionIndex, focusedOptionIndex, icon, doNotAskOption, canBeParent, helpId, null); } public MessageDialog(@NlsContexts.DialogMessage @Nullable String message, @@ -92,6 +107,18 @@ public class MessageDialog extends DialogWrapper { @Nullable Icon icon, @Nullable com.intellij.openapi.ui.DoNotAskOption doNotAskOption, @Nullable String helpId) { + _init(title, message, options, defaultOptionIndex, focusedOptionIndex, icon, doNotAskOption, helpId, null); + } + + protected void _init(@NlsContexts.DialogTitle String title, + @NlsContexts.DialogMessage @Nullable String message, + String @NotNull [] options, + int defaultOptionIndex, + int focusedOptionIndex, + @Nullable Icon icon, + @Nullable com.intellij.openapi.ui.DoNotAskOption doNotAskOption, + @Nullable String helpId, + @Nullable String invocationPlace) { setTitle(title); myMessage = message; myOptions = options; @@ -100,6 +127,7 @@ public class MessageDialog extends DialogWrapper { myIcon = icon; myHelpId = helpId; setDoNotAskOption(doNotAskOption); + setInvocationPlace(invocationPlace); init(); } diff --git a/platform/platform-impl/api-dump-unreviewed.txt b/platform/platform-impl/api-dump-unreviewed.txt index 94c0bf29c799..4c36825922e5 100644 --- a/platform/platform-impl/api-dump-unreviewed.txt +++ b/platform/platform-impl/api-dump-unreviewed.txt @@ -15743,7 +15743,7 @@ c:com.intellij.ui.messages.MessagesServiceImpl - showErrorDialog(com.intellij.openapi.project.Project,java.lang.String,java.lang.String):V - showInputDialog(com.intellij.openapi.project.Project,java.awt.Component,java.lang.String,java.lang.String,javax.swing.Icon,java.lang.String,com.intellij.openapi.ui.InputValidator,com.intellij.openapi.util.TextRange,java.lang.String):java.lang.String - showInputDialogWithCheckBox(java.lang.String,java.lang.String,java.lang.String,Z,Z,javax.swing.Icon,java.lang.String,com.intellij.openapi.ui.InputValidator):com.intellij.openapi.util.Pair -- showMessageDialog(com.intellij.openapi.project.Project,java.awt.Component,java.lang.String,java.lang.String,java.lang.String[],I,I,javax.swing.Icon,com.intellij.openapi.ui.DoNotAskOption,Z,java.lang.String):I +- showMessageDialog(com.intellij.openapi.project.Project,java.awt.Component,java.lang.String,java.lang.String,java.lang.String[],I,I,javax.swing.Icon,com.intellij.openapi.ui.DoNotAskOption,Z,java.lang.String,java.lang.String):I - showMoreInfoMessageDialog(com.intellij.openapi.project.Project,java.lang.String,java.lang.String,java.lang.String,java.lang.String[],I,I,javax.swing.Icon):I - showMultilineInputDialog(com.intellij.openapi.project.Project,java.lang.String,java.lang.String,java.lang.String,javax.swing.Icon,com.intellij.openapi.ui.InputValidator):java.lang.String - showPasswordDialog(com.intellij.openapi.project.Project,java.lang.String,java.lang.String,javax.swing.Icon,com.intellij.openapi.ui.InputValidator):java.lang.String diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/collectors/fus/ui/DialogInvocationPlaceEP.kt b/platform/platform-impl/src/com/intellij/internal/statistic/collectors/fus/ui/DialogInvocationPlaceEP.kt new file mode 100644 index 000000000000..b9878e7ec560 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/internal/statistic/collectors/fus/ui/DialogInvocationPlaceEP.kt @@ -0,0 +1,16 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.internal.statistic.collectors.fus.ui + +import com.intellij.openapi.extensions.RequiredElement +import com.intellij.util.xmlb.annotations.Attribute +import org.jetbrains.annotations.ApiStatus + +/** + * Extension point to register an invocation place ID for a dialog to record in feature usage statistics. + */ +@ApiStatus.Internal +internal class DialogInvocationPlaceEP { + @Attribute("id") + @RequiredElement + var id: String? = null +} diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/collectors/fus/ui/DialogInvocationPlacesCollector.kt b/platform/platform-impl/src/com/intellij/internal/statistic/collectors/fus/ui/DialogInvocationPlacesCollector.kt new file mode 100644 index 000000000000..e5ea56bcea75 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/internal/statistic/collectors/fus/ui/DialogInvocationPlacesCollector.kt @@ -0,0 +1,47 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.internal.statistic.collectors.fus.ui + +import com.intellij.internal.statistic.utils.getPluginInfoByDescriptor +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import com.intellij.openapi.extensions.ExtensionPointListener +import com.intellij.openapi.extensions.ExtensionPointName +import com.intellij.openapi.extensions.PluginDescriptor +import com.intellij.util.application +import kotlinx.collections.immutable.toImmutableList +import java.util.concurrent.ConcurrentSkipListSet + +@Service(Service.Level.APP) +internal class DialogInvocationPlacesCollector private constructor() { + private val invocationPlaces: MutableSet = ConcurrentSkipListSet() + + init { + processExtensions() + } + + companion object { + @JvmStatic + fun getInstance(): DialogInvocationPlacesCollector = application.service() + private val EP_NAME: ExtensionPointName = ExtensionPointName("com.intellij.dialogInvocationPlace") + } + + private fun processExtensions() { + EP_NAME.processWithPluginDescriptor { extension, pluginDescriptor -> + addInvocationPlace(extension, pluginDescriptor) + } + EP_NAME.addExtensionPointListener(object : ExtensionPointListener { + override fun extensionAdded(extension: DialogInvocationPlaceEP, pluginDescriptor: PluginDescriptor) { + addInvocationPlace(extension, pluginDescriptor) + } + }, null) + } + + private fun addInvocationPlace(extension: DialogInvocationPlaceEP, pluginDescriptor: PluginDescriptor) { + val info = getPluginInfoByDescriptor(pluginDescriptor) + if (info.isSafeToReport() && extension.id != null) { + invocationPlaces.add(extension.id!!) + } + } + + fun getInvocationPlaces(): List = invocationPlaces.toImmutableList() +} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/collectors/fus/ui/FeatureUsageUiEventsImpl.kt b/platform/platform-impl/src/com/intellij/internal/statistic/collectors/fus/ui/FeatureUsageUiEventsImpl.kt index f597ca30717e..f9c0ca45c75b 100644 --- a/platform/platform-impl/src/com/intellij/internal/statistic/collectors/fus/ui/FeatureUsageUiEventsImpl.kt +++ b/platform/platform-impl/src/com/intellij/internal/statistic/collectors/fus/ui/FeatureUsageUiEventsImpl.kt @@ -5,14 +5,18 @@ import com.intellij.internal.statistic.eventLog.EventLogGroup import com.intellij.internal.statistic.eventLog.FeatureUsageData import com.intellij.internal.statistic.eventLog.FeatureUsageUiEvents import com.intellij.internal.statistic.eventLog.events.* +import com.intellij.internal.statistic.eventLog.events.EventFields.StringValidatedByCustomRule import com.intellij.internal.statistic.eventLog.fus.FeatureUsageLogger +import com.intellij.internal.statistic.eventLog.validator.ValidationResultType +import com.intellij.internal.statistic.eventLog.validator.rules.EventContext +import com.intellij.internal.statistic.eventLog.validator.rules.impl.CustomValidationRule import com.intellij.internal.statistic.service.fus.collectors.CounterUsagesCollector import com.intellij.openapi.options.Configurable import com.intellij.openapi.options.ex.ConfigurableWrapper import com.intellij.openapi.ui.DialogWrapper internal object DialogsCounterUsagesCollector : CounterUsagesCollector() { - private val GROUP = EventLogGroup("ui.dialogs", 61) + private val GROUP = EventLogGroup("ui.dialogs", 62) val EXIT_CODE: PrimitiveEventField = object: PrimitiveEventField() { override val name: String = "code" @@ -34,16 +38,26 @@ internal object DialogsCounterUsagesCollector : CounterUsagesCollector() { } val DIALOG_CLASS: ClassEventField = EventFields.Class("dialog_class") + val INVOCATION_PLACE: StringEventField = StringValidatedByCustomRule("dialog_invocation_place", ListValidationRule::class.java) - val SHOW: VarargEventId = GROUP.registerVarargEvent("show", DIALOG_CLASS, EventFields.PluginInfo) - val CLOSE: VarargEventId = GROUP.registerVarargEvent("close", DIALOG_CLASS, EXIT_CODE, EventFields.PluginInfo) - val HELP: VarargEventId = GROUP.registerVarargEvent("help.clicked", DIALOG_CLASS, EventFields.PluginInfo) + val SHOW: VarargEventId = GROUP.registerVarargEvent("show", DIALOG_CLASS, INVOCATION_PLACE, EventFields.PluginInfo) + val CLOSE: VarargEventId = GROUP.registerVarargEvent("close", DIALOG_CLASS, INVOCATION_PLACE, EXIT_CODE, EventFields.PluginInfo) + val HELP: VarargEventId = GROUP.registerVarargEvent("help.clicked", DIALOG_CLASS, INVOCATION_PLACE, EventFields.PluginInfo) override fun getGroup(): EventLogGroup = GROUP } +internal class ListValidationRule : CustomValidationRule() { + override fun getRuleId(): String = "dialog_invocation_place" + override fun doValidate(data: String, context: EventContext): ValidationResultType { + val invocationPlaces = DialogInvocationPlacesCollector.getInstance().getInvocationPlaces() + if (invocationPlaces.contains(data)) return ValidationResultType.ACCEPTED + return ValidationResultType.REJECTED + } +} + internal object SettingsCounterUsagesCollector : CounterUsagesCollector() { - private val GROUP = EventLogGroup("ui.settings", 62) + private val GROUP = EventLogGroup("ui.settings", 63) private val CONFIGURABLE_CLASS = EventFields.Class("configurable") val SELECT: EventId3?, Boolean, Long> = GROUP.registerEvent("select", @@ -94,24 +108,27 @@ internal class FeatureUsageUiEventsImpl : FeatureUsageUiEvents { event.log(wrapper?.project, (wrapper?.configurable ?: configurable)::class.java) } - override fun logShowDialog(clazz: Class<*>) { + override fun logShowDialog(clazz: Class<*>, invocationPlace: String?) { if (FeatureUsageLogger.getInstance().isEnabled()) { - DialogsCounterUsagesCollector.SHOW.log(DialogsCounterUsagesCollector.DIALOG_CLASS.with(clazz)) + DialogsCounterUsagesCollector.SHOW.log(DialogsCounterUsagesCollector.DIALOG_CLASS.with(clazz), + DialogsCounterUsagesCollector.INVOCATION_PLACE.with(invocationPlace)) } } - override fun logCloseDialog(clazz: Class<*>, exitCode: Int) { + override fun logCloseDialog(clazz: Class<*>, exitCode: Int, invocationPlace: String?) { if (FeatureUsageLogger.getInstance().isEnabled()) { DialogsCounterUsagesCollector.CLOSE.log( DialogsCounterUsagesCollector.DIALOG_CLASS.with(clazz), + DialogsCounterUsagesCollector.INVOCATION_PLACE.with(invocationPlace), DialogsCounterUsagesCollector.EXIT_CODE.with(exitCode) ) } } - override fun logClickOnHelpDialog(clazz: Class<*>) { + override fun logClickOnHelpDialog(clazz: Class<*>, invocationPlace: String?) { if (FeatureUsageLogger.getInstance().isEnabled()) { - DialogsCounterUsagesCollector.HELP.log(DialogsCounterUsagesCollector.DIALOG_CLASS.with(clazz)) + DialogsCounterUsagesCollector.HELP.log(DialogsCounterUsagesCollector.DIALOG_CLASS.with(clazz), + DialogsCounterUsagesCollector.INVOCATION_PLACE.with(invocationPlace)) } } } \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectManagerImpl.kt b/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectManagerImpl.kt index 26f55e8113fd..9cf15a7c9837 100644 --- a/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectManagerImpl.kt +++ b/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectManagerImpl.kt @@ -86,6 +86,7 @@ import com.intellij.util.runSuppressing import com.intellij.workspaceModel.ide.impl.jpsMetrics import kotlinx.coroutines.* import org.jetbrains.annotations.ApiStatus.Internal +import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.TestOnly import org.jetbrains.annotations.VisibleForTesting import java.io.IOException @@ -1375,17 +1376,21 @@ private suspend fun confirmOpenNewProject(options: OpenProjectTask): Int { val openInExistingFrame = withContext(Dispatchers.EDT) { // readAction is not enough writeIntentReadAction { + @NonNls + val actionPlace = "Open project action" if (options.isNewProject) MessageDialogBuilder.yesNoCancel(ideUICustomization.projectMessage("title.new.project"), message) .yesText(IdeBundle.message("button.existing.frame")) .noText(IdeBundle.message("button.new.frame")) .doNotAsk(ProjectNewWindowDoNotAskOption()) + .invocationPlace(actionPlace) .guessWindowAndAsk() else MessageDialogBuilder.yesNoCancel(ideUICustomization.projectMessage("title.open.project"), message) .yesText(IdeBundle.message("button.existing.frame")) .noText(IdeBundle.message("button.new.frame")) .doNotAsk(ProjectNewWindowDoNotAskOption()) + .invocationPlace(actionPlace) .guessWindowAndAsk() } } diff --git a/platform/platform-impl/src/com/intellij/ui/messages/AlertMessagesManager.kt b/platform/platform-impl/src/com/intellij/ui/messages/AlertMessagesManager.kt index 118020393127..d133297b457a 100644 --- a/platform/platform-impl/src/com/intellij/ui/messages/AlertMessagesManager.kt +++ b/platform/platform-impl/src/com/intellij/ui/messages/AlertMessagesManager.kt @@ -63,9 +63,10 @@ internal class AlertMessagesManager { focusedOptionIndex: Int, icon: Icon?, doNotAskOption: DoNotAskOption?, - helpId: String?): Int { + helpId: String?, + invocationPlace: String?): Int { val dialog = AlertDialog(project, parentComponent, message, title, options, defaultOptionIndex, focusedOptionIndex, getIcon(icon), - doNotAskOption, helpId) + doNotAskOption, helpId, invocationPlace) AppIcon.getInstance().requestAttention(project, true) dialog.show() return dialog.exitCode @@ -100,7 +101,8 @@ class AlertDialog(project: Project?, val myFocusedOptionIndex: Int, icon: Icon, doNotAskOption: com.intellij.openapi.ui.DoNotAskOption?, - val myHelpId: String?) : DialogWrapper(project, parentComponent, false, IdeModalityType.IDE, false) { + val myHelpId: String?, + invocationPlace: String? = null) : DialogWrapper(project, parentComponent, false, IdeModalityType.IDE, false) { private val myIsTitleComponent = SystemInfoRt.isMac || !Registry.`is`("ide.message.dialogs.as.swing.alert.show.title.bar", false) @@ -117,6 +119,7 @@ class AlertDialog(project: Project?, init { title = myTitle setDoNotAskOption(doNotAskOption) + setInvocationPlace(invocationPlace) if (myIsTitleComponent && !SystemInfoRt.isMac) { myCloseButton = object : InplaceButton(IconButton(null, AllIcons.Windows.CloseActive, null, null), { diff --git a/platform/platform-impl/src/com/intellij/ui/messages/MessagesServiceImpl.java b/platform/platform-impl/src/com/intellij/ui/messages/MessagesServiceImpl.java index 6d76c6e02244..32be591f31e9 100644 --- a/platform/platform-impl/src/com/intellij/ui/messages/MessagesServiceImpl.java +++ b/platform/platform-impl/src/com/intellij/ui/messages/MessagesServiceImpl.java @@ -44,7 +44,8 @@ public class MessagesServiceImpl implements MessagesService { @Nullable Icon icon, @Nullable DoNotAskOption doNotAskOption, boolean alwaysUseIdeaUI, - @Nullable String helpId) { + @Nullable String helpId, + @Nullable String invocationPlace) { if (isApplicationInUnitTestOrHeadless()) { return TestDialogManager.getTestImplementation().show(message); } @@ -52,11 +53,11 @@ public class MessagesServiceImpl implements MessagesService { AlertMessagesManager alertMessagesManager = AlertMessagesManager.getInstanceIfPossible(); if (alertMessagesManager != null) { return alertMessagesManager.showMessageDialog(project, parentComponent, message, title, options, defaultOptionIndex, - focusedOptionIndex, icon, doNotAskOption, helpId); + focusedOptionIndex, icon, doNotAskOption, helpId, invocationPlace); } MessageDialog dialog = new MessageDialog(project, parentComponent, message, title, options, defaultOptionIndex, focusedOptionIndex, - icon, doNotAskOption, false, helpId); + icon, doNotAskOption, false, helpId, invocationPlace); dialog.show(); return dialog.getExitCode(); } @@ -79,7 +80,7 @@ public class MessagesServiceImpl implements MessagesService { AlertMessagesManager alertMessagesManager = AlertMessagesManager.getInstanceIfPossible(); if (alertMessagesManager != null) { return alertMessagesManager.showMessageDialog(project, null, message, title, options, defaultOptionIndex, - focusedOptionIndex, icon, null, null); + focusedOptionIndex, icon, null, null, null); } } diff --git a/platform/platform-resources/src/META-INF/LangExtensions.xml b/platform/platform-resources/src/META-INF/LangExtensions.xml index d64e7db5cc2c..9057628eb558 100644 --- a/platform/platform-resources/src/META-INF/LangExtensions.xml +++ b/platform/platform-resources/src/META-INF/LangExtensions.xml @@ -1212,6 +1212,7 @@ + diff --git a/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml b/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml index f47c2e7d7079..18857bb837e3 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml @@ -188,7 +188,7 @@ - + diff --git a/platform/platform-resources/src/META-INF/PlatformExtensions.xml b/platform/platform-resources/src/META-INF/PlatformExtensions.xml index 5c8fe3a9a6a0..e494c6027995 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensions.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensions.xml @@ -525,6 +525,7 @@ serviceImplementation="com.intellij.internal.statistic.collectors.fus.actions.persistence.ActionsCollectorImpl"/> +