Skip to content

Commit d29c6d5

Browse files
authored
Refactor and clean up buildinfo, add heartbeat (#8892)
- BuildInfo field datastoreApiVersion was unused and out of date for a long time, removed now - Refactored BuildInfo out of the controller into separate service - Add heartbeat event ------ - [x] Removed dev-only changes like prints and application.conf edits - [x] Considered [common edge cases](../blob/master/.github/common_edge_cases.md) - [x] Needs datastore update after deployment
1 parent 6f39289 commit d29c6d5

File tree

7 files changed

+74
-26
lines changed

7 files changed

+74
-26
lines changed

app/WebknossosModule.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import com.scalableminds.webknossos.datastore.storage.DataVaultService
33
import controllers.{Application, InitialDataService}
44
import files.WkTempFileService
55
import mail.MailchimpTicker
6-
import models.analytics.AnalyticsSessionService
7-
import models.annotation.{AnnotationMutexService, AnnotationStore, AnnotationDataSourceTemporaryStore}
6+
import models.analytics.{AnalyticsService, AnalyticsSessionService}
7+
import models.annotation.{AnnotationDataSourceTemporaryStore, AnnotationMutexService, AnnotationStore}
88
import models.dataset.{DatasetService, ThumbnailCachingService}
99
import models.job.{JobService, WorkerLivenessService}
1010
import models.organization.FreeCreditTransactionService
@@ -43,5 +43,6 @@ class WebknossosModule extends AbstractModule {
4343
bind(classOf[AnnotationDataSourceTemporaryStore]).asEagerSingleton()
4444
bind(classOf[CertificateValidationService]).asEagerSingleton()
4545
bind(classOf[FreeCreditTransactionService]).asEagerSingleton()
46+
bind(classOf[AnalyticsService]).asEagerSingleton()
4647
}
4748
}

app/controllers/Application.scala

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package controllers
22

3-
import com.scalableminds.util.mvc.ApiVersioning
43
import com.scalableminds.util.tools.{Fox, FoxImplicits}
54
import com.typesafe.config.ConfigRenderOptions
65
import mail.{DefaultMails, Send}
@@ -12,43 +11,29 @@ import play.api.mvc.{Action, AnyContent, Result}
1211
import play.silhouette.api.Silhouette
1312
import security.{CertificateValidationService, WkEnv}
1413
import utils.sql.{SimpleSQLDAO, SqlClient}
15-
import utils.{StoreModules, WkConf}
14+
import utils.{BuildInfoService, WkConf}
1615

1716
import javax.inject.Inject
1817
import scala.concurrent.ExecutionContext
1918

2019
class Application @Inject()(actorSystem: ActorSystem,
2120
userService: UserService,
22-
releaseInformationDAO: ReleaseInformationDAO,
21+
buildInfoService: BuildInfoService,
2322
organizationDAO: OrganizationDAO,
2423
conf: WkConf,
2524
defaultMails: DefaultMails,
26-
storeModules: StoreModules,
2725
sil: Silhouette[WkEnv],
2826
certificateValidationService: CertificateValidationService)(implicit ec: ExecutionContext)
29-
extends Controller
30-
with ApiVersioning {
27+
extends Controller {
3128

3229
private lazy val Mailer =
3330
actorSystem.actorSelection("/user/mailActor")
3431

3532
// Note: This route is used by external applications, keep stable
3633
def buildInfo: Action[AnyContent] = sil.UserAwareAction.async {
3734
for {
38-
schemaVersion <- releaseInformationDAO.getSchemaVersion.futureBox
39-
} yield {
40-
addRemoteOriginHeaders(
41-
Ok(
42-
Json.obj(
43-
"webknossos" -> Json.toJson(
44-
webknossos.BuildInfo.toMap.view.mapValues(_.toString).filterKeys(_ != "certificatePublicKey").toMap),
45-
"schemaVersion" -> schemaVersion.toOption,
46-
"httpApiVersioning" -> apiVersioningInfo,
47-
"localDataStoreEnabled" -> storeModules.localDataStoreEnabled,
48-
"localTracingStoreEnabled" -> storeModules.localTracingStoreEnabled
49-
))
50-
)
51-
}
35+
buildInfoJson <- buildInfoService.buildInfoJson
36+
} yield addRemoteOriginHeaders(Ok(buildInfoJson))
5237
}
5338

5439
// This only changes on server restart, so we can cache the full result.

app/models/analytics/AnalyticsEvent.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,3 +230,15 @@ case class FrontendAnalyticsEvent(user: User, eventType: String, eventProperties
230230
override def eventProperties(analyticsLookUpService: AnalyticsLookUpService): Fox[JsObject] =
231231
Fox.successful(eventProperties ++ Json.obj("is_frontend_event" -> true))
232232
}
233+
234+
case class WebknossosHeartbeatAnalyticsEvent(user: User, buildInfoJson: JsObject)(implicit ec: ExecutionContext)
235+
extends AnalyticsEvent {
236+
def eventType: String = "webknossos_heartbeat"
237+
def eventProperties(analyticsLookUpService: AnalyticsLookUpService): Fox[JsObject] =
238+
Fox.successful(
239+
Json.obj(
240+
"build_info" -> buildInfoJson,
241+
)
242+
)
243+
244+
}

app/models/analytics/AnalyticsService.scala

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@ import com.scalableminds.webknossos.datastore.rpc.RPC
88
import com.typesafe.scalalogging.LazyLogging
99
import models.user.{MultiUserDAO, UserDAO}
1010
import com.scalableminds.util.tools.Box.tryo
11+
import com.scalableminds.webknossos.datastore.helpers.IntervalScheduler
12+
import org.apache.pekko.actor.ActorSystem
1113
import play.api.http.Status.UNAUTHORIZED
14+
import play.api.inject.ApplicationLifecycle
1215
import play.api.libs.json._
13-
import utils.WkConf
16+
import utils.{BuildInfoService, WkConf}
1417

1518
import javax.inject.Inject
1619
import scala.concurrent.ExecutionContext
@@ -20,8 +23,13 @@ class AnalyticsService @Inject()(rpc: RPC,
2023
wkConf: WkConf,
2124
analyticsLookUpService: AnalyticsLookUpService,
2225
analyticsSessionService: AnalyticsSessionService,
23-
analyticsDAO: AnalyticsDAO)(implicit ec: ExecutionContext)
26+
analyticsDAO: AnalyticsDAO,
27+
userDAO: UserDAO,
28+
buildInfoService: BuildInfoService,
29+
val actorSystem: ActorSystem,
30+
val lifecycle: ApplicationLifecycle)(implicit val ec: ExecutionContext)
2431
extends LazyLogging
32+
with IntervalScheduler
2533
with FoxImplicits {
2634

2735
private lazy val conf = wkConf.BackendAnalytics
@@ -75,6 +83,17 @@ class AnalyticsService @Inject()(rpc: RPC,
7583
}
7684
Fox.successful(())
7785
}
86+
87+
override protected def tickerInterval: FiniteDuration = 24 hours
88+
89+
override protected def tick(): Fox[_] =
90+
for {
91+
oldestUser <- userDAO.findOldestActive
92+
buildInfoJson <- Fox.fromFuture(buildInfoService.buildInfoJson)
93+
event = WebknossosHeartbeatAnalyticsEvent(oldestUser, buildInfoJson)
94+
_ = track(event)
95+
} yield ()
96+
7897
}
7998

8099
class AnalyticsLookUpService @Inject()(userDAO: UserDAO, multiUserDAO: MultiUserDAO, wkConf: WkConf)

app/models/user/User.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,13 @@ class UserDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContext)
168168
def findIdsByMultiUserId(multiUserId: ObjectId): Fox[Seq[ObjectId]] =
169169
run(q"SELECT _id FROM $existingCollectionName WHERE _multiUser = $multiUserId".as[ObjectId])
170170

171+
def findOldestActive: Fox[User] =
172+
for {
173+
r <- run(
174+
q"SELECT $columns FROM $existingCollectionName WHERE NOT isDeactivated ORDER BY created LIMIT 1".as[UsersRow])
175+
parsed <- parseFirst(r, "oldestActive")
176+
} yield parsed
177+
171178
def buildSelectionPredicates(isEditableOpt: Option[Boolean],
172179
isTeamManagerOrAdminOpt: Option[Boolean],
173180
isAdminOpt: Option[Boolean],

app/utils/BuildInfoService.scala

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package utils
2+
3+
import com.scalableminds.util.mvc.ApiVersioning
4+
import controllers.ReleaseInformationDAO
5+
import play.api.libs.json.{JsObject, Json}
6+
7+
import javax.inject.Inject
8+
import scala.concurrent.{ExecutionContext, Future}
9+
10+
class BuildInfoService @Inject()(releaseInformationDAO: ReleaseInformationDAO, storeModules: StoreModules)
11+
extends ApiVersioning {
12+
13+
def buildInfoJson(implicit ec: ExecutionContext): Future[JsObject] =
14+
for {
15+
schemaVersion <- releaseInformationDAO.getSchemaVersion.futureBox
16+
} yield
17+
Json.obj(
18+
"webknossos" -> Json.toJson(
19+
webknossos.BuildInfo.toMap.view.mapValues(_.toString).filterKeys(_ != "certificatePublicKey").toMap),
20+
"schemaVersion" -> schemaVersion.toOption,
21+
"httpApiVersioning" -> apiVersioningInfo,
22+
"localDataStoreEnabled" -> storeModules.localDataStoreEnabled,
23+
"localTracingStoreEnabled" -> storeModules.localTracingStoreEnabled
24+
)
25+
}

project/BuildInfoSettings.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import sbt.Keys.{name, sbtVersion, scalaVersion}
2+
import sbtbuildinfo.BuildInfoOption
23
import sbtbuildinfo.BuildInfoPlugin.autoImport.*
34

45
import scala.language.postfixOps
@@ -34,7 +35,6 @@ object BuildInfoSettings {
3435
"ciBuild" -> ciBuild,
3536
"ciTag" -> ciTag,
3637
"version" -> webknossosVersion,
37-
"datastoreApiVersion" -> "2.0",
3838
"certificatePublicKey" -> certificatePublicKey
3939
),
4040
buildInfoPackage := "webknossos",
@@ -51,7 +51,6 @@ object BuildInfoSettings {
5151
"ciBuild" -> ciBuild,
5252
"ciTag" -> ciTag,
5353
"version" -> webknossosVersion,
54-
"datastoreApiVersion" -> "2.0"
5554
),
5655
buildInfoPackage := "webknossosDatastore",
5756
buildInfoOptions := Seq(BuildInfoOption.ToJson)

0 commit comments

Comments
 (0)