Skip to content

Commit

Permalink
DEV2-2807: Explanatory IDE indications in status bar (#530)
Browse files Browse the repository at this point in the history
  • Loading branch information
Assaf Sapir authored May 25, 2023
1 parent 1c16e25 commit 823122a
Show file tree
Hide file tree
Showing 22 changed files with 254 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ public class StaticConfig {
public static final String REMOTE_BETA_VERSION_URL_PROPERTY = "TABNINE_REMOTE_BETA_VERSION_URL";
public static final String LOG_FILE_PATH_PROPERTY = "TABNINE_LOG_FILE_PATH";
public static final Icon ICON = IconLoader.findIcon("/icons/tabnine-icon-13px.png");
public static final Icon GLYPH = IconLoader.findIcon("icons/icon-13px.png");
public static final Icon PROBLEM_GLYPH = IconLoader.findIcon("icons/problem-icon-13px.png");
public static final String ICON_AND_NAME_PATH = "icons/tabnine-starter-13px.png";
public static final Icon ICON_AND_NAME_STARTER =
IconLoader.findIcon("/icons/tabnine-starter-13px.png");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.tabnineCommon.userSettings

import com.intellij.ide.util.PropertiesComponent
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.PersistentStateComponent
import com.intellij.openapi.components.State
Expand All @@ -10,6 +11,8 @@ import com.tabnineCommon.inline.render.GraphicsUtils

val settingsDefaultColor = GraphicsUtils.niceContrastColor.rgb

const val PROPERTIES_COMPONENT_NAME = "com.tabnine.enterprise-url"

/**
* This package (`userSettings`) is heavily influenced by the docs from here:
* https://plugins.jetbrains.com/docs/intellij/settings-tutorial.html
Expand All @@ -27,10 +30,11 @@ class AppSettingsState : PersistentStateComponent<AppSettingsState?> {
var debounceTime: Long = 0
var autoImportEnabled: Boolean = true
var binariesFolderOverride: String = ""
var cloud2Url: String = ""
var cloud2Url: String = getInitialCloudUrlFromProperties()
set(value) {
replaceCustomRepository(field, value)
field = value.trim()
replaceCustomRepository(field, value)
PropertiesComponent.getInstance().setValue(PROPERTIES_COMPONENT_NAME, field)
}
var useIJProxySettings: Boolean = true

Expand All @@ -55,6 +59,12 @@ class AppSettingsState : PersistentStateComponent<AppSettingsState?> {
}

companion object {
@JvmStatic
private fun getInitialCloudUrlFromProperties(): String {
val current = PropertiesComponent.getInstance().getValue(PROPERTIES_COMPONENT_NAME)
return current ?: ""
}

@JvmStatic
val instance: AppSettingsState
get() = ApplicationManager.getApplication().getService(AppSettingsState::class.java)
Expand Down
Binary file added Common/src/main/resources/icons/icon-13px.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Common/src/main/resources/icons/icon-13px_dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion Tabnine/src/main/java/com/tabnine/Initializer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ class Initializer : PreloadingActivity(), StartupActivity {
connectionLostNotificationHandler.startConnectionLostListener()
ServiceManager.getService(BinaryStateService::class.java).startUpdateLoop()
initTabnineLogger()

initListeners()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.tabnineCommon.lifecycle.BinaryStateService
import com.tabnineCommon.logging.initTabnineLogger
import com.tabnineCommon.notifications.ConnectionLostNotificationHandler
import com.tabnineCommon.userSettings.AppSettingsState
import com.tabnineSelfHosted.binary.lifecycle.UserInfoService
import java.util.concurrent.atomic.AtomicBoolean

class Initializer : PreloadingActivity(), StartupActivity {
Expand All @@ -30,6 +31,7 @@ class Initializer : PreloadingActivity(), StartupActivity {
val host = AppSettingsState.instance.cloud2Url
SelfHostedInitializer().initialize(host)
ServiceManager.getService(BinaryStateService::class.java).startUpdateLoop()
ServiceManager.getService(UserInfoService::class.java).startUpdateLoop()
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.tabnineSelfHosted.binary.lifecycle

import com.intellij.util.messages.Topic
import com.tabnineSelfHosted.binary.requests.userInfo.UserInfoResponse

fun interface UserInfoChangeNotifier {
companion object {
val USER_INFO_CHANGED_TOPIC: Topic<UserInfoChangeNotifier> = Topic.create(
"User Info Changed Notifier",
UserInfoChangeNotifier::class.java
)
}

fun stateChanged(state: UserInfoResponse)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.tabnineSelfHosted.binary.lifecycle

import com.intellij.openapi.application.ApplicationManager
import com.intellij.util.concurrency.AppExecutorUtil
import com.intellij.util.messages.MessageBus
import com.tabnineCommon.general.DependencyContainer
import com.tabnineSelfHosted.binary.requests.userInfo.UserInfoRequest
import com.tabnineSelfHosted.binary.requests.userInfo.UserInfoResponse
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean

class UserInfoService {
private val scheduler = AppExecutorUtil.getAppScheduledExecutorService()
private val messageBus: MessageBus = ApplicationManager.getApplication().messageBus
private val updateLoopStarted = AtomicBoolean(false)
private val binaryRequestFacade = DependencyContainer.instanceOfBinaryRequestFacade()
var lastUserInfoResponse: UserInfoResponse? = null

fun startUpdateLoop() {
if (updateLoopStarted.getAndSet(true)) {
return
}

scheduler.scheduleWithFixedDelay(Runnable { updateState() }, 0, 2, TimeUnit.SECONDS)
}

private fun updateState() {
val userInfoResponse = binaryRequestFacade.executeRequest(UserInfoRequest())
if (userInfoResponse != null) {
if (userInfoResponse != this.lastUserInfoResponse) {
messageBus
.syncPublisher(UserInfoChangeNotifier.USER_INFO_CHANGED_TOPIC)
.stateChanged(userInfoResponse)
}
this.lastUserInfoResponse = userInfoResponse
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.tabnineSelfHosted.binary.requests.userInfo

import com.tabnineCommon.binary.BinaryRequest

class UserInfoRequest : BinaryRequest<UserInfoResponse> {
override fun response(): Class<UserInfoResponse> {
return UserInfoResponse::class.java
}

override fun serialize(): Any {
return mapOf("UserInfo" to this)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.tabnineSelfHosted.binary.requests.userInfo

import com.tabnineCommon.binary.BinaryResponse

data class UserInfoResponse(
var email: String = "",
var team: Team? = null,
var isLoggedIn: Boolean = false,
var verified: Boolean = false,
) : BinaryResponse

data class Team(
val name: String = "",
val role: Role = Role.Member
)

enum class Role {
Admin,
Member
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.tabnineSelfHosted.statusBar

import com.intellij.ide.BrowserUtil
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.DefaultActionGroup
import com.intellij.openapi.diagnostic.Logger
Expand All @@ -10,27 +11,40 @@ import com.tabnineCommon.binary.requests.login.LoginRequest
import com.tabnineCommon.binary.requests.login.LogoutRequest
import com.tabnineCommon.general.DependencyContainer
import com.tabnineCommon.userSettings.AppSettingsConfigurable
import com.tabnineCommon.userSettings.AppSettingsState

const val OPEN_TABNINE_SETTINGS_TEXT = "Open Tabnine settings"
const val LOGIN_TEXT = "Sign in to Tabnine"
const val LOGOUT_TEXT = "Sign out of Tabnine"
const val GOTO_FAQ_TEXT = "Get help"
const val FAQ_URL = "https://support.tabnine.com/hc/en-us/articles/5760725346193-Connectivity-possible-issues"

object SelfHostedStatusBarActions {
private val binaryRequestFacade = DependencyContainer.instanceOfBinaryRequestFacade()

@JvmStatic
fun buildStatusBarActionsGroup(
project: Project?,
isLoggedIn: Boolean
loginStatus: UserLoginStatus
): DefaultActionGroup {
val actions = ArrayList<AnAction>()
actions.add(
if (isLoggedIn) {
createLogoutAction()
} else {
createLoginAction()
}
)
if (AppSettingsState.instance.cloud2Url.isNotBlank()) {
actions.add(
when (loginStatus) {
UserLoginStatus.Unknown -> {
createGoToFAQAction()
}

UserLoginStatus.LoggedIn -> {
createLogoutAction()
}

UserLoginStatus.LoggedOut -> {
createLoginAction()
}
}
)
}

project?.let {
actions.add(createOpenSettingsAction(it))
Expand Down Expand Up @@ -63,4 +77,11 @@ object SelfHostedStatusBarActions {
)
}
}

private fun createGoToFAQAction(): DumbAwareAction {
return DumbAwareAction.create(GOTO_FAQ_TEXT) {
Logger.getInstance(javaClass).info("Sending to FAQ")
BrowserUtil.open(FAQ_URL)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.tabnineSelfHosted.statusBar

import com.intellij.ide.DataManager
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.popup.JBPopupFactory
Expand All @@ -11,41 +12,55 @@ import com.intellij.openapi.wm.StatusBarWidget.MultipleTextValuesPresentation
import com.intellij.openapi.wm.impl.status.EditorBasedWidget
import com.intellij.util.Consumer
import com.tabnineCommon.binary.requests.config.CloudConnectionHealthStatus
import com.tabnineCommon.binary.requests.config.StateResponse
import com.tabnineCommon.general.StaticConfig
import com.tabnineCommon.lifecycle.BinaryStateChangeNotifier
import com.tabnineCommon.lifecycle.BinaryStateService
import com.tabnineSelfHosted.binary.lifecycle.UserInfoChangeNotifier
import com.tabnineSelfHosted.binary.lifecycle.UserInfoService
import com.tabnineSelfHosted.binary.requests.userInfo.UserInfoResponse
import com.tabnineSelfHosted.statusBar.SelfHostedStatusBarActions.buildStatusBarActionsGroup
import java.awt.event.MouseEvent
import javax.swing.Icon

class TabnineSelfHostedStatusBarWidget(project: Project) : EditorBasedWidget(project), StatusBarWidget, MultipleTextValuesPresentation {
private var cloudConnectionHealthStatus = CloudConnectionHealthStatus.Ok
private var username: String? = null
class TabnineSelfHostedStatusBarWidget(project: Project) :
EditorBasedWidget(project),
StatusBarWidget,
MultipleTextValuesPresentation {

init {
// register for state changes (we will get notified whenever the state changes)
ApplicationManager.getApplication()
.messageBus
.connect(this)
.subscribe(
BinaryStateChangeNotifier.STATE_CHANGED_TOPIC,
BinaryStateChangeNotifier { (_, _, _, cloudConnectionHealthStatus1, _, userName): StateResponse ->
cloudConnectionHealthStatus = cloudConnectionHealthStatus1
username = userName
BinaryStateChangeNotifier { _ ->
update()
}
)

ApplicationManager.getApplication()
.messageBus
.connect(this)
.subscribe(
UserInfoChangeNotifier.USER_INFO_CHANGED_TOPIC,
UserInfoChangeNotifier { _ ->
update()
}
)
}

override fun getIcon(): Icon? {
if (cloudConnectionHealthStatus === CloudConnectionHealthStatus.Failed) {
return StaticConfig.ICON_AND_NAME_CONNECTION_LOST_ENTERPRISE
}
override fun getIcon(): Icon {
val cloudConnectionHealthStatus = getCloudConnectionHealthStatus()
val userInfo = getLastUserStatus()
val hasCloud2UrlConfigured = hasCloud2UrlConfigured()
if (!hasCloud2UrlConfigured ||
cloudConnectionHealthStatus === CloudConnectionHealthStatus.Failed ||
userInfo == null || !userInfo.isLoggedIn || userInfo.team == null
) {
return StaticConfig.PROBLEM_GLYPH
}

return if (hasCloud2UrlConfigured && !username.isNullOrBlank()) {
StaticConfig.ICON_AND_NAME_ENTERPRISE
} else StaticConfig.ICON_AND_NAME_CONNECTION_LOST_ENTERPRISE
return StaticConfig.GLYPH
}

private fun hasCloud2UrlConfigured(): Boolean {
Expand All @@ -55,7 +70,6 @@ class TabnineSelfHostedStatusBarWidget(project: Project) : EditorBasedWidget(pro
)
}

// Compatability implementation. DO NOT ADD @Override.
override fun getPresentation(): StatusBarWidget.WidgetPresentation {
return this
}
Expand All @@ -71,26 +85,32 @@ class TabnineSelfHostedStatusBarWidget(project: Project) : EditorBasedWidget(pro

// Compatability implementation. DO NOT ADD @Override.
override fun getTooltipText(): String {
if (username.isNullOrBlank()) {
if (!hasCloud2UrlConfigured()) {
return "Click to set the server URL."
}

val userInfo = getLastUserStatus()
if ((userInfo?.email).isNullOrBlank()) {
return "Click for sign in to use Tabnine Enterprise."
}
val hasCloud2UrlConfigured = hasCloud2UrlConfigured()
val suffix = if (hasCloud2UrlConfigured) "Click to set the server URL." else "Server URL: ${StaticConfig.getTabnineEnterpriseHost().get()}"
val suffix = "Server URL: ${StaticConfig.getTabnineEnterpriseHost().get()}"

return "Connected to Tabnine Enterprise as $username. $suffix"
return "Connected to Tabnine Enterprise as ${userInfo!!.email}. $suffix"
}

override fun getClickConsumer(): Consumer<MouseEvent>? {
return null
}

override fun getPopupStep(): ListPopup {
val cloudConnectionHealthStatus = getCloudConnectionHealthStatus()
val userInfo = getLastUserStatus()
return JBPopupFactory.getInstance()
.createActionGroupPopup(
null,
buildStatusBarActionsGroup(
if (myStatusBar != null) myStatusBar.project else null,
!username.isNullOrBlank()
getUserLoginStatus(cloudConnectionHealthStatus, userInfo?.email)
),
DataManager.getInstance()
.getDataContext(if (myStatusBar != null) myStatusBar.component else null),
Expand All @@ -100,7 +120,26 @@ class TabnineSelfHostedStatusBarWidget(project: Project) : EditorBasedWidget(pro
}

override fun getSelectedValue(): String {
return "\u0000"
val cloudConnectionHealthStatus = getCloudConnectionHealthStatus()
val userInfo = getLastUserStatus()

if (!hasCloud2UrlConfigured()) {
return "Tabnine Enterprise: Set your Tabnine URL"
}

if (cloudConnectionHealthStatus === CloudConnectionHealthStatus.Failed) {
return "Tabnine Enterprise: Server connectivity issue"
}

if (userInfo == null || !userInfo.isLoggedIn) {
return "Tabnine Enterprise: Sign in using your Tabnine account"
}

if (userInfo.team == null) {
return "Tabnine Enterprise: Not part of a team"
}

return "Tabnine Enterprise"
}

private fun update() {
Expand All @@ -110,4 +149,12 @@ class TabnineSelfHostedStatusBarWidget(project: Project) : EditorBasedWidget(pro
}
myStatusBar.updateWidget(ID())
}

private fun getLastUserStatus(): UserInfoResponse? {
return ServiceManager.getService(UserInfoService::class.java).lastUserInfoResponse
}

private fun getCloudConnectionHealthStatus(): CloudConnectionHealthStatus {
return ServiceManager.getService(BinaryStateService::class.java).lastStateResponse.cloudConnectionHealthStatus
}
}
Loading

0 comments on commit 823122a

Please sign in to comment.