Skip to content

Commit a16fa59

Browse files
authored
Show status of Builder ID connections and increase coherence of associated actions (#3518)
1 parent 896b24c commit a16fa59

File tree

11 files changed

+240
-204
lines changed

11 files changed

+240
-204
lines changed

jetbrains-core/resources/META-INF/plugin.xml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,6 @@ with what features/services are supported.
412412
<action id="aws.toolkit.toolwindow.credentials.rightGroup.more" class="software.aws.toolkits.jetbrains.core.credentials.actions.MoreConnectionActionsAction"/>
413413
<group id="aws.toolkit.toolwindow.credentials.rightGroup.more.group">
414414
<action id="aws.toolkit.toolwindow.newConnection" class="software.aws.toolkits.jetbrains.core.credentials.actions.NewConnectionAction"/>
415-
<action id="aws.toolkit.toolwindow.sso.signout" class="software.aws.toolkits.jetbrains.core.credentials.actions.SsoSignoutAction"/>
416415
</group>
417416
<action id="aws.toolkit.toolwindow.credentials.rightGroup.help" class="software.aws.toolkits.jetbrains.core.credentials.actions.CredentialsHelpAction"/>
418417

jetbrains-core/src/software/aws/toolkits/jetbrains/core/credentials/AwsSettingsPanel.kt

Lines changed: 73 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
package software.aws.toolkits.jetbrains.core.credentials
55

6+
import com.intellij.icons.AllIcons
67
import com.intellij.ide.DataManager
78
import com.intellij.openapi.application.ApplicationManager
89
import com.intellij.openapi.project.Project
@@ -12,8 +13,14 @@ import com.intellij.openapi.wm.StatusBar
1213
import com.intellij.openapi.wm.StatusBarWidget
1314
import com.intellij.openapi.wm.StatusBarWidgetFactory
1415
import com.intellij.util.Consumer
16+
import kotlinx.coroutines.delay
17+
import kotlinx.coroutines.isActive
18+
import kotlinx.coroutines.launch
19+
import software.aws.toolkits.jetbrains.core.coroutines.disposableCoroutineScope
20+
import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener
1521
import software.aws.toolkits.resources.message
1622
import java.awt.event.MouseEvent
23+
import javax.swing.Icon
1724

1825
private const val WIDGET_ID = "AwsSettingsPanel"
1926

@@ -47,16 +54,48 @@ private class AwsSettingsPanel(private val project: Project) :
4754

4855
override fun getPresentation(): StatusBarWidget.WidgetPresentation = this
4956

50-
override fun getTooltipText() = "${message("settings.title")} [${accountSettingsManager.connectionState.displayMessage}]"
57+
override fun getTooltipText(): String {
58+
val displayMessage = when (val connection = connectionManager.activeConnection()) {
59+
null, is AwsConnectionManagerConnection -> accountSettingsManager.connectionState.displayMessage
60+
else -> connection.label
61+
}
62+
63+
return "${message("settings.title")} [$displayMessage]"
64+
}
5165

5266
override fun getSelectedValue(): String {
53-
val displayText = when (val connection = connectionManager.activeConnection()) {
54-
null -> message("settings.credentials.none_selected")
55-
is AwsConnectionManagerConnection -> accountSettingsManager.connectionState.shortMessage
56-
else -> connection.label
67+
if (!accountSettingsManager.connectionState.isTerminal) {
68+
return accountSettingsManager.connectionState.shortMessage
69+
}
70+
71+
val currentProfileInvalid = accountSettingsManager.connectionState.let { it.isTerminal && it !is ConnectionState.ValidConnection }
72+
val invalidBearerConnections = lazyGetUnauthedBearerConnections()
73+
74+
if (currentProfileInvalid || invalidBearerConnections.isNotEmpty()) {
75+
val numInvalid = invalidBearerConnections.size + if (currentProfileInvalid) 1 else 0
76+
if (numInvalid == 1) {
77+
invalidBearerConnections.firstOrNull()?.let {
78+
return message("settings.statusbar.widget.format", message("settings.statusbar.widget.expired.1", it.label))
79+
}
80+
81+
return message("settings.statusbar.widget.format", accountSettingsManager.connectionState.shortMessage)
82+
}
83+
84+
return message("settings.statusbar.widget.format", message("settings.statusbar.widget.expired.n", numInvalid))
5785
}
5886

59-
return "AWS: $displayText"
87+
val totalConnections = ToolkitAuthManager.getInstance().listConnections().size + CredentialManager.getInstance().getCredentialIdentifiers().size
88+
if (totalConnections == 1) {
89+
val displayText = when (val connection = connectionManager.activeConnection()) {
90+
null -> message("settings.credentials.none_selected")
91+
is AwsConnectionManagerConnection -> accountSettingsManager.connectionState.shortMessage
92+
else -> connection.label
93+
}
94+
95+
return message("settings.statusbar.widget.format", displayText)
96+
}
97+
98+
return message("settings.statusbar.widget.format", message("settings.statusbar.widget.connections.n", totalConnections))
6099
}
61100

62101
override fun getPopupStep(): ListPopup = settingsSelector.createPopup(DataManager.getInstance().getDataContext(statusBar.component))
@@ -67,7 +106,28 @@ private class AwsSettingsPanel(private val project: Project) :
67106
this.statusBar = statusBar
68107
project.messageBus.connect(this).subscribe(AwsConnectionManager.CONNECTION_SETTINGS_STATE_CHANGED, this)
69108
ApplicationManager.getApplication().messageBus.connect(this).subscribe(ToolkitConnectionManagerListener.TOPIC, this)
109+
ApplicationManager.getApplication().messageBus.connect(this).subscribe(
110+
BearerTokenProviderListener.TOPIC,
111+
object : BearerTokenProviderListener {
112+
override fun onChange(providerId: String) {
113+
updateWidget()
114+
}
115+
116+
override fun invalidate(providerId: String) {
117+
updateWidget()
118+
}
119+
}
120+
)
121+
70122
updateWidget()
123+
124+
// ideally should be through notification bus. instead we simulate the update() method used by the actions
125+
disposableCoroutineScope(this, "AwsSettingsPanel icon update loop").launch {
126+
while (isActive) {
127+
updateWidget()
128+
delay(10000)
129+
}
130+
}
71131
}
72132

73133
override fun activeConnectionChanged(newConnection: ToolkitConnection?) {
@@ -82,5 +142,12 @@ private class AwsSettingsPanel(private val project: Project) :
82142
statusBar.updateWidget(ID())
83143
}
84144

145+
override fun getIcon(): Icon? =
146+
if (lazyGetUnauthedBearerConnections().isNotEmpty()) {
147+
AllIcons.General.Warning
148+
} else {
149+
null
150+
}
151+
85152
override fun dispose() {}
86153
}

jetbrains-core/src/software/aws/toolkits/jetbrains/core/credentials/ConnectionSettingsMenuBuilder.kt

Lines changed: 110 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33

44
package software.aws.toolkits.jetbrains.core.credentials
55

6+
import com.intellij.icons.AllIcons
67
import com.intellij.openapi.actionSystem.ActionManager
78
import com.intellij.openapi.actionSystem.AnAction
89
import com.intellij.openapi.actionSystem.AnActionEvent
910
import com.intellij.openapi.actionSystem.DefaultActionGroup
1011
import com.intellij.openapi.actionSystem.Separator
1112
import com.intellij.openapi.actionSystem.ToggleAction
1213
import com.intellij.openapi.project.DumbAware
14+
import com.intellij.openapi.project.DumbAwareAction
1315
import com.intellij.openapi.project.Project
1416
import software.aws.toolkits.core.credentials.CredentialIdentifier
1517
import software.aws.toolkits.core.region.AwsRegion
@@ -19,32 +21,99 @@ import software.aws.toolkits.resources.message
1921

2022
class ConnectionSettingsMenuBuilder private constructor() {
2123
private data class RegionSelectionSettings(val currentSelection: AwsRegion?, val onChange: (AwsRegion) -> Unit)
22-
private data class CredentialsSelectionSettings(val currentSelection: CredentialIdentifier?, val onChange: (CredentialIdentifier) -> Unit)
24+
private data class ProfileSelectionSettings(val currentSelection: CredentialIdentifier?, val onChange: (CredentialIdentifier) -> Unit)
25+
26+
private sealed interface IdentitySelectionSettings
27+
private data class SelectableIdentitySelectionSettings(
28+
val currentSelection: AwsBearerTokenConnection?,
29+
val onChange: (AwsBearerTokenConnection) -> Unit
30+
) : IdentitySelectionSettings
31+
private data class ActionsIdentitySelectionSettings(val project: Project?) : IdentitySelectionSettings
2332

2433
private var regionSelectionSettings: RegionSelectionSettings? = null
25-
private var credentialsSelectionSettings: CredentialsSelectionSettings? = null
34+
private var profileSelectionSettings: ProfileSelectionSettings? = null
35+
private var identitySelectionSettings: IdentitySelectionSettings? = null
2636
private var accountSettingsManager: AwsConnectionManager? = null
2737

2838
fun withRegions(currentSelection: AwsRegion?, onChange: (AwsRegion) -> Unit): ConnectionSettingsMenuBuilder = apply {
2939
regionSelectionSettings = RegionSelectionSettings(currentSelection, onChange)
3040
}
3141

3242
fun withCredentials(currentSelection: CredentialIdentifier?, onChange: (CredentialIdentifier) -> Unit): ConnectionSettingsMenuBuilder = apply {
33-
credentialsSelectionSettings = CredentialsSelectionSettings(currentSelection, onChange)
43+
profileSelectionSettings = ProfileSelectionSettings(currentSelection, onChange)
3444
}
3545

3646
fun withRecentChoices(project: Project): ConnectionSettingsMenuBuilder = apply {
3747
accountSettingsManager = AwsConnectionManager.getInstance(project)
3848
}
3949

50+
fun withIndividualIdentitySettings(project: Project) {
51+
identitySelectionSettings = SelectableIdentitySelectionSettings(
52+
currentSelection = ToolkitConnectionManager.getInstance(project).activeConnection() as? AwsBearerTokenConnection,
53+
onChange = ToolkitConnectionManager.getInstance(project)::switchConnection
54+
)
55+
}
56+
57+
fun withIndividualIdentityActions(project: Project?) {
58+
identitySelectionSettings = ActionsIdentitySelectionSettings(project)
59+
}
60+
4061
fun build(): DefaultActionGroup {
4162
val topLevelGroup = DefaultActionGroup()
4263

64+
identitySelectionSettings?.let { settings ->
65+
val connections = ToolkitAuthManager.getInstance().listConnections().filterIsInstance<AwsBearerTokenConnection>()
66+
if (connections.isEmpty()) {
67+
return@let
68+
}
69+
70+
topLevelGroup.add(Separator.create(message("settings.credentials.individual_identity_sub_menu")))
71+
val actions = when (settings) {
72+
is SelectableIdentitySelectionSettings -> {
73+
connections.map {
74+
object : DumbAwareToggleAction<AwsBearerTokenConnection>(
75+
title = it.label,
76+
value = it,
77+
selected = it == settings.currentSelection,
78+
onSelect = settings.onChange
79+
) {
80+
override fun update(e: AnActionEvent) {
81+
super.update(e)
82+
if (value.lazyIsUnauthedBearerConnection()) {
83+
e.presentation.icon = AllIcons.General.Warning
84+
}
85+
}
86+
}
87+
}
88+
}
89+
90+
is ActionsIdentitySelectionSettings -> {
91+
connections.map {
92+
IndividualIdentityActionGroup(it)
93+
}
94+
}
95+
}
96+
97+
topLevelGroup.addAll(actions)
98+
99+
topLevelGroup.add(Separator.create())
100+
}
101+
102+
val profileActions = createProfileActions()
43103
val regionActions = createRegionActions()
104+
105+
// no header if only regions
106+
if (profileActions.isNotEmpty() && regionActions.isNotEmpty()) {
107+
// both profiles & regions
108+
topLevelGroup.add(Separator.create(message("settings.credentials.iam_and_regions")))
109+
} else if (profileActions.isNotEmpty() && regionActions.isEmpty()) {
110+
// only profiles
111+
topLevelGroup.add(Separator.create(message("settings.credentials.iam")))
112+
}
113+
44114
val regionSettings = regionSelectionSettings
45115
val recentRegions = accountSettingsManager?.recentlyUsedRegions()
46116
if (recentRegions?.isNotEmpty() == true && regionSettings != null) {
47-
topLevelGroup.add(Separator.create(message("settings.regions.recent")))
48117
recentRegions.forEach {
49118
topLevelGroup.add(SwitchRegionAction(it, it == regionSettings.currentSelection, regionSettings.onChange))
50119
}
@@ -56,11 +125,11 @@ class ConnectionSettingsMenuBuilder private constructor() {
56125
topLevelGroup.addAll(regionActions)
57126
}
58127

59-
val profileActions = createProfileActions()
60-
val credentialsSettings = credentialsSelectionSettings
128+
topLevelGroup.add(Separator.create())
129+
130+
val credentialsSettings = profileSelectionSettings
61131
val recentCredentials = accountSettingsManager?.recentlyUsedCredentials()
62132
if (recentCredentials?.isNotEmpty() == true && credentialsSettings != null) {
63-
topLevelGroup.add(Separator.create(message("settings.credentials.recent")))
64133
recentCredentials.forEach {
65134
topLevelGroup.add(SwitchCredentialsAction(it, it == credentialsSettings.currentSelection, credentialsSettings.onChange))
66135
}
@@ -111,7 +180,7 @@ class ConnectionSettingsMenuBuilder private constructor() {
111180
}
112181

113182
private fun createProfileActions(): List<AnAction> = buildList {
114-
val (currentSelection, onChange) = credentialsSelectionSettings ?: return@buildList
183+
val (currentSelection, onChange) = profileSelectionSettings ?: return@buildList
115184

116185
add(Separator.create(message("settings.credentials")))
117186

@@ -153,6 +222,39 @@ class ConnectionSettingsMenuBuilder private constructor() {
153222
onSelect: (CredentialIdentifier) -> Unit
154223
) : DumbAwareToggleAction<CredentialIdentifier>(value.displayName, value, selected, onSelect)
155224

225+
inner class IndividualIdentityActionGroup(private val value: AwsBearerTokenConnection) :
226+
DefaultActionGroup(
227+
{
228+
val suffix = if (value.lazyIsUnauthedBearerConnection()) {
229+
message("credentials.individual_identity.expired")
230+
} else {
231+
message("credentials.individual_identity.connected")
232+
}
233+
234+
"${value.label} $suffix"
235+
},
236+
true
237+
) {
238+
init {
239+
templatePresentation.icon = if (value.lazyIsUnauthedBearerConnection()) AllIcons.General.Warning else null
240+
241+
addAll(
242+
object : DumbAwareAction(message("credentials.individual_identity.reconnect")) {
243+
override fun actionPerformed(e: AnActionEvent) {
244+
reauthProviderIfNeeded(value)
245+
}
246+
},
247+
248+
object : DumbAwareAction(message("credentials.individual_identity.signout")) {
249+
override fun actionPerformed(e: AnActionEvent) {
250+
val settings = identitySelectionSettings as? ActionsIdentitySelectionSettings
251+
logoutFromSsoConnection(settings?.project, value)
252+
}
253+
}
254+
)
255+
}
256+
}
257+
156258
companion object {
157259
fun connectionSettingsMenuBuilder(): ConnectionSettingsMenuBuilder = ConnectionSettingsMenuBuilder()
158260
}

0 commit comments

Comments
 (0)