Skip to content

Commit f10ae02

Browse files
authored
Fix ARN reference contributor returning duplicate entries (#3071)
* Fix ARN reference contributor returning duplicate entries * Additional ARN anywhere tweaks (#3078)
1 parent 2b61983 commit f10ae02

File tree

12 files changed

+172
-41
lines changed

12 files changed

+172
-41
lines changed

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ junit5 = "5.6.2"
1414
kotlin = "1.6.10"
1515
kotlinCoroutines = "1.3.9"
1616
mockito = "4.0.0"
17-
telemetryGenerator = "1.0.25"
17+
telemetryGenerator = "1.0.40"
1818
testLogger = "3.1.0"
1919
testRetry = "1.2.1"
2020
slf4j = "1.7.36"

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,10 @@ with what features/services are supported.
325325
<action id="aws.toolkit.open.telemetry.viewer" class="software.aws.toolkits.jetbrains.services.telemetry.OpenTelemetryAction"/>
326326
<action id="aws.settings.refresh" class="software.aws.toolkits.jetbrains.core.credentials.RefreshConnectionAction"/>
327327
<action id="aws.toolkit.showFeedback" class="software.aws.toolkits.jetbrains.ui.feedback.ShowFeedbackDialogAction"/>
328+
<action id="aws.toolkit.open.arn.browser" class="software.aws.toolkits.jetbrains.services.federation.OpenArnInConsoleEditorPopupAction">
329+
<add-to-group group-id="EditorPopupMenu"/>
330+
</action>
331+
328332
<group id="aws.toolkit.explorer.titleBar" popup="false" compact="true">
329333
<reference id="aws.settings.refresh"/>
330334
<separator/>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.aws.toolkits.jetbrains
5+
6+
object ToolkitPlaces {
7+
const val EXPLORER_TOOL_WINDOW = "ExplorerToolWindow"
8+
const val EDITOR_PSI_REFERENCE = "Editor"
9+
}

jetbrains-core/src/software/aws/toolkits/jetbrains/core/explorer/ExplorerToolWindow.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import com.intellij.util.ui.JBUI
4242
import com.intellij.util.ui.UIUtil
4343
import com.intellij.util.ui.tree.TreeUtil
4444
import org.jetbrains.concurrency.CancellablePromise
45+
import software.aws.toolkits.jetbrains.ToolkitPlaces
4546
import software.aws.toolkits.jetbrains.core.credentials.AwsConnectionManager
4647
import software.aws.toolkits.jetbrains.core.credentials.ChangeSettingsMode
4748
import software.aws.toolkits.jetbrains.core.credentials.ConnectionSettingsStateChangeNotifier
@@ -247,7 +248,7 @@ class ExplorerToolWindow(project: Project) : SimpleToolWindowPanel(true, true),
247248

248249
val actionGroup = DefaultActionGroup(totalActions)
249250
if (actionGroup.childrenCount > 0) {
250-
val popupMenu = actionManager.createActionPopupMenu(explorerToolWindowPlace, actionGroup)
251+
val popupMenu = actionManager.createActionPopupMenu(ToolkitPlaces.EXPLORER_TOOL_WINDOW, actionGroup)
251252
popupMenu.component.show(comp, x, y)
252253
}
253254
}
@@ -327,7 +328,6 @@ class ExplorerToolWindow(project: Project) : SimpleToolWindowPanel(true, true),
327328

328329
companion object {
329330
fun getInstance(project: Project): ExplorerToolWindow = ServiceManager.getService(project, ExplorerToolWindow::class.java)
330-
const val explorerToolWindowPlace = "ExplorerToolWindow"
331331
}
332332
}
333333

jetbrains-core/src/software/aws/toolkits/jetbrains/services/federation/AwsConsoleUrlFactory.kt

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import com.fasterxml.jackson.annotation.JsonProperty
77
import com.fasterxml.jackson.databind.DeserializationFeature
88
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
99
import com.fasterxml.jackson.module.kotlin.readValue
10+
import com.intellij.ide.BrowserUtil
11+
import com.intellij.openapi.application.ApplicationManager
12+
import com.intellij.openapi.project.Project
1013
import org.apache.http.client.entity.UrlEncodedFormEntity
1114
import org.apache.http.client.methods.HttpPost
1215
import org.apache.http.impl.client.HttpClientBuilder
@@ -15,7 +18,16 @@ import software.amazon.awssdk.auth.credentials.AwsSessionCredentials
1518
import software.amazon.awssdk.services.sts.StsClient
1619
import software.aws.toolkits.core.ConnectionSettings
1720
import software.aws.toolkits.core.region.AwsRegion
21+
import software.aws.toolkits.core.utils.error
22+
import software.aws.toolkits.core.utils.getLogger
1823
import software.aws.toolkits.jetbrains.core.AwsClientManager
24+
import software.aws.toolkits.jetbrains.core.credentials.getConnectionSettings
25+
import software.aws.toolkits.jetbrains.utils.notifyError
26+
import software.aws.toolkits.jetbrains.utils.notifyNoActiveCredentialsError
27+
import software.aws.toolkits.resources.message
28+
import software.aws.toolkits.telemetry.DeeplinkTelemetry
29+
import software.aws.toolkits.telemetry.Result
30+
import java.net.URLEncoder
1931
import java.time.Duration
2032

2133
object AwsConsoleUrlFactory {
@@ -132,7 +144,35 @@ object AwsConsoleUrlFactory {
132144
return "${federationUrl(region)}?${UrlEncodedFormEntity(params).toUrlEncodedString()}"
133145
}
134146

147+
fun openArnInConsole(project: Project, place: String, arn: String) {
148+
val connectionSettings = project.getConnectionSettings()
149+
150+
if (connectionSettings == null) {
151+
notifyNoActiveCredentialsError(project)
152+
return
153+
}
154+
155+
ApplicationManager.getApplication().executeOnPooledThread {
156+
try {
157+
val encodedArn = URLEncoder.encode(arn, Charsets.UTF_8)
158+
val encodedUa = URLEncoder.encode(AwsClientManager.userAgent, Charsets.UTF_8)
159+
val url = AwsConsoleUrlFactory.getSigninUrl(
160+
connectionSettings,
161+
"/go/view?arn=$encodedArn&source=$encodedUa"
162+
)
163+
BrowserUtil.browse(url)
164+
DeeplinkTelemetry.open(project, source = place, passive = false, result = Result.Succeeded)
165+
} catch (e: Exception) {
166+
val message = message("general.open_in_aws_console.error")
167+
notifyError(content = message, project = project)
168+
LOG.error(e) { message }
169+
DeeplinkTelemetry.open(project, source = place, passive = false, result = Result.Failed)
170+
}
171+
}
172+
}
173+
135174
private val mapper = jacksonObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
175+
private val LOG = getLogger<AwsConsoleUrlFactory>()
136176
}
137177

138178
private data class GetSigninTokenRequest(
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.aws.toolkits.jetbrains.services.federation
5+
6+
import com.intellij.openapi.actionSystem.ActionPlaces
7+
import com.intellij.openapi.actionSystem.AnActionEvent
8+
import com.intellij.openapi.actionSystem.CommonDataKeys
9+
import com.intellij.openapi.editor.Editor
10+
import com.intellij.openapi.editor.ex.util.EditorUtil
11+
import com.intellij.openapi.project.DumbAwareAction
12+
13+
class OpenArnInConsoleEditorPopupAction : DumbAwareAction() {
14+
override fun actionPerformed(e: AnActionEvent) {
15+
val editor = e.getData(CommonDataKeys.EDITOR)
16+
val selection = editor.selection() ?: return
17+
val project = e.getData(CommonDataKeys.PROJECT) ?: return
18+
19+
AwsConsoleUrlFactory.openArnInConsole(project, ActionPlaces.EDITOR_POPUP, selection)
20+
}
21+
22+
override fun update(e: AnActionEvent) {
23+
val editor = e.getData(CommonDataKeys.EDITOR)
24+
val isAvailable = if (editor == null || !EditorUtil.isRealFileEditor(editor)) {
25+
false
26+
} else {
27+
editor.selection()?.startsWith("arn:", ignoreCase = true) == true
28+
}
29+
30+
e.presentation.isEnabledAndVisible = isAvailable
31+
}
32+
33+
private fun Editor?.selection() = this?.selectionModel?.selectedText
34+
}

jetbrains-core/src/software/aws/toolkits/jetbrains/services/federation/psireferences/ArnPsiReferenceContributor.kt

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@
44
package software.aws.toolkits.jetbrains.services.federation.psireferences
55

66
import com.intellij.patterns.PsiElementPattern
7+
import com.intellij.psi.ElementManipulators
78
import com.intellij.psi.PsiElement
89
import com.intellij.psi.PsiReferenceContributor
910
import com.intellij.psi.PsiReferenceRegistrar
1011
import com.intellij.util.ProcessingContext
12+
import software.aws.toolkits.core.utils.debug
13+
import software.aws.toolkits.core.utils.error
14+
import software.aws.toolkits.core.utils.getLogger
1115

1216
class ArnPsiReferenceContributor : PsiReferenceContributor() {
1317
override fun registerReferenceProviders(registrar: PsiReferenceRegistrar) {
@@ -17,9 +21,22 @@ class ArnPsiReferenceContributor : PsiReferenceContributor() {
1721
) {
1822
override fun accepts(o: Any?, context: ProcessingContext): Boolean {
1923
if (o == null || o !is PsiElement) return false
24+
val manipulator = ElementManipulators.getManipulator(o)
25+
if (manipulator == null) return false
2026

21-
if (o.text.contains("arn:")) {
22-
return true
27+
try {
28+
val text = o.text
29+
val range = manipulator.getRangeInElement(o)
30+
if (range.length > text.length || range.endOffset > text.length) {
31+
LOG.debug { "Manipulator range: $range doesn't fit in PsiElement text: $text" }
32+
return false
33+
}
34+
if (range.substring(text).contains("arn:")) {
35+
return true
36+
}
37+
} catch (e: Exception) {
38+
LOG.error(e) { "Error while checking PsiElement" }
39+
return false
2340
}
2441

2542
return false
@@ -29,4 +46,8 @@ class ArnPsiReferenceContributor : PsiReferenceContributor() {
2946
PsiReferenceRegistrar.LOWER_PRIORITY
3047
)
3148
}
49+
50+
companion object {
51+
private val LOG = getLogger<ArnPsiReferenceContributor>()
52+
}
3253
}

jetbrains-core/src/software/aws/toolkits/jetbrains/services/federation/psireferences/ArnPsiReferenceProvider.kt

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,29 @@
44
package software.aws.toolkits.jetbrains.services.federation.psireferences
55

66
import com.intellij.openapi.util.TextRange
7+
import com.intellij.psi.ElementManipulators
78
import com.intellij.psi.PsiElement
89
import com.intellij.psi.PsiReference
910
import com.intellij.psi.PsiReferenceProvider
1011
import com.intellij.util.ProcessingContext
1112

1213
class ArnPsiReferenceProvider : PsiReferenceProvider() {
14+
// these results should probably be cached with [CachedValuesManager]
1315
override fun getReferencesByElement(element: PsiElement, context: ProcessingContext): Array<PsiReference> {
16+
val manipulator = ElementManipulators.getManipulator(element)
17+
if (manipulator != null) {
18+
val range = manipulator.getRangeInElement(element)
19+
// TODO: we can definitely make this more robust
20+
// assume this was a string that was quoted
21+
if (range.length == element.textRange.length - 2) {
22+
val substring = range.substring(element.text)
23+
if (substring.startsWith("arn:")) {
24+
// don't do anything fancy and just treat it as a full match
25+
return arrayOf(ArnReference(element, range, substring))
26+
}
27+
}
28+
}
29+
1430
val matches = ARN_REGEX.findAll(element.text)
1531
return matches.map {
1632
ArnReference(
@@ -24,6 +40,6 @@ class ArnPsiReferenceProvider : PsiReferenceProvider() {
2440
companion object {
2541
// partition service region account (optional)
2642
// v v v v resource-type resource
27-
val ARN_REGEX = "arn:aws[^/:]*:[^/:]*:[^:]*:[^/:]*:(?:[^:\\s\\/]*[:\\/])?(?:[^\\s'\\\"\\*\\?\\\\]*)".toRegex()
43+
val ARN_REGEX = "arn:aws[^/:]*:[^/:]*:[^:]*:[^/:]*:(?:[^:\\s\\/]*[:\\/])?(?:[^\\s'\\\"\\\\]*)".toRegex()
2844
}
2945
}

jetbrains-core/src/software/aws/toolkits/jetbrains/services/federation/psireferences/ArnReference.kt

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,13 @@
33

44
package software.aws.toolkits.jetbrains.services.federation.psireferences
55

6-
import com.intellij.ide.BrowserUtil
7-
import com.intellij.openapi.application.ApplicationManager
86
import com.intellij.openapi.paths.WebReference
97
import com.intellij.openapi.util.TextRange
108
import com.intellij.psi.PsiElement
119
import com.intellij.psi.SyntheticElement
1210
import com.intellij.psi.impl.FakePsiElement
13-
import software.amazon.awssdk.services.sts.model.StsException
14-
import software.aws.toolkits.core.utils.error
15-
import software.aws.toolkits.core.utils.getLogger
16-
import software.aws.toolkits.jetbrains.core.credentials.getConnectionSettings
11+
import software.aws.toolkits.jetbrains.ToolkitPlaces
1712
import software.aws.toolkits.jetbrains.services.federation.AwsConsoleUrlFactory
18-
import software.aws.toolkits.jetbrains.utils.notifyError
19-
import software.aws.toolkits.jetbrains.utils.notifyNoActiveCredentialsError
20-
import software.aws.toolkits.resources.message
2113

2214
class ArnReference(element: PsiElement, textRange: TextRange, private val arn: String) : WebReference(element, textRange) {
2315
inner class MyFakePsiElement : FakePsiElement(), SyntheticElement {
@@ -27,26 +19,8 @@ class ArnReference(element: PsiElement, textRange: TextRange, private val arn: S
2719

2820
override fun navigate(requestFocus: Boolean) {
2921
val project = element.project
30-
val connectionSettings = project.getConnectionSettings()
3122

32-
if (connectionSettings == null) {
33-
notifyNoActiveCredentialsError(project)
34-
return
35-
}
36-
37-
ApplicationManager.getApplication().executeOnPooledThread {
38-
try {
39-
BrowserUtil.browse(AwsConsoleUrlFactory.getSigninUrl(connectionSettings, "/go/view/$arn"))
40-
} catch (e: StsException) {
41-
val message = message("general.open_in_aws_console.no_permission")
42-
notifyError(content = message, project = project)
43-
getLogger<ArnReference>().error(e) { message }
44-
} catch (e: Exception) {
45-
val message = message("general.open_in_aws_console.error")
46-
notifyError(content = message, project = project)
47-
getLogger<ArnReference>().error(e) { message }
48-
}
49-
}
23+
AwsConsoleUrlFactory.openArnInConsole(project, ToolkitPlaces.EDITOR_PSI_REFERENCE, arn)
5024
}
5125
}
5226
override fun resolve() = MyFakePsiElement()

jetbrains-core/src/software/aws/toolkits/jetbrains/services/lambda/actions/DeployServerlessApplicationAction.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ import kotlinx.coroutines.runBlocking
2323
import kotlinx.coroutines.withContext
2424
import org.jetbrains.yaml.YAMLFileType
2525
import software.amazon.awssdk.services.cloudformation.CloudFormationClient
26+
import software.aws.toolkits.jetbrains.ToolkitPlaces
2627
import software.aws.toolkits.jetbrains.core.awsClient
2728
import software.aws.toolkits.jetbrains.core.coroutines.getCoroutineUiContext
2829
import software.aws.toolkits.jetbrains.core.credentials.AwsConnectionManager
2930
import software.aws.toolkits.jetbrains.core.executables.ExecutableInstance
3031
import software.aws.toolkits.jetbrains.core.executables.ExecutableManager
3132
import software.aws.toolkits.jetbrains.core.executables.getExecutable
32-
import software.aws.toolkits.jetbrains.core.explorer.ExplorerToolWindow
3333
import software.aws.toolkits.jetbrains.core.explorer.refreshAwsTree
3434
import software.aws.toolkits.jetbrains.services.cloudformation.describeStack
3535
import software.aws.toolkits.jetbrains.services.cloudformation.executeChangeSetAndWait
@@ -80,7 +80,7 @@ class DeployServerlessApplicationAction : AnAction(
8080
return@thenAccept
8181
}
8282

83-
val templateFile = if (e.place == ExplorerToolWindow.explorerToolWindowPlace) {
83+
val templateFile = if (e.place == ToolkitPlaces.EXPLORER_TOOL_WINDOW) {
8484
runBlocking(edtContext) {
8585
FileChooser.chooseFile(
8686
FileChooserDescriptorFactory.createSingleFileDescriptor(YAMLFileType.YML),
@@ -211,7 +211,7 @@ class DeployServerlessApplicationAction : AnAction(
211211
e.presentation.isVisible = if (LambdaHandlerResolver.supportedRuntimeGroups().isEmpty()) {
212212
false
213213
} else {
214-
if (e.place == ExplorerToolWindow.explorerToolWindowPlace) {
214+
if (e.place == ToolkitPlaces.EXPLORER_TOOL_WINDOW) {
215215
true
216216
} else {
217217
getSamTemplateFile(e) != null

0 commit comments

Comments
 (0)