Skip to content

Commit 4bfdd4b

Browse files
Merge pull request #2048 from digma-ai/feature/digmathon
Digmathon
2 parents c4e8cf4 + 03cec42 commit 4bfdd4b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+2131
-76
lines changed

ide-common/src/main/java/org/digma/intellij/plugin/analytics/Environment.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ private void refreshEnvironments() {
155155
List<String> envsFromBackend = analyticsService.getRawEnvironments();
156156

157157
if (envsFromBackend.isEmpty()) {
158-
Log.log(LOGGER::warn, "Error loading environments or no environments added yet: {}", envsFromBackend);
158+
Log.log(LOGGER::trace, "Error loading environments or no environments added yet: {}", envsFromBackend);
159159
envsFromBackend = new ArrayList<>();
160160
} else {
161161
Log.log(LOGGER::trace, "Got environments {}", envsFromBackend);
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package org.digma.intellij.plugin.digmathon
2+
3+
import com.intellij.util.messages.Topic
4+
5+
fun interface DigmathonActivationEvent {
6+
7+
companion object {
8+
@JvmStatic
9+
@Topic.AppLevel
10+
val DIGMATHON_ACTIVATION_TOPIC: Topic<DigmathonActivationEvent> = Topic.create(
11+
"DIGMATHON ACTIVATION",
12+
DigmathonActivationEvent::class.java
13+
)
14+
}
15+
16+
17+
fun digmathonActivationStateChanged(isActive: Boolean)
18+
19+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package org.digma.intellij.plugin.digmathon
2+
3+
import com.google.common.hash.Hashing
4+
import com.intellij.credentialStore.CredentialAttributes
5+
import com.intellij.credentialStore.generateServiceName
6+
import com.intellij.ide.passwordSafe.PasswordSafe
7+
import org.digma.intellij.plugin.common.UserId
8+
import java.nio.charset.StandardCharsets
9+
10+
11+
private const val MY_SERVICE = "org.digma.digmathon.productKey"
12+
private const val MY_KEY = "product-key-2024.4"
13+
14+
class DigmathonProductKey {
15+
16+
private val myHash = "b9fe040958b98f68533511125bc104435bfefd4293b328060992767d51333321"
17+
18+
@Throws(InvalidProductKeyException::class)
19+
fun validateAndSave(productKey: String) {
20+
validate(productKey)
21+
val credentialAttributes = createCredentialAttributes()
22+
PasswordSafe.instance.setPassword(credentialAttributes, productKey)
23+
}
24+
25+
@Throws(InvalidProductKeyException::class)
26+
fun validateAndGet(): String? {
27+
val credentialAttributes = createCredentialAttributes()
28+
val productKey = PasswordSafe.instance.getPassword(credentialAttributes)
29+
return productKey?.let {
30+
validate(it)
31+
it
32+
}
33+
}
34+
35+
36+
@Throws(InvalidProductKeyException::class)
37+
private fun validate(productKey: String) {
38+
if (productKey.isBlank()) {
39+
throw InvalidProductKeyException(productKey, "product key is empty")
40+
}
41+
//the sha256 was produces for upper case so uppercase before validation
42+
val hash = Hashing.sha256().hashString(productKey.uppercase(), StandardCharsets.UTF_8).toString()
43+
if (hash != myHash) {
44+
throw InvalidProductKeyException(productKey, "product key invalid,hash don't match")
45+
}
46+
}
47+
48+
49+
private fun createCredentialAttributes(): CredentialAttributes {
50+
return CredentialAttributes(
51+
generateServiceName(MY_SERVICE, MY_KEY),
52+
UserId.userId,
53+
this::class.java
54+
)
55+
}
56+
57+
58+
fun clear() {
59+
val credentialAttributes = createCredentialAttributes()
60+
PasswordSafe.instance.setPassword(credentialAttributes, null)
61+
}
62+
63+
}
64+
65+
class InvalidProductKeyException(val productKey: String, message: String) : RuntimeException(message)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package org.digma.intellij.plugin.digmathon
2+
3+
import com.intellij.util.messages.Topic
4+
5+
fun interface DigmathonProductKeyStateChangedEvent {
6+
7+
companion object {
8+
@JvmStatic
9+
@Topic.AppLevel
10+
val PRODUCT_KEY_STATE_CHANGED_TOPIC: Topic<DigmathonProductKeyStateChangedEvent> = Topic.create(
11+
"PRODUCT KEY STATE CHANGED",
12+
DigmathonProductKeyStateChangedEvent::class.java
13+
)
14+
}
15+
16+
17+
fun productKey(productKey: String?)
18+
19+
}
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
package org.digma.intellij.plugin.digmathon
2+
3+
import com.intellij.collaboration.async.disposingScope
4+
import com.intellij.openapi.Disposable
5+
import com.intellij.openapi.application.ApplicationManager
6+
import com.intellij.openapi.components.Service
7+
import com.intellij.openapi.components.service
8+
import com.intellij.openapi.diagnostic.Logger
9+
import kotlinx.coroutines.CancellationException
10+
import kotlinx.coroutines.cancel
11+
import kotlinx.coroutines.delay
12+
import kotlinx.coroutines.isActive
13+
import kotlinx.coroutines.launch
14+
import org.digma.intellij.plugin.common.allowSlowOperation
15+
import org.digma.intellij.plugin.common.findActiveProject
16+
import org.digma.intellij.plugin.errorreporting.ErrorReporter
17+
import org.digma.intellij.plugin.log.Log
18+
import org.digma.intellij.plugin.notifications.NotificationUtil
19+
import org.digma.intellij.plugin.persistence.PersistenceService
20+
import org.digma.intellij.plugin.posthog.ActivityMonitor
21+
import java.time.LocalDate
22+
import java.time.LocalDateTime
23+
import java.time.ZoneId
24+
import java.time.ZonedDateTime
25+
import java.util.concurrent.atomic.AtomicBoolean
26+
import java.util.concurrent.atomic.AtomicReference
27+
import java.util.function.Supplier
28+
import kotlin.time.Duration.Companion.minutes
29+
30+
31+
@Service(Service.Level.APP)
32+
class DigmathonService : Disposable {
33+
34+
35+
private val logger = Logger.getInstance(this::class.java)
36+
37+
private val digmathonInfo = AtomicReference(
38+
DigmathonInfo(
39+
LocalDate.of(2024, 4, 5).atStartOfDay().atZone(ZoneId.systemDefault()),
40+
LocalDate.of(2024, 4, 13).atStartOfDay().atZone(ZoneId.systemDefault())
41+
)
42+
)
43+
44+
private val isDigmathonActive = AtomicBoolean(digmathonInfo.get().isActive())
45+
46+
val viewedInsights = PersistenceService.getInstance().getDigmathonInsightsViewed()
47+
?.split(",")?.toMutableSet() ?: mutableSetOf()
48+
49+
var isUserFinishedDigmathon = PersistenceService.getInstance().isFinishDigmathonGameForUser()
50+
51+
52+
companion object {
53+
@JvmStatic
54+
fun getInstance(): DigmathonService {
55+
return service<DigmathonService>()
56+
}
57+
}
58+
59+
60+
override fun dispose() {
61+
//do nothing, used as parent disposable
62+
}
63+
64+
65+
init {
66+
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()) {
88+
end()
89+
} else {
90+
91+
@Suppress("UnstableApiUsage")
92+
disposingScope().launch {
93+
94+
//let the project load and hopefully all jcef apps
95+
delay(1.minutes.inWholeMilliseconds)
96+
97+
while (isActive) {
98+
try {
99+
100+
if (!isDigmathonActive.get() && digmathonInfo.get().isActive()) {
101+
start()
102+
}
103+
104+
if (isDigmathonActive.get() && digmathonInfo.get().isEnded()) {
105+
end()
106+
cancel("digmathon ended")
107+
}
108+
109+
delay(1.minutes.inWholeMilliseconds)
110+
111+
} catch (ce: CancellationException) {
112+
Log.log(logger::info, "digmathon timer canceled {}", ce)
113+
} catch (e: Throwable) {
114+
Log.warnWithException(logger, e, "error in digmathon timer {}", e)
115+
ErrorReporter.getInstance().reportError("DigmathonService.timer", e)
116+
}
117+
}
118+
}
119+
}
120+
}
121+
122+
123+
private fun start() {
124+
isDigmathonActive.set(true)
125+
fireStateChangedEvent()
126+
reportEvent("start")
127+
}
128+
129+
130+
private fun end() {
131+
isDigmathonActive.set(false)
132+
DigmathonProductKey().clear()
133+
fireStateChangedEvent()
134+
fireProductKeyStateChanged()
135+
reportEvent("end")
136+
}
137+
138+
139+
//user is active only with these conditions
140+
fun isUserActive(): Boolean {
141+
return digmathonInfo.get().isActive() && getProductKey() != null
142+
}
143+
144+
private fun fireStateChangedEvent() {
145+
ApplicationManager.getApplication().messageBus.syncPublisher(DigmathonActivationEvent.DIGMATHON_ACTIVATION_TOPIC)
146+
.digmathonActivationStateChanged(digmathonInfo.get().isActive())
147+
}
148+
149+
150+
fun getDigmathonState(): DigmathonInfo {
151+
return digmathonInfo.get()
152+
}
153+
154+
fun setProductKey(productKey: String) {
155+
try {
156+
allowSlowOperation {
157+
DigmathonProductKey().validateAndSave(productKey)
158+
reportEvent("set product key")
159+
}
160+
} catch (e: InvalidProductKeyException) {
161+
reportEvent("product key invalid", mapOf("productKey" to e.productKey))
162+
ErrorReporter.getInstance().reportError("${this::class.java.simpleName}.setProductKey", e)
163+
findActiveProject()?.let {
164+
NotificationUtil.showBalloonWarning(it, "invalid Digmathon product key")
165+
}
166+
167+
} catch (e: Throwable) {
168+
ErrorReporter.getInstance().reportError("${this::class.java.simpleName}.setProductKey", e)
169+
} finally {
170+
fireProductKeyStateChanged()
171+
}
172+
}
173+
174+
private fun fireProductKeyStateChanged() {
175+
ApplicationManager.getApplication().messageBus.syncPublisher(DigmathonProductKeyStateChangedEvent.PRODUCT_KEY_STATE_CHANGED_TOPIC)
176+
.productKey(getProductKey())
177+
}
178+
179+
180+
fun getProductKey(): String? {
181+
return try {
182+
allowSlowOperation(Supplier {
183+
DigmathonProductKey().validateAndGet()
184+
})
185+
} catch (e: InvalidProductKeyException) {
186+
reportEvent("product key invalid", mapOf("productKey" to e.productKey))
187+
ErrorReporter.getInstance().reportError("${this::class.java.simpleName}.setProductKey", e)
188+
null
189+
} catch (e: Throwable) {
190+
ErrorReporter.getInstance().reportError("${this::class.java.simpleName}.setProductKey", e)
191+
null
192+
}
193+
}
194+
195+
196+
data class DigmathonInfo(val startTime: ZonedDateTime, val endTime: ZonedDateTime) {
197+
198+
fun isActive(): Boolean {
199+
val now = LocalDateTime.now().atZone(ZoneId.systemDefault())
200+
201+
return (now.equals(startTime) || now.isAfter(startTime)) &&
202+
now.isBefore(endTime)
203+
}
204+
205+
fun isEnded(): Boolean {
206+
return !isActive()
207+
}
208+
}
209+
210+
211+
private fun reportEvent(eventType: String, details: Map<String, String> = mapOf()) {
212+
findActiveProject()?.let {
213+
ActivityMonitor.getInstance(it).reportDigmathonEvent(eventType, details)
214+
}
215+
}
216+
217+
fun addInsightsViewed(insightsTypesViewed: List<String>) {
218+
if (digmathonInfo.get().isActive()) {
219+
this.viewedInsights.addAll(insightsTypesViewed)
220+
flushInsightsViewedToPersistence()
221+
}
222+
}
223+
224+
private fun flushInsightsViewedToPersistence() {
225+
PersistenceService.getInstance().setDigmathonInsightsViewed(viewedInsights.joinToString(","))
226+
}
227+
228+
fun setFinishDigmathonGameForUser() {
229+
PersistenceService.getInstance().setFinishDigmathonGameForUser()
230+
isUserFinishedDigmathon = true
231+
fireUserFinishedDigmathon()
232+
reportEvent("user finished game")
233+
}
234+
235+
236+
private fun fireUserFinishedDigmathon() {
237+
ApplicationManager.getApplication().messageBus.syncPublisher(UserFinishedDigmathonEvent.USER_FINISHED_DIGMATHON_TOPIC)
238+
.userFinishedDigmathon()
239+
}
240+
241+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.digma.intellij.plugin.digmathon
2+
3+
import com.intellij.openapi.project.Project
4+
import com.intellij.openapi.startup.StartupActivity
5+
6+
class DigmathonStartup : StartupActivity.DumbAware {
7+
8+
override fun runActivity(project: Project) {
9+
DigmathonService.getInstance()
10+
}
11+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package org.digma.intellij.plugin.digmathon
2+
3+
import com.intellij.util.messages.Topic
4+
5+
fun interface UserFinishedDigmathonEvent {
6+
7+
companion object {
8+
@JvmStatic
9+
@Topic.AppLevel
10+
val USER_FINISHED_DIGMATHON_TOPIC: Topic<UserFinishedDigmathonEvent> = Topic.create(
11+
"USER FINISHED DIGMATHON",
12+
UserFinishedDigmathonEvent::class.java
13+
)
14+
}
15+
16+
17+
fun userFinishedDigmathon()
18+
19+
}

0 commit comments

Comments
 (0)