From c3cc2bfb1b4fd223dd304395925d84e50b3b62d4 Mon Sep 17 00:00:00 2001 From: Manodnya Bhoite Date: Tue, 12 Nov 2024 16:23:22 -0800 Subject: [PATCH 1/7] Display toast notifications with actions --- .../DisplayToastNotifications.kt | 25 +++++++++++++++++++ .../jetbrains/utils/NotificationUtils.kt | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/DisplayToastNotifications.kt diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/DisplayToastNotifications.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/DisplayToastNotifications.kt new file mode 100644 index 00000000000..e1ec7bd2cd7 --- /dev/null +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/DisplayToastNotifications.kt @@ -0,0 +1,25 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.core.notifications + +import com.intellij.notification.NotificationType +import com.intellij.openapi.actionSystem.AnAction +import software.aws.toolkits.jetbrains.utils.notifySticky + +object DisplayToastNotifications { + fun show(title: String, message: String, action: List, notificationType: ToastNotificationType) { + val notifyType = when (notificationType) { + ToastNotificationType.CRITICAL -> NotificationType.ERROR + ToastNotificationType.WARNING -> NotificationType.WARNING + ToastNotificationType.PRODUCT_UPDATE -> NotificationType.INFORMATION + } + notifySticky(notifyType, title, message, null, action) + } +} + +enum class ToastNotificationType { + CRITICAL, + WARNING, + PRODUCT_UPDATE, +} diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/utils/NotificationUtils.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/utils/NotificationUtils.kt index 0bd81b96227..924db699653 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/utils/NotificationUtils.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/utils/NotificationUtils.kt @@ -48,7 +48,7 @@ private fun notify(type: NotificationType, title: String, content: String = "", notify(notification, project) } -private fun notifySticky(type: NotificationType, title: String, content: String = "", project: Project? = null, notificationActions: Collection) { +fun notifySticky(type: NotificationType, title: String, content: String = "", project: Project? = null, notificationActions: Collection) { val notification = Notification(GROUP_DISPLAY_ID_STICKY, title, content, type) notificationActions.forEach { notification.addAction(if (it !is NotificationAction) createNotificationExpiringAction(it) else it) From 09729c72c1dfbe1ba441176c1fb68010880aa773 Mon Sep 17 00:00:00 2001 From: Manodnya Bhoite Date: Fri, 15 Nov 2024 13:52:07 -0800 Subject: [PATCH 2/7] Condition matcher for displaying notifications --- .../notifications/CustomizeNotificationsUi.kt | 13 ++ .../DisplayToastNotifications.kt | 21 +- .../notifications/NotificationFormatUtils.kt | 181 ++++++++++++++ .../core/notifications/RulesEngine.kt | 220 ++++++++++++++++++ .../tst-resources/exampleNotification2.json | 124 ++++++++++ .../tst-resources/olderNotification.json | 115 +++++++++ .../NotificationFormatUtilsTest.kt | 37 +++ 7 files changed, 702 insertions(+), 9 deletions(-) create mode 100644 plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/CustomizeNotificationsUi.kt create mode 100644 plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtils.kt create mode 100644 plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/RulesEngine.kt create mode 100644 plugins/core/jetbrains-community/tst-resources/exampleNotification2.json create mode 100644 plugins/core/jetbrains-community/tst-resources/olderNotification.json create mode 100644 plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtilsTest.kt diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/CustomizeNotificationsUi.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/CustomizeNotificationsUi.kt new file mode 100644 index 00000000000..c9deddcb60d --- /dev/null +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/CustomizeNotificationsUi.kt @@ -0,0 +1,13 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.core.notifications + +fun checkSeverity(notificationSeverity: String): NotificationSeverity = when (notificationSeverity) { + "Critical" -> NotificationSeverity.CRITICAL + "Warning" -> NotificationSeverity.WARNING + "Info" -> NotificationSeverity.INFO + else -> NotificationSeverity.INFO +} + +// TODO: Add actions that can be performed from the notifications here diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/DisplayToastNotifications.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/DisplayToastNotifications.kt index e1ec7bd2cd7..76cb13a2b40 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/DisplayToastNotifications.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/DisplayToastNotifications.kt @@ -5,21 +5,24 @@ package software.aws.toolkits.jetbrains.core.notifications import com.intellij.notification.NotificationType import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.project.Project import software.aws.toolkits.jetbrains.utils.notifySticky object DisplayToastNotifications { - fun show(title: String, message: String, action: List, notificationType: ToastNotificationType) { + fun show(title: String, message: String, action: List, notificationType: NotificationSeverity) { val notifyType = when (notificationType) { - ToastNotificationType.CRITICAL -> NotificationType.ERROR - ToastNotificationType.WARNING -> NotificationType.WARNING - ToastNotificationType.PRODUCT_UPDATE -> NotificationType.INFORMATION + NotificationSeverity.CRITICAL -> NotificationType.ERROR + NotificationSeverity.WARNING -> NotificationType.WARNING + NotificationSeverity.INFO -> NotificationType.INFORMATION } notifySticky(notifyType, title, message, null, action) } -} -enum class ToastNotificationType { - CRITICAL, - WARNING, - PRODUCT_UPDATE, + fun shouldShow(project: Project, notificationData: NotificationData) { + if (RulesEngine.displayNotification(notificationData, project)) { + val notificationContent = notificationData.content.locale + val severity = notificationData.severity + show(notificationContent.title, notificationContent.description, emptyList(), checkSeverity(severity)) + } + } } diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtils.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtils.kt new file mode 100644 index 00000000000..a17f853839c --- /dev/null +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtils.kt @@ -0,0 +1,181 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.core.notifications + +import com.fasterxml.jackson.annotation.JsonProperty + +data class NotificationsList( + @JsonProperty("schema") + val schema: Schema, + @JsonProperty("notifications") + val notifications: List, +) + +data class Schema( + @JsonProperty("version") + val version: String, +) + +data class NotificationData( + @JsonProperty("id") + val id: String, + @JsonProperty("schedule") + val schedule: NotificationSchedule, + @JsonProperty("severity") + val severity: String, + @JsonProperty("condition") + val condition: NotificationDisplayCondition?, + @JsonProperty("content") + val content: NotificationContentDescriptionLocale, + @JsonProperty("actions") + val actions: List? = emptyList(), +) + +data class NotificationSchedule( + @JsonProperty("type") + val type: String, +) + +enum class NotificationSeverity { + INFO, + WARNING, + CRITICAL, +} + +data class NotificationContentDescriptionLocale( + @JsonProperty("en-US") + val locale: NotificationContentDescription, +) + +data class NotificationContentDescription( + @JsonProperty("title") + val title: String, + @JsonProperty("description") + val description: String, +) + +data class NotificationFollowupActions( + @JsonProperty("type") + val type: String, + @JsonProperty("content") + val content: NotificationFollowupActionsContent, +) + +data class NotificationFollowupActionsContent( + @JsonProperty("en-US") + val locale: NotificationActionDescription, +) + +data class NotificationActionDescription( + @JsonProperty("title") + val title: String, + @JsonProperty("url") + val url: String?, +) + +data class NotificationDisplayCondition( + @JsonProperty("compute") + val compute: ComputeType?, + @JsonProperty("os") + val os: SystemType?, + @JsonProperty("ide") + val ide: SystemType?, + @JsonProperty("extension") + val extension: List?, + @JsonProperty("authx") + val authx: List, +) + +data class ComputeType( + @JsonProperty("type") + val type: NotificationExpression?, + @JsonProperty("architecture") + val architecture: NotificationExpression?, +) + +data class SystemType( + @JsonProperty("type") + val type: NotificationExpression?, + @JsonProperty("version") + val version: NotificationExpression?, +) + +data class ExtensionType( + @JsonProperty("id") + val id: String?, + @JsonProperty("version") + val version: NotificationExpression?, +) + +open class NotificationExpression + +open class NotificationOperation : NotificationExpression() + +data class NotCondition( + @JsonProperty("not") + val expectedValue: NotificationExpression, +) : NotificationExpression() + +data class OrCondition( + @JsonProperty("or") + val expectedValueList: List, +) : NotificationExpression() + +data class AndCondition( + @JsonProperty("and") + val expectedValueList: List, +) : NotificationExpression() + +data class AuthxType( + @JsonProperty("feature") + val feature: String, + @JsonProperty("type") + val type: NotificationExpression?, + @JsonProperty("region") + val region: NotificationExpression?, + @JsonProperty("connectionState") + val connectionState: NotificationExpression?, + @JsonProperty("ssoscopes") + val ssoScopes: NotificationExpression?, +) + +data class ComparisonCondition( + @JsonProperty("==") + val expectedValue: String, +) : NotificationOperation() + +data class NotEqualsCondition( + @JsonProperty("!=") + val expectedValue: String, +) : NotificationOperation() + +data class GreaterThanCondition( + @JsonProperty(">") + val expectedValue: String, +) : NotificationOperation() + +data class GreaterThanOrEqualsCondition( + @JsonProperty(">=") + val expectedValue: String, +) : NotificationOperation() + +data class LessThanCondition( + @JsonProperty("<") + val expectedValue: String, +) : NotificationOperation() + +data class LessThanOrEqualsCondition( + @JsonProperty("<=") + val expectedValue: String, +) : NotificationOperation() + +data class InCondition( + @JsonProperty("anyOf") + val expectedValueList: List, +) : NotificationOperation() + +data class NotInCondition( + @JsonProperty("noneOf") + val expectedValueList: List, +) : NotificationOperation() diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/RulesEngine.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/RulesEngine.kt new file mode 100644 index 00000000000..27a214e7eda --- /dev/null +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/RulesEngine.kt @@ -0,0 +1,220 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.core.notifications + +import com.intellij.ide.plugins.PluginManagerCore +import com.intellij.openapi.application.ApplicationInfo +import com.intellij.openapi.extensions.PluginId +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.SystemInfo +import software.aws.toolkits.jetbrains.core.gettingstarted.editor.ActiveConnection +import software.aws.toolkits.jetbrains.core.gettingstarted.editor.ActiveConnectionType +import software.aws.toolkits.jetbrains.core.gettingstarted.editor.BearerTokenFeatureSet +import software.aws.toolkits.jetbrains.core.gettingstarted.editor.checkBearerConnectionValidity +import software.aws.toolkits.jetbrains.core.gettingstarted.editor.checkIamProfileByCredentialType +import software.aws.toolkits.jetbrains.utils.isRunningOnRemoteBackend + +object RulesEngine { + + fun displayNotification(notification: NotificationData, project: Project): Boolean { + // If no conditions provided, show display the notification to everyone + val shouldShow = notification.condition?.let { matchesAllRules(it, project) } ?: true + return shouldShow + } + + fun matchesAllRules(notificationConditions: NotificationDisplayCondition, project: Project): Boolean { + val sysDetails = getCurrentSystemAndConnectionDetails() + // If any of these conditions are null, we assume the condition to be true + val compute = notificationConditions.compute?.let { matchesCompute(it, sysDetails.computeType, sysDetails.computeArchitecture) } ?: true + val os = notificationConditions.os?.let { matchesOs(it, sysDetails.osType, sysDetails.osVersion) } ?: true + val ide = notificationConditions.ide?.let { matchesIde(it, sysDetails.ideType, sysDetails.ideVersion) } ?: true + val extension = matchesExtension(notificationConditions.extension, sysDetails.pluginVersions) + val authx = matchesAuth(notificationConditions.authx, project) + return compute && os && ide && extension && authx + } + + private fun matchesCompute(notificationCompute: ComputeType, actualCompute: String, actualArchitecture: String): Boolean { + val type = notificationCompute.type?.let { evaluateNotificationExpression(it, actualCompute) } ?: true + val architecture = notificationCompute.architecture?.let { evaluateNotificationExpression(it, actualArchitecture) } ?: return true + return type && architecture + } + + private fun matchesOs(notificationOs: SystemType, actualOs: String, actualOsVersion: String): Boolean { + val os = notificationOs.type?.let { evaluateNotificationExpression(it, actualOs) } ?: true + val osVersion = notificationOs.version?.let { evaluateNotificationExpression(it, actualOsVersion) } ?: true + return os && osVersion + } + + private fun matchesIde(notificationIde: SystemType, actualIde: String, actualIdeVersion: String): Boolean { + val ide = notificationIde.type?.let { evaluateNotificationExpression(it, actualIde) } ?: true + val ideVersion = notificationIde.version?.let { evaluateNotificationExpression(it, actualIdeVersion) } ?: true + return ide && ideVersion + } + + private fun matchesExtension(notificationExtension: List?, actualPluginVersions: Map): Boolean { + if (notificationExtension.isNullOrEmpty()) return true + val extensionsToBeChecked = notificationExtension.map { it.id } + val pluginVersions = actualPluginVersions.filterKeys { extensionsToBeChecked.contains(it) } + return notificationExtension.all { extension -> + val actualVersion = pluginVersions[extension.id] + if (actualVersion == null) { + true + } else { + extension.version?.let { evaluateNotificationExpression(it, actualVersion) } ?: true + } + } + } + + private fun matchesAuth(notificationAuth: List?, project: Project): Boolean { + if (notificationAuth.isNullOrEmpty()) return true + return notificationAuth.all { feature -> + val actualConnection = when (feature.feature) { + "q" -> getConnectionDetailsForFeature(project, BearerTokenFeatureSet.Q) + "codeCatalyst" -> getConnectionDetailsForFeature(project, BearerTokenFeatureSet.CODECATALYST) + "toolkit" -> getConnectionDetailsForToolkit(project) + else -> return true + } + + if (actualConnection == null) { + false + } else { + val authType = feature.type?.let { evaluateNotificationExpression(it, actualConnection.connectionType) } ?: true + val authRegion = feature.region?.let { evaluateNotificationExpression(it, actualConnection.region) } ?: true + val connectionState = feature.connectionState?.let { evaluateNotificationExpression(it, actualConnection.connectionState) } ?: true + // TODO: Add condition for matching scopes + authType && authRegion && connectionState + } + } + } + + private fun evaluateNotificationExpression(notificationExpression: NotificationExpression, value: String): Boolean = when (notificationExpression) { + is NotificationOperation -> performNotifOp(notificationExpression, value) + is NotCondition -> performNotOp(notificationExpression, value) + is OrCondition -> performOrOp(notificationExpression, value) + is AndCondition -> performAndOp(notificationExpression, value) + else -> true + } + + private fun performNotifOp(notificationOperation: NotificationOperation, actualValue: String): Boolean = when (notificationOperation) { + is ComparisonCondition -> notificationOperation.expectedValue == actualValue + is NotEqualsCondition -> notificationOperation.expectedValue != actualValue + is GreaterThanCondition -> actualValue > notificationOperation.expectedValue + is LessThanCondition -> actualValue < notificationOperation.expectedValue + is GreaterThanOrEqualsCondition -> actualValue >= notificationOperation.expectedValue + is LessThanOrEqualsCondition -> actualValue <= notificationOperation.expectedValue + is InCondition -> notificationOperation.expectedValueList.contains(actualValue) + is NotInCondition -> !notificationOperation.expectedValueList.contains(actualValue) + else -> true + } + + private fun performNotOp(notificationOperation: NotCondition, actualValue: String): Boolean = + !evaluateNotificationExpression(notificationOperation.expectedValue, actualValue) + + private fun performOrOp(notificationOperation: OrCondition, actualValue: String): Boolean = + notificationOperation.expectedValueList.any { evaluateNotificationExpression(it, actualValue) } + + private fun performAndOp(notificationOperation: AndCondition, actualValue: String): Boolean = + notificationOperation.expectedValueList.all { evaluateNotificationExpression(it, actualValue) } +} + +fun getCurrentSystemAndConnectionDetails(): SystemDetails { + val computeType: String = if (isRunningOnRemoteBackend()) "Remote" else "Local" + val computeArchitecture: String = SystemInfo.OS_ARCH + + val osType: String = SystemInfo.OS_NAME + val osVersion: String = SystemInfo.OS_VERSION + + val ideInfo = ApplicationInfo.getInstance() + val ideType: String = ideInfo.build.productCode + val ideVersion = ideInfo.shortVersion + + val pluginVersionMap = createPluginVersionMap() + + return SystemDetails(computeType, computeArchitecture, osType, osVersion, ideType, ideVersion, pluginVersionMap) +} + +data class AuthDetails( + val qConnection: ActiveConnection, + val codeCatalystConnection: ActiveConnection, + val explorerConnection: ActiveConnection, +) + +data class FeatureAuthDetails( + val connectionType: String, + val region: String, + val connectionState: String, +) + +data class SystemDetails( + val computeType: String, + val computeArchitecture: String, + val osType: String, + val osVersion: String, + val ideType: String, + val ideVersion: String, + val pluginVersions: Map, +) + +fun createPluginVersionMap(): Map { + val pluginVersionMap = mutableMapOf() + val pluginIds = listOf( + "amazon.q", + "aws.toolkit.core", + "aws.toolkit" + ) + pluginIds.forEach { pluginId -> + val plugin = PluginManagerCore.getPlugin(PluginId.getId(pluginId)) + val pluginVersion = plugin?.version + if (pluginVersion != null) { + pluginVersionMap[pluginId] = pluginVersion + } + } + return pluginVersionMap +} + +private fun getConnectionDetailsForToolkit(project: Project): FeatureAuthDetails? { + val connection = checkIamProfileByCredentialType(project) + if (connection.activeConnectionIam == null) return null + val authType = when (connection.connectionType) { + ActiveConnectionType.IAM_IDC -> "Idc" + ActiveConnectionType.IAM -> "Iam" + else -> "Unknown" + } + val authRegion = connection.activeConnectionIam?.defaultRegionId ?: "Unknown" + + val connectionState = when (connection) { + is ActiveConnection.NotConnected -> "NotConnected" + is ActiveConnection.ValidIam -> "Connected" + is ActiveConnection.ExpiredIam -> "Expired" + else -> "Unknown" + } + return FeatureAuthDetails( + authType, + authRegion, + connectionState + ) +} + +private fun getConnectionDetailsForFeature(project: Project, featureId: BearerTokenFeatureSet): FeatureAuthDetails? { + val connection = checkBearerConnectionValidity(project, featureId) + if (connection.activeConnectionBearer == null) return null + val authType = when (connection.connectionType) { + ActiveConnectionType.BUILDER_ID -> "BuilderId" + ActiveConnectionType.IAM_IDC -> "Idc" + else -> "Unknown" + } + val authRegion = connection.activeConnectionBearer?.region ?: "Unknown" + + val connectionState = when (connection) { + is ActiveConnection.NotConnected -> "NotConnected" + is ActiveConnection.ValidBearer -> "Connected" + is ActiveConnection.ExpiredBearer -> "Expired" + else -> "Unknown" + } + return FeatureAuthDetails( + authType, + authRegion, + connectionState + ) +} diff --git a/plugins/core/jetbrains-community/tst-resources/exampleNotification2.json b/plugins/core/jetbrains-community/tst-resources/exampleNotification2.json new file mode 100644 index 00000000000..344f45191c7 --- /dev/null +++ b/plugins/core/jetbrains-community/tst-resources/exampleNotification2.json @@ -0,0 +1,124 @@ +{ + "schema": { + "version": "2.0" +}, + "notifications": [ + { + + "id": "example_id_12344", + "schedule": { + "type": "StartUp" + }, + "severity": "Critical", + "condition": { + "compute": { + "type": { + "or": [ + { + "==": "ec2" + }, + { + "==": "desktop" + } + ] + }, + "architecture": { + "!=": "x64" + } + }, + "os": { + "type": { + "anyOf": [ + "Darwin", + "Linux" + ] + }, + "version": { + "<=": "23.0.1.0" + } + + }, + "ide": { + "type": { + "noneOf": [ + "PyCharm", + "IDEA" + ] + }, + "version": { + "and": [ + { + ">=": "1.0" + }, + { + "<": "2.0" + } + ] + } + }, + "extensions": [ + { + "id": "aws.toolkit", + "version": { + "!=": "1.3334" + } + }, + { + "id": "amazon.q", + "version": { + "!=": "3.37.0" + } + } + ] + + , + "authx": [{ + "feature" : "q", + "type": { + "anyOf": [ + "IamIdentityCenter", + "AwsBuilderId" + ] + }, + "region": { + "==": "us-east-1" + }, + "connectionState": { + "!=": "Connected" + }, + "ssoScopes": { + "noneOf": [ + "codewhisperer:scope1", + "sso:account:access" + ] + } + } ] + }, + "content": { + "en-US": { + "title": "Look at this!", + "description": "Some bug is there" + } + }, + "actions": [ + { + "type": "ShowMarketplace", + "content": { + "en-US": { + "title": "Go to market" + } + } + }, + { + "type": "ShowUrl", + "content": { + "en-US": { + "title": "Click me!", + "url": "http://nowhere" + } + } + } + ] + } + ] +} diff --git a/plugins/core/jetbrains-community/tst-resources/olderNotification.json b/plugins/core/jetbrains-community/tst-resources/olderNotification.json new file mode 100644 index 00000000000..9ceee415d37 --- /dev/null +++ b/plugins/core/jetbrains-community/tst-resources/olderNotification.json @@ -0,0 +1,115 @@ +{ + "schema": { + "version": "2.0" + }, + "notifications": [ + { + + "id": "example_id_12344", + "schedule": { + "type": "StartUp" + }, + "severity": "Critical", + "condition": { + "compute": { + "type": { + "or": [ + { + "==": "ec2" + }, + { + "==": "desktop" + } + ] + }, + "architecture": { + "!=": "x64" + } + }, + "os": { + "type": { + "anyOf": [ + "Darwin", + "Linux" + ] + }, + "version": { + "<=": "23.0.1.0" + } + + }, + "ide": { + "type": { + "noneOf": [ + "PyCharm", + "IDEA" + ] + }, + "version": { + "and": [ + { + ">=": "1.0" + }, + { + "<": "2.0" + } + ] + } + }, + "extension": { + "type": { + "==": "AWS Toolkit for JetBrains" + }, + "version": { + "<": "1.47.0.0" + } + }, + "authx": { + "type": { + "anyOf": [ + "IamIdentityCenter", + "AwsBuilderId" + ] + }, + "region": { + "==": "us-east-1" + }, + "connectionState": { + "!=": "Connected" + }, + "ssoScopes": { + "noneOf": [ + "codewhisperer:scope1", + "sso:account:access" + ] + } + } + }, + "content": { + "en-US": { + "title": "Look at this!", + "description": "Some bug is there" + } + }, + "actions": [ + { + "type": "ShowMarketplace", + "content": { + "en-US": { + "title": "Go to market" + } + } + }, + { + "type": "ShowUrl", + "content": { + "en-US": { + "title": "Click me!", + "url": "http://nowhere" + } + } + } + ] + } + ] +} diff --git a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtilsTest.kt b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtilsTest.kt new file mode 100644 index 00000000000..18fd35adb18 --- /dev/null +++ b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtilsTest.kt @@ -0,0 +1,37 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.core.notifications + +import com.fasterxml.jackson.databind.DeserializationFeature +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import org.junit.Before +import org.junit.Test +import org.junit.jupiter.api.assertDoesNotThrow +import software.aws.toolkits.core.utils.exists +import software.aws.toolkits.core.utils.inputStream +import java.io.InputStream +import java.nio.file.Paths + +class NotificationFormatUtilsTest { + lateinit var notificationJson: InputStream + + @Before + fun setUp() { + val file = javaClass.getResource("/exampleNotification2.json")?.let { Paths.get(it.toURI()).takeIf { f -> f.exists() } } + ?: throw RuntimeException("Test file not found") + notificationJson = file.inputStream() + } + + @Test + fun checkValidity() { + assertDoesNotThrow { + mapper.readValue(notificationJson) + } + } + + companion object { + val mapper = jacksonObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + } +} From e909a0ef6d5854142bb163fa4cbb2509f40f356a Mon Sep 17 00:00:00 2001 From: Manodnya Bhoite Date: Tue, 19 Nov 2024 23:52:31 -0800 Subject: [PATCH 3/7] Modified deserialization cases and added tests --- .../NotificationCustomDeserializers.kt | 121 ++++++++++ .../notifications/NotificationFormatUtils.kt | 154 ++++++++----- .../core/notifications/RulesEngine.kt | 43 ++-- .../NotificationFormatUtilsTest.kt | 119 +++++++++- .../NotificationFormatUtilsTestCases.kt | 206 ++++++++++++++++++ 5 files changed, 546 insertions(+), 97 deletions(-) create mode 100644 plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/NotificationCustomDeserializers.kt create mode 100644 plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtilsTestCases.kt diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/NotificationCustomDeserializers.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/NotificationCustomDeserializers.kt new file mode 100644 index 00000000000..2b6adce2720 --- /dev/null +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/NotificationCustomDeserializers.kt @@ -0,0 +1,121 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.core.notifications + +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.core.JsonToken +import com.fasterxml.jackson.databind.DeserializationContext +import com.fasterxml.jackson.databind.JsonDeserializer +import com.fasterxml.jackson.databind.JsonMappingException +import com.fasterxml.jackson.databind.JsonNode + +class OperationConditionDeserializer : JsonDeserializer() { + override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.OperationCondition = when (parser.currentToken) { + JsonToken.VALUE_STRING -> { + // Handle direct string value + NotificationExpression.OperationCondition(parser.valueAsString) + } + else -> throw JsonMappingException(parser, "Cannot deserialize OperatingCondition") + } +} + +class ComparisonConditionDeserializer : JsonDeserializer() { + override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.ComparisonCondition { + val op = OperationConditionDeserializer().deserialize(parser, ctxt) + return NotificationExpression.ComparisonCondition(op.value) + } +} + +class NotEqualsConditionDeserializer : JsonDeserializer() { + override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.NotEqualsCondition { + val op = OperationConditionDeserializer().deserialize(parser, ctxt) + return NotificationExpression.NotEqualsCondition(op.value) + } +} +class GreaterThanConditionDeserializer : JsonDeserializer() { + override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.GreaterThanCondition { + val op = OperationConditionDeserializer().deserialize(parser, ctxt) + return NotificationExpression.GreaterThanCondition(op.value) + } +} +class GreaterThanOrEqualsConditionDeserializer : JsonDeserializer() { + override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.GreaterThanOrEqualsCondition { + val op = OperationConditionDeserializer().deserialize(parser, ctxt) + return NotificationExpression.GreaterThanOrEqualsCondition(op.value) + } +} +class LessThanConditionDeserializer : JsonDeserializer() { + override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.LessThanCondition { + val op = OperationConditionDeserializer().deserialize(parser, ctxt) + return NotificationExpression.LessThanCondition(op.value) + } +} +class LessThanOrEqualsConditionDeserializer : JsonDeserializer() { + override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.LessThanOrEqualsCondition { + val op = OperationConditionDeserializer().deserialize(parser, ctxt) + return NotificationExpression.LessThanOrEqualsCondition(op.value) + } +} +class ComplexOperationConditionDeserializer : JsonDeserializer() { + override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.ComplexOperationCondition { + val node = parser.codec.readTree(parser) + if (!node.isArray) { + throw JsonMappingException(parser, "anyOf/noneOf must contain an array of values") + } + val values = node.map { it.asText() } + return NotificationExpression.ComplexOperationCondition(values) + } +} +class AnyOfConditionDeserializer : JsonDeserializer() { + override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.AnyOfCondition { + val op = ComplexOperationConditionDeserializer().deserialize(parser, ctxt) + return NotificationExpression.AnyOfCondition(op.value) + } +} + +class NoneOfConditionDeserializer : JsonDeserializer() { + override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.NoneOfCondition { + val op = ComplexOperationConditionDeserializer().deserialize(parser, ctxt) + return NotificationExpression.NoneOfCondition(op.value) + } +} + +class ComplexConditionDeserializer : JsonDeserializer() { + override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.ComplexCondition { + val node = parser.codec.readTree(parser) + if (!node.isArray) { + throw JsonMappingException(parser, "or/and must contain an array of values") + } + return NotificationExpression.ComplexCondition(node.toNotificationExpressions(parser)) + } +} +class OrConditionDeserializer : JsonDeserializer() { + override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.OrCondition { + val op = ComplexConditionDeserializer().deserialize(parser, ctxt) + return NotificationExpression.OrCondition(op.expectedValueList) + } +} + +class AndConditionDeserializer : JsonDeserializer() { + override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.AndCondition { + val op = ComplexConditionDeserializer().deserialize(parser, ctxt) + return NotificationExpression.AndCondition(op.expectedValueList) + } +} + +class NotConditionDeserializer : JsonDeserializer() { + override fun deserialize(p: JsonParser, ctxt: DeserializationContext): NotificationExpression.NotCondition { + val node = p.codec.readTree(p) + val parser = node.traverse(p.codec) + parser.nextToken() + + return NotificationExpression.NotCondition(parser.readValueAs(NotificationExpression::class.java)) + } +} + +private fun JsonNode.toNotificationExpressions(p: JsonParser): List = this.map { element -> + val parser = element.traverse(p.codec) + parser.nextToken() + parser.readValueAs(NotificationExpression::class.java) +} diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtils.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtils.kt index a17f853839c..aaca3879e78 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtils.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtils.kt @@ -4,12 +4,15 @@ package software.aws.toolkits.jetbrains.core.notifications import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.annotation.JsonSubTypes +import com.fasterxml.jackson.annotation.JsonTypeInfo +import com.fasterxml.jackson.databind.annotation.JsonDeserialize data class NotificationsList( @JsonProperty("schema") val schema: Schema, @JsonProperty("notifications") - val notifications: List, + val notifications: List?, ) data class Schema( @@ -84,7 +87,7 @@ data class NotificationDisplayCondition( @JsonProperty("extension") val extension: List?, @JsonProperty("authx") - val authx: List, + val authx: List?, ) data class ComputeType( @@ -108,24 +111,95 @@ data class ExtensionType( val version: NotificationExpression?, ) -open class NotificationExpression - -open class NotificationOperation : NotificationExpression() - -data class NotCondition( - @JsonProperty("not") - val expectedValue: NotificationExpression, -) : NotificationExpression() - -data class OrCondition( - @JsonProperty("or") - val expectedValueList: List, -) : NotificationExpression() - -data class AndCondition( - @JsonProperty("and") - val expectedValueList: List, -) : NotificationExpression() +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.WRAPPER_OBJECT +) +@JsonSubTypes( + JsonSubTypes.Type(value = NotificationExpression.ComparisonCondition::class, name = "=="), + JsonSubTypes.Type(value = NotificationExpression.NotEqualsCondition::class, name = "!="), + JsonSubTypes.Type(value = NotificationExpression.GreaterThanCondition::class, name = ">"), + JsonSubTypes.Type(value = NotificationExpression.GreaterThanOrEqualsCondition::class, name = ">="), + JsonSubTypes.Type(value = NotificationExpression.LessThanCondition::class, name = "<"), + JsonSubTypes.Type(value = NotificationExpression.LessThanOrEqualsCondition::class, name = "<="), + JsonSubTypes.Type(value = NotificationExpression.AnyOfCondition::class, name = "anyOf"), + JsonSubTypes.Type(value = NotificationExpression.NotCondition::class, name = "not"), + JsonSubTypes.Type(value = NotificationExpression.OrCondition::class, name = "or"), + JsonSubTypes.Type(value = NotificationExpression.AndCondition::class, name = "and"), + JsonSubTypes.Type(value = NotificationExpression.NoneOfCondition::class, name = "noneOf") +) +sealed class NotificationExpression { + @JsonDeserialize(using = NotConditionDeserializer::class) + data class NotCondition( + val expectedValue: NotificationExpression, + ) : NotificationExpression() + + @JsonDeserialize(using = OrConditionDeserializer::class) + data class OrCondition( + val expectedValueList: List, + ) : NotificationExpression() + + @JsonDeserialize(using = AndConditionDeserializer::class) + data class AndCondition( + val expectedValueList: List, + ) : NotificationExpression() + + @JsonDeserialize(using = ComplexConditionDeserializer::class) + data class ComplexCondition( + val expectedValueList: List, + ) : NotificationExpression() + + // General class for comparison operators + @JsonDeserialize(using = OperationConditionDeserializer::class) + data class OperationCondition( + val value: String, + ) : NotificationExpression() + + @JsonDeserialize(using = ComplexOperationConditionDeserializer::class) + data class ComplexOperationCondition( + val value: List, + ) : NotificationExpression() + + @JsonDeserialize(using = ComparisonConditionDeserializer::class) + data class ComparisonCondition( + val value: String, + ) : NotificationExpression() + + @JsonDeserialize(using = NotEqualsConditionDeserializer::class) + data class NotEqualsCondition( + val value: String, + ) : NotificationExpression() + + @JsonDeserialize(using = GreaterThanConditionDeserializer::class) + data class GreaterThanCondition( + val value: String, + ) : NotificationExpression() + + @JsonDeserialize(using = GreaterThanOrEqualsConditionDeserializer::class) + data class GreaterThanOrEqualsCondition( + val value: String, + ) : NotificationExpression() + + @JsonDeserialize(using = LessThanConditionDeserializer::class) + data class LessThanCondition( + val value: String, + ) : NotificationExpression() + + @JsonDeserialize(using = LessThanOrEqualsConditionDeserializer::class) + data class LessThanOrEqualsCondition( + val value: String, + ) : NotificationExpression() + + @JsonDeserialize(using = AnyOfConditionDeserializer::class) + data class AnyOfCondition( + val value: List, + ) : NotificationExpression() + + @JsonDeserialize(using = NoneOfConditionDeserializer::class) + data class NoneOfCondition( + val value: List, + ) : NotificationExpression() +} data class AuthxType( @JsonProperty("feature") @@ -139,43 +213,3 @@ data class AuthxType( @JsonProperty("ssoscopes") val ssoScopes: NotificationExpression?, ) - -data class ComparisonCondition( - @JsonProperty("==") - val expectedValue: String, -) : NotificationOperation() - -data class NotEqualsCondition( - @JsonProperty("!=") - val expectedValue: String, -) : NotificationOperation() - -data class GreaterThanCondition( - @JsonProperty(">") - val expectedValue: String, -) : NotificationOperation() - -data class GreaterThanOrEqualsCondition( - @JsonProperty(">=") - val expectedValue: String, -) : NotificationOperation() - -data class LessThanCondition( - @JsonProperty("<") - val expectedValue: String, -) : NotificationOperation() - -data class LessThanOrEqualsCondition( - @JsonProperty("<=") - val expectedValue: String, -) : NotificationOperation() - -data class InCondition( - @JsonProperty("anyOf") - val expectedValueList: List, -) : NotificationOperation() - -data class NotInCondition( - @JsonProperty("noneOf") - val expectedValueList: List, -) : NotificationOperation() diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/RulesEngine.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/RulesEngine.kt index 27a214e7eda..d7553f09dc8 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/RulesEngine.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/RulesEngine.kt @@ -36,7 +36,7 @@ object RulesEngine { private fun matchesCompute(notificationCompute: ComputeType, actualCompute: String, actualArchitecture: String): Boolean { val type = notificationCompute.type?.let { evaluateNotificationExpression(it, actualCompute) } ?: true - val architecture = notificationCompute.architecture?.let { evaluateNotificationExpression(it, actualArchitecture) } ?: return true + val architecture = notificationCompute.architecture?.let { evaluateNotificationExpression(it, actualArchitecture) } ?: true return type && architecture } @@ -89,32 +89,27 @@ object RulesEngine { } private fun evaluateNotificationExpression(notificationExpression: NotificationExpression, value: String): Boolean = when (notificationExpression) { - is NotificationOperation -> performNotifOp(notificationExpression, value) - is NotCondition -> performNotOp(notificationExpression, value) - is OrCondition -> performOrOp(notificationExpression, value) - is AndCondition -> performAndOp(notificationExpression, value) + is NotificationExpression.NotCondition -> performNotOp(notificationExpression, value) + is NotificationExpression.OrCondition -> performOrOp(notificationExpression, value) + is NotificationExpression.AndCondition -> performAndOp(notificationExpression, value) + is NotificationExpression.ComparisonCondition -> notificationExpression.value == value + is NotificationExpression.NotEqualsCondition -> notificationExpression.value != value + is NotificationExpression.GreaterThanCondition -> value > notificationExpression.value + is NotificationExpression.LessThanCondition -> value < notificationExpression.value + is NotificationExpression.GreaterThanOrEqualsCondition -> value >= notificationExpression.value + is NotificationExpression.LessThanOrEqualsCondition -> value <= notificationExpression.value + is NotificationExpression.AnyOfCondition -> notificationExpression.value.contains(value) + is NotificationExpression.NoneOfCondition -> !notificationExpression.value.contains(value) else -> true } - private fun performNotifOp(notificationOperation: NotificationOperation, actualValue: String): Boolean = when (notificationOperation) { - is ComparisonCondition -> notificationOperation.expectedValue == actualValue - is NotEqualsCondition -> notificationOperation.expectedValue != actualValue - is GreaterThanCondition -> actualValue > notificationOperation.expectedValue - is LessThanCondition -> actualValue < notificationOperation.expectedValue - is GreaterThanOrEqualsCondition -> actualValue >= notificationOperation.expectedValue - is LessThanOrEqualsCondition -> actualValue <= notificationOperation.expectedValue - is InCondition -> notificationOperation.expectedValueList.contains(actualValue) - is NotInCondition -> !notificationOperation.expectedValueList.contains(actualValue) - else -> true - } - - private fun performNotOp(notificationOperation: NotCondition, actualValue: String): Boolean = + private fun performNotOp(notificationOperation: NotificationExpression.NotCondition, actualValue: String): Boolean = !evaluateNotificationExpression(notificationOperation.expectedValue, actualValue) - private fun performOrOp(notificationOperation: OrCondition, actualValue: String): Boolean = + private fun performOrOp(notificationOperation: NotificationExpression.OrCondition, actualValue: String): Boolean = notificationOperation.expectedValueList.any { evaluateNotificationExpression(it, actualValue) } - private fun performAndOp(notificationOperation: AndCondition, actualValue: String): Boolean = + private fun performAndOp(notificationOperation: NotificationExpression.AndCondition, actualValue: String): Boolean = notificationOperation.expectedValueList.all { evaluateNotificationExpression(it, actualValue) } } @@ -134,12 +129,6 @@ fun getCurrentSystemAndConnectionDetails(): SystemDetails { return SystemDetails(computeType, computeArchitecture, osType, osVersion, ideType, ideVersion, pluginVersionMap) } -data class AuthDetails( - val qConnection: ActiveConnection, - val codeCatalystConnection: ActiveConnection, - val explorerConnection: ActiveConnection, -) - data class FeatureAuthDetails( val connectionType: String, val region: String, @@ -196,7 +185,7 @@ private fun getConnectionDetailsForToolkit(project: Project): FeatureAuthDetails ) } -private fun getConnectionDetailsForFeature(project: Project, featureId: BearerTokenFeatureSet): FeatureAuthDetails? { +fun getConnectionDetailsForFeature(project: Project, featureId: BearerTokenFeatureSet): FeatureAuthDetails? { val connection = checkBearerConnectionValidity(project, featureId) if (connection.activeConnectionBearer == null) return null val authType = when (connection.connectionType) { diff --git a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtilsTest.kt b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtilsTest.kt index 18fd35adb18..d7ec14a1c04 100644 --- a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtilsTest.kt +++ b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtilsTest.kt @@ -6,32 +6,131 @@ package software.aws.toolkits.jetbrains.core.notifications import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.readValue -import org.junit.Before -import org.junit.Test +import com.intellij.testFramework.ApplicationExtension +import com.intellij.testFramework.ProjectRule +import io.mockk.every +import io.mockk.mockkStatic +import io.mockk.unmockkAll +import org.assertj.core.api.Assertions.assertThat +import org.junit.Rule +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource import software.aws.toolkits.core.utils.exists import software.aws.toolkits.core.utils.inputStream +import software.aws.toolkits.jetbrains.core.gettingstarted.editor.BearerTokenFeatureSet import java.io.InputStream import java.nio.file.Paths +import java.util.stream.Stream +@ExtendWith(ApplicationExtension::class) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) class NotificationFormatUtilsTest { - lateinit var notificationJson: InputStream + @Rule + @JvmField + val projectRule = ProjectRule() - @Before + private lateinit var mockSystemDetails: SystemDetails + private lateinit var exampleNotification: InputStream + + @BeforeAll fun setUp() { - val file = javaClass.getResource("/exampleNotification2.json")?.let { Paths.get(it.toURI()).takeIf { f -> f.exists() } } - ?: throw RuntimeException("Test file not found") - notificationJson = file.inputStream() + mockSystemDetails = SystemDetails( + computeType = "Local", + computeArchitecture = "x86_64", + osType = "Linux", + osVersion = "5.4.0", + ideType = "IC", + ideVersion = "2023.1", + pluginVersions = mapOf( + "aws.toolkit" to "1.0", + "amazon.q" to "2.0" + ) + ) + + exampleNotification = javaClass.getResource("/exampleNotification2.json")?.let { + Paths.get(it.toURI()).takeIf { f -> f.exists() } + }?.inputStream() ?: throw RuntimeException("Test not found") + + mockkStatic("software.aws.toolkits.jetbrains.core.notifications.RulesEngineKt") + every { getCurrentSystemAndConnectionDetails() } returns mockSystemDetails + every { getConnectionDetailsForFeature(projectRule.project, BearerTokenFeatureSet.Q) } returns FeatureAuthDetails( + "Idc", + "us-west-2", + "Connected" + ) + } + + @AfterAll + fun tearDown() { + unmockkAll() + } + + @Test + fun `test System Details`() { + val result = getCurrentSystemAndConnectionDetails() + assertThat(mockSystemDetails).isEqualTo(result) } @Test - fun checkValidity() { + fun `check Json Validity which has all the required fields`() { assertDoesNotThrow { - mapper.readValue(notificationJson) + mapper.readValue(exampleNotification) + } + } + + @Test + fun `No schema version associated with the notification file throws an exception`() { + assertThrows { + mapper.readValue(exampleNotificationWithoutSchema) } } + @Test + fun `No notifications present with the version file does not throw an exception`() { + assertDoesNotThrow { + mapper.readValue(exampleNotificationWithoutNotification) + } + } + + @ParameterizedTest + @MethodSource("validNotifications") + fun `The notification is shown`(notification: String) { + val notificationData = mapper.readValue(notification) + val shouldShow = RulesEngine.displayNotification(notificationData, projectRule.project) + assertThat(shouldShow).isTrue + } + + @ParameterizedTest + @MethodSource("invalidNotifications") + fun `The notification is not shown`(notification: String) { + val notificationData = mapper.readValue(notification) + val shouldShow = RulesEngine.displayNotification(notificationData, projectRule.project) + assertThat(shouldShow).isFalse + } + companion object { - val mapper = jacksonObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + @JvmStatic + fun validNotifications(): Stream = Stream.of( + Arguments.of(notificationWithConditionsOrActions), + Arguments.of(notificationWithoutConditionsOrActions), + Arguments.of(notificationWithValidConnection) + ) + + @JvmStatic + fun invalidNotifications(): Stream = Stream.of( + Arguments.of(validComputeInvalidOs), + Arguments.of(invalidExtensionVersion), + Arguments.of(invalidIdeTypeAndVersion) + ) + + private val mapper = jacksonObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) } } diff --git a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtilsTestCases.kt b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtilsTestCases.kt new file mode 100644 index 00000000000..f24a8bafb93 --- /dev/null +++ b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtilsTestCases.kt @@ -0,0 +1,206 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.core.notifications + +val validComputeInvalidOs = """{ + "id": "example_id_12344", + "schedule": { + "type": "StartUp" +}, + "severity": "Critical", + "condition": { + "compute": { + "type": {"==": "Local"} +}, +"os": { + "type": {"==": "Windows"} +} +}, + "actions": [ + { + "type": "ShowMarketplace", + "content": { + "en-US": { + "title": "Go to market" + } + } + } + ], + "content": { + "en-US": { + "title": "Look at this!", + "description": "Some bug is there" +} +} +} +""".trimIndent() + +val invalidExtensionVersion = """{ + "id": "example_id_12344", + "schedule": { + "type": "StartUp" +}, + "severity": "Critical", + "condition": { + "extension": [ + { + "id": "aws.toolkit", + "version": { + "!=": "1.3334" + } + }, + { + "id": "amazon.q", + "version": { + ">": "3.37.0" + } + } + ] +}, + "content": { + "en-US": { + "title": "Look at this!", + "description": "Some bug is there" +} +} +} +""".trimIndent() + +val exampleNotificationWithoutSchema = """ + { + "notifications": [ + { + "id": "notification-001", + "title": "Test Notification", + "description": "This is a test notification", + "type": "INFO", + + "rules": { + "computeType": "Local", + "osType": "Linux", + "ideType": "IC", + "pluginVersion": { + "aws.toolkit": "1.0" + } + } + } + ] + } +""".trimIndent() + +val exampleNotificationWithoutNotification = """ + { + "schema": { + "version": "2.0" +} + + } +""".trimIndent() + +val notificationWithoutConditionsOrActions = """ + { + "id": "example_id_12344", + "schedule": { + "type": "StartUp" + }, + "severity": "Critical", + "content": { + "en-US": { + "title": "Look at this!", + "description": "Some bug is there" + } + } + } + + +""".trimIndent() + +val notificationWithConditionsOrActions = """ + { + "id": "example_id_12344", + "schedule": { + "type": "StartUp" + }, + "severity": "Critical", + "condition": { + "compute": { + "type": { + + "==": "Local" + + } + } + }, + "actions": [ + { + "type": "ShowMarketplace", + "content": { + "en-US": { + "title": "Go to market" + } + } + } + ], + "content": { + "en-US": { + "title": "Look at this!", + "description": "Some bug is there" + } + } + } + +""".trimIndent() + +val notificationWithValidConnection = """{ + "id": "example_id_12344", + "schedule": { + "type": "StartUp" +}, + "severity": "Critical", + "condition": { + "authx": [{ + "feature" : "q", + "type": { + "anyOf": [ + "Idc", + "BuilderId" + ] + }, + "region": { + "==": "us-west-2" + }, + "connectionState": { + "==": "Connected" + } + } ] +}, + "content": { + "en-US": { + "title": "Look at this!", + "description": "Some bug is there" +} +} +} +""".trimIndent() + +val invalidIdeTypeAndVersion = """{ + "id": "example_id_12344", + "schedule": { + "type": "StartUp" +}, + "severity": "Critical", + "condition": { + "ide": { + "type": {"noneOf": ["IC","IU","RD"]}, + "version": {"!=": "1.3334"} +} +}, + "content": { + "en-US": { + "title": "Look at this!", + "description": "Some bug is there" +} +} +} +""".trimIndent() From 56c18882115657eee1ecc1d8f4c5244fb7f577a4 Mon Sep 17 00:00:00 2001 From: Manodnya Bhoite Date: Wed, 20 Nov 2024 00:02:35 -0800 Subject: [PATCH 4/7] not required file change --- .../jetbrains/core/notifications/DisplayToastNotifications.kt | 3 +-- .../software/aws/toolkits/jetbrains/utils/NotificationUtils.kt | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/DisplayToastNotifications.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/DisplayToastNotifications.kt index 76cb13a2b40..6fc5cbe0d83 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/DisplayToastNotifications.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/DisplayToastNotifications.kt @@ -6,7 +6,6 @@ package software.aws.toolkits.jetbrains.core.notifications import com.intellij.notification.NotificationType import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.project.Project -import software.aws.toolkits.jetbrains.utils.notifySticky object DisplayToastNotifications { fun show(title: String, message: String, action: List, notificationType: NotificationSeverity) { @@ -15,7 +14,7 @@ object DisplayToastNotifications { NotificationSeverity.WARNING -> NotificationType.WARNING NotificationSeverity.INFO -> NotificationType.INFORMATION } - notifySticky(notifyType, title, message, null, action) + } fun shouldShow(project: Project, notificationData: NotificationData) { diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/utils/NotificationUtils.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/utils/NotificationUtils.kt index 924db699653..0bd81b96227 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/utils/NotificationUtils.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/utils/NotificationUtils.kt @@ -48,7 +48,7 @@ private fun notify(type: NotificationType, title: String, content: String = "", notify(notification, project) } -fun notifySticky(type: NotificationType, title: String, content: String = "", project: Project? = null, notificationActions: Collection) { +private fun notifySticky(type: NotificationType, title: String, content: String = "", project: Project? = null, notificationActions: Collection) { val notification = Notification(GROUP_DISPLAY_ID_STICKY, title, content, type) notificationActions.forEach { notification.addAction(if (it !is NotificationAction) createNotificationExpiringAction(it) else it) From dbfcefc0a513b7b9e504df35368a842fd3ee4ee1 Mon Sep 17 00:00:00 2001 From: Manodnya Bhoite Date: Wed, 20 Nov 2024 15:37:48 -0800 Subject: [PATCH 5/7] feedback 1 --- .../DisplayToastNotifications.kt | 21 ++- .../notifications/NotificationFormatUtils.kt | 62 ++----- .../NotificationFormatUtilsTest.kt | 18 +- .../NotificationFormatUtilsTestCases.kt | 156 ++++++++++++++++++ 4 files changed, 191 insertions(+), 66 deletions(-) diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/DisplayToastNotifications.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/DisplayToastNotifications.kt index 6fc5cbe0d83..03e1de5d794 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/DisplayToastNotifications.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/DisplayToastNotifications.kt @@ -3,25 +3,24 @@ package software.aws.toolkits.jetbrains.core.notifications -import com.intellij.notification.NotificationType -import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.project.Project object DisplayToastNotifications { - fun show(title: String, message: String, action: List, notificationType: NotificationSeverity) { - val notifyType = when (notificationType) { - NotificationSeverity.CRITICAL -> NotificationType.ERROR - NotificationSeverity.WARNING -> NotificationType.WARNING - NotificationSeverity.INFO -> NotificationType.INFORMATION - } - - } +// fun show(title: String, message: String, action: List, notificationType: NotificationSeverity) { +// val notifyType = when (notificationType) { +// NotificationSeverity.CRITICAL -> NotificationType.ERROR +// NotificationSeverity.WARNING -> NotificationType.WARNING +// NotificationSeverity.INFO -> NotificationType.INFORMATION +// } +// } fun shouldShow(project: Project, notificationData: NotificationData) { if (RulesEngine.displayNotification(notificationData, project)) { val notificationContent = notificationData.content.locale val severity = notificationData.severity - show(notificationContent.title, notificationContent.description, emptyList(), checkSeverity(severity)) + notificationContent + severity + // show(notificationContent.title, notificationContent.description, emptyList(), checkSeverity(severity)) } } } diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtils.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtils.kt index aaca3879e78..425eaf4c00a 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtils.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtils.kt @@ -9,34 +9,24 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo import com.fasterxml.jackson.databind.annotation.JsonDeserialize data class NotificationsList( - @JsonProperty("schema") val schema: Schema, - @JsonProperty("notifications") val notifications: List?, ) data class Schema( - @JsonProperty("version") val version: String, ) data class NotificationData( - @JsonProperty("id") val id: String, - @JsonProperty("schedule") val schedule: NotificationSchedule, - @JsonProperty("severity") val severity: String, - @JsonProperty("condition") val condition: NotificationDisplayCondition?, - @JsonProperty("content") val content: NotificationContentDescriptionLocale, - @JsonProperty("actions") val actions: List? = emptyList(), ) data class NotificationSchedule( - @JsonProperty("type") val type: String, ) @@ -52,16 +42,12 @@ data class NotificationContentDescriptionLocale( ) data class NotificationContentDescription( - @JsonProperty("title") val title: String, - @JsonProperty("description") val description: String, ) data class NotificationFollowupActions( - @JsonProperty("type") val type: String, - @JsonProperty("content") val content: NotificationFollowupActionsContent, ) @@ -71,43 +57,30 @@ data class NotificationFollowupActionsContent( ) data class NotificationActionDescription( - @JsonProperty("title") val title: String, - @JsonProperty("url") val url: String?, ) data class NotificationDisplayCondition( - @JsonProperty("compute") val compute: ComputeType?, - @JsonProperty("os") val os: SystemType?, - @JsonProperty("ide") val ide: SystemType?, - @JsonProperty("extension") val extension: List?, - @JsonProperty("authx") val authx: List?, ) data class ComputeType( - @JsonProperty("type") val type: NotificationExpression?, - @JsonProperty("architecture") val architecture: NotificationExpression?, ) data class SystemType( - @JsonProperty("type") val type: NotificationExpression?, - @JsonProperty("version") val version: NotificationExpression?, ) data class ExtensionType( - @JsonProperty("id") val id: String?, - @JsonProperty("version") val version: NotificationExpression?, ) @@ -128,88 +101,83 @@ data class ExtensionType( JsonSubTypes.Type(value = NotificationExpression.AndCondition::class, name = "and"), JsonSubTypes.Type(value = NotificationExpression.NoneOfCondition::class, name = "noneOf") ) -sealed class NotificationExpression { +sealed interface NotificationExpression { @JsonDeserialize(using = NotConditionDeserializer::class) data class NotCondition( val expectedValue: NotificationExpression, - ) : NotificationExpression() + ) : NotificationExpression @JsonDeserialize(using = OrConditionDeserializer::class) data class OrCondition( val expectedValueList: List, - ) : NotificationExpression() + ) : NotificationExpression @JsonDeserialize(using = AndConditionDeserializer::class) data class AndCondition( val expectedValueList: List, - ) : NotificationExpression() + ) : NotificationExpression @JsonDeserialize(using = ComplexConditionDeserializer::class) data class ComplexCondition( val expectedValueList: List, - ) : NotificationExpression() + ) : NotificationExpression // General class for comparison operators @JsonDeserialize(using = OperationConditionDeserializer::class) data class OperationCondition( val value: String, - ) : NotificationExpression() + ) : NotificationExpression @JsonDeserialize(using = ComplexOperationConditionDeserializer::class) data class ComplexOperationCondition( val value: List, - ) : NotificationExpression() + ) : NotificationExpression @JsonDeserialize(using = ComparisonConditionDeserializer::class) data class ComparisonCondition( val value: String, - ) : NotificationExpression() + ) : NotificationExpression @JsonDeserialize(using = NotEqualsConditionDeserializer::class) data class NotEqualsCondition( val value: String, - ) : NotificationExpression() + ) : NotificationExpression @JsonDeserialize(using = GreaterThanConditionDeserializer::class) data class GreaterThanCondition( val value: String, - ) : NotificationExpression() + ) : NotificationExpression @JsonDeserialize(using = GreaterThanOrEqualsConditionDeserializer::class) data class GreaterThanOrEqualsCondition( val value: String, - ) : NotificationExpression() + ) : NotificationExpression @JsonDeserialize(using = LessThanConditionDeserializer::class) data class LessThanCondition( val value: String, - ) : NotificationExpression() + ) : NotificationExpression @JsonDeserialize(using = LessThanOrEqualsConditionDeserializer::class) data class LessThanOrEqualsCondition( val value: String, - ) : NotificationExpression() + ) : NotificationExpression @JsonDeserialize(using = AnyOfConditionDeserializer::class) data class AnyOfCondition( val value: List, - ) : NotificationExpression() + ) : NotificationExpression @JsonDeserialize(using = NoneOfConditionDeserializer::class) data class NoneOfCondition( val value: List, - ) : NotificationExpression() + ) : NotificationExpression } data class AuthxType( - @JsonProperty("feature") val feature: String, - @JsonProperty("type") val type: NotificationExpression?, - @JsonProperty("region") val region: NotificationExpression?, - @JsonProperty("connectionState") val connectionState: NotificationExpression?, - @JsonProperty("ssoscopes") val ssoScopes: NotificationExpression?, ) diff --git a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtilsTest.kt b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtilsTest.kt index d7ec14a1c04..c3f3ba7b828 100644 --- a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtilsTest.kt +++ b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtilsTest.kt @@ -102,16 +102,18 @@ class NotificationFormatUtilsTest { @ParameterizedTest @MethodSource("validNotifications") - fun `The notification is shown`(notification: String) { + fun `The notification is shown`(notification: String, expectedData: NotificationData) { val notificationData = mapper.readValue(notification) + assertThat(notificationData).isEqualTo(expectedData) val shouldShow = RulesEngine.displayNotification(notificationData, projectRule.project) assertThat(shouldShow).isTrue } @ParameterizedTest @MethodSource("invalidNotifications") - fun `The notification is not shown`(notification: String) { + fun `The notification is not shown`(notification: String, expectedData: NotificationData) { val notificationData = mapper.readValue(notification) + assertThat(notificationData).isEqualTo(expectedData) val shouldShow = RulesEngine.displayNotification(notificationData, projectRule.project) assertThat(shouldShow).isFalse } @@ -119,16 +121,16 @@ class NotificationFormatUtilsTest { companion object { @JvmStatic fun validNotifications(): Stream = Stream.of( - Arguments.of(notificationWithConditionsOrActions), - Arguments.of(notificationWithoutConditionsOrActions), - Arguments.of(notificationWithValidConnection) + Arguments.of(notificationWithConditionsOrActions, notificationWithConditionsOrActionsData), + Arguments.of(notificationWithoutConditionsOrActions, notificationsWithoutConditionsOrActionsData), + Arguments.of(notificationWithValidConnection, notificationWithValidConnectionData) ) @JvmStatic fun invalidNotifications(): Stream = Stream.of( - Arguments.of(validComputeInvalidOs), - Arguments.of(invalidExtensionVersion), - Arguments.of(invalidIdeTypeAndVersion) + Arguments.of(validComputeInvalidOs, validOsInvalidComputeData), + Arguments.of(invalidExtensionVersion, invalidExtensionVersionData), + Arguments.of(invalidIdeTypeAndVersion, invalidIdeTypeAndVersionData) ) private val mapper = jacksonObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) diff --git a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtilsTestCases.kt b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtilsTestCases.kt index f24a8bafb93..9a39843b3f6 100644 --- a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtilsTestCases.kt +++ b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtilsTestCases.kt @@ -36,6 +36,36 @@ val validComputeInvalidOs = """{ } """.trimIndent() +val validOsInvalidComputeData = NotificationData( + id = "example_id_12344", + schedule = NotificationSchedule(type = "StartUp"), + severity = "Critical", + condition = NotificationDisplayCondition( + compute = ComputeType(type = NotificationExpression.ComparisonCondition("Local"), architecture = null), + os = SystemType(type = NotificationExpression.ComparisonCondition("Windows"), version = null), + ide = null, + extension = null, + authx = null + ), + actions = listOf( + NotificationFollowupActions( + type = "ShowMarketplace", + content = NotificationFollowupActionsContent( + NotificationActionDescription( + title = "Go to market", + url = null + ) + ) + ) + ), + content = NotificationContentDescriptionLocale( + NotificationContentDescription( + title = "Look at this!", + description = "Some bug is there" + ) + ) +) + val invalidExtensionVersion = """{ "id": "example_id_12344", "schedule": { @@ -67,6 +97,36 @@ val invalidExtensionVersion = """{ } """.trimIndent() +val invalidExtensionVersionData = NotificationData( + id = "example_id_12344", + schedule = NotificationSchedule(type = "StartUp"), + severity = "Critical", + condition = NotificationDisplayCondition( + compute = null, + os = null, + ide = null, + extension = listOf( + ExtensionType( + id = "aws.toolkit", + version = NotificationExpression.NotEqualsCondition("1.3334") + ), + ExtensionType( + id = "amazon.q", + version = NotificationExpression.GreaterThanCondition("3.37.0") + ) + ), + authx = null + + ), + actions = emptyList(), + content = NotificationContentDescriptionLocale( + NotificationContentDescription( + title = "Look at this!", + description = "Some bug is there" + ) + ) +) + val exampleNotificationWithoutSchema = """ { "notifications": [ @@ -116,6 +176,20 @@ val notificationWithoutConditionsOrActions = """ """.trimIndent() +val notificationsWithoutConditionsOrActionsData = NotificationData( + id = "example_id_12344", + schedule = NotificationSchedule(type = "StartUp"), + severity = "Critical", + condition = null, + actions = emptyList(), + content = NotificationContentDescriptionLocale( + NotificationContentDescription( + title = "Look at this!", + description = "Some bug is there" + ) + ) +) + val notificationWithConditionsOrActions = """ { "id": "example_id_12344", @@ -152,6 +226,36 @@ val notificationWithConditionsOrActions = """ """.trimIndent() +val notificationWithConditionsOrActionsData = NotificationData( + id = "example_id_12344", + schedule = NotificationSchedule(type = "StartUp"), + severity = "Critical", + condition = NotificationDisplayCondition( + compute = ComputeType(type = NotificationExpression.ComparisonCondition("Local"), architecture = null), + os = null, + ide = null, + extension = null, + authx = null + ), + actions = listOf( + NotificationFollowupActions( + type = "ShowMarketplace", + content = NotificationFollowupActionsContent( + NotificationActionDescription( + title = "Go to market", + url = null + ) + ) + ) + ), + content = NotificationContentDescriptionLocale( + NotificationContentDescription( + title = "Look at this!", + description = "Some bug is there" + ) + ) +) + val notificationWithValidConnection = """{ "id": "example_id_12344", "schedule": { @@ -184,6 +288,34 @@ val notificationWithValidConnection = """{ } """.trimIndent() +val notificationWithValidConnectionData = NotificationData( + id = "example_id_12344", + schedule = NotificationSchedule(type = "StartUp"), + severity = "Critical", + condition = NotificationDisplayCondition( + compute = null, + os = null, + ide = null, + extension = null, + authx = listOf( + AuthxType( + feature = "q", + type = NotificationExpression.AnyOfCondition(listOf("Idc", "BuilderId")), + region = NotificationExpression.ComparisonCondition("us-west-2"), + connectionState = NotificationExpression.ComparisonCondition("Connected"), + ssoScopes = null + ) + ) + ), + actions = emptyList(), + content = NotificationContentDescriptionLocale( + NotificationContentDescription( + title = "Look at this!", + description = "Some bug is there" + ) + ) +) + val invalidIdeTypeAndVersion = """{ "id": "example_id_12344", "schedule": { @@ -204,3 +336,27 @@ val invalidIdeTypeAndVersion = """{ } } """.trimIndent() + +val invalidIdeTypeAndVersionData = NotificationData( + id = "example_id_12344", + schedule = NotificationSchedule(type = "StartUp"), + severity = "Critical", + condition = NotificationDisplayCondition( + compute = null, + os = null, + ide = SystemType( + type = NotificationExpression.NoneOfCondition(listOf("IC", "IU", "RD")), + version = NotificationExpression.NotEqualsCondition("1.3334") + ), + extension = null, + authx = null + + ), + actions = emptyList(), + content = NotificationContentDescriptionLocale( + NotificationContentDescription( + title = "Look at this!", + description = "Some bug is there" + ) + ) +) From 218c8edd9d56c01bc23687fa9e3c00f247262cf7 Mon Sep 17 00:00:00 2001 From: Manodnya Bhoite Date: Wed, 20 Nov 2024 15:43:58 -0800 Subject: [PATCH 6/7] modified the base class --- .../DisplayToastNotifications.kt | 20 +------------------ .../notifications/ProcessNotificationsBase.kt | 9 +++++++-- .../core/notifications/RulesEngine.kt | 2 +- .../NotificationFormatUtilsTest.kt | 4 ++-- 4 files changed, 11 insertions(+), 24 deletions(-) diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/DisplayToastNotifications.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/DisplayToastNotifications.kt index 03e1de5d794..341e600a261 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/DisplayToastNotifications.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/DisplayToastNotifications.kt @@ -5,22 +5,4 @@ package software.aws.toolkits.jetbrains.core.notifications import com.intellij.openapi.project.Project -object DisplayToastNotifications { -// fun show(title: String, message: String, action: List, notificationType: NotificationSeverity) { -// val notifyType = when (notificationType) { -// NotificationSeverity.CRITICAL -> NotificationType.ERROR -// NotificationSeverity.WARNING -> NotificationType.WARNING -// NotificationSeverity.INFO -> NotificationType.INFORMATION -// } -// } - - fun shouldShow(project: Project, notificationData: NotificationData) { - if (RulesEngine.displayNotification(notificationData, project)) { - val notificationContent = notificationData.content.locale - val severity = notificationData.severity - notificationContent - severity - // show(notificationContent.title, notificationContent.description, emptyList(), checkSeverity(severity)) - } - } -} +object DisplayToastNotifications {} diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/ProcessNotificationsBase.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/ProcessNotificationsBase.kt index 9d60ccd6be0..1c28ffa8e96 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/ProcessNotificationsBase.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/ProcessNotificationsBase.kt @@ -3,6 +3,8 @@ package software.aws.toolkits.jetbrains.core.notifications +import com.intellij.openapi.project.Project + class ProcessNotificationsBase { init { // TODO: install a listener for the polling class @@ -17,8 +19,11 @@ class ProcessNotificationsBase { // iterates through the 2 lists and processes each notification(if it isn't dismissed) } - fun processNotification() { - // TODO: calls the Rule engine and notifies listeners + fun processNotification(project: Project, notificationData: NotificationData) { + val shouldShow = RulesEngine.displayNotification(project, notificationData) + if(shouldShow) { + // TODO: notifies listeners + } } fun notifyListenerForNotification() { diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/RulesEngine.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/RulesEngine.kt index d7553f09dc8..bfb54952481 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/RulesEngine.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/RulesEngine.kt @@ -17,7 +17,7 @@ import software.aws.toolkits.jetbrains.utils.isRunningOnRemoteBackend object RulesEngine { - fun displayNotification(notification: NotificationData, project: Project): Boolean { + fun displayNotification(project: Project, notification: NotificationData): Boolean { // If no conditions provided, show display the notification to everyone val shouldShow = notification.condition?.let { matchesAllRules(it, project) } ?: true return shouldShow diff --git a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtilsTest.kt b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtilsTest.kt index c3f3ba7b828..ce5806c6e8e 100644 --- a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtilsTest.kt +++ b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtilsTest.kt @@ -105,7 +105,7 @@ class NotificationFormatUtilsTest { fun `The notification is shown`(notification: String, expectedData: NotificationData) { val notificationData = mapper.readValue(notification) assertThat(notificationData).isEqualTo(expectedData) - val shouldShow = RulesEngine.displayNotification(notificationData, projectRule.project) + val shouldShow = RulesEngine.displayNotification(projectRule.project, notificationData) assertThat(shouldShow).isTrue } @@ -114,7 +114,7 @@ class NotificationFormatUtilsTest { fun `The notification is not shown`(notification: String, expectedData: NotificationData) { val notificationData = mapper.readValue(notification) assertThat(notificationData).isEqualTo(expectedData) - val shouldShow = RulesEngine.displayNotification(notificationData, projectRule.project) + val shouldShow = RulesEngine.displayNotification(projectRule.project, notificationData) assertThat(shouldShow).isFalse } From 5743e94cdb4cc8cabf97130e3990258f1129eb47 Mon Sep 17 00:00:00 2001 From: Manodnya Bhoite Date: Thu, 21 Nov 2024 09:21:01 -0800 Subject: [PATCH 7/7] modified test instance lifecycle --- .../core/notifications/DisplayToastNotifications.kt | 4 +--- .../core/notifications/ProcessNotificationsBase.kt | 2 +- .../core/notifications/NotificationFormatUtilsTest.kt | 10 ++++------ 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/DisplayToastNotifications.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/DisplayToastNotifications.kt index 341e600a261..870672f78a8 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/DisplayToastNotifications.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/DisplayToastNotifications.kt @@ -3,6 +3,4 @@ package software.aws.toolkits.jetbrains.core.notifications -import com.intellij.openapi.project.Project - -object DisplayToastNotifications {} +object DisplayToastNotifications diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/ProcessNotificationsBase.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/ProcessNotificationsBase.kt index 1c28ffa8e96..cd11ba4ef2e 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/ProcessNotificationsBase.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/ProcessNotificationsBase.kt @@ -21,7 +21,7 @@ class ProcessNotificationsBase { fun processNotification(project: Project, notificationData: NotificationData) { val shouldShow = RulesEngine.displayNotification(project, notificationData) - if(shouldShow) { + if (shouldShow) { // TODO: notifies listeners } } diff --git a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtilsTest.kt b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtilsTest.kt index ce5806c6e8e..35311f21f4f 100644 --- a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtilsTest.kt +++ b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/notifications/NotificationFormatUtilsTest.kt @@ -13,10 +13,9 @@ import io.mockk.mockkStatic import io.mockk.unmockkAll import org.assertj.core.api.Assertions.assertThat import org.junit.Rule -import org.junit.jupiter.api.AfterAll -import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.extension.ExtendWith @@ -31,7 +30,6 @@ import java.nio.file.Paths import java.util.stream.Stream @ExtendWith(ApplicationExtension::class) -@TestInstance(TestInstance.Lifecycle.PER_CLASS) class NotificationFormatUtilsTest { @Rule @JvmField @@ -40,7 +38,7 @@ class NotificationFormatUtilsTest { private lateinit var mockSystemDetails: SystemDetails private lateinit var exampleNotification: InputStream - @BeforeAll + @BeforeEach fun setUp() { mockSystemDetails = SystemDetails( computeType = "Local", @@ -68,7 +66,7 @@ class NotificationFormatUtilsTest { ) } - @AfterAll + @AfterEach fun tearDown() { unmockkAll() }