11package org.digma.intellij.plugin.digmathon
22
3+ import com.fasterxml.jackson.core.type.TypeReference
34import com.intellij.collaboration.async.disposingScope
45import com.intellij.openapi.Disposable
56import com.intellij.openapi.application.ApplicationManager
@@ -12,17 +13,18 @@ import kotlinx.coroutines.delay
1213import kotlinx.coroutines.isActive
1314import kotlinx.coroutines.launch
1415import org.digma.intellij.plugin.common.allowSlowOperation
16+ import org.digma.intellij.plugin.common.createObjectMapperWithJavaTimeModule
1517import org.digma.intellij.plugin.common.findActiveProject
1618import org.digma.intellij.plugin.errorreporting.ErrorReporter
1719import org.digma.intellij.plugin.log.Log
1820import org.digma.intellij.plugin.notifications.NotificationUtil
1921import org.digma.intellij.plugin.persistence.PersistenceService
2022import org.digma.intellij.plugin.posthog.ActivityMonitor
23+ import java.time.Instant
2124import java.time.LocalDate
2225import java.time.LocalDateTime
2326import java.time.ZoneId
2427import java.time.ZonedDateTime
25- import java.util.concurrent.atomic.AtomicBoolean
2628import java.util.concurrent.atomic.AtomicReference
2729import java.util.function.Supplier
2830import kotlin.time.Duration.Companion.minutes
@@ -31,25 +33,24 @@ import kotlin.time.Duration.Companion.minutes
3133@Service(Service .Level .APP )
3234class DigmathonService : Disposable {
3335
34-
3536 private val logger = Logger .getInstance(this ::class .java)
3637
3738 private val digmathonInfo = AtomicReference (
3839 DigmathonInfo (
39- LocalDate .of(2024 , 4 , 5 ).atStartOfDay().atZone(ZoneId .systemDefault()),
40- LocalDate .of(2024 , 4 , 17 ).atStartOfDay().atZone(ZoneId .systemDefault())
40+ LocalDate .of(2024 , 5 , 1 ).atStartOfDay().atZone(ZoneId .systemDefault()),
41+ LocalDate .of(2024 , 5 , 14 ).atStartOfDay().atZone(ZoneId .systemDefault())
4142 )
4243 )
4344
44- private val isDigmathonActive = AtomicBoolean (digmathonInfo.get().isActive())
45-
46- val viewedInsights = PersistenceService .getInstance().getDigmathonInsightsViewed()
47- ?.split(" ," )?.toMutableSet() ? : mutableSetOf ()
45+ val viewedInsights: MutableMap <String , Instant > = readInsightsViewedFromPersistence()
4846
4947 var isUserFinishedDigmathon = PersistenceService .getInstance().isFinishDigmathonGameForUser()
5048
5149
5250 companion object {
51+
52+ private val objectMapper = createObjectMapperWithJavaTimeModule()
53+
5354 @JvmStatic
5455 fun getInstance (): DigmathonService {
5556 return service<DigmathonService >()
@@ -64,27 +65,7 @@ class DigmathonService : Disposable {
6465
6566 init {
6667
67- // for development,simulate a 5 minutes event that starts 2 minutes after IDE start and lasts for 5 minutes
68- // val simulateStart = System.getProperty("org.digma.digmathon.simulate.startAfterMinutes")
69- // val simulatePeriod = System.getProperty("org.digma.digmathon.simulate.periodMinutes")
70- //
71- // if (simulateStart != null) {
72- // val startAfter = simulateStart.toLong()
73- // val endAfter = (simulatePeriod.toLongOrNull() ?: 10) + startAfter
74- //
75- // digmathonInfo.set(
76- // DigmathonInfo(
77- // LocalDateTime.now().plusMinutes(startAfter).atZone(ZoneId.systemDefault()),
78- // LocalDateTime.now().plusMinutes(endAfter).atZone(ZoneId.systemDefault())
79- // )
80- // )
81- // DigmathonProductKey().clear()
82- // isDigmathonActive.set(digmathonInfo.get().isActive())
83- // isUserFinishedDigmathon = false
84- // }
85-
86-
87- if (isDigmathonActive.get() && digmathonInfo.get().isEnded()) {
68+ if (isDigmathonStartedForUser() && digmathonInfo.get().isEnded()) {
8869 end()
8970 } else {
9071
@@ -97,11 +78,11 @@ class DigmathonService : Disposable {
9778 while (isActive && digmathonInfo.get().isActive()) {
9879 try {
9980
100- if (! isDigmathonActive.get () && digmathonInfo.get().isActive()) {
81+ if (! isDigmathonStartedForUser () && digmathonInfo.get().isActive()) {
10182 start()
10283 }
10384
104- if (isDigmathonActive.get () && digmathonInfo.get().isEnded()) {
85+ if (isDigmathonStartedForUser () && digmathonInfo.get().isEnded()) {
10586 end()
10687 cancel(" digmathon ended" )
10788 }
@@ -116,23 +97,46 @@ class DigmathonService : Disposable {
11697 }
11798 }
11899
119- if (digmathonInfo.get().isEnded()) {
100+ // the job may be canceled by the system, do a last check
101+ if (isDigmathonStartedForUser() && digmathonInfo.get().isEnded()) {
120102 end()
121103 }
122104 }
123105 }
124106 }
125107
126108
109+ // this is needed so that we can call start and end only once
110+ private fun isDigmathonStartedForUser (): Boolean {
111+ return PersistenceService .getInstance().isDigmathonStartedForUser()
112+ }
113+
114+ private fun markStartedDigmathonForUser () {
115+ PersistenceService .getInstance().setDigmathonStartedForUser(true )
116+ }
117+
118+ private fun markEndedDigmathonForUser () {
119+ PersistenceService .getInstance().setDigmathonStartedForUser(false )
120+ }
121+
122+
127123 private fun start () {
128- isDigmathonActive.set(true )
124+ // reset persistence properties every time a new digmathon starts
125+ PersistenceService .getInstance().setFinishDigmathonGameForUser(false )
126+ PersistenceService .getInstance().setDigmathonInsightsViewed(null )
127+ PersistenceService .getInstance().setDigmathonInsightsViewedLastUpdated(null )
128+ markStartedDigmathonForUser()
129129 fireStateChangedEvent()
130130 reportEvent(" start" )
131131 }
132132
133133
134134 private fun end () {
135- isDigmathonActive.set(false )
135+ // reset persistence properties for next time
136+ PersistenceService .getInstance().setFinishDigmathonGameForUser(false )
137+ PersistenceService .getInstance().setDigmathonInsightsViewed(null )
138+ PersistenceService .getInstance().setDigmathonInsightsViewedLastUpdated(null )
139+ markEndedDigmathonForUser()
136140 DigmathonProductKey ().clear()
137141 fireStateChangedEvent()
138142 fireProductKeyStateChanged()
@@ -201,43 +205,62 @@ class DigmathonService : Disposable {
201205 }
202206
203207
204- data class DigmathonInfo (val startTime : ZonedDateTime , val endTime : ZonedDateTime ) {
205-
206- fun isActive (): Boolean {
207- val now = LocalDateTime .now().atZone(ZoneId .systemDefault())
208-
209- return (now.equals(startTime) || now.isAfter(startTime)) &&
210- now.isBefore(endTime)
211- }
212-
213- fun isEnded (): Boolean {
214- return ! isActive()
215- }
216- }
217-
218-
219208 private fun reportEvent (eventType : String , details : Map <String , String > = mapOf()) {
220209 findActiveProject()?.let {
221210 ActivityMonitor .getInstance(it).reportDigmathonEvent(eventType, details)
222211 }
223212 }
224213
225214 fun addInsightsViewed (insightsTypesViewed : List <String >) {
226- if (digmathonInfo.get().isActive()) {
227- this .viewedInsights.addAll(insightsTypesViewed)
215+ if (digmathonInfo.get().isActive() && ! allInsightsExists(insightsTypesViewed)) {
216+ // add only new insight types
217+ insightsTypesViewed.forEach {
218+ this .viewedInsights.computeIfAbsent(it) { Instant .now() }
219+ }
228220 flushInsightsViewedToPersistence()
229221 }
230222 }
231223
224+ private fun allInsightsExists (insightsTypesViewed : List <String >): Boolean {
225+ return this .viewedInsights.keys.containsAll(insightsTypesViewed)
226+ }
227+
228+
232229 private fun flushInsightsViewedToPersistence () {
233- PersistenceService .getInstance().setDigmathonInsightsViewed(viewedInsights.joinToString(" ," ))
230+ try {
231+ val json = objectMapper.writeValueAsString(viewedInsights)
232+ PersistenceService .getInstance().setDigmathonInsightsViewed(json)
233+ } catch (e: Throwable ) {
234+ ErrorReporter .getInstance().reportError(" DigmathonService.flushInsightsViewedToPersistence" , e)
235+ }
236+ }
237+
238+ private fun readInsightsViewedFromPersistence (): MutableMap <String , Instant > {
239+ val json = PersistenceService .getInstance().getDigmathonInsightsViewed() ? : return mutableMapOf ()
240+ return try {
241+ val ref = object : TypeReference <Map <String , Instant >>() {}
242+ val viewedInsights = objectMapper.readValue(json, ref)
243+ viewedInsights.toMutableMap()
244+ } catch (e: Throwable ) {
245+ ErrorReporter .getInstance().reportError(" DigmathonService.readInsightsViewedFromPersistence" , e)
246+ mutableMapOf ()
247+ }
248+ }
249+
250+
251+ fun getDigmathonInsightsViewedLastUpdated (): Instant ? {
252+ return PersistenceService .getInstance().getDigmathonInsightsViewedLastUpdated()
253+ }
254+
255+ fun updateDigmathonInsightsViewedLastUpdated () {
256+ PersistenceService .getInstance().setDigmathonInsightsViewedLastUpdated(Instant .now())
234257 }
235258
236259 fun setFinishDigmathonGameForUser () {
237- PersistenceService .getInstance().setFinishDigmathonGameForUser()
260+ PersistenceService .getInstance().setFinishDigmathonGameForUser(true )
238261 isUserFinishedDigmathon = true
239262 fireUserFinishedDigmathon()
240- reportEvent(" user finished game " )
263+ reportEvent(" user finished digmathon " )
241264 }
242265
243266
@@ -246,4 +269,20 @@ class DigmathonService : Disposable {
246269 .userFinishedDigmathon()
247270 }
248271
272+
273+ data class DigmathonInfo (val startTime : ZonedDateTime , val endTime : ZonedDateTime ) {
274+
275+ fun isActive (): Boolean {
276+ val now = LocalDateTime .now().atZone(ZoneId .systemDefault())
277+
278+ return (now.equals(startTime) || now.isAfter(startTime)) &&
279+ now.isBefore(endTime)
280+ }
281+
282+ fun isEnded (): Boolean {
283+ return ! isActive()
284+ }
285+ }
286+
287+
249288}
0 commit comments