Skip to content

Commit

Permalink
Report Visualization Errors (#1671)
Browse files Browse the repository at this point in the history
Add `executionContext/visualisationEvaluationFailed`
notification
  • Loading branch information
4e6 authored and iamrecursion committed Apr 28, 2021
1 parent 3cb9e1b commit 7f7094f
Show file tree
Hide file tree
Showing 36 changed files with 1,425 additions and 300 deletions.
83 changes: 69 additions & 14 deletions docs/language-server/protocol-language-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ transport formats, please look [here](./protocol-architecture).
- [`executionContext/detachVisualisation`](#executioncontextdetachvisualisation)
- [`executionContext/modifyVisualisation`](#executioncontextmodifyvisualisation)
- [`executionContext/visualisationUpdate`](#executioncontextvisualisationupdate)
- [`executionContext/visualisationEvaluationFailed`](#executioncontextvisualisationevaluationfailed)
- [Search Operations](#search-operations)
- [Suggestions Database Example](#suggestions-database-example)
- [`search/getSuggestionsDatabase`](#searchgetsuggestionsdatabase)
Expand Down Expand Up @@ -151,7 +152,6 @@ transport formats, please look [here](./protocol-architecture).
- [`ModuleNotFoundError`](#modulenotfounderror)
- [`VisualisationNotFoundError`](#visualisationnotfounderror)
- [`VisualisationExpressionError`](#visualisationexpressionerror)
- [`VisualisationEvaluationError`](#visualisationevaluationerror)
- [`FileNotOpenedError`](#filenotopenederror)
- [`TextEditValidationError`](#texteditvalidationerror)
- [`InvalidVersionError`](#invalidversionerror)
Expand Down Expand Up @@ -1284,6 +1284,7 @@ destroying the context.
- [`executionContext/modifyVisualisation`](#executioncontextmodifyvisualisation)
- [`executionContext/detachVisualisation`](#executioncontextdetachvisualisation)
- [`executionContext/visualisationUpdate`](#executioncontextvisualisationupdate)
- [`executionContext/visualisationEvaluationFailed`](#executioncontextvisualisationevaluationfailed)

#### Disables

Expand Down Expand Up @@ -2912,6 +2913,51 @@ root_type VisualisationUpdate;

N/A

### `executionContext/visualisationEvaluationFailed`

Signals that an evaluation of a visualisation expression on the computed value
has failed.

- **Type:** Notification
- **Direction:** Server -> Client
- **Connection:** Protocol
- **Visibility:** Public

#### Parameters

```typescript
interface VisualisationEvaluationFailed {
/**
* An execution context identifier.
*/
contextId: ContextId;

/**
* A visualisation identifier.
*/
visualisationId: UUID;

/**
* An identifier of a visualised expression.
*/
expressionId: UUID;

/**
* An error message.
*/
message: string;

/**
* Detailed information about the error.
*/
diagnostic?: Diagnostic;
}
```

#### Errors

N/A

## Search Operations

Search operations allow requesting for the autocomplete suggestions and search
Expand Down Expand Up @@ -3456,6 +3502,9 @@ null;
The language server component also has its own set of errors. This section is
not a complete specification and will be updated as new errors are added.

Besides the required `code` and `message` fields, the errors may have a `data`
field which can store additional error-specific payload.

### `AccessDeniedError`

It signals that a user doesn't have access to a resource.
Expand Down Expand Up @@ -3603,24 +3652,30 @@ It signals that the visualisation cannot be found.
### `VisualisationExpressionError`

It signals that the expression specified in the `VisualisationConfiguration`
cannot be evaluated.
cannot be evaluated. The error contains an optional `data` field of type
[`Diagnostic`](#diagnostic) providing error details.

```typescript
"error" : {
"code" : 2007,
"message" : "Evaluation of the visualisation expression failed [i is not defined]"
}
```

### `VisualisationEvaluationError`

It is a push message. It signals that an evaluation of a code responsible for
generating visualisation data failed.

```typescript
"error" : {
"code" : 2008,
"message" : "Evaluation of the visualisation failed [cannot execute foo]"
"data" : {
"kind" : "Error",
"message" : "i is not defined",
"path" : null,
"location" : {
"start" : {
"line" : 0,
"character" : 8
},
"end" : {
"line" : 0,
"character" : 9
}
},
"expressionId" : "aa1f75c4-8c4d-493d-a6a7-72123a52f084",
"stack" : []
}
}
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,7 @@ import org.enso.languageserver.requesthandler.file.{
ReadBinaryFileHandler,
WriteBinaryFileHandler
}
import org.enso.languageserver.runtime.ContextRegistryProtocol.{
VisualisationEvaluationFailed,
VisualisationUpdate
}
import org.enso.languageserver.runtime.ContextRegistryProtocol.VisualisationUpdate
import org.enso.languageserver.session.BinarySession
import org.enso.languageserver.util.UnhandledLogging
import org.enso.languageserver.util.binary.DecodingFailure
Expand Down Expand Up @@ -121,10 +118,6 @@ class BinaryConnectionController(
case update: VisualisationUpdate =>
val updatePacket = convertVisualisationUpdateToOutPacket(update)
outboundChannel ! updatePacket

case VisualisationEvaluationFailed(_, msg) =>
val errorPacket = ErrorFactory.createVisualisationEvaluationError(msg)
outboundChannel ! errorPacket
}

private def connectionEndHandler(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,6 @@ object ErrorFactory {
): ByteBuffer =
createGenericError(0, "Service error", maybeCorrelationId)

/** Creates a visualisation expression error as a binary packet.
*
* @param msg an error message
* @return an FlatBuffer representation of the created error
*/
def createVisualisationEvaluationError(msg: String): ByteBuffer =
createGenericError(
2008,
s"Evaluation of the visualisation failed [$msg]"
)

/** Creates a generic error inside a [[FlatBufferBuilder]].
*
* @param code an error code
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,24 @@ class JsonConnectionController(
ExecutionContextExecutionStatus.Params(contextId, diagnostics)
)

case ContextRegistryProtocol.VisualisationEvaluationFailed(
contextId,
visualisationId,
expressionId,
message,
diagnostic
) =>
webActor ! Notification(
VisualisationEvaluationFailed,
VisualisationEvaluationFailed.Params(
contextId,
visualisationId,
expressionId,
message,
diagnostic
)
)

case SearchProtocol.SuggestionsDatabaseUpdateNotification(
version,
updates
Expand All @@ -230,6 +248,7 @@ class JsonConnectionController(
SearchApi.SuggestionsDatabaseUpdates,
SearchApi.SuggestionsDatabaseUpdates.Params(updates, version)
)

case InputOutputProtocol.OutputAppended(output, outputKind) =>
outputKind match {
case StandardOutput =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,5 +72,6 @@ object JsonRpc {
.registerNotification(StandardErrorAppended)
.registerNotification(WaitingForStandardInput)
.registerNotification(SuggestionsDatabaseUpdates)
.registerNotification(VisualisationEvaluationFailed)

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ import akka.actor.{Actor, ActorLogging, ActorRef, Props}
import akka.pattern.pipe
import org.enso.languageserver.data.Config
import org.enso.languageserver.runtime.ContextRegistryProtocol.{
ExecutionDiagnostic,
ExecutionDiagnosticKind,
ExecutionFailure,
ExecutionStackTraceElement,
VisualisationContext,
VisualisationUpdate
}
Expand Down Expand Up @@ -51,6 +47,8 @@ final class ContextEventsListener(
import ContextEventsListener.RunExpressionUpdates
import context.dispatcher

private val failureMapper = RuntimeFailureMapper(config)

override def preStart(): Unit = {
if (updatesSendRate.length > 0) {
context.system.scheduler.scheduleWithFixedDelay(
Expand Down Expand Up @@ -86,21 +84,33 @@ final class ContextEventsListener(
val payload =
ContextRegistryProtocol.ExecutionFailedNotification(
contextId,
toProtocolError(error)
failureMapper.toProtocolFailure(error)
)
sessionRouter ! DeliverToJsonController(rpcSession.clientId, payload)

case Api.ExecutionUpdate(`contextId`, diagnostics) =>
val payload =
ContextRegistryProtocol.ExecutionDiagnosticNotification(
contextId,
diagnostics.map(toProtocolDiagnostic)
diagnostics.map(failureMapper.toProtocolDiagnostic)
)
sessionRouter ! DeliverToJsonController(rpcSession.clientId, payload)

case Api.VisualisationEvaluationFailed(`contextId`, msg) =>
case Api.VisualisationEvaluationFailed(
`contextId`,
visualisationId,
expressionId,
message,
diagnostic
) =>
val payload =
ContextRegistryProtocol.VisualisationEvaluationFailed(contextId, msg)
ContextRegistryProtocol.VisualisationEvaluationFailed(
contextId,
visualisationId,
expressionId,
message,
diagnostic.map(failureMapper.toProtocolDiagnostic)
)
sessionRouter ! DeliverToJsonController(rpcSession.clientId, payload)

case RunExpressionUpdates if expressionUpdates.nonEmpty =>
Expand Down Expand Up @@ -198,68 +208,6 @@ final class ContextEventsListener(
case Api.ProfilingInfo.ExecutionTime(t) =>
ProfilingInfo.ExecutionTime(t)
}

/** Convert the runtime failure message to the context registry protocol
* representation.
*
* @param error the error message
* @return the registry protocol representation fo the diagnostic message
*/
private def toProtocolError(
error: Api.ExecutionResult.Failure
): ExecutionFailure =
ExecutionFailure(
error.message,
error.file.flatMap(config.findRelativePath)
)

/** Convert the runtime diagnostic message to the context registry protocol
* representation.
*
* @param diagnostic the diagnostic message
* @return the registry protocol representation of the diagnostic message
*/
private def toProtocolDiagnostic(
diagnostic: Api.ExecutionResult.Diagnostic
): ExecutionDiagnostic =
ExecutionDiagnostic(
toDiagnosticType(diagnostic.kind),
diagnostic.message,
diagnostic.file.flatMap(config.findRelativePath),
diagnostic.location,
diagnostic.expressionId,
diagnostic.stack.map(toStackTraceElement)
)

/** Convert the runtime diagnostic type to the context registry protocol
* representation.
*
* @param kind the diagnostic type
* @return the registry protocol representation of the diagnostic type
*/
private def toDiagnosticType(
kind: Api.DiagnosticType
): ExecutionDiagnosticKind =
kind match {
case Api.DiagnosticType.Error() => ExecutionDiagnosticKind.Error
case Api.DiagnosticType.Warning() => ExecutionDiagnosticKind.Warning
}

/** Convert the runtime stack trace element to the context registry protocol
* representation.
*
* @param element the runtime stack trace element
* @return the registry protocol representation of the stack trace element
*/
private def toStackTraceElement(
element: Api.StackTraceElement
): ExecutionStackTraceElement =
ExecutionStackTraceElement(
element.functionName,
element.file.flatMap(config.findRelativePath),
element.location,
element.expressionId
)
}

object ContextEventsListener {
Expand Down
Loading

0 comments on commit 7f7094f

Please # to comment.