Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@ class NotConditionDeserializer : JsonDeserializer<NotificationExpression.NotCond
}
}

// Create a custom deserializer if needed
class NotificationTypeDeserializer : JsonDeserializer<NotificationScheduleType>() {
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): NotificationScheduleType =
NotificationScheduleType.fromString(p.valueAsString)
}

Comment on lines +117 to +122
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not convinced we actually need a custom deserializer but ok

private fun JsonNode.toNotificationExpressions(p: JsonParser): List<NotificationExpression> = this.map { element ->
val parser = element.traverse(p.codec)
parser.nextToken()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,32 @@ data class NotificationData(
)

data class NotificationSchedule(
val type: String,
)
@JsonDeserialize(using = NotificationTypeDeserializer::class)
val type: NotificationScheduleType,
) {
constructor(type: String) : this(NotificationScheduleType.fromString(type))
}

enum class NotificationSeverity {
INFO,
WARNING,
CRITICAL,
}

enum class NotificationScheduleType {
STARTUP,
EMERGENCY,
;

companion object {
fun fromString(value: String): NotificationScheduleType =
when (value.lowercase()) {
"startup" -> STARTUP
else -> EMERGENCY
}
}
}

data class NotificationContentDescriptionLocale(
@JsonProperty("en-US")
val locale: NotificationContentDescription,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ class NotificationPanel : BorderLayoutPanel() {
init {
isOpaque = false
addToCenter(wrapper)
ProcessNotificationsBase.showBannerNotification.forEach {
updateNotificationPanel(it.value)
BannerNotificationService.getInstance().getNotifications().forEach { (_, content) ->
updateNotificationPanel(content)
}
}

private fun removeNotificationPanel(notificationId: String) = runInEdt {
ProcessNotificationsBase.showBannerNotification.remove(notificationId) // TODO: add id to dismissed notification list
BannerNotificationService.getInstance().removeNotification(notificationId)
NotificationDismissalState.getInstance().dismissNotification(notificationId)
wrapper.removeAll()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@ package software.aws.toolkits.jetbrains.core.notifications
import com.fasterxml.jackson.module.kotlin.readValue
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.PersistentStateComponent
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.State
import com.intellij.openapi.components.Storage
import com.intellij.util.Alarm
import com.intellij.util.AlarmFactory
import com.intellij.util.io.HttpRequests
Expand Down Expand Up @@ -63,32 +60,6 @@ object NotificationEndpoint {
private const val DEFAULT_ENDPOINT = "" // TODO: Replace with actual endpoint
}

@State(name = "notificationEtag", storages = [Storage("aws.xml")])
class NotificationEtagState : PersistentStateComponent<NotificationEtagConfiguration> {
private var state = NotificationEtagConfiguration()

override fun getState(): NotificationEtagConfiguration = state

override fun loadState(state: NotificationEtagConfiguration) {
this.state = state
}

var etag: String?
get() = state.etag
set(value) {
state.etag = value
}

companion object {
fun getInstance(): NotificationEtagState =
ApplicationManager.getApplication().getService(NotificationEtagState::class.java)
}
}

data class NotificationEtagConfiguration(
var etag: String? = null,
)

@Service(Service.Level.APP)
internal final class NotificationPollingService : Disposable {
private val isFirstPoll = AtomicBoolean(true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ internal class NotificationServiceInitializer : ProjectActivity {
override suspend fun execute(project: Project) {
if (initialized.compareAndSet(false, true)) {
val service = NotificationPollingService.getInstance()
ProcessNotificationsBase()
service.startPolling()
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// 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.openapi.components.PersistentStateComponent
import com.intellij.openapi.components.RoamingType
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.State
import com.intellij.openapi.components.Storage
import com.intellij.openapi.components.service

@Service
@State(name = "notificationDismissals", storages = [Storage("aws.xml", roamingType = RoamingType.DISABLED)])
class NotificationDismissalState : PersistentStateComponent<NotificationDismissalConfiguration> {
private val state = NotificationDismissalConfiguration()

override fun getState(): NotificationDismissalConfiguration = state

override fun loadState(state: NotificationDismissalConfiguration) {
this.state.dismissedNotificationIds.clear()
this.state.dismissedNotificationIds.addAll(state.dismissedNotificationIds)
}

fun isDismissed(notificationId: String): Boolean =
state.dismissedNotificationIds.contains(notificationId)

fun dismissNotification(notificationId: String) {
state.dismissedNotificationIds.add(notificationId)
}

companion object {
fun getInstance(): NotificationDismissalState =
service()
}
}

data class NotificationDismissalConfiguration(
var dismissedNotificationIds: MutableSet<String> = mutableSetOf(),
)

@Service
@State(name = "notificationEtag", storages = [Storage("aws.xml", roamingType = RoamingType.DISABLED)])
class NotificationEtagState : PersistentStateComponent<NotificationEtagConfiguration> {
private val state = NotificationEtagConfiguration()

override fun getState(): NotificationEtagConfiguration = state

override fun loadState(state: NotificationEtagConfiguration) {
this.state.etag = state.etag
}

var etag: String?
get() = state.etag
set(value) {
state.etag = value
}

companion object {
fun getInstance(): NotificationEtagState =
service()
}
}

data class NotificationEtagConfiguration(
var etag: String? = null,
)

@Service
class BannerNotificationService {
private val notifications = mutableMapOf<String, BannerContent>()

fun addNotification(id: String, content: BannerContent) {
notifications[id] = content
}

fun getNotifications(): Map<String, BannerContent> = notifications

fun removeNotification(id: String) {
notifications.remove(id)
}

companion object {
fun getInstance(): BannerNotificationService =
service()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package software.aws.toolkits.jetbrains.core.notifications

import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import com.intellij.notification.NotificationType
Expand All @@ -15,16 +16,20 @@ import com.intellij.openapi.project.Project
import software.aws.toolkits.core.utils.inputStream
import software.aws.toolkits.jetbrains.utils.notifyStickyWithData
import java.nio.file.Paths
import java.util.concurrent.atomic.AtomicBoolean

object NotificationMapperUtil {
val mapper = jacksonObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
val mapper: ObjectMapper = jacksonObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
}
private var isStartup: AtomicBoolean = AtomicBoolean(true)

@Service(Service.Level.PROJECT)
class ProcessNotificationsBase {
class ProcessNotificationsBase(
private val project: Project,
) {
private val notifListener = mutableListOf<NotifListener>()
init {
NotificationPollingService.getInstance().addObserver { ->
NotificationPollingService.getInstance().addObserver {
retrieveStartupAndEmergencyNotifications()
}
}
Expand All @@ -39,8 +44,23 @@ class ProcessNotificationsBase {
}

fun retrieveStartupAndEmergencyNotifications() {
// TODO: separates notifications into startup and emergency
// iterates through the 2 lists and processes each notification(if it isn't dismissed)
val isStartupPoll = isStartup.compareAndSet(true, false)
val notifications = getNotificationsFromFile()
notifications?.let { notificationsList ->
val activeNotifications = notificationsList.notifications
?.filter { notification ->
// Keep notification if:
// - it's not a startup notification, OR
// - it is a startup notification AND this is the first poll
notification.schedule.type != NotificationScheduleType.STARTUP || isStartupPoll
}
?.filter { notification ->
!NotificationDismissalState.getInstance().isDismissed(notification.id)
}
.orEmpty()

activeNotifications.forEach { processNotification(project, it) }
}
}

fun processNotification(project: Project, notificationData: NotificationData) {
Expand All @@ -63,7 +83,7 @@ class ProcessNotificationsBase {
)
if (severity == "Critical") {
val bannerContent = BannerContent(notificationContent.title, notificationContent.description, followupActions, notificationData.id)
showBannerNotification[notificationData.id] = bannerContent
BannerNotificationService.getInstance().addNotification(notificationData.id, bannerContent)
notifyListenerForNotification(bannerContent)
}
}
Expand All @@ -87,7 +107,6 @@ class ProcessNotificationsBase {
companion object {
fun getInstance(project: Project): ProcessNotificationsBase = project.service()

val showBannerNotification = mutableMapOf<String, BannerContent>()
private const val NOTIFICATIONS_PATH = "aws-static-resources/notifications.json"
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import com.intellij.ui.ScrollPaneFactory
import org.slf4j.LoggerFactory
import software.aws.toolkits.core.utils.warn
import software.aws.toolkits.jetbrains.core.help.HelpIds
import software.aws.toolkits.jetbrains.core.notifications.ProcessNotificationsBase
import software.aws.toolkits.jetbrains.core.notifications.BannerNotificationService
import software.aws.toolkits.jetbrains.core.notifications.NotificationDismissalState
import software.aws.toolkits.resources.AwsCoreBundle
import javax.swing.JLabel
import javax.swing.JTextArea
Expand Down Expand Up @@ -66,8 +67,8 @@ fun notifyStickyWithData(
createNotificationExpiringAction(
object : AnAction("Dismiss") {
override fun actionPerformed(e: AnActionEvent) {
ProcessNotificationsBase.showBannerNotification.remove(id)
// TODO: add id to dismissed notification list
BannerNotificationService.getInstance().removeNotification(id)
NotificationDismissalState.getInstance().dismissNotification(id)
}
}
)
Expand Down
Loading
Loading