diff --git a/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/activities/InitActivity.kt b/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/activities/InitActivity.kt index 97dc2c3d..2eb7cd60 100644 --- a/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/activities/InitActivity.kt +++ b/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/activities/InitActivity.kt @@ -15,8 +15,5 @@ class InitActivity : StartupActivity { TaskTrackerPlugin.initPlugin() } - // TODO: show an error message to the user if an error occurs - override fun runActivity(project: Project) { - TaskTrackerPlugin.initializationHandler.setupEnvironment(project) // TODO: move to the start point of the task - } + override fun runActivity(project: Project) = Unit } diff --git a/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/config/scenario/models/Scenario.kt b/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/config/scenario/models/Scenario.kt index 180b8e33..5cc5a47a 100644 --- a/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/config/scenario/models/Scenario.kt +++ b/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/config/scenario/models/Scenario.kt @@ -12,6 +12,10 @@ import kotlinx.serialization.encoding.Encoder import org.jetbrains.research.tasktracker.ui.main.panel.storage.MainPanelStorage import java.util.* +/** + * Structure of the research scenario. It consists of steps. + * @see [ScenarioStep] + */ @Serializable data class Scenario( @Serializable(with = QueueSerializer::class) val steps: Queue @@ -19,6 +23,9 @@ data class Scenario( @Transient private val logger: Logger = Logger.getInstance(javaClass) + @Transient + private var currentSteps = LinkedList(steps) + @Transient private var currentStepIterator: Iterator? = null @@ -26,12 +33,12 @@ data class Scenario( var isValid: Boolean var step: ScenarioStep do { - if (steps.isEmpty()) { + if (currentSteps.isEmpty()) { logger.warn("No steps found!") return null } - step = steps.poll() + step = currentSteps.poll() isValid = step.isValid() if (!isValid) { logger.warn("Found useless step, all configs are null. Skip it") @@ -54,6 +61,10 @@ data class Scenario( return currentStepIterator?.next() } + fun reset() { + currentSteps = LinkedList(steps) + } + private fun Iterator?.notNullAndHasNext() = this?.hasNext() != true private fun cleanStepSettings() = diff --git a/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/config/scenario/models/ScenarioStep.kt b/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/config/scenario/models/ScenarioStep.kt index e61580be..0779736f 100644 --- a/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/config/scenario/models/ScenarioStep.kt +++ b/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/config/scenario/models/ScenarioStep.kt @@ -7,6 +7,13 @@ import org.jetbrains.research.tasktracker.config.ide.MainIdeConfig import org.jetbrains.research.tasktracker.handler.BaseProjectHandler import org.jetbrains.research.tasktracker.ui.main.panel.storage.MainPanelStorage +/** + * This is the part of the research scenario. Each step contains Units. + * Units can be shuffled or ordered, depends on the [mode] field. + * Also, Scenario step can contain [ideConfig] settings which will + * be setup on the step start and destroyed on the step end. + * @see [ScenarioUnit] + */ @Serializable data class ScenarioStep( private val units: List, diff --git a/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/config/scenario/models/ScenarioUnit.kt b/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/config/scenario/models/ScenarioUnit.kt index 85066a14..4c9bab1d 100644 --- a/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/config/scenario/models/ScenarioUnit.kt +++ b/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/config/scenario/models/ScenarioUnit.kt @@ -7,26 +7,52 @@ import org.jetbrains.research.tasktracker.config.ide.MainIdeConfig @Serializable sealed interface ScenarioUnit +/** + * List of tasks from which user selects a task to run. + * The chosen task is removed from the list after completion. + * The process runs until there are no tasks left. + */ @Serializable @SerialName("List") class TaskListUnit(val taskIds: List) : ScenarioUnit +/** + * List of tasks in which user can select one task. + * After completion of this task next unit will be processed. + * Unselected tasks won't be present. + */ @Serializable @SerialName("SingleList") class TaskListWithSingleChoiceUnit(val taskIds: List) : ScenarioUnit +/** + * Task unit loaded by the id. + * @see [org.jetbrains.research.tasktracker.config.content.TaskContentConfig] + * And [org.jetbrains.research.tasktracker.config.content.task.ProgrammingTask] + */ @Serializable @SerialName("Task") class TaskUnit(val id: String) : ScenarioUnit +/** + * Setting Unit represents by MainIdeConfig format. + * @see [org.jetbrains.research.tasktracker.config.ide.MainIdeConfig]. + */ @Serializable @SerialName("Setting") class IdeSettingUnit(val mainIdeConfig: MainIdeConfig) : ScenarioUnit +/** + * Survey unit. Uses *id* of the survey defined in the SurveyConfig. + * @see [org.jetbrains.research.tasktracker.config.survey.SurveyConfig]. + */ @Serializable @SerialName("Survey") class SurveyUnit(val id: String) : ScenarioUnit +/** + * Opens external url in the JCEF. + */ @Serializable @SerialName("External") class ExternalSourceUnit(val url: String) : ScenarioUnit diff --git a/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/config/survey/HtmlQuestion.kt b/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/config/survey/HtmlQuestion.kt index c108efab..b2fcbaaf 100644 --- a/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/config/survey/HtmlQuestion.kt +++ b/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/config/survey/HtmlQuestion.kt @@ -2,6 +2,7 @@ package org.jetbrains.research.tasktracker.config.survey import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient /** * Radio button id and value @@ -23,24 +24,38 @@ sealed class HtmlQuestion { /** * `h` tag size. For example, h1, h2, and so on. */ - private val textSize: Int = defaultTextSize - get() = if (field < minTextSize || field > maxTextSize) defaultTextSize else field + private val textSize: Int = DEFAULT_TEXT_SIZE + get() = if (field < MIN_TEXT_SIZE || field > MAX_TEXT_SIZE) DEFAULT_TEXT_SIZE else field /** * Sets 'required' on the input if this field is mandatory to fill out. */ @SerialName("required") - private val isRequired: Boolean = false - abstract fun toHtml(): String + protected val isRequired: Boolean = false + internal abstract fun toHtml(): String + + val html: String + get() = buildString { + append("
") + append(toHtml()) + append("
") + } + + @Transient + private var alreadyRequired = false protected fun Any?.asParameter(name: String): String = this?.let { "$name=\"$it\"" } ?: "" - protected fun isRequiredString(): String = if (isRequired) "required" else "" - protected fun htmlText() = "$text" + protected open fun isRequiredString(): String = + if (isRequired && !alreadyRequired) "required".also { alreadyRequired = true } else "" + + protected fun htmlText() = "$text" + + private fun isRequiredClass(): String = if (isRequired) "class=\"required\"" else "" companion object { - private const val minTextSize = 1 - private const val maxTextSize = 5 - private const val defaultTextSize = 3 + private const val MIN_TEXT_SIZE = 1 + private const val MAX_TEXT_SIZE = 5 + private const val DEFAULT_TEXT_SIZE = 3 } } @@ -118,6 +133,8 @@ data class TextAreaHtmlQuestion( append("") } + + override fun isRequiredString(): String = if (isRequired) "data-req=\"required\"" else "" } /** diff --git a/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/config/survey/Survey.kt b/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/config/survey/Survey.kt index 78c15bb7..f3d17bdb 100644 --- a/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/config/survey/Survey.kt +++ b/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/config/survey/Survey.kt @@ -4,5 +4,5 @@ import kotlinx.serialization.Serializable @Serializable data class Survey(val id: String, val htmlQuestions: List) { - fun toHtml() = htmlQuestions.joinToString(System.lineSeparator()) { it.toHtml() } + fun toHtml() = htmlQuestions.joinToString(System.lineSeparator()) { it.html } } diff --git a/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/handler/ide/IdeHandler.kt b/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/handler/ide/IdeHandler.kt index 828c7be1..a716c5f1 100644 --- a/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/handler/ide/IdeHandler.kt +++ b/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/handler/ide/IdeHandler.kt @@ -5,21 +5,23 @@ import org.jetbrains.research.tasktracker.config.ide.MainIdeConfig import org.jetbrains.research.tasktracker.handler.BaseProjectHandler class IdeHandler(override val config: MainIdeConfig, override val project: Project) : BaseProjectHandler { - private val childHandlers: List + private val childHandlers: List init { with(config) { - childHandlers = listOfNotNull(inspectionConfig, settingsConfig).map { it.buildHandler(project) } + childHandlers = listOfNotNull(inspectionConfig, settingsConfig).mapNotNull { it.buildHandler(project) } } } override fun setup() { childHandlers.forEach { - it?.setup() + it.setup() } } override fun destroy() { - // TODO destroy + childHandlers.forEach { + it.destroy() + } } } diff --git a/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/handler/ide/InspectionHandler.kt b/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/handler/ide/InspectionHandler.kt index 6f96dc84..09c001a3 100644 --- a/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/handler/ide/InspectionHandler.kt +++ b/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/handler/ide/InspectionHandler.kt @@ -60,10 +60,11 @@ class InspectionHandler(override val config: InspectionConfig, override val proj } override fun destroy() { - initialProfile?.let { - ProjectInspectionProfileManager.getInstance(project).setCurrentProfile(it) - } - inspectionDisposable?.let { Disposer.dispose(it) } + // TODO rewrite inspection handler, now it raises an error. +// initialProfile?.let { +// ProjectInspectionProfileManager.getInstance(project).setCurrentProfile(it) +// } +// inspectionDisposable?.let { Disposer.dispose(it) } } private fun Collection.enableInspections(profile: InspectionProfileImpl, project: Project) = diff --git a/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/ui/main/panel/MainPluginPanelFactory.kt b/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/ui/main/panel/MainPluginPanelFactory.kt index 4cd29e61..8789286b 100644 --- a/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/ui/main/panel/MainPluginPanelFactory.kt +++ b/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/ui/main/panel/MainPluginPanelFactory.kt @@ -15,15 +15,12 @@ import com.intellij.ui.jcef.JBCefApp import com.intellij.util.ui.JBUI import kotlinx.serialization.json.Json import org.jetbrains.concurrency.Promise -import org.jetbrains.research.tasktracker.TaskTrackerPlugin import org.jetbrains.research.tasktracker.config.content.task.base.Task -import org.jetbrains.research.tasktracker.modelInference.model.EmoModel import org.jetbrains.research.tasktracker.tracking.BaseTracker import org.jetbrains.research.tasktracker.tracking.TaskFileHandler import org.jetbrains.research.tasktracker.tracking.activity.ActivityTracker import org.jetbrains.research.tasktracker.tracking.fileEditor.FileEditorTracker import org.jetbrains.research.tasktracker.tracking.toolWindow.ToolWindowTracker -import org.jetbrains.research.tasktracker.tracking.webcam.WebCamTracker import org.jetbrains.research.tasktracker.tracking.webcam.collectAllDevices import org.jetbrains.research.tasktracker.ui.main.panel.models.AgreementChecker import org.jetbrains.research.tasktracker.ui.main.panel.models.ButtonState @@ -79,10 +76,6 @@ class MainPluginPanelFactory : ToolWindowFactory { if (trackers.isNotEmpty()) { // Otherwise we can lose data return } - TaskTrackerPlugin.mainConfig.emotionConfig?.let { - GlobalPluginStorage.emoPredictor = EmoModel(it) - } ?: error("emotion config must exist by this moment") - trackers.addAll( listOf( ActivityTracker(project), @@ -90,12 +83,15 @@ class MainPluginPanelFactory : ToolWindowFactory { FileEditorTracker(project) ) ) - GlobalPluginStorage.emoPredictor?.let { - trackers.add(WebCamTracker(project, it)) - } trackers.forEach { it.startTracking() } } + fun stopTracking() { + trackers.forEach { + it.stopTracking() + } + } + fun loadBasePage( template: HtmlTemplate, buttonTextKey: String? = null, @@ -185,7 +181,7 @@ class MainPluginPanelFactory : ToolWindowFactory { * * @return **true** if any required field is not filled. **false** otherwise. */ - fun checkInputs(): Promise = + fun checkAgreementInputs(): Promise = mainWindow.executeJavaScriptAsync("checkAllInputs()").then { val agreementChecker = Json.decodeFromString(AgreementChecker.serializer(), it.toString()) if (agreementChecker.allRequiredChecked()) { @@ -195,6 +191,10 @@ class MainPluginPanelFactory : ToolWindowFactory { true } + fun checkSurveyInputs(): Promise = mainWindow.executeJavaScriptAsync("checkAllInputs()").then { + it.toBoolean() + } + fun openExternalUrl(url: String) = mainWindow.openExternalUrl(url) fun setNextAction(listener: ActionListener) = nextButton.setListener(listener) diff --git a/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/ui/main/panel/panelStates/DefaultStates.kt b/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/ui/main/panel/panelStates/DefaultStates.kt index 8ed66128..d504f4b5 100644 --- a/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/ui/main/panel/panelStates/DefaultStates.kt +++ b/ij-plugin/src/main/kotlin/org/jetbrains/research/tasktracker/ui/main/panel/panelStates/DefaultStates.kt @@ -9,7 +9,6 @@ import org.jetbrains.research.tasktracker.config.content.task.base.Task import org.jetbrains.research.tasktracker.config.content.task.base.TaskWithFiles import org.jetbrains.research.tasktracker.config.scenario.models.* import org.jetbrains.research.tasktracker.tracking.TaskFileHandler -import org.jetbrains.research.tasktracker.tracking.activity.ActivityTracker import org.jetbrains.research.tasktracker.ui.main.panel.MainPluginPanelFactory import org.jetbrains.research.tasktracker.ui.main.panel.runOnSuccess import org.jetbrains.research.tasktracker.ui.main.panel.storage.MainPanelStorage @@ -26,7 +25,7 @@ typealias Panel = MainPluginPanelFactory fun Panel.agreementAcceptance() { loadBasePage(AgreementTemplate.loadCurrentTemplate(), "ui.button.next", false) setNextAction { - checkInputs().runOnSuccess { + checkAgreementInputs().runOnSuccess { if (!it) { welcomePage() } else { @@ -40,13 +39,14 @@ fun Panel.agreementAcceptance() { * Switches the panel to the plugin description window. */ fun Panel.welcomePage() { - loadBasePage(MainPageTemplate.loadCurrentTemplate(), "ui.button.next", true) + loadBasePage(MainPageTemplate.loadCurrentTemplate(), "ui.button.next", false) setNextAction { + TaskTrackerPlugin.initializationHandler.setupEnvironment(project) + startTracking() processScenario() } } -// TODO refactor it for many configs /** * Switches the panel to the task selection window. */ @@ -65,7 +65,6 @@ private fun Panel.selectTask(taskIds: List, allRequired: Boolean = true) * Loads configs by selected task and language */ fun Panel.processTask(id: String): Task { - startTracking() // TODO val task = MainPanelStorage.taskIdTask.values.find { it.id == id } ?: error("Can't find task with id '$id'") ApplicationManager.getApplication().invokeAndWait { @@ -83,8 +82,6 @@ fun Panel.processTask(id: String): Task { */ private fun Panel.solveTask(id: String, nextTasks: List = emptyList()) { val task = processTask(id) - val activityTracker = ActivityTracker(project) - activityTracker.startTracking() // TODO start tracking for all trackers instead of this one loadBasePage(SolvePageTemplate(task)) setNextAction { TaskFileHandler.disposeTask(project, task) @@ -103,11 +100,17 @@ fun Panel.survey(id: String) { ?: error("Survey with id `$id` hasn't been found.") loadBasePage(SurveyTemplate(survey)) setNextAction { - val surveyParser = SurveyParser(mainWindow, project) - GlobalScope.launch { - surveyParser.parseAndLog(survey) + checkSurveyInputs().runOnSuccess { + if (it) { + val surveyParser = SurveyParser(mainWindow, project) + GlobalScope.launch { + surveyParser.parseAndLog(survey) + } + processScenario() + } else { + notifyError(project, UIBundle.message("ui.please.fill")) + } } - processScenario() } } @@ -117,7 +120,7 @@ fun Panel.serverErrorPage() { } fun Panel.finalPage() { - loadBasePage(FinalPageTemplate.loadCurrentTemplate(), "ui.button.welcome") + loadBasePage(FinalPageTemplate.loadCurrentTemplate(), "ui.button.welcome", false) setNextAction { welcomePage() } @@ -157,6 +160,8 @@ fun Panel.processScenario() { } null -> { + scenario.reset() + stopTracking() finalPage() } } diff --git a/ij-plugin/src/main/resources/org/jetbrains/research/tasktracker/config/scenario_default.yaml b/ij-plugin/src/main/resources/org/jetbrains/research/tasktracker/config/scenario_default.yaml index d2a4e93e..f7fc01a7 100644 --- a/ij-plugin/src/main/resources/org/jetbrains/research/tasktracker/config/scenario_default.yaml +++ b/ij-plugin/src/main/resources/org/jetbrains/research/tasktracker/config/scenario_default.yaml @@ -6,11 +6,18 @@ scenario: - ! id: "default" mode: SHUFFLED + ideConfig: + settingsConfig: + theme: LIGHT + inspectionConfig: + mode: DISABLE_SELECTED + inspectionNames: + - "EmptyFinallyBlock" - units: - ! mainIdeConfig: settingsConfig: - theme: LIGHT + enableCodeCompletion: OFF - ! taskIds: - "hello" diff --git a/ij-plugin/src/main/resources/org/jetbrains/research/tasktracker/config/settings_default.yaml b/ij-plugin/src/main/resources/org/jetbrains/research/tasktracker/config/settings_default.yaml index de3e58cd..7d71b9f4 100644 --- a/ij-plugin/src/main/resources/org/jetbrains/research/tasktracker/config/settings_default.yaml +++ b/ij-plugin/src/main/resources/org/jetbrains/research/tasktracker/config/settings_default.yaml @@ -1,3 +1,3 @@ -enableCodeCompletion: OFF -enableZenMode: OFF +enableCodeCompletion: DEFAULT +enableZenMode: DEFAULT theme: DEFAULT \ No newline at end of file diff --git a/ij-plugin/src/main/resources/org/jetbrains/research/tasktracker/config/survey_default.yaml b/ij-plugin/src/main/resources/org/jetbrains/research/tasktracker/config/survey_default.yaml index 9bfa72ea..af8106fa 100644 --- a/ij-plugin/src/main/resources/org/jetbrains/research/tasktracker/config/survey_default.yaml +++ b/ij-plugin/src/main/resources/org/jetbrains/research/tasktracker/config/survey_default.yaml @@ -11,6 +11,7 @@ surveys: - ! text: 2. On a scale of 1 (very unlikely) to 5 (very likely), how likely are you to continue using the Emotion Tracker in the future? elementId: likely + required: true info: - id: likely1 value: 1 @@ -204,5 +205,6 @@ surveys: - !