Skip to content

Commit a1a5728

Browse files
authored
Add deserialization for notification messages retrieved from the notification file and criteria on whether it should be displayed (#5093)
* Display toast notifications with actions * Condition matcher for displaying notifications * Modified deserialization cases and added tests * not required file change * feedback 1 * modified the base class * modified test instance lifecycle
1 parent a235000 commit a1a5728

File tree

10 files changed

+1276
-2
lines changed

10 files changed

+1276
-2
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.aws.toolkits.jetbrains.core.notifications
5+
6+
fun checkSeverity(notificationSeverity: String): NotificationSeverity = when (notificationSeverity) {
7+
"Critical" -> NotificationSeverity.CRITICAL
8+
"Warning" -> NotificationSeverity.WARNING
9+
"Info" -> NotificationSeverity.INFO
10+
else -> NotificationSeverity.INFO
11+
}
12+
13+
// TODO: Add actions that can be performed from the notifications here
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.aws.toolkits.jetbrains.core.notifications
5+
6+
object DisplayToastNotifications
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.aws.toolkits.jetbrains.core.notifications
5+
6+
import com.fasterxml.jackson.core.JsonParser
7+
import com.fasterxml.jackson.core.JsonToken
8+
import com.fasterxml.jackson.databind.DeserializationContext
9+
import com.fasterxml.jackson.databind.JsonDeserializer
10+
import com.fasterxml.jackson.databind.JsonMappingException
11+
import com.fasterxml.jackson.databind.JsonNode
12+
13+
class OperationConditionDeserializer : JsonDeserializer<NotificationExpression.OperationCondition>() {
14+
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.OperationCondition = when (parser.currentToken) {
15+
JsonToken.VALUE_STRING -> {
16+
// Handle direct string value
17+
NotificationExpression.OperationCondition(parser.valueAsString)
18+
}
19+
else -> throw JsonMappingException(parser, "Cannot deserialize OperatingCondition")
20+
}
21+
}
22+
23+
class ComparisonConditionDeserializer : JsonDeserializer<NotificationExpression.ComparisonCondition>() {
24+
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.ComparisonCondition {
25+
val op = OperationConditionDeserializer().deserialize(parser, ctxt)
26+
return NotificationExpression.ComparisonCondition(op.value)
27+
}
28+
}
29+
30+
class NotEqualsConditionDeserializer : JsonDeserializer<NotificationExpression.NotEqualsCondition>() {
31+
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.NotEqualsCondition {
32+
val op = OperationConditionDeserializer().deserialize(parser, ctxt)
33+
return NotificationExpression.NotEqualsCondition(op.value)
34+
}
35+
}
36+
class GreaterThanConditionDeserializer : JsonDeserializer<NotificationExpression.GreaterThanCondition>() {
37+
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.GreaterThanCondition {
38+
val op = OperationConditionDeserializer().deserialize(parser, ctxt)
39+
return NotificationExpression.GreaterThanCondition(op.value)
40+
}
41+
}
42+
class GreaterThanOrEqualsConditionDeserializer : JsonDeserializer<NotificationExpression.GreaterThanOrEqualsCondition>() {
43+
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.GreaterThanOrEqualsCondition {
44+
val op = OperationConditionDeserializer().deserialize(parser, ctxt)
45+
return NotificationExpression.GreaterThanOrEqualsCondition(op.value)
46+
}
47+
}
48+
class LessThanConditionDeserializer : JsonDeserializer<NotificationExpression.LessThanCondition>() {
49+
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.LessThanCondition {
50+
val op = OperationConditionDeserializer().deserialize(parser, ctxt)
51+
return NotificationExpression.LessThanCondition(op.value)
52+
}
53+
}
54+
class LessThanOrEqualsConditionDeserializer : JsonDeserializer<NotificationExpression.LessThanOrEqualsCondition>() {
55+
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.LessThanOrEqualsCondition {
56+
val op = OperationConditionDeserializer().deserialize(parser, ctxt)
57+
return NotificationExpression.LessThanOrEqualsCondition(op.value)
58+
}
59+
}
60+
class ComplexOperationConditionDeserializer : JsonDeserializer<NotificationExpression.ComplexOperationCondition>() {
61+
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.ComplexOperationCondition {
62+
val node = parser.codec.readTree<JsonNode>(parser)
63+
if (!node.isArray) {
64+
throw JsonMappingException(parser, "anyOf/noneOf must contain an array of values")
65+
}
66+
val values = node.map { it.asText() }
67+
return NotificationExpression.ComplexOperationCondition(values)
68+
}
69+
}
70+
class AnyOfConditionDeserializer : JsonDeserializer<NotificationExpression.AnyOfCondition>() {
71+
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.AnyOfCondition {
72+
val op = ComplexOperationConditionDeserializer().deserialize(parser, ctxt)
73+
return NotificationExpression.AnyOfCondition(op.value)
74+
}
75+
}
76+
77+
class NoneOfConditionDeserializer : JsonDeserializer<NotificationExpression.NoneOfCondition>() {
78+
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.NoneOfCondition {
79+
val op = ComplexOperationConditionDeserializer().deserialize(parser, ctxt)
80+
return NotificationExpression.NoneOfCondition(op.value)
81+
}
82+
}
83+
84+
class ComplexConditionDeserializer : JsonDeserializer<NotificationExpression.ComplexCondition>() {
85+
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.ComplexCondition {
86+
val node = parser.codec.readTree<JsonNode>(parser)
87+
if (!node.isArray) {
88+
throw JsonMappingException(parser, "or/and must contain an array of values")
89+
}
90+
return NotificationExpression.ComplexCondition(node.toNotificationExpressions(parser))
91+
}
92+
}
93+
class OrConditionDeserializer : JsonDeserializer<NotificationExpression.OrCondition>() {
94+
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.OrCondition {
95+
val op = ComplexConditionDeserializer().deserialize(parser, ctxt)
96+
return NotificationExpression.OrCondition(op.expectedValueList)
97+
}
98+
}
99+
100+
class AndConditionDeserializer : JsonDeserializer<NotificationExpression.AndCondition>() {
101+
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): NotificationExpression.AndCondition {
102+
val op = ComplexConditionDeserializer().deserialize(parser, ctxt)
103+
return NotificationExpression.AndCondition(op.expectedValueList)
104+
}
105+
}
106+
107+
class NotConditionDeserializer : JsonDeserializer<NotificationExpression.NotCondition>() {
108+
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): NotificationExpression.NotCondition {
109+
val node = p.codec.readTree<JsonNode>(p)
110+
val parser = node.traverse(p.codec)
111+
parser.nextToken()
112+
113+
return NotificationExpression.NotCondition(parser.readValueAs(NotificationExpression::class.java))
114+
}
115+
}
116+
117+
private fun JsonNode.toNotificationExpressions(p: JsonParser): List<NotificationExpression> = this.map { element ->
118+
val parser = element.traverse(p.codec)
119+
parser.nextToken()
120+
parser.readValueAs(NotificationExpression::class.java)
121+
}
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.aws.toolkits.jetbrains.core.notifications
5+
6+
import com.fasterxml.jackson.annotation.JsonProperty
7+
import com.fasterxml.jackson.annotation.JsonSubTypes
8+
import com.fasterxml.jackson.annotation.JsonTypeInfo
9+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
10+
11+
data class NotificationsList(
12+
val schema: Schema,
13+
val notifications: List<NotificationData>?,
14+
)
15+
16+
data class Schema(
17+
val version: String,
18+
)
19+
20+
data class NotificationData(
21+
val id: String,
22+
val schedule: NotificationSchedule,
23+
val severity: String,
24+
val condition: NotificationDisplayCondition?,
25+
val content: NotificationContentDescriptionLocale,
26+
val actions: List<NotificationFollowupActions>? = emptyList(),
27+
)
28+
29+
data class NotificationSchedule(
30+
val type: String,
31+
)
32+
33+
enum class NotificationSeverity {
34+
INFO,
35+
WARNING,
36+
CRITICAL,
37+
}
38+
39+
data class NotificationContentDescriptionLocale(
40+
@JsonProperty("en-US")
41+
val locale: NotificationContentDescription,
42+
)
43+
44+
data class NotificationContentDescription(
45+
val title: String,
46+
val description: String,
47+
)
48+
49+
data class NotificationFollowupActions(
50+
val type: String,
51+
val content: NotificationFollowupActionsContent,
52+
)
53+
54+
data class NotificationFollowupActionsContent(
55+
@JsonProperty("en-US")
56+
val locale: NotificationActionDescription,
57+
)
58+
59+
data class NotificationActionDescription(
60+
val title: String,
61+
val url: String?,
62+
)
63+
64+
data class NotificationDisplayCondition(
65+
val compute: ComputeType?,
66+
val os: SystemType?,
67+
val ide: SystemType?,
68+
val extension: List<ExtensionType>?,
69+
val authx: List<AuthxType>?,
70+
)
71+
72+
data class ComputeType(
73+
val type: NotificationExpression?,
74+
val architecture: NotificationExpression?,
75+
)
76+
77+
data class SystemType(
78+
val type: NotificationExpression?,
79+
val version: NotificationExpression?,
80+
)
81+
82+
data class ExtensionType(
83+
val id: String?,
84+
val version: NotificationExpression?,
85+
)
86+
87+
@JsonTypeInfo(
88+
use = JsonTypeInfo.Id.NAME,
89+
include = JsonTypeInfo.As.WRAPPER_OBJECT
90+
)
91+
@JsonSubTypes(
92+
JsonSubTypes.Type(value = NotificationExpression.ComparisonCondition::class, name = "=="),
93+
JsonSubTypes.Type(value = NotificationExpression.NotEqualsCondition::class, name = "!="),
94+
JsonSubTypes.Type(value = NotificationExpression.GreaterThanCondition::class, name = ">"),
95+
JsonSubTypes.Type(value = NotificationExpression.GreaterThanOrEqualsCondition::class, name = ">="),
96+
JsonSubTypes.Type(value = NotificationExpression.LessThanCondition::class, name = "<"),
97+
JsonSubTypes.Type(value = NotificationExpression.LessThanOrEqualsCondition::class, name = "<="),
98+
JsonSubTypes.Type(value = NotificationExpression.AnyOfCondition::class, name = "anyOf"),
99+
JsonSubTypes.Type(value = NotificationExpression.NotCondition::class, name = "not"),
100+
JsonSubTypes.Type(value = NotificationExpression.OrCondition::class, name = "or"),
101+
JsonSubTypes.Type(value = NotificationExpression.AndCondition::class, name = "and"),
102+
JsonSubTypes.Type(value = NotificationExpression.NoneOfCondition::class, name = "noneOf")
103+
)
104+
sealed interface NotificationExpression {
105+
@JsonDeserialize(using = NotConditionDeserializer::class)
106+
data class NotCondition(
107+
val expectedValue: NotificationExpression,
108+
) : NotificationExpression
109+
110+
@JsonDeserialize(using = OrConditionDeserializer::class)
111+
data class OrCondition(
112+
val expectedValueList: List<NotificationExpression>,
113+
) : NotificationExpression
114+
115+
@JsonDeserialize(using = AndConditionDeserializer::class)
116+
data class AndCondition(
117+
val expectedValueList: List<NotificationExpression>,
118+
) : NotificationExpression
119+
120+
@JsonDeserialize(using = ComplexConditionDeserializer::class)
121+
data class ComplexCondition(
122+
val expectedValueList: List<NotificationExpression>,
123+
) : NotificationExpression
124+
125+
// General class for comparison operators
126+
@JsonDeserialize(using = OperationConditionDeserializer::class)
127+
data class OperationCondition(
128+
val value: String,
129+
) : NotificationExpression
130+
131+
@JsonDeserialize(using = ComplexOperationConditionDeserializer::class)
132+
data class ComplexOperationCondition(
133+
val value: List<String>,
134+
) : NotificationExpression
135+
136+
@JsonDeserialize(using = ComparisonConditionDeserializer::class)
137+
data class ComparisonCondition(
138+
val value: String,
139+
) : NotificationExpression
140+
141+
@JsonDeserialize(using = NotEqualsConditionDeserializer::class)
142+
data class NotEqualsCondition(
143+
val value: String,
144+
) : NotificationExpression
145+
146+
@JsonDeserialize(using = GreaterThanConditionDeserializer::class)
147+
data class GreaterThanCondition(
148+
val value: String,
149+
) : NotificationExpression
150+
151+
@JsonDeserialize(using = GreaterThanOrEqualsConditionDeserializer::class)
152+
data class GreaterThanOrEqualsCondition(
153+
val value: String,
154+
) : NotificationExpression
155+
156+
@JsonDeserialize(using = LessThanConditionDeserializer::class)
157+
data class LessThanCondition(
158+
val value: String,
159+
) : NotificationExpression
160+
161+
@JsonDeserialize(using = LessThanOrEqualsConditionDeserializer::class)
162+
data class LessThanOrEqualsCondition(
163+
val value: String,
164+
) : NotificationExpression
165+
166+
@JsonDeserialize(using = AnyOfConditionDeserializer::class)
167+
data class AnyOfCondition(
168+
val value: List<String>,
169+
) : NotificationExpression
170+
171+
@JsonDeserialize(using = NoneOfConditionDeserializer::class)
172+
data class NoneOfCondition(
173+
val value: List<String>,
174+
) : NotificationExpression
175+
}
176+
177+
data class AuthxType(
178+
val feature: String,
179+
val type: NotificationExpression?,
180+
val region: NotificationExpression?,
181+
val connectionState: NotificationExpression?,
182+
val ssoScopes: NotificationExpression?,
183+
)

plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/ProcessNotificationsBase.kt

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

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

6+
import com.intellij.openapi.project.Project
7+
68
class ProcessNotificationsBase {
79
init {
810
// TODO: install a listener for the polling class
@@ -17,8 +19,11 @@ class ProcessNotificationsBase {
1719
// iterates through the 2 lists and processes each notification(if it isn't dismissed)
1820
}
1921

20-
fun processNotification() {
21-
// TODO: calls the Rule engine and notifies listeners
22+
fun processNotification(project: Project, notificationData: NotificationData) {
23+
val shouldShow = RulesEngine.displayNotification(project, notificationData)
24+
if (shouldShow) {
25+
// TODO: notifies listeners
26+
}
2227
}
2328

2429
fun notifyListenerForNotification() {

0 commit comments

Comments
 (0)