diff --git a/ampli.json b/ampli.json new file mode 100644 index 0000000..c9b42bc --- /dev/null +++ b/ampli.json @@ -0,0 +1,14 @@ +{ + "Zone": "us", + "OrgId": "62972", + "WorkspaceId": "418f2561-c6f7-4d6c-9e33-459672266311", + "SourceId": "755ed219-73a5-4bf7-bd2f-48cf12072780", + "Runtime": "jre:kotlin-ampli", + "Platform": "JRE", + "Language": "Kotlin", + "SDK": "com.amplitude:java-sdk:1.+", + "Path": "./src/main/kotlin/com/amplitude/ampli", + "Branch": "main", + "Version": "1.0.0", + "VersionId": "ac62c3f9-e92b-4502-8a50-3fd8f13c4103" +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 2951e71..bd3398e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -44,6 +44,10 @@ repositories { } dependencies { + implementation("com.amplitude:java-sdk:[1.8.0,2.0)") + implementation("org.json:json:20201115") + implementation("com.vaadin:license-checker:1.13.3") + intellijPlatform { intellijIdeaUltimate(buildVersion) bundledPlugin("com.intellij.java") diff --git a/src/main/kotlin/com/amplitude/ampli/Ampli.kt b/src/main/kotlin/com/amplitude/ampli/Ampli.kt new file mode 100644 index 0000000..ef7f537 --- /dev/null +++ b/src/main/kotlin/com/amplitude/ampli/Ampli.kt @@ -0,0 +1,287 @@ +// +// Ampli - A strong typed wrapper for your Analytics +// +// This file is generated by Amplitude. +// To update run 'ampli pull intellij' +// +// Required dependencies: com.amplitude:java-sdk:[1.8.0,2.0), org.json:json:20201115 +// Tracking Plan Version: 1 +// Build: 1.0.0 +// Runtime: jre:kotlin-ampli +// +// [View Tracking Plan](https://data.amplitude.com/vaadin/IDE%20Plugins/events/main/latest) +// +// [Full Setup +// Instructions](https://data.amplitude.com/vaadin/IDE%20Plugins/implementation/intellij) +// + +package com.amplitude.ampli + +import com.amplitude.Amplitude +import com.amplitude.MiddlewareExtra +import com.amplitude.Plan +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject + +abstract class Event>( + val eventType: String, + val eventProperties: Map?, + val options: EventOptions?, + private val eventFactory: (eventProperties: Map?, options: EventOptions?) -> E +) { + fun options(userId: String? = null, deviceId: String? = null): E { + return this.options( + EventOptions( + userId = userId, + deviceId = deviceId, + )) + } + + fun options(options: EventOptions): E { + return this.eventFactory(this.eventProperties?.toMap(), options) + } +} + +class LoadOptions( + val environment: Ampli.Environment? = null, + val disabled: Boolean? = null, + val client: LoadClientOptions? = null +) + +class EventOptions( + val userId: String? = null, + val deviceId: String? = null, + val timestamp: Long? = null, + val locationLat: Double? = null, + val locationLng: Double? = null, + val appVersion: String? = null, + val versionName: String? = null, + val platform: String? = null, + val osName: String? = null, + val osVersion: String? = null, + val deviceBrand: String? = null, + val deviceManufacturer: String? = null, + val deviceModel: String? = null, + val carrier: String? = null, + val country: String? = null, + val region: String? = null, + val city: String? = null, + val dma: String? = null, + val idfa: String? = null, + val idfv: String? = null, + val adid: String? = null, + val androidId: String? = null, + val language: String? = null, + val partnerId: String? = null, + val ip: String? = null, + val price: Double? = null, + val quantity: Int? = null, + val revenue: Double? = null, + val productId: String? = null, + val revenueType: String? = null, + val eventId: Int? = null, + val sessionId: Long? = null, + val insertId: String? = null, + val plan: Plan? = null +) + +class LoadClientOptions(val apiKey: String? = null, val instance: Amplitude? = null, val plan: Plan? = null) + +val ampli = Ampli() + +open class Ampli { + companion object { + val API_KEY: Map = mapOf(Environment.IDEPLUGINS to "5332f8777b8ce7f12dcbf6c9d749488d") + + private val observePlan: Plan = + Plan() + .setBranch("main") + .setSource("intellij") + .setVersion("1") + .setVersionId("ac62c3f9-e92b-4502-8a50-3fd8f13c4103") + } + + enum class Environment { + IDEPLUGINS + } + + private var disabled: Boolean = false + + val isLoaded: Boolean + get() { + return this._client != null + } + + private var _client: Amplitude? = null + val client: Amplitude + get() { + this.isInitializedAndEnabled() + return this._client!! + } + + /** Options should have 'environment', 'client.api_key' or 'client.instance' */ + open fun load(options: LoadOptions) { + this.disabled = options.disabled ?: false + if (this.isLoaded) { + System.err.println( + "Warning: Ampli is already initialized. ampli.load() should be called once at application start up.") + return + } + + var apiKey = "" + if (options.client?.apiKey != null) { + apiKey = options.client.apiKey + } + if (options.environment != null) { + apiKey = API_KEY[options.environment].toString() + } + + when { + options.client?.instance != null -> { + this._client = options.client.instance + } + apiKey != "" -> { + this._client = Amplitude.getInstance() + this._client?.init(apiKey) + } + else -> { + System.err.println("ampli.load() requires 'environment', 'client.apiKey', or 'client.instance'") + return + } + } + + this._client?.setPlan(options.client?.plan ?: observePlan) + + // set IngestionMetadata with backwards compatibility, min Java SDK version 1.10.1. + try { + val clazz = Class.forName("com.amplitude.IngestionMetadata") + val setSourceNameMethod = clazz.getMethod("setSourceName", String::class.java) + val setSourceVersionMethod = clazz.getMethod("setSourceVersion", String::class.java) + val ingestionMetadata = clazz.newInstance() + setSourceNameMethod.invoke(ingestionMetadata, "jre-kotlin-ampli") + setSourceVersionMethod.invoke(ingestionMetadata, "1.0.0") + val setIngestionMetadata = Amplitude::class.java.getMethod("setIngestionMetadata", clazz) + setIngestionMetadata.invoke(this._client, ingestionMetadata) + } catch (e: ClassNotFoundException) { + println("com.amplitude.IngestionMetadata is available starting from Java SDK 1.10.1 version") + } catch (e: NoSuchMethodException) { + println("com.amplitude.IngestionMetadata is available starting from Java SDK 1.10.1 version") + } catch (e: SecurityException) { + println("com.amplitude.IngestionMetadata is available starting from Java SDK 1.10.1 version") + } catch (e: Exception) { + System.err.println("Unexpected error when setting IngestionMetadata") + } + } + + open fun track(userId: String?, event: Event<*>, options: EventOptions? = null, extra: MiddlewareExtra? = null) { + if (!isInitializedAndEnabled()) { + return + } + val amplitudeEvent = this.createAmplitudeEvent(event.eventType, event.options, options, userId) + amplitudeEvent.eventProperties = this.getEventPropertiesJson(event) + + this._client?.logEvent(amplitudeEvent, extra) + } + + open fun identify(userId: String?, options: EventOptions? = null, extra: MiddlewareExtra? = null) { + if (!this.isInitializedAndEnabled()) { + return + } + val amplitudeEvent = this.createAmplitudeEvent("Identify", null, options, userId) + + this._client?.logEvent(amplitudeEvent, extra) + } + + open fun flush() { + if (!this.isInitializedAndEnabled()) { + return + } + this._client?.flushEvents() + } + + private fun createAmplitudeEvent( + eventType: String, + options: EventOptions?, + overrideOptions: EventOptions?, + overrideUserId: String? + ): com.amplitude.Event { + val event = + com.amplitude.Event( + eventType, + overrideUserId ?: overrideOptions?.userId ?: options?.userId, + overrideOptions?.deviceId ?: options?.deviceId) + (overrideOptions?.timestamp ?: options?.timestamp)?.let { event.timestamp = it } + (overrideOptions?.locationLat ?: options?.locationLat)?.let { event.locationLat = it } + (overrideOptions?.locationLng ?: options?.locationLng)?.let { event.locationLng = it } + (overrideOptions?.appVersion ?: options?.appVersion)?.let { event.appVersion = it } + (overrideOptions?.versionName ?: options?.versionName)?.let { event.versionName = it } + (overrideOptions?.platform ?: options?.platform)?.let { event.platform = it } + (overrideOptions?.osName ?: options?.osName)?.let { event.osName = it } + (overrideOptions?.osVersion ?: options?.osVersion)?.let { event.osVersion = it } + (overrideOptions?.deviceBrand ?: options?.deviceBrand)?.let { event.deviceBrand = it } + (overrideOptions?.deviceManufacturer ?: options?.deviceManufacturer)?.let { event.deviceManufacturer = it } + (overrideOptions?.deviceModel ?: options?.deviceModel)?.let { event.deviceModel = it } + (overrideOptions?.carrier ?: options?.carrier)?.let { event.carrier = it } + (overrideOptions?.country ?: options?.country)?.let { event.country = it } + (overrideOptions?.region ?: options?.region)?.let { event.region = it } + (overrideOptions?.city ?: options?.city)?.let { event.city = it } + (overrideOptions?.dma ?: options?.dma)?.let { event.dma = it } + (overrideOptions?.idfa ?: options?.idfa)?.let { event.idfa = it } + (overrideOptions?.idfv ?: options?.idfv)?.let { event.idfv = it } + (overrideOptions?.adid ?: options?.adid)?.let { event.adid = it } + (overrideOptions?.androidId ?: options?.androidId)?.let { event.androidId = it } + (overrideOptions?.language ?: options?.language)?.let { event.language = it } + (overrideOptions?.partnerId ?: options?.partnerId)?.let { event.partnerId = it } + (overrideOptions?.ip ?: options?.ip)?.let { event.ip = it } + (overrideOptions?.price ?: options?.price)?.let { event.price = it } + (overrideOptions?.quantity ?: options?.quantity)?.let { event.quantity = it } + (overrideOptions?.revenue ?: options?.revenue)?.let { event.revenue = it } + (overrideOptions?.productId ?: options?.productId)?.let { event.productId = it } + (overrideOptions?.revenueType ?: options?.revenueType)?.let { event.revenueType = it } + (overrideOptions?.eventId ?: options?.eventId)?.let { event.eventId = it } + (overrideOptions?.sessionId ?: options?.sessionId)?.let { event.sessionId = it } + (overrideOptions?.insertId ?: options?.insertId)?.let { event.insertId = it } + (overrideOptions?.plan ?: options?.plan)?.let { event.plan = it } + return event + } + + private fun isInitializedAndEnabled(): Boolean { + if (!this.isLoaded) { + System.err.println("Ampli is not yet initialized. Have you called `ampli.load()` on app start?") + return false + } + return !this.disabled + } + + private fun getEventPropertiesJson(event: Event<*>?): JSONObject? { + if (event?.eventProperties == null) { + return null + } + + val json = JSONObject() + + event.eventProperties.entries.forEach { eventPropertyEntry -> + val key = eventPropertyEntry.key + val value = eventPropertyEntry.value + + try { + value?.let { json.put(key, if (value.javaClass.isArray) getJsonArray(value) else value) } + ?: run { json.put(key, JSONObject.NULL) } + } catch (e: JSONException) { + System.err.println("Error converting properties to JSONObject: ${e.message}") + } + } + + return json + } + + private fun getJsonArray(value: Any): JSONArray { + return try { + JSONArray(value) + } catch (e: JSONException) { + System.err.printf("Error converting value to JSONArray: %s%n", e.message) + JSONArray() + } + } +} diff --git a/src/main/kotlin/com/vaadin/plugin/listeners/ConfigurationCheckVaadinProjectListener.kt b/src/main/kotlin/com/vaadin/plugin/listeners/ConfigurationCheckVaadinProjectListener.kt index 634a453..cf5e96f 100644 --- a/src/main/kotlin/com/vaadin/plugin/listeners/ConfigurationCheckVaadinProjectListener.kt +++ b/src/main/kotlin/com/vaadin/plugin/listeners/ConfigurationCheckVaadinProjectListener.kt @@ -1,5 +1,6 @@ package com.vaadin.plugin.listeners +import com.amplitude.ampli.ampli import com.intellij.debugger.JavaDebuggerBundle import com.intellij.debugger.settings.DebuggerSettings import com.intellij.ide.actionsOnSave.ActionsOnSaveConfigurable @@ -12,6 +13,8 @@ import com.intellij.notification.Notifications import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.options.ShowSettingsUtil import com.intellij.openapi.project.Project +import com.intellij.openapi.project.ProjectManager +import com.intellij.openapi.project.ProjectManagerListener import com.intellij.openapi.vcs.VcsBundle import com.intellij.openapi.vcs.VcsConfiguration import com.intellij.openapi.vcs.VcsShowConfirmationOption @@ -20,6 +23,7 @@ import com.vaadin.plugin.actions.VaadinCompileOnSaveActionInfo import com.vaadin.plugin.copilot.CopilotPluginUtil import com.vaadin.plugin.utils.VaadinHomeUtil import com.vaadin.plugin.utils.VaadinIcons +import com.vaadin.plugin.utils.trackPluginInitialized class ConfigurationCheckVaadinProjectListener : VaadinProjectListener { @@ -46,6 +50,7 @@ class ConfigurationCheckVaadinProjectListener : VaadinProjectListener { checkReloadClassesSetting(project) checkVcsAddConfirmationSetting(project) checkCompileOnSave(project) + initAmplitude(project) RunOnceUtil.runOnceForApp("hotswap-version-check-" + CopilotPluginUtil.getPluginVersion()) { VaadinHomeUtil.updateOrInstallHotSwapJar() } @@ -123,4 +128,17 @@ class ConfigurationCheckVaadinProjectListener : VaadinProjectListener { } } } + + private fun initAmplitude(project: Project) { + trackPluginInitialized() + ProjectManager.getInstance() + .addProjectManagerListener( + project, + object : ProjectManagerListener { + override fun projectClosing(project: Project) { + ampli.flush() + } + }, + ) + } } diff --git a/src/main/kotlin/com/vaadin/plugin/module/VaadinProjectBuilderAdapter.kt b/src/main/kotlin/com/vaadin/plugin/module/VaadinProjectBuilderAdapter.kt index 285f3da..0960019 100644 --- a/src/main/kotlin/com/vaadin/plugin/module/VaadinProjectBuilderAdapter.kt +++ b/src/main/kotlin/com/vaadin/plugin/module/VaadinProjectBuilderAdapter.kt @@ -11,6 +11,7 @@ import com.intellij.openapi.observable.properties.PropertyGraph import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VfsUtil import com.vaadin.plugin.utils.VaadinProjectUtil +import com.vaadin.plugin.utils.trackProjectCreated import java.io.File class VaadinProjectBuilderAdapter(private val vaadinWizard: VaadinProjectWizard = VaadinProjectWizard()) : @@ -28,7 +29,9 @@ class VaadinProjectBuilderAdapter(private val vaadinWizard: VaadinProjectWizard return super.createProject(name, path)?.let { project -> project.putUserData(VaadinProjectUtil.PROJECT_DOWNLOADED_PROP_KEY, projectDownloadedProperty) projectDownloadedProperty.afterChange { afterProjectCreated(project) } - VaadinProjectUtil.downloadAndExtract(project, vaadinWizard.projectModel!!.getDownloadLink(project)) + val downloadLink = vaadinWizard.projectModel!!.getDownloadLink(project) + VaadinProjectUtil.downloadAndExtract(project, downloadLink) + trackProjectCreated(downloadLink) project } } diff --git a/src/main/kotlin/com/vaadin/plugin/ui/VaadinStatusBarInfoPopupPanel.kt b/src/main/kotlin/com/vaadin/plugin/ui/VaadinStatusBarInfoPopupPanel.kt index 1b628ee..2c25811 100644 --- a/src/main/kotlin/com/vaadin/plugin/ui/VaadinStatusBarInfoPopupPanel.kt +++ b/src/main/kotlin/com/vaadin/plugin/ui/VaadinStatusBarInfoPopupPanel.kt @@ -11,6 +11,7 @@ import com.intellij.util.ui.JBFont import com.intellij.util.ui.UIUtil import com.vaadin.plugin.copilot.CopilotPluginUtil import com.vaadin.plugin.utils.hasEndpoints +import com.vaadin.plugin.utils.trackManualCopilotRestart import java.awt.Component import javax.swing.JButton import javax.swing.JComponent @@ -60,6 +61,7 @@ class VaadinStatusBarInfoPopupPanel(private val project: Project) : JPanel() { restart.addActionListener { CopilotPluginUtil.removeDotFile(project) CopilotPluginUtil.saveDotFile(project) + trackManualCopilotRestart() DumbService.getInstance(project).smartInvokeLater { VaadinStatusBarWidget.update(project) afterRestart?.invoke() diff --git a/src/main/kotlin/com/vaadin/plugin/ui/settings/VaadinSettings.kt b/src/main/kotlin/com/vaadin/plugin/ui/settings/VaadinSettings.kt new file mode 100644 index 0000000..702f24b --- /dev/null +++ b/src/main/kotlin/com/vaadin/plugin/ui/settings/VaadinSettings.kt @@ -0,0 +1,29 @@ +package com.vaadin.plugin.ui.settings + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.components.PersistentStateComponent +import com.intellij.openapi.components.State +import com.intellij.openapi.components.Storage +import org.jetbrains.annotations.NotNull + +@State(name = "com.vaadin.plugin.ui.settings.VaadinSettings", storages = [Storage("VaadinSettings.xml")]) +internal class VaadinSettings : PersistentStateComponent { + internal class State { + var sendUsageStatistics: Boolean = true + } + + private var myState = State() + + override fun getState(): State { + return myState + } + + override fun loadState(@NotNull state: State) { + myState = state + } + + companion object { + val instance: VaadinSettings + get() = ApplicationManager.getApplication().getService(VaadinSettings::class.java) + } +} diff --git a/src/main/kotlin/com/vaadin/plugin/ui/settings/VaadinSettingsComponent.kt b/src/main/kotlin/com/vaadin/plugin/ui/settings/VaadinSettingsComponent.kt new file mode 100644 index 0000000..2efb0bf --- /dev/null +++ b/src/main/kotlin/com/vaadin/plugin/ui/settings/VaadinSettingsComponent.kt @@ -0,0 +1,30 @@ +package com.vaadin.plugin.ui.settings + +import com.intellij.ui.components.JBCheckBox +import com.intellij.util.ui.FormBuilder +import javax.swing.JComponent +import javax.swing.JPanel + +/** Supports creating and managing a [JPanel] for the Settings Dialog. */ +class VaadinSettingsComponent { + val panel: JPanel + private val sendUsageStatistics = JBCheckBox("Send usage statistics") + + init { + panel = + FormBuilder.createFormBuilder() + .addComponent(sendUsageStatistics, 1) + .addTooltip("Help us improve Vaadin plugin by sending anonymous usage statistics") + .addComponentFillVertically(JPanel(), 0) + .panel + } + + val preferredFocusedComponent: JComponent + get() = sendUsageStatistics + + var sendUsageStatisticsStatus: Boolean + get() = sendUsageStatistics.isSelected + set(newStatus) { + sendUsageStatistics.isSelected = newStatus + } +} diff --git a/src/main/kotlin/com/vaadin/plugin/ui/settings/VaadinSettingsConfigurable.kt b/src/main/kotlin/com/vaadin/plugin/ui/settings/VaadinSettingsConfigurable.kt new file mode 100644 index 0000000..e446f1d --- /dev/null +++ b/src/main/kotlin/com/vaadin/plugin/ui/settings/VaadinSettingsConfigurable.kt @@ -0,0 +1,43 @@ +package com.vaadin.plugin.ui.settings + +import com.intellij.openapi.options.Configurable +import java.util.Objects +import javax.swing.JComponent + +/** Provides controller functionality for application settings. */ +internal class VaadinSettingsConfigurable : Configurable { + + private var mySettingsComponent: VaadinSettingsComponent? = null + + override fun getPreferredFocusedComponent(): JComponent { + return mySettingsComponent!!.preferredFocusedComponent + } + + override fun createComponent(): JComponent { + mySettingsComponent = VaadinSettingsComponent() + return mySettingsComponent!!.panel + } + + override fun isModified(): Boolean { + val state: VaadinSettings.State = Objects.requireNonNull(VaadinSettings.instance.state) + return mySettingsComponent!!.sendUsageStatisticsStatus != state.sendUsageStatistics + } + + override fun apply() { + val state: VaadinSettings.State = Objects.requireNonNull(VaadinSettings.instance.state) + state.sendUsageStatistics = mySettingsComponent!!.sendUsageStatisticsStatus + } + + override fun reset() { + val state: VaadinSettings.State = Objects.requireNonNull(VaadinSettings.instance.state) + mySettingsComponent!!.sendUsageStatisticsStatus = state.sendUsageStatistics + } + + override fun disposeUIResources() { + mySettingsComponent = null + } + + override fun getDisplayName(): String { + return "Vaadin" + } +} diff --git a/src/main/kotlin/com/vaadin/plugin/utils/AmpliUtil.kt b/src/main/kotlin/com/vaadin/plugin/utils/AmpliUtil.kt new file mode 100644 index 0000000..7f63b6b --- /dev/null +++ b/src/main/kotlin/com/vaadin/plugin/utils/AmpliUtil.kt @@ -0,0 +1,72 @@ +package com.vaadin.plugin.utils + +import com.amplitude.ampli.Ampli +import com.amplitude.ampli.Event +import com.amplitude.ampli.EventOptions +import com.amplitude.ampli.LoadOptions +import com.amplitude.ampli.ampli +import com.intellij.openapi.application.ApplicationInfo +import com.intellij.util.io.DigestUtil +import com.vaadin.plugin.copilot.CopilotPluginUtil +import com.vaadin.plugin.ui.settings.VaadinSettings +import com.vaadin.pro.licensechecker.LocalProKey +import com.vaadin.pro.licensechecker.MachineId +import com.vaadin.pro.licensechecker.ProKey +import java.nio.charset.Charset + +private val eventOptions = + EventOptions( + versionName = CopilotPluginUtil.getPluginVersion(), + platform = "intellij", + deviceModel = if (isUltimate()) "ultimate" else "community", + language = System.getProperty("user.language"), + country = System.getProperty("user.country"), + region = System.getProperty("user.region"), + osName = System.getProperty("os.name"), + osVersion = System.getProperty("os.version"), + appVersion = ApplicationInfo.getInstance().fullVersion) + +private class VaadinPluginEvent(eventType: String, eventProperties: Map?) : + Event( + eventType, + eventProperties, + options = eventOptions, + eventFactory = { _, _ -> VaadinPluginEvent(eventType, eventProperties) }) + +private var userId: String? = null + +private fun getUserId(): String? { + if (userId == null) { + val proKey: ProKey? = LocalProKey.get() + userId = + if (proKey != null) { + "pro-${DigestUtil.sha256Hex(proKey.proKey.toByteArray(Charset.defaultCharset()))}" + } else { + MachineId.get() + } + ampli.load(LoadOptions(Ampli.Environment.IDEPLUGINS)) + ampli.identify(userId, eventOptions) + } + return userId +} + +private val enabled: Boolean + get() = VaadinSettings.instance.state.sendUsageStatistics + +internal fun trackPluginInitialized() { + if (enabled) { + ampli.track(getUserId(), VaadinPluginEvent("plugin-initialized", null)) + } +} + +internal fun trackProjectCreated(downloadUrl: String) { + if (enabled) { + ampli.track(getUserId(), VaadinPluginEvent("project-created", mapOf("downloadUrl" to downloadUrl))) + } +} + +internal fun trackManualCopilotRestart() { + if (enabled) { + ampli.track(getUserId(), VaadinPluginEvent("manual-copilot-restart", null)) + } +} diff --git a/src/main/kotlin/com/vaadin/plugin/utils/VaadinProjectUtil.kt b/src/main/kotlin/com/vaadin/plugin/utils/VaadinProjectUtil.kt index 7090d4c..a4c474b 100644 --- a/src/main/kotlin/com/vaadin/plugin/utils/VaadinProjectUtil.kt +++ b/src/main/kotlin/com/vaadin/plugin/utils/VaadinProjectUtil.kt @@ -2,6 +2,7 @@ package com.vaadin.plugin.utils import com.intellij.ide.plugins.PluginManager import com.intellij.java.library.JavaLibraryUtil.hasLibraryClass +import com.intellij.openapi.application.ApplicationInfo import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.extensions.PluginId import com.intellij.openapi.observable.properties.GraphProperty @@ -85,3 +86,5 @@ internal fun hasVaadin(module: com.intellij.openapi.module.Module): Boolean = ha internal fun hasEndpoints(): Boolean = PluginId.findId(ENDPOINTS_PLUGIN_ID)?.let { PluginManager.isPluginInstalled(it) } ?: false + +internal fun isUltimate(): Boolean = ApplicationInfo.getInstance().apiVersion.startsWith("IU-") diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 857fdb7..afd93d0 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -52,6 +52,14 @@ + + +