Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ALTAPPS-710: Shared study plan analytics #454

Merged
merged 1 commit into from
Apr 28, 2023
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -26,6 +26,7 @@ enum class HyperskillAnalyticPart(val partName: String) {
TRACK_MODAL("track_modal"),
STREAK_WIDGET("streak_widget"),
STREAK_FREEZE_MODAL("streak_freeze_modal"),
STAGE_IMPLEMENT_DEPRECATED_PLACEHOLDER("stage_implement_deprecated_placeholder"),
STAGE_IMPLEMENT_UNSUPPORTED_MODAL("stage_implement_unsupported_modal"),
STUDY_PLAN_SECTION("study_plan_section"),
STUDY_PLAN_SECTION_ACTIVITIES("study_plan_section_activities")
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,5 +72,8 @@ enum class HyperskillAnalyticTarget(val targetName: String) {
STREAK_FREEZE_MODAL("streak_freeze_modal"),
GET_IT("get_it"),
CONTINUE_LEARNING("continue_learning"),
STAGE_IMPLEMENT_UNSUPPORTED_MODAL("stage_implement_unsupported_modal")
STAGE_IMPLEMENT_UNSUPPORTED_MODAL("stage_implement_unsupported_modal"),
RETRY("retry"),
SECTION("section"),
ACTIVITY("activity")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package org.hyperskill.app.study_plan.domain.analytic

import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticAction
import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticEvent
import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticPart
import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRoute
import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTarget
import ru.nobird.app.core.model.mapOfNotNull

/**
* Represents an analytic event for clicking on an activity in the study plan.
*
* JSON payload:
* ```
* {
* "route": "/study-plan"",
* "action": "click",
* "part": "study_plan_section_activities",
* "target": "activity",
* "context":
* {
* "id": 1,
* "type": 10,
* "target_type": "step",
* "target_id": 1
* }
* }
* ```
*
* @property activityId id of the activity
* @property activityType type of the activity
* @property activityTargetType type of the activity target
* @property activityTargetId id of the activity target
*
* @see HyperskillAnalyticEvent
*/
class StudyPlanClickedActivityHyperskillAnalyticEvent(
val activityId: Long,
val activityType: Int?,
val activityTargetType: String?,
val activityTargetId: Long
) : HyperskillAnalyticEvent(
HyperskillAnalyticRoute.StudyPlan(),
HyperskillAnalyticAction.CLICK,
HyperskillAnalyticPart.STUDY_PLAN_SECTION_ACTIVITIES,
HyperskillAnalyticTarget.ACTIVITY
) {
companion object {
private const val ID = "id"
private const val TYPE = "type"
private const val TARGET_TYPE = "target_type"
private const val TARGET_ID = "target_id"
}

override val params: Map<String, Any>
get() = super.params +
mapOf(
PARAM_CONTEXT to mapOfNotNull(
ID to activityId,
TYPE to activityType,
TARGET_TYPE to activityTargetType,
TARGET_ID to activityTargetId
)
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRou
import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTarget

/**
* Represents a PullToRefresh analytic event an study plan screen.
* Represents a PullToRefresh analytic event.
*
* JSON payload:
* ```
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.hyperskill.app.study_plan.domain.analytic

import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticAction
import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticEvent
import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticPart
import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRoute
import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTarget

/**
* Represents a click analytic event of the error state placeholder in the section activities.
*
* JSON payload:
* ```
* {
* "route": "/study-plan"",
* "action": "click",
* "part": "study_plan_section_activities",
* "target": "retry",
* "context":
* {
* "section_id": 123
* }
* }
* ```
* @see HyperskillAnalyticEvent
*/
class StudyPlanClickedRetryActivitiesLoadingHyperskillAnalyticEvent(
val sectionId: Long
) : HyperskillAnalyticEvent(
HyperskillAnalyticRoute.StudyPlan(),
HyperskillAnalyticAction.CLICK,
HyperskillAnalyticPart.STUDY_PLAN_SECTION_ACTIVITIES,
HyperskillAnalyticTarget.RETRY
) {
companion object {
private const val SECTION_ID = "section_id"
}

override val params: Map<String, Any>
get() = super.params +
mapOf(PARAM_CONTEXT to mapOf(SECTION_ID to sectionId))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.hyperskill.app.study_plan.domain.analytic

import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticAction
import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticEvent
import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticPart
import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRoute
import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTarget

/**
* Represents a click analytic event of the error state placeholder retry button.
*
* JSON payload:
* ```
* {
* "route": "/study-plan"",
* "action": "click",
* "part": "main",
* "target": "retry"
* }
* ```
* @see HyperskillAnalyticEvent
*/
class StudyPlanClickedRetryContentLoadingHyperskillAnalyticEvent : HyperskillAnalyticEvent(
HyperskillAnalyticRoute.StudyPlan(),
HyperskillAnalyticAction.CLICK,
HyperskillAnalyticPart.MAIN,
HyperskillAnalyticTarget.RETRY
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package org.hyperskill.app.study_plan.domain.analytic

import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticAction
import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticEvent
import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticPart
import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRoute
import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticTarget

/**
* Represents a click analytic event of the study plan section, when section is expanded or collapsed.
*
* JSON payload:
* ```
* {
* "route": "/study-plan"",
* "action": "click",
* "part": "study_plan_section",
* "target": "section",
* "context":
* {
* "section_id": 123,
* "is_expanded": true | false
* }
* }
* ```
*
* @property sectionId id of the section
* @property isExpanded true if section is expanded, false if collapsed
*
* @see HyperskillAnalyticEvent
*/
class StudyPlanClickedSectionHyperskillAnalyticEvent(
val sectionId: Long,
val isExpanded: Boolean
) : HyperskillAnalyticEvent(
HyperskillAnalyticRoute.StudyPlan(),
HyperskillAnalyticAction.CLICK,
HyperskillAnalyticPart.STUDY_PLAN_SECTION,
HyperskillAnalyticTarget.SECTION
) {
companion object {
private const val SECTION_ID = "section_id"
private const val IS_EXPANDED = "is_expanded"
}

override val params: Map<String, Any>
get() = super.params +
mapOf(
PARAM_CONTEXT to mapOf(
SECTION_ID to sectionId,
IS_EXPANDED to isExpanded
)
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import org.hyperskill.app.analytic.domain.model.hyperskill.HyperskillAnalyticRou
* ```
* @see HyperskillAnalyticEvent
*/
// TODO: Use this event for StudyPlanScreenFeature
class StudyPlanViewedHyperskillAnalyticEvent : HyperskillAnalyticEvent(
HyperskillAnalyticRoute.StudyPlan(),
HyperskillAnalyticAction.VIEW
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ package org.hyperskill.app.study_plan.widget.presentation
import org.hyperskill.app.learning_activities.domain.model.LearningActivityTargetType
import org.hyperskill.app.learning_activities.domain.model.LearningActivityType
import org.hyperskill.app.step.domain.model.StepRoute
import org.hyperskill.app.study_plan.domain.analytic.StudyPlanClickedActivityHyperskillAnalyticEvent
import org.hyperskill.app.study_plan.domain.analytic.StudyPlanClickedRetryActivitiesLoadingHyperskillAnalyticEvent
import org.hyperskill.app.study_plan.domain.analytic.StudyPlanClickedRetryContentLoadingHyperskillAnalyticEvent
import org.hyperskill.app.study_plan.domain.analytic.StudyPlanClickedSectionHyperskillAnalyticEvent
import org.hyperskill.app.study_plan.domain.analytic.StudyPlanStageImplementUnsupportedModalClickedGoToHomeScreenHyperskillAnalyticEvent
import org.hyperskill.app.study_plan.domain.analytic.StudyPlanStageImplementUnsupportedModalHiddenHyperskillAnalyticEvent
import org.hyperskill.app.study_plan.domain.analytic.StudyPlanStageImplementUnsupportedModalShownHyperskillAnalyticEvent
Expand All @@ -26,7 +30,7 @@ class StudyPlanWidgetReducer : StateReducer<State, Message, Action> {
is StudyPlanWidgetFeature.SectionsFetchResult.Success ->
handleSectionsFetchSuccess(state, message)
is Message.RetryContentLoading ->
coldContentFetch()
handleRetryContentLoading()
is Message.RetryActivitiesLoading ->
handleRetryActivitiesLoading(state, message)
is Message.ReloadContentInBackground ->
Expand Down Expand Up @@ -64,7 +68,7 @@ class StudyPlanWidgetReducer : StateReducer<State, Message, Action> {
null
}
is Message.SectionClicked ->
changeSectionExpanse(state, message.sectionId)
changeSectionExpanse(state, message.sectionId, shouldLogAnalyticEvent = true)
is Message.ActivityClicked ->
handleActivityClicked(state, message.activityId)
is Message.StageImplementUnsupportedModalGoToHomeClicked ->
Expand Down Expand Up @@ -149,7 +153,7 @@ class StudyPlanWidgetReducer : StateReducer<State, Message, Action> {
)

return if (sortedVisibleSections.isNotEmpty()) {
changeSectionExpanse(loadedSectionsState, sortedVisibleSections.first())
changeSectionExpanse(loadedSectionsState, sortedVisibleSections.first(), shouldLogAnalyticEvent = false)
} else {
loadedSectionsState to emptySet()
}
Expand Down Expand Up @@ -180,6 +184,13 @@ class StudyPlanWidgetReducer : StateReducer<State, Message, Action> {
}
) to emptySet()

private fun handleRetryContentLoading(): StudyPlanWidgetReducerResult {
val (state, actions) = coldContentFetch()
return state to actions + InternalAction.LogAnalyticEvent(
StudyPlanClickedRetryContentLoadingHyperskillAnalyticEvent()
)
}

private fun handleRetryActivitiesLoading(
state: State,
message: Message.RetryActivitiesLoading
Expand All @@ -194,18 +205,28 @@ class StudyPlanWidgetReducer : StateReducer<State, Message, Action> {
InternalAction.FetchActivities(
sectionId = message.sectionId,
activitiesIds = section.studyPlanSection.activities
),
InternalAction.LogAnalyticEvent(
StudyPlanClickedRetryActivitiesLoadingHyperskillAnalyticEvent(message.sectionId)
)
)
}

private fun changeSectionExpanse(
state: State,
sectionId: Long
sectionId: Long,
shouldLogAnalyticEvent: Boolean
): StudyPlanWidgetReducerResult {
val section =
state.studyPlanSections[sectionId] ?: return state to emptySet()
val isExpanded = !section.isExpanded

val logAnalyticEventAction = if (shouldLogAnalyticEvent) {
InternalAction.LogAnalyticEvent(StudyPlanClickedSectionHyperskillAnalyticEvent(sectionId, isExpanded))
} else {
null
}

fun updateSectionState(contentStatus: StudyPlanWidgetFeature.ContentStatus = section.contentStatus): State =
state.copy(
studyPlanSections = state.studyPlanSections.update(
Expand All @@ -218,29 +239,43 @@ class StudyPlanWidgetReducer : StateReducer<State, Message, Action> {
when (section.contentStatus) {
StudyPlanWidgetFeature.ContentStatus.IDLE,
StudyPlanWidgetFeature.ContentStatus.ERROR -> {
updateSectionState(StudyPlanWidgetFeature.ContentStatus.LOADING) to setOf(
updateSectionState(StudyPlanWidgetFeature.ContentStatus.LOADING) to setOfNotNull(
InternalAction.FetchActivities(
sectionId = sectionId,
activitiesIds = section.studyPlanSection.activities
)
),
logAnalyticEventAction
)
}

// activities are loading at the moment or already loaded
StudyPlanWidgetFeature.ContentStatus.LOADING,
StudyPlanWidgetFeature.ContentStatus.LOADED -> {
updateSectionState() to emptySet()
updateSectionState() to setOfNotNull(logAnalyticEventAction)
}
}
} else {
updateSectionState() to emptySet()
updateSectionState() to setOfNotNull(logAnalyticEventAction)
}
}

private fun handleActivityClicked(state: State, activityId: Long): StudyPlanWidgetReducerResult {
val activity = state.activities[activityId]
if (activity?.isCurrent != true) return state to emptySet()
val action = when (activity.type) {
val activity = state.activities[activityId] ?: return state to emptySet()

val logAnalyticEventAction = InternalAction.LogAnalyticEvent(
StudyPlanClickedActivityHyperskillAnalyticEvent(
activityId = activity.id,
activityType = activity.type?.value,
activityTargetType = activity.targetType?.value,
activityTargetId = activity.targetId
)
)

if (!activity.isCurrent) {
return state to setOf(logAnalyticEventAction)
}

val viewAction = when (activity.type) {
LearningActivityType.IMPLEMENT_STAGE -> {
val projectId = state.studyPlan?.projectId
if (projectId != null && activity.targetType == LearningActivityTargetType.STAGE) {
Expand All @@ -265,7 +300,7 @@ class StudyPlanWidgetReducer : StateReducer<State, Message, Action> {
}
else -> null
}
return state to setOfNotNull(action)
return state to setOfNotNull(viewAction, logAnalyticEventAction)
}

private fun <K, V> Map<K, V>.update(key: K, value: V): Map<K, V> =
Expand Down
Loading