[python] in-product survey about user's job for pycharm (PY-74269)

(cherry picked from commit 5ce25d3c418b54649e6a52a892bc84b837251e76)

IJ-MR-148062

GitOrigin-RevId: 0f3602d64459a2632bff07243e7e166ec59e9665
This commit is contained in:
Aleksei Kniazev
2024-10-29 18:07:35 +01:00
committed by intellij-monorepo-bot
parent d93cc5cd2c
commit 88df1335c4
9 changed files with 235 additions and 1 deletions

View File

@@ -153,6 +153,8 @@
<orderEntry type="module" module-name="intellij.libraries.ktor.client" />
<orderEntry type="module" module-name="intellij.libraries.ktor.client.cio" />
<orderEntry type="module" module-name="intellij.platform.util.progress" />
<orderEntry type="module" module-name="intellij.platform.feedback" />
<orderEntry type="library" name="kotlinx-datetime-jvm" level="project" />
<orderEntry type="module" module-name="intellij.platform.ide.remote" />
<orderEntry type="module" module-name="intellij.platform.ide.ui" />
</component>

View File

@@ -638,6 +638,10 @@ The Python plug-in provides smart editing for Python scripts. The feature set of
key="quickfix.ranking.ml"
defaultValue="[IN_EXPERIMENT*|ENABLED|DISABLED]"
description="Enable ML ranking in quick fix for missing imports"/>
<feedback.idleFeedbackSurvey implementation="com.jetbrains.python.statistics.feedback.PythonJobSurvey"/>
<statistics.counterUsagesCollector implementationClass="com.jetbrains.python.statistics.feedback.PythonJobStatisticsCollector"/>
<backgroundPostStartupActivity implementation="com.jetbrains.python.statistics.feedback.PythonFirstLaunchChecker"/>
</extensions>
<extensionPoints>

View File

@@ -1551,4 +1551,19 @@ package.install.with.options.dialog.message=Options:
package.install.with.options.dialog.title=Package Install with Options
python.toolwindow.packages.collapse.all.action=Collapse All
django.template.language=Template Language
python.error=Error
python.error=Error
python.survey.user.job.notification.group=PyCharm Job Survey
python.survey.user.job.notification.title=Feedback In IDE
python.survey.user.job.notification.content=Tell us about your Python development experience. Just one question.
python.survey.user.job.dialog.title=Feedback
python.survey.user.job.dialog.blocks.top=Python development
python.survey.user.job.dialog.blocks.checkbox.group=What do you use Python for?
python.survey.user.job.dialog.blocks.checkbox.data=Data analysis
python.survey.user.job.dialog.blocks.checkbox.ml=Machine learning
python.survey.user.job.dialog.blocks.checkbox.web=Web development
python.survey.user.job.dialog.blocks.checkbox.scripts=Writing automation scripts / parsers / tests / system administration
python.survey.user.job.dialog.blocks.checkbox.other=Other

View File

@@ -0,0 +1,35 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.statistics.feedback
import com.intellij.openapi.components.BaseState
import com.intellij.openapi.project.Project
import com.intellij.openapi.startup.ProjectActivity
import kotlinx.datetime.Clock
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toJavaLocalDateTime
import kotlinx.datetime.toLocalDateTime
import java.time.Duration
class PythonJobSurveyState : BaseState() {
var firstLaunch by string()
}
class PythonFirstLaunchChecker : ProjectActivity {
override suspend fun execute(project: Project) {
val state = PythonJobSurveyService.getInstance().state ?: return
if (state.firstLaunch == null) {
val dateTime = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())
state.firstLaunch = LocalDateTime.Formats.ISO.format(dateTime)
}
}
}
fun shouldShowSurvey(): Boolean {
val firstLaunch = PythonJobSurveyService.getInstance().state?.firstLaunch ?: return false
val dateTime = LocalDateTime.Formats.ISO.parse(firstLaunch)
// Start checking on the third day of usage
return Duration.between(dateTime.toJavaLocalDateTime(), java.time.LocalDateTime.now()).toDays() >= 3
}

View File

@@ -0,0 +1,38 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.statistics.feedback
import com.intellij.internal.statistic.eventLog.EventLogGroup
import com.intellij.internal.statistic.eventLog.events.EventFields
import com.intellij.internal.statistic.service.fus.collectors.CounterUsagesCollector
import com.intellij.internal.statistic.eventLog.events.EventId2
import com.intellij.internal.statistic.eventLog.validator.ValidationResultType
import com.intellij.internal.statistic.eventLog.validator.rules.EventContext
import com.intellij.internal.statistic.eventLog.validator.rules.impl.CustomValidationRule
object PythonJobStatisticsCollector : CounterUsagesCollector() {
private val GROUP = EventLogGroup("python.job.statistics", 1)
private val USE_FOR = EventFields.StringList("use_for", listOf("data_analysis", "ml", "web_dev", "scripts"))
private val OTHER = EventFields.StringValidatedByCustomRule<TrueValidationRule>("other")
private val JOB_EVENT: EventId2<List<String>, String?> = GROUP.registerEvent("job.event", USE_FOR, OTHER)
@JvmStatic
fun logJobEvent(useFor: List<String>, other: String?) {
JOB_EVENT.log(useFor, other)
}
override fun getGroup(): EventLogGroup {
return GROUP
}
}
class TrueValidationRule() : CustomValidationRule() {
override fun getRuleId(): String {
return "python-user-job-other"
}
override fun doValidate(data: String, context: EventContext): ValidationResultType {
return ValidationResultType.ACCEPTED
}
}

View File

@@ -0,0 +1,11 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.statistics.feedback
import com.intellij.platform.feedback.FeedbackSurvey
import com.intellij.platform.feedback.InIdeFeedbackSurveyConfig
import com.intellij.platform.feedback.InIdeFeedbackSurveyType
class PythonJobSurvey : FeedbackSurvey() {
override val feedbackSurveyType: InIdeFeedbackSurveyType<InIdeFeedbackSurveyConfig> =
InIdeFeedbackSurveyType(PythonJobSurveyConfig())
}

View File

@@ -0,0 +1,42 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.statistics.feedback
import com.intellij.openapi.project.Project
import com.intellij.platform.feedback.InIdeFeedbackSurveyConfig
import com.intellij.platform.feedback.dialog.BlockBasedFeedbackDialog
import com.intellij.platform.feedback.dialog.SystemDataJsonSerializable
import com.intellij.platform.feedback.impl.notification.RequestFeedbackNotification
import com.intellij.util.PlatformUtils
import com.jetbrains.python.PyBundle.message
import kotlinx.datetime.LocalDate
class PythonJobSurveyConfig : InIdeFeedbackSurveyConfig {
override fun createFeedbackDialog(project: Project, forTest: Boolean): BlockBasedFeedbackDialog<out SystemDataJsonSerializable> {
return PythonUserJobFeedbackDialog(project, forTest)
}
override val surveyId: String = "python_user_job_survey"
override val requireIdeEAP: Boolean = true
override val lastDayOfFeedbackCollection: LocalDate
get() = LocalDate(2024, 12, 31)
override fun checkIdeIsSuitable(): Boolean = PlatformUtils.isPyCharmCommunity()
override fun checkExtraConditionSatisfied(project: Project): Boolean = shouldShowSurvey()
override fun createNotification(project: Project, forTest: Boolean): RequestFeedbackNotification {
return RequestFeedbackNotification(
message("python.survey.user.job.notification.group"),
message("python.survey.user.job.notification.title"),
message("python.survey.user.job.notification.content"),
)
}
override fun updateStateAfterNotificationShowed(project: Project) {
}
override fun updateStateAfterDialogClosedOk(project: Project) {
}
}

View File

@@ -0,0 +1,25 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.statistics.feedback
import com.intellij.openapi.components.PersistentStateComponent
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.State
import com.intellij.openapi.components.Storage
import com.intellij.openapi.components.service
@Service(Service.Level.APP)
@State(name="PythonJobSurveyService", storages = [Storage("pycharm-job-survey-service.xml")])
class PythonJobSurveyService : PersistentStateComponent<PythonJobSurveyState> {
private var state = PythonJobSurveyState()
override fun getState(): PythonJobSurveyState? = state
override fun loadState(state: PythonJobSurveyState) {
this.state = state
}
companion object {
@JvmStatic
fun getInstance(): PythonJobSurveyService = service()
}
}

View File

@@ -0,0 +1,62 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.statistics.feedback
import com.intellij.openapi.project.Project
import com.intellij.platform.feedback.dialog.BlockBasedFeedbackDialog
import com.intellij.platform.feedback.dialog.CommonFeedbackSystemData
import com.intellij.platform.feedback.dialog.showFeedbackSystemInfoDialog
import com.intellij.platform.feedback.dialog.uiBlocks.CheckBoxGroupBlock
import com.intellij.platform.feedback.dialog.uiBlocks.CheckBoxItemData
import com.intellij.platform.feedback.dialog.uiBlocks.FeedbackBlock
import com.intellij.platform.feedback.dialog.uiBlocks.TopLabelBlock
import com.jetbrains.python.PyBundle.message
class PythonUserJobFeedbackDialog(
project: Project?,
forTest: Boolean,
) : BlockBasedFeedbackDialog<CommonFeedbackSystemData>(project, forTest) {
override val myFeedbackReportId: String
get() = "python_user_job_survey"
override val myTitle: String
get() = message("python.survey.user.job.dialog.title")
val items = listOf(
CheckBoxItemData(message("python.survey.user.job.dialog.blocks.checkbox.data"), "data_analysis"),
CheckBoxItemData(message("python.survey.user.job.dialog.blocks.checkbox.ml"), "ml"),
CheckBoxItemData(message("python.survey.user.job.dialog.blocks.checkbox.web"), "web_dev"),
CheckBoxItemData(message("python.survey.user.job.dialog.blocks.checkbox.scripts"), "scripts"),
)
override val myBlocks: List<FeedbackBlock> = listOf(
TopLabelBlock(message("python.survey.user.job.dialog.blocks.top")),
CheckBoxGroupBlock(message("python.survey.user.job.dialog.blocks.checkbox.group"), items, "python_job_checkbox")
.addOtherTextField(message("python.survey.user.job.dialog.blocks.checkbox.other"))
.requireAnswer(),
)
override val mySystemInfoData: CommonFeedbackSystemData
get() = CommonFeedbackSystemData.getCurrentData()
override val myShowFeedbackSystemInfoDialog: () -> Unit = {
showFeedbackSystemInfoDialog(myProject, mySystemInfoData)
}
override fun sendFeedbackData() {
super.sendFeedbackData()
val builder = StringBuilder()
(myBlocks[1] as CheckBoxGroupBlock).collectBlockTextDescription(builder)
val selectedItems = items.filter { it.property }.map { it.jsonElementName }
val other = builder.toString().lines()[5].substringAfter(message("python.survey.user.job.dialog.blocks.checkbox.other") + ": ")
PythonJobStatisticsCollector.logJobEvent(selectedItems, other)
}
init {
init()
}
}