diff --git a/python/intellij.python.community.impl.iml b/python/intellij.python.community.impl.iml
index 605dd0ff08ec..47086ef55bb4 100644
--- a/python/intellij.python.community.impl.iml
+++ b/python/intellij.python.community.impl.iml
@@ -153,6 +153,8 @@
+
+
diff --git a/python/pluginCore/resources/META-INF/plugin.xml b/python/pluginCore/resources/META-INF/plugin.xml
index 58e36234692b..7f7363603493 100644
--- a/python/pluginCore/resources/META-INF/plugin.xml
+++ b/python/pluginCore/resources/META-INF/plugin.xml
@@ -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"/>
+
+
+
+
diff --git a/python/pluginResources/messages/PyBundle.properties b/python/pluginResources/messages/PyBundle.properties
index 4a464135643a..55e62e605e3b 100644
--- a/python/pluginResources/messages/PyBundle.properties
+++ b/python/pluginResources/messages/PyBundle.properties
@@ -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
\ No newline at end of file
+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
+
diff --git a/python/src/com/jetbrains/python/statistics/feedback/PyCharmJobSurveyUtils.kt b/python/src/com/jetbrains/python/statistics/feedback/PyCharmJobSurveyUtils.kt
new file mode 100644
index 000000000000..74b9b9efec5c
--- /dev/null
+++ b/python/src/com/jetbrains/python/statistics/feedback/PyCharmJobSurveyUtils.kt
@@ -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
+}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/statistics/feedback/PythonJobStatisticsCollector.kt b/python/src/com/jetbrains/python/statistics/feedback/PythonJobStatisticsCollector.kt
new file mode 100644
index 000000000000..336bc6e1b8a9
--- /dev/null
+++ b/python/src/com/jetbrains/python/statistics/feedback/PythonJobStatisticsCollector.kt
@@ -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("other")
+ private val JOB_EVENT: EventId2, String?> = GROUP.registerEvent("job.event", USE_FOR, OTHER)
+
+ @JvmStatic
+ fun logJobEvent(useFor: List, 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
+ }
+}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/statistics/feedback/PythonJobSurvey.kt b/python/src/com/jetbrains/python/statistics/feedback/PythonJobSurvey.kt
new file mode 100644
index 000000000000..e33fecadca01
--- /dev/null
+++ b/python/src/com/jetbrains/python/statistics/feedback/PythonJobSurvey.kt
@@ -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 =
+ InIdeFeedbackSurveyType(PythonJobSurveyConfig())
+}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/statistics/feedback/PythonJobSurveyConfig.kt b/python/src/com/jetbrains/python/statistics/feedback/PythonJobSurveyConfig.kt
new file mode 100644
index 000000000000..d057515c22a3
--- /dev/null
+++ b/python/src/com/jetbrains/python/statistics/feedback/PythonJobSurveyConfig.kt
@@ -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 {
+ 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) {
+
+ }
+}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/statistics/feedback/PythonJobSurveyService.kt b/python/src/com/jetbrains/python/statistics/feedback/PythonJobSurveyService.kt
new file mode 100644
index 000000000000..5f7a941ebab8
--- /dev/null
+++ b/python/src/com/jetbrains/python/statistics/feedback/PythonJobSurveyService.kt
@@ -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 {
+ private var state = PythonJobSurveyState()
+
+ override fun getState(): PythonJobSurveyState? = state
+
+ override fun loadState(state: PythonJobSurveyState) {
+ this.state = state
+ }
+
+ companion object {
+ @JvmStatic
+ fun getInstance(): PythonJobSurveyService = service()
+ }
+}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/statistics/feedback/PythonUserJobFeedbackDialog.kt b/python/src/com/jetbrains/python/statistics/feedback/PythonUserJobFeedbackDialog.kt
new file mode 100644
index 000000000000..c4a34fc06324
--- /dev/null
+++ b/python/src/com/jetbrains/python/statistics/feedback/PythonUserJobFeedbackDialog.kt
@@ -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(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 = 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()
+ }
+}
\ No newline at end of file