Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 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,13 @@ class NotConditionDeserializer : JsonDeserializer<NotificationExpression.NotCond
}
}

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

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,33 @@ 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 {
return 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 @@ -21,7 +21,8 @@ class NotificationPanel : BorderLayoutPanel() {
}

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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// 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.application.ApplicationManager
import com.intellij.openapi.components.PersistentStateComponent
import com.intellij.openapi.components.State
import com.intellij.openapi.components.Storage

@State(name = "notificationDismissals", storages = [Storage("aws.xml")])
Copy link
Contributor

Choose a reason for hiding this comment

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

consider using BaseState

class NotificationDismissalState : PersistentStateComponent<NotificationDismissalConfiguration> {

Check warning on line 12 in plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/NotificationPersistantState.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Extension class should be final and non-public

Service implementation should not be public. If a service is supposed to be used outside its module, extract an interface from it and specify it as serviceInterface in plugin.xml.
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 =
ApplicationManager.getApplication().getService(NotificationDismissalState::class.java)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
fun getInstance(): NotificationDismissalState =
ApplicationManager.getApplication().getService(NotificationDismissalState::class.java)
fun getInstance(): NotificationDismissalState =
ApplicationManager.getApplication().getService<NotificationDismissalState>()

}
}

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

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

Check warning on line 40 in plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/notifications/NotificationPersistantState.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Extension class should be final and non-public

Service implementation should not be public. If a service is supposed to be used outside its module, extract an interface from it and specify it as serviceInterface in plugin.xml.
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 =
ApplicationManager.getApplication().getService(NotificationEtagState::class.java)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
fun getInstance(): NotificationEtagState =
ApplicationManager.getApplication().getService(NotificationEtagState::class.java)
fun getInstance(): NotificationEtagState =
ApplicationManager.getApplication().getService<NotificationEtagState>()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm getting a syntax error when I do this. I think this is for newer versions of Intellij SDK. I don't see any other usages of the bottom code in our repository, but I do see the ::class.java version.

Copy link
Contributor

Choose a reason for hiding this comment

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

can you try using getInstance(): NotificationETagState = service()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

that works!

}
}

data class NotificationEtagConfiguration(
var etag: String? = null,
)
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
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)
}
?: emptyList()

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

fun processNotification(project: Project, notificationData: NotificationData) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ 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.NotificationDismissalState
import software.aws.toolkits.jetbrains.core.notifications.ProcessNotificationsBase
import software.aws.toolkits.resources.AwsCoreBundle
import javax.swing.JLabel
Expand Down Expand Up @@ -67,7 +68,7 @@ fun notifyStickyWithData(
object : AnAction("Dismiss") {
override fun actionPerformed(e: AnActionEvent) {
ProcessNotificationsBase.showBannerNotification.remove(id)
Copy link
Contributor

Choose a reason for hiding this comment

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

state should not be stored in companion objects

// TODO: add id to dismissed notification list
NotificationDismissalState.getInstance().dismissNotification(id)
}
}
)
Expand Down
Loading
Loading