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

Fix scenario bugs #86

Merged
merged 7 commits into from
Dec 5, 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 @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,33 @@ 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<ScenarioStep>
) {
@Transient
private val logger: Logger = Logger.getInstance(javaClass)

@Transient
private var currentSteps = LinkedList(steps)

@Transient
private var currentStepIterator: Iterator<ScenarioUnit>? = null

private fun getNextStep(): ScenarioStep? {
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")
Expand All @@ -54,6 +61,10 @@ data class Scenario(
return currentStepIterator?.next()
}

fun reset() {
currentSteps = LinkedList(steps)
}

private fun Iterator<ScenarioUnit>?.notNullAndHasNext() = this?.hasNext() != true

private fun cleanStepSettings() =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<ScenarioUnit>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>) : 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<String>) : 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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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("<div class=\"box\" id=\"${elementId}___container\">")
append(toHtml())
append("</div>")
}

@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() = "<h$textSize>$text</h$textSize>"
protected open fun isRequiredString(): String =
if (isRequired && !alreadyRequired) "required".also { alreadyRequired = true } else ""

protected fun htmlText() = "<h$textSize ${isRequiredClass()}>$text</h$textSize>"

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
}
}

Expand Down Expand Up @@ -118,6 +133,8 @@ data class TextAreaHtmlQuestion(
append("<textarea name=\"$elementId\" id=\"$elementId\" ${rows.asParameter("rows")} ")
append("${cols.asParameter("cols")} ${isRequiredString()}></textarea>")
}

override fun isRequiredString(): String = if (isRequired) "data-req=\"required\"" else ""
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ import kotlinx.serialization.Serializable

@Serializable
data class Survey(val id: String, val htmlQuestions: List<HtmlQuestion>) {
fun toHtml() = htmlQuestions.joinToString(System.lineSeparator()) { it.toHtml() }
fun toHtml() = htmlQuestions.joinToString(System.lineSeparator()) { it.html }
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<BaseProjectHandler?>
private val childHandlers: List<BaseProjectHandler>

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()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>.enableInspections(profile: InspectionProfileImpl, project: Project) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -79,23 +76,22 @@ 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),
ToolWindowTracker(project),
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,
Expand Down Expand Up @@ -185,7 +181,7 @@ class MainPluginPanelFactory : ToolWindowFactory {
*
* @return **true** if any required field is not filled. **false** otherwise.
*/
fun checkInputs(): Promise<Boolean> =
fun checkAgreementInputs(): Promise<Boolean> =
mainWindow.executeJavaScriptAsync("checkAllInputs()").then {
val agreementChecker = Json.decodeFromString(AgreementChecker.serializer(), it.toString())
if (agreementChecker.allRequiredChecked()) {
Expand All @@ -195,6 +191,10 @@ class MainPluginPanelFactory : ToolWindowFactory {
true
}

fun checkSurveyInputs(): Promise<Boolean> = mainWindow.executeJavaScriptAsync("checkAllInputs()").then {
it.toBoolean()
}

fun openExternalUrl(url: String) = mainWindow.openExternalUrl(url)

fun setNextAction(listener: ActionListener) = nextButton.setListener(listener)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand All @@ -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.
*/
Expand All @@ -65,7 +65,6 @@ private fun Panel.selectTask(taskIds: List<String>, 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 {
Expand All @@ -83,8 +82,6 @@ fun Panel.processTask(id: String): Task {
*/
private fun Panel.solveTask(id: String, nextTasks: List<String> = 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)
Expand All @@ -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()
}
}

Expand All @@ -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()
}
Expand Down Expand Up @@ -157,6 +160,8 @@ fun Panel.processScenario() {
}

null -> {
scenario.reset()
stopTracking()
finalPage()
}
}
Expand Down
Loading
Loading