[feedback] IJPL-177421 Permanent CSAT survey in IDEs

Store feedback dates globally to consider several major versions and RD scenario

(cherry picked from commit 7194d98f019efecc0dbbca2e8b097be19fd11cfc)


(cherry picked from commit 8729b09d1ef21adbecbf3cb15cdd9a329046ebf3)

IJ-MR-155667

GitOrigin-RevId: f908baec1667d1e10cada10250253ce315d9ff6c
This commit is contained in:
Yuriy Artamonov
2025-02-17 20:39:03 +01:00
committed by intellij-monorepo-bot
parent 579d2eefad
commit 88af5d2f2d
4 changed files with 71 additions and 18 deletions

View File

@@ -26,7 +26,7 @@ internal class CsatFeedbackNextDayAction : AnAction(), ActionRemoteBehaviorSpeci
NotificationGroupManager.getInstance().getNotificationGroup("System Messages")
.createNotification(
"Next CSAT feedback day is " + nextDate.date.format(DateTimeFormatter.ISO_DATE) +
"Next CSAT feedback day is " + nextDate.date.format(DateTimeFormatter.ISO_DATE) + ". " +
"User is${if (!nextDate.isNewUser) " not " else " "}new.",
NotificationType.INFORMATION
)

View File

@@ -6,6 +6,9 @@ import com.intellij.idea.AppMode
import com.intellij.internal.statistic.eventLog.fus.MachineIdManager
import com.intellij.openapi.application.ApplicationInfo
import com.intellij.openapi.application.ConfigImportHelper
import com.intellij.openapi.client.ClientKind
import com.intellij.openapi.client.currentSessionOrNull
import com.intellij.openapi.client.sessions
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.registry.Registry
@@ -26,8 +29,6 @@ import kotlin.math.abs
internal const val USER_CONSIDERED_NEW_DAYS = 30
internal const val NEW_USER_SURVEY_PERIOD = 29
internal const val EXISTING_USER_SURVEY_PERIOD = 97
internal const val CSAT_SURVEY_LAST_FEEDBACK_DATE_KEY = "csat.survey.last.feedback.date"
internal const val CSAT_SURVEY_LAST_NOTIFICATION_DATE_KEY = "csat.survey.last.notification.date"
internal class CsatFeedbackSurvey : FeedbackSurvey() {
override val feedbackSurveyType: InIdeFeedbackSurveyType<InIdeFeedbackSurveyConfig> =
@@ -54,10 +55,19 @@ internal class CsatFeedbackSurveyConfig : InIdeFeedbackSurveyConfig {
}
override fun updateStateAfterDialogClosedOk(project: Project) {
PropertiesComponent.getInstance().setValue(CSAT_SURVEY_LAST_FEEDBACK_DATE_KEY, getCsatToday().format(DateTimeFormatter.ISO_LOCAL_DATE))
CsatGlobalSettings.getInstance().lastFeedbackDate = getCsatToday().format(DateTimeFormatter.ISO_LOCAL_DATE)
}
override fun checkExtraConditionSatisfied(project: Project): Boolean {
if (project.currentSessionOrNull?.isGuest == true) {
LOG.debug("We are a CWM guest, do not really need CSAT")
return false
}
if (project.sessions(ClientKind.GUEST).isNotEmpty()) {
LOG.debug("We are the CWM host at the moment, not the perfect time for CSAT")
return false
}
if (ConfigImportHelper.isFirstSession()) {
LOG.debug("It's a first user session, skip the survey")
return false
@@ -66,7 +76,7 @@ internal class CsatFeedbackSurveyConfig : InIdeFeedbackSurveyConfig {
val today = getCsatToday()
LOG.debug("Today is ${today.format(DateTimeFormatter.ISO_LOCAL_DATE)}")
PropertiesComponent.getInstance().getValue(CSAT_SURVEY_LAST_NOTIFICATION_DATE_KEY)
CsatGlobalSettings.getInstance().lastNotificationDate
?.let { tryParseDate(it) }
?.let {
if (it.isEqual(today)) {
@@ -75,7 +85,7 @@ internal class CsatFeedbackSurveyConfig : InIdeFeedbackSurveyConfig {
}
}
val lastFeedbackDate = PropertiesComponent.getInstance().getValue(CSAT_SURVEY_LAST_FEEDBACK_DATE_KEY)
val lastFeedbackDate = CsatGlobalSettings.getInstance().lastFeedbackDate
?.let { tryParseDate(it) }
if (lastFeedbackDate != null && lastFeedbackDate.plusDays(EXISTING_USER_SURVEY_PERIOD.toLong()).isAfter(today)) {
LOG.debug("User recently filled the survey, vacation period is in progress")
@@ -116,10 +126,10 @@ internal class CsatFeedbackSurveyConfig : InIdeFeedbackSurveyConfig {
}
override fun updateStateAfterNotificationShowed(project: Project) {
val propertiesComponent = PropertiesComponent.getInstance()
propertiesComponent.setValue(CSAT_SURVEY_LAST_NOTIFICATION_DATE_KEY, getCsatToday().format(DateTimeFormatter.ISO_LOCAL_DATE))
CsatGlobalSettings.getInstance().lastNotificationDate = getCsatToday().format(DateTimeFormatter.ISO_LOCAL_DATE)
// disable an automatic Evaluate Feedback form so we don't have them both shown
propertiesComponent.setValue("evaluation.feedback.enabled", "false")
PropertiesComponent.getInstance().setValue("evaluation.feedback.enabled", "false")
}
}

View File

@@ -0,0 +1,48 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.platform.feedback.csat
import com.intellij.frontend.HostIdeInfoService
import com.intellij.ide.Prefs
import com.intellij.openapi.application.ApplicationInfo
import com.intellij.openapi.components.service
private const val CSAT_NEW_USER_CREATED_AT_KEY = "csat.user.created.at"
private const val CSAT_SURVEY_LAST_FEEDBACK_DATE_KEY = "csat.survey.last.feedback.date"
private const val CSAT_SURVEY_LAST_NOTIFICATION_DATE_KEY = "csat.survey.last.notification.date"
internal class CsatGlobalSettings private constructor(
private val productCode: String,
) {
companion object {
fun getInstance(): CsatGlobalSettings {
return CsatGlobalSettings(service<HostIdeInfoService>().getHostInfo()?.productCode
?: ApplicationInfo.getInstance().build.productCode)
}
}
private fun productKey(keyName: String): String = "JetBrains.$productCode.$keyName"
var lastFeedbackDate: String?
get() = Prefs.get(productKey(CSAT_SURVEY_LAST_FEEDBACK_DATE_KEY), null)
set(value) {
val key = productKey(CSAT_SURVEY_LAST_FEEDBACK_DATE_KEY)
Prefs.put(key, value)
Prefs.flush(key)
}
var lastNotificationDate: String?
get() = Prefs.get(productKey(CSAT_SURVEY_LAST_NOTIFICATION_DATE_KEY), null)
set(value) {
val key = productKey(CSAT_SURVEY_LAST_NOTIFICATION_DATE_KEY)
Prefs.put(key, value)
Prefs.flush(key)
}
var newUserCreatedAt: String?
get() = Prefs.get(productKey(CSAT_NEW_USER_CREATED_AT_KEY), null)
set(value) {
val key = productKey(CSAT_NEW_USER_CREATED_AT_KEY)
Prefs.put(key, value)
Prefs.flush(key)
}
}

View File

@@ -1,7 +1,6 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.platform.feedback.csat
import com.intellij.ide.util.PropertiesComponent
import com.intellij.openapi.application.ConfigImportHelper
import com.intellij.openapi.project.Project
import com.intellij.openapi.startup.ProjectActivity
@@ -9,16 +8,12 @@ import com.intellij.openapi.util.registry.Registry
import java.time.LocalDate
import java.time.format.DateTimeFormatter.ISO_LOCAL_DATE
internal const val CSAT_NEW_USER_CREATED_AT_PROPERTY = "csat.user.created.at"
internal class CsatNewUserTracker : ProjectActivity {
override suspend fun execute(project: Project) {
if (ConfigImportHelper.isNewUser()) {
val propertiesComponent = PropertiesComponent.getInstance()
if (!propertiesComponent.isValueSet(CSAT_NEW_USER_CREATED_AT_PROPERTY)) {
propertiesComponent.setValue(CSAT_NEW_USER_CREATED_AT_PROPERTY,
LocalDate.now().format(ISO_LOCAL_DATE))
val settings = CsatGlobalSettings.getInstance()
if (settings.newUserCreatedAt == null) {
settings.newUserCreatedAt = LocalDate.now().format(ISO_LOCAL_DATE)
}
}
}
@@ -30,6 +25,6 @@ internal fun getCsatUserCreatedDate(): LocalDate? {
return tryParseDate(mocked)
}
val date = PropertiesComponent.getInstance().getValue(CSAT_NEW_USER_CREATED_AT_PROPERTY) ?: return null
val date = CsatGlobalSettings.getInstance().newUserCreatedAt ?: return null
return tryParseDate(date)
}