Skip to content

Instantly share code, notes, and snippets.

@Sarthak2601
Created July 15, 2020 15:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Sarthak2601/f7032eb5b17dc490a756f5eee86ef719 to your computer and use it in GitHub Desktop.
Save Sarthak2601/f7032eb5b17dc490a756f5eee86ef719 to your computer and use it in GitHub Desktop.
package org.oppia.domain.oppialogger.analytics
import android.content.Context
import androidx.lifecycle.LiveData
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.oppia.app.model.EventLog
import org.oppia.app.model.EventLog.EventAction
import org.oppia.app.model.EventLog.Priority
import org.oppia.app.model.OppiaEventLogs
import org.oppia.data.persistence.PersistentCacheStore
import org.oppia.domain.oppialogger.EventLogStorageCacheSize
import org.oppia.util.data.AsyncResult
import org.oppia.util.data.DataProviders
import org.oppia.util.logging.ConsoleLogger
import org.oppia.util.logging.EventLogger
import org.oppia.util.networking.NetworkConnectionUtil
import org.oppia.util.networking.NetworkConnectionUtil.ConnectionStatus.CELLULAR
import org.oppia.util.networking.NetworkConnectionUtil.ConnectionStatus.LOCAL
import org.oppia.util.networking.NetworkConnectionUtil.ConnectionStatus.NONE
import org.oppia.util.threading.BackgroundDispatcher
import javax.inject.Inject
/** Controller for handling analytics event logging. */
class AnalyticsController @Inject constructor(
private val eventLogger: EventLogger,
cacheStoreFactory: PersistentCacheStore.Factory,
private val dataProviders: DataProviders,
private val consoleLogger: ConsoleLogger,
private val networkConnectionUtil: NetworkConnectionUtil,
@BackgroundDispatcher private val backgroundCoroutineDispatcher: CoroutineDispatcher,
@EventLogStorageCacheSize private val eventLogStorageCacheSize: Int
) {
private val eventLogStore =
cacheStoreFactory.create("event_logs", OppiaEventLogs.getDefaultInstance())
private val coroutineScope = CoroutineScope(backgroundCoroutineDispatcher)
/**
* Logs transition events.
* These events are given HIGH priority.
*/
fun logTransitionEvent(
context: Context,
timestamp: Long,
eventAction: EventAction,
eventContext: EventLog.Context?
) {
eventLogger.logEvent(
context,
createEventLog(
timestamp,
eventAction,
eventContext,
Priority.ESSENTIAL
)
)
}
/**
* Logs click events.
* These events are given LOW priority.
*/
fun logClickEvent(
context: Context,
timestamp: Long,
eventAction: EventAction,
eventContext: EventLog.Context?
) {
eventLogger.logEvent(
context,
createEventLog(
timestamp,
eventAction,
eventContext,
Priority.OPTIONAL
)
)
}
/** Returns an event log containing relevant data for event reporting. */
private fun createEventLog(
timestamp: Long,
eventAction: EventAction,
eventContext: EventLog.Context?,
priority: Priority
): EventLog {
val event: EventLog.Builder = EventLog.newBuilder()
event.timestamp = timestamp
event.actionName = eventAction
event.priority = priority
if (eventContext != null)
event.context = eventContext
return event.build()
}
/** Returns the context of an event related to exploration. */
fun createExplorationContext(
topicId: String,
storyId: String,
explorationId: String
): EventLog.Context {
return EventLog.Context.newBuilder()
.setExplorationContext(
EventLog.ExplorationContext.newBuilder()
.setTopicId(topicId)
.setStoryId(storyId)
.setExplorationId(explorationId)
.build()
)
.build()
}
/** Returns the context of an event related to question. */
fun createQuestionContext(
questionId: String,
skillId: List<String>
): EventLog.Context {
return EventLog.Context.newBuilder()
.setQuestionContext(
EventLog.QuestionContext.newBuilder()
.setQuestionId(questionId)
.addAllSkillId(skillId)
.build()
)
.build()
}
/** Returns the context of an event related to topic. */
fun createTopicContext(
topicId: String
): EventLog.Context {
return EventLog.Context.newBuilder()
.setTopicContext(
EventLog.TopicContext.newBuilder()
.setTopicId(topicId)
.build()
)
.build()
}
/** Returns the context of an event related to story. */
fun createStoryContext(
topicId: String,
storyId: String
): EventLog.Context {
return EventLog.Context.newBuilder()
.setStoryContext(
EventLog.StoryContext.newBuilder()
.setTopicId(topicId)
.setStoryId(storyId)
.build()
)
.build()
}
/** Returns the context of an event related to concept card. */
fun createConceptCardContext(
skillId: String
): EventLog.Context {
return EventLog.Context.newBuilder()
.setConceptCardContext(
EventLog.ConceptCardContext.newBuilder()
.setSkillId(skillId)
.build()
)
.build()
}
/** Returns the context of an event related to revision card. */
fun createRevisionCardContext(
topicId: String,
subTopicId: String
): EventLog.Context {
return EventLog.Context.newBuilder()
.setRevisionCardContext(
EventLog.RevisionCardContext.newBuilder()
.setTopicId(topicId)
.setSubTopicId(subTopicId)
.build()
)
.build()
}
fun checkNetworkAndUpload(eventLog: EventLog, context: Context){
when(networkConnectionUtil.getCurrentConnectionStatus()){
NONE -> addEventLog(eventLog)
else -> eventLogger.logEvent(context, eventLog)
}
}
/** Adds an event to the storage. */
private fun addEventLog(eventLog: EventLog) {
coroutineScope.launch {
val storeSize = eventLogStore.readDataAsync().await().eventLogList.size + 1
if (storeSize > eventLogStorageCacheSize){
removeEvent(getLeastRecentEvent())
}
}
eventLogStore.storeDataAsync(updateInMemoryCache = true) {
it.toBuilder().addEventLog(eventLog).build()
}.invokeOnCompletion {
it?.let {
consoleLogger.e(
"DOMAIN",
"Failed to store event log",
it
)
}
}
}
/**
* Returns the least recent event from the existing store on the basis of recency and priority.
* At first, it checks the least recent event which has OPTIONAL priority.
* If that returns null, then the least recent event regardless of the priority is returned.
*/
private suspend fun getLeastRecentEvent(): EventLog? =
eventLogStore.readDataAsync().await().eventLogList
.filter { it.priority == EventLog.Priority.OPTIONAL }
.minBy { it.timestamp } ?: eventLogStore.readDataAsync().await().eventLogList
.minBy { it.timestamp }
/** Removes an [eventLog] from the [eventLogStore]. */
private suspend fun removeEvent(eventLog: EventLog?) {
val eventLogList : MutableList<EventLog> =
eventLogStore.readDataAsync().await().eventLogList.toMutableList()
eventLogList.remove(eventLog)
removeAllEvents()
eventLogStore.storeDataAsync(true){
it.toBuilder().addAllEventLog(eventLogList).build()
}.invokeOnCompletion {
it?.let {
consoleLogger.e(
"DOMAIN",
"Failed to store event log",
it
)
}
}
// eventLog?.let { eventLogStore.readDataAsync().await().eventLogList.remove(eventLog)}
}
/** Removes all events present in the [eventLogStore]. */
private fun removeAllEvents() {
eventLogStore.clearCacheAsync().invokeOnCompletion {
it?.let {
consoleLogger.e(
"DOMAIN",
"Failed to remove all event logs",
it
)
}
}
}
/**
* Returns a [LiveData] result which can be used to get [OppiaEventLogs]
* for the purpose of uploading in the presence of network connectivity.
*/
fun getEventLogs(): LiveData<AsyncResult<OppiaEventLogs>> {
return dataProviders.convertToLiveData(eventLogStore)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment