From e5425d35a05cc04cb36ea853b42ad6ce330f42a1 Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Tue, 12 Sep 2023 18:27:37 +0100 Subject: [PATCH 01/44] Log visualization expressions generated by IDE (#7756) close #7608 Changelog: - update: log separately the evaluation of visualization expression and its arguments - update: add visualization expression, its arguments, and the value type to the log # Important Notes Example ``` [TRACE] [2023-09-06T20:41:45+03:00] [enso] Executing visualization [VisualizationConfiguration(d195fdd8-d4e8-400f-a0d4-e50417eddd0a,ModuleMethod(MethodPointer(Standard.Visualization.Table.Visualization,Standard.Visualization.Table.Visualization,prepare_visualization),Vector(1000)),local.New_Project_1.Main)] on expression [ddc060df-9b59-48e5-bc61-aca849347343] of [class org.enso.interpreter.runtime.data.vector.Vector$Generic]... ``` --- .../org/enso/polyglot/runtime/Runtime.scala | 17 +- .../command/ModifyVisualizationCmd.scala | 2 +- .../job/ProgramExecutionSupport.scala | 29 ++- .../job/UpsertVisualizationJob.scala | 204 ++++++++++++++---- 4 files changed, 189 insertions(+), 63 deletions(-) diff --git a/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala b/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala index 281867ec4fdc..d67ef67c5bcb 100644 --- a/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala +++ b/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala @@ -607,7 +607,8 @@ object Runtime { ) ) sealed trait VisualizationExpression extends ToLogString { - def module: String + def module: String + def positionalArgumentsExpressions: Vector[String] } object VisualizationExpression { @@ -619,11 +620,13 @@ object Runtime { case class Text(module: String, expression: String) extends VisualizationExpression { + override val positionalArgumentsExpressions: Vector[String] = + Vector() + /** @inheritdoc */ override def toLogString(shouldMask: Boolean): String = s"Text(module=$module" + - s",expression=" + - (if (shouldMask) STUB else expression) + + s",expression=$expression" + ")" } @@ -644,9 +647,9 @@ object Runtime { /** @inheritdoc */ override def toLogString(shouldMask: Boolean): String = s"ModuleMethod(methodPointer=$methodPointer," + - s"positionalArgumentsExpressions=" + - (if (shouldMask) STUB else positionalArgumentsExpressions) + - s")" + "positionalArgumentsExpressions=" + + positionalArgumentsExpressions.mkString("[", ",", "]") + + ")" } } @@ -667,7 +670,7 @@ object Runtime { s"VisualizationConfiguration(" + s"executionContextId=$executionContextId," + s"expression=${expression.toLogString(shouldMask)})" + - s"visualizationModule=${visualizationModule})" + s"visualizationModule=$visualizationModule)" } /** An operation applied to the suggestion argument. */ diff --git a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/ModifyVisualizationCmd.scala b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/ModifyVisualizationCmd.scala index 44b651556c55..f8f1434f8dad 100644 --- a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/ModifyVisualizationCmd.scala +++ b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/ModifyVisualizationCmd.scala @@ -58,7 +58,7 @@ class ModifyVisualizationCmd( existingVisualization.map(_.expressionId).orElse { val jobFilter: PartialFunction[Job[_], Option[ExpressionId]] = { case upsert: UpsertVisualizationJob - if upsert.getVisualizationId() == request.visualizationId => + if upsert.visualizationId == request.visualizationId => Some(upsert.key) } ctx.jobControlPlane.jobInProgress(jobFilter) diff --git a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala index 27a70d1dc356..3125d8a09a89 100644 --- a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala +++ b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala @@ -16,6 +16,7 @@ import org.enso.interpreter.instrument.execution.{ import org.enso.interpreter.instrument.profiling.ExecutionTime import org.enso.interpreter.instrument._ import org.enso.interpreter.node.callable.FunctionCallInstrumentationNode.FunctionCall +import org.enso.interpreter.node.expression.builtin.meta.TypeOfNode import org.enso.interpreter.runtime.`type`.Types import org.enso.interpreter.runtime.callable.function.Function import org.enso.interpreter.runtime.control.ThreadInterruptedException @@ -34,7 +35,9 @@ import java.io.File import java.util.UUID import java.util.function.Consumer import java.util.logging.Level + import scala.jdk.OptionConverters._ +import scala.util.Try /** Provides support for executing Enso code. Adds convenient methods to * run Enso programs in a Truffle context. @@ -483,8 +486,14 @@ object ProgramExecutionSupport { Either .catchNonFatal { ctx.executionService.getLogger.log( - Level.FINE, - s"Executing visualization ${visualization.expressionId}" + Level.FINEST, + "Executing visualization [{0}] on expression [{1}] of [{2}]...", + Array[Object]( + visualization.config, + expressionId, + Try(TypeOfNode.getUncached.execute(expressionValue)) + .getOrElse(expressionValue.getClass) + ) ) ctx.executionService.callFunctionWithInstrument( visualization.cache, @@ -496,10 +505,6 @@ object ProgramExecutionSupport { .flatMap(visualizationResultToBytes) val result = errorOrVisualizationData match { case Left(_: ThreadInterruptedException) => - ctx.executionService.getLogger.log( - Level.FINE, - s"Visualization thread interrupted ${visualization.expressionId}." - ) Completion.Interrupted case Left(error) => @@ -507,7 +512,14 @@ object ProgramExecutionSupport { Option(error.getMessage).getOrElse(error.getClass.getSimpleName) ctx.executionService.getLogger.log( Level.WARNING, - s"Visualization evaluation failed: $message." + "Execution of visualization [{0}] on value [{1}] of [{2}] failed.", + Array[Object]( + visualization.config, + expressionId, + Try(TypeOfNode.getUncached.execute(expressionValue)) + .getOrElse(expressionValue.getClass), + error + ) ) ctx.endpoint.sendToClient( Api.Response( @@ -525,7 +537,8 @@ object ProgramExecutionSupport { case Right(data) => ctx.executionService.getLogger.log( Level.FINEST, - s"Visualization computed ${visualization.expressionId}." + s"Visualization executed [{0}].", + expressionId ) ctx.endpoint.sendToClient( Api.Response( diff --git a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/UpsertVisualizationJob.scala b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/UpsertVisualizationJob.scala index 9ba25c5264e0..0780ccb83122 100644 --- a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/UpsertVisualizationJob.scala +++ b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/UpsertVisualizationJob.scala @@ -37,7 +37,7 @@ import java.util.logging.Level */ class UpsertVisualizationJob( requestId: Option[Api.RequestId], - visualizationId: Api.VisualizationId, + val visualizationId: Api.VisualizationId, expressionId: Api.ExpressionId, config: Api.VisualizationConfiguration ) extends UniqueJob[Option[Executable]]( @@ -46,15 +46,9 @@ class UpsertVisualizationJob( false ) { - /** Return the id of the visualization associated with this job - * - * @return visualization id - */ - def getVisualizationId(): Api.VisualizationId = visualizationId - /** @inheritdoc */ override def run(implicit ctx: RuntimeContext): Option[Executable] = { - implicit val logger = ctx.executionService.getLogger + implicit val logger: TruffleLogger = ctx.executionService.getLogger val lockTimestamp = ctx.locking.acquireContextLock(config.executionContextId) try { @@ -108,7 +102,11 @@ class UpsertVisualizationJob( ctx.locking.releaseContextLock(config.executionContextId) logger.log( Level.FINEST, - s"Kept context lock [UpsertVisualizationJob] for ${System.currentTimeMillis() - lockTimestamp} milliseconds" + "Kept context lock [{0}] for {1} milliseconds.", + Array( + getClass.getSimpleName, + System.currentTimeMillis() - lockTimestamp + ) ) } } @@ -135,7 +133,7 @@ class UpsertVisualizationJob( object UpsertVisualizationJob { /** The number of times to retry the expression evaluation. */ - val MaxEvaluationRetryCount: Int = 5 + private val MaxEvaluationRetryCount: Int = 5 /** Base trait for evaluation failures. */ @@ -218,87 +216,169 @@ object UpsertVisualizationJob { else Left(ModuleNotFound(moduleName)) } - /** Evaluate the visualization expression in a given module. + /** Evaluate the visualization arguments in a given module. * * @param module the module where to evaluate arguments for the expression + * @param argumentExpressions the list of argument expression to the visualization function + * @param ctx the runtime context + * @return either the evaluation result or an evaluation failure + */ + private def evaluateArgumentExpressions( + module: Module, + argumentExpressions: Vector[String], + retryCount: Int = 0 + )(implicit + ctx: RuntimeContext + ): Either[EvaluationFailure, Vector[AnyRef]] = { + val z: Either[EvaluationFailure, Vector[AnyRef]] = Right(Vector()) + argumentExpressions.foldLeft(z) { (result, expr) => + for { + acc <- result + res <- evaluateArgumentExpression(module, expr, retryCount) + } yield acc :+ res + } + } + + /** Evaluate the visualization argument in a given module. + * + * @param module the module where to evaluate arguments for the expression + * @param argumentExpression the argument expression to the visualization function + * @param ctx the runtime context + * @return either the evaluation result or an evaluation failure + */ + private def evaluateArgumentExpression( + module: Module, + argumentExpression: String, + retryCount: Int + )(implicit + ctx: RuntimeContext + ): Either[EvaluationFailure, AnyRef] = { + Either + .catchNonFatal { + ctx.executionService.evaluateExpression(module, argumentExpression) + } + .leftFlatMap { + case _: ThreadInterruptedException + if retryCount < MaxEvaluationRetryCount => + evaluateArgumentExpression( + module, + argumentExpression, + retryCount + 1 + ) + + case error: ThreadInterruptedException => + ctx.executionService.getLogger.log( + Level.SEVERE, + "Evaluation of visualization argument [{0}] in module [{1}] was interrupted [{2}] times.", + Array[Object]( + argumentExpression, + module.getName.toString, + retryCount: Integer, + error + ) + ) + Left( + EvaluationFailed( + s"Evaluation of visualization argument was interrupted [$retryCount] times.", + ProgramExecutionSupport.getDiagnosticOutcome.lift(error) + ) + ) + + case error => + ctx.executionService.getLogger.log( + Level.SEVERE, + "Evaluation of visualization argument [{0}] failed in module [{1}] with [{2}]: {3}", + Array[Object]( + argumentExpression, + module.getName.toString, + error.getClass.getSimpleName, + error.getMessage, + error + ) + ) + Left( + EvaluationFailed( + Option(error.getMessage).getOrElse(error.getClass.getSimpleName), + ProgramExecutionSupport.getDiagnosticOutcome.lift(error) + ) + ) + + } + } + + /** Evaluate the visualization expression in a given module. + * * @param expression the visualization expression * @param expressionModule the module where to evaluate the expression * @param retryCount the number of attempted retries * @param ctx the runtime context * @return either the evaluation result or an evaluation failure */ - private def evaluateModuleExpression( - module: Module, + private def evaluateVisualizationFunction( expression: Api.VisualizationExpression, expressionModule: Module, - retryCount: Int = 0 + retryCount: Int )(implicit ctx: RuntimeContext - ): Either[EvaluationFailure, EvaluationResult] = + ): Either[EvaluationFailure, AnyRef] = Either .catchNonFatal { - val (callback, arguments) = expression match { + expression match { case Api.VisualizationExpression.Text(_, expression) => - val callback = ctx.executionService.evaluateExpression( + ctx.executionService.evaluateExpression( expressionModule, expression ) - val arguments = Vector() - (callback, arguments) case Api.VisualizationExpression.ModuleMethod( Api.MethodPointer(_, definedOnType, name), - argumentExpressions + _ ) => - val callback = ctx.executionService.prepareFunctionCall( + ctx.executionService.prepareFunctionCall( expressionModule, QualifiedName.fromString(definedOnType).item, name ) - val arguments = argumentExpressions.map( - ctx.executionService.evaluateExpression(module, _) - ) - (callback, arguments) } - EvaluationResult(module, callback, arguments) } .leftFlatMap { case _: ThreadInterruptedException if retryCount < MaxEvaluationRetryCount => - ctx.executionService.getLogger.log( - Level.FINE, - s"Evaluation of visualization was interrupted. Retrying [${retryCount + 1}]." - ) - evaluateModuleExpression( - module, + evaluateVisualizationFunction( expression, expressionModule, retryCount + 1 ) case error: ThreadInterruptedException => - val message = - s"Evaluation of visualization failed after [$retryCount] times " + - s"[${error.getClass.getSimpleName}]." - ctx.executionService.getLogger - .log( - Level.SEVERE, - message + ctx.executionService.getLogger.log( + Level.SEVERE, + "Evaluation of visualization [{0}] in module [{1}] was interrupted [{2}] times.", + Array[Object]( + expression, + expressionModule, + retryCount: Integer, + error ) + ) Left( EvaluationFailed( - message, + s"Evaluation of visualization was interrupted [$retryCount] times.", ProgramExecutionSupport.getDiagnosticOutcome.lift(error) ) ) case error => - ctx.executionService.getLogger - .log( - Level.SEVERE, - "Evaluation of visualization failed: " + - s"[${error.getClass}] ${error.getMessage}", + ctx.executionService.getLogger.log( + Level.SEVERE, + "Evaluation of visualization [{0}] failed in module [{1}] with [{2}]: {3}", + Array[Object]( + expression, + expressionModule, + error.getClass, + error.getMessage, error ) + ) Left( EvaluationFailed( Option(error.getMessage).getOrElse(error.getClass.getSimpleName), @@ -307,6 +387,36 @@ object UpsertVisualizationJob { ) } + /** Evaluate the visualization expression in a given module. + * + * @param module the module where to evaluate arguments for the expression + * @param expression the visualization expression + * @param expressionModule the module where to evaluate the expression + * @param retryCount the number of attempted retries + * @param ctx the runtime context + * @return either the evaluation result or an evaluation failure + */ + private def evaluateModuleExpression( + module: Module, + expression: Api.VisualizationExpression, + expressionModule: Module, + retryCount: Int = 0 + )(implicit + ctx: RuntimeContext + ): Either[EvaluationFailure, EvaluationResult] = { + for { + callback <- evaluateVisualizationFunction( + expression, + expressionModule, + retryCount + ) + arguments <- evaluateArgumentExpressions( + module, + expression.positionalArgumentsExpressions + ) + } yield EvaluationResult(module, callback, arguments) + } + /** Evaluate the visualization expression. * * @param module module to evaluate the expression arguments at @@ -323,12 +433,12 @@ object UpsertVisualizationJob { for { module <- findModule(module) expressionModule <- findModule(expression.module) - expression <- evaluateModuleExpression( + evaluationResult <- evaluateModuleExpression( module, expression, expressionModule ) - } yield expression + } yield evaluationResult } /** Update the visualization state. From e875781e2914bed6aef34421678e34c53f769176 Mon Sep 17 00:00:00 2001 From: somebody1234 Date: Wed, 13 Sep 2023 20:06:24 +1000 Subject: [PATCH 02/44] Vue widgets (#7728) Adds widgets: - Checkbox (with sorting) - Numeric slider - Dropdown (accepting a list of strings) - Closes #7731 - Placeholder (underscore - has no actions) # Important Notes The widgets are currently added to every node, but are not synced with the yjs representation. This is intentional, as (afaict) the format for the AST representation is not yet finalized. There are a number of design differences, for practical reasons: - The dropdown now has a scrollbar. - As a side effect, the sort button needed to be moved left, to avoid overlapping with the scrollbar. - Note that it is *not* centered in the 8px horizontal padding. It is 4px wide, and has 1px left and 3px right padding: `.||||...`. (Note that the 8px horizontal padding from the design is retained. - 4px of vertical padding has been inserted, so that there is *some* padding between the bubble for the selected item, and the outer dropdown container, when the first item is selected. Note that this is different to the 8px - 16px of right margin has been inserted after every item. This is the same amount of padding that is added by the bubble. This means that the dropdown does not change in width when a long item is selected. Design issues: - The sort button for the dropdown overlaps the text --- app/gui2/src/App.vue | 2 +- app/gui2/src/assets/base.css | 19 ++- app/gui2/src/assets/icons.svg | 33 ++++ app/gui2/src/assets/icons/sort_descending.svg | 8 + app/gui2/src/components/GraphNode.vue | 12 +- app/gui2/src/components/NavBreadcrumb.vue | 2 +- .../src/components/widgets/CheckboxWidget.vue | 28 ++++ .../src/components/widgets/DropdownWidget.vue | 156 ++++++++++++++++++ .../components/widgets/PlaceholderWidget.vue | 5 + .../src/components/widgets/SliderWidget.vue | 82 +++++++++ app/gui2/src/util/events.ts | 20 +++ 11 files changed, 359 insertions(+), 8 deletions(-) create mode 100644 app/gui2/src/assets/icons/sort_descending.svg create mode 100644 app/gui2/src/components/widgets/CheckboxWidget.vue create mode 100644 app/gui2/src/components/widgets/DropdownWidget.vue create mode 100644 app/gui2/src/components/widgets/PlaceholderWidget.vue create mode 100644 app/gui2/src/components/widgets/SliderWidget.vue diff --git a/app/gui2/src/App.vue b/app/gui2/src/App.vue index 413b30cbab7b..83bb71c01c69 100644 --- a/app/gui2/src/App.vue +++ b/app/gui2/src/App.vue @@ -1,5 +1,5 @@