Skip to content

Commit

Permalink
[1988] Add support for optional help text on widgets
Browse files Browse the repository at this point in the history
Bug: #1988
Signed-off-by: Pierre-Charles David <pierre-charles.david@obeo.fr>
  • Loading branch information
pcdavid committed Jun 1, 2023
1 parent 9ed75cf commit 351349f
Show file tree
Hide file tree
Showing 146 changed files with 1,906 additions and 232 deletions.
26 changes: 26 additions & 0 deletions CHANGELOG.adoc
@@ -1,5 +1,31 @@
= Changelog

== v2023.8.0 (unreleased)

=== Shapes

- Add support for Help Expressions in Form widgets

=== Architectural decision records

- [ADR-103] Add support for Help Expressions in Form widgets

=== Breaking changes

=== Dependency update

=== Bug fixes

=== New Features

=== Improvements

- https://github.com/eclipse-sirius/sirius-components/issues/1988[#1988] All Form widgets can now define (if relevant) a dynamically computed "help text".
Widgets which define such a help text have a new "?" icon next to their labels; the actual help text is accessible as a tooltip on this icon.
For View-based widgets, this materializes as an AQL `helpExpression`.
The help text can include multiple lines (separated by `\n`), but no text formatting.
:image:doc/images/Widget_Help_Tooltip.png[Example of a help tooltip on a widget]

== v2023.6.0 (unreleased)

=== Shapes
Expand Down
50 changes: 50 additions & 0 deletions doc/adrs/103_help_expressions.adoc
@@ -0,0 +1,50 @@
= ADR-103 - Add support for Help Expressions in Form widgets

== Context

In the desktop version of EEF-based properties views, widgets support an optional "Help" text which is exposed as a tooltip on widgets which define one.
This is helpful to add user-oriented documentation on the property represented by the widget.
It is not currently supported in the Sirius Components' _Form_ representation.

== Decision

All widget descriptions in the _Form_ representation (including the View-based Forms) will have an optional "help text provider".
If defined, the corresponding widget instances will have a visible "?" icon which, when hovered, will display the corresponding help text to the end-user.
The help content will be a plain text (no formatting).
It will be computed in the context of the semantic element of the widget so that the text can be adapted to the current state of the element.

== Solution

The overall design of the solution will follow the same approach as for completion support.

We will add a new (optional) field `Function<VariableManager, String> helpTextProvider` to the core `AbstractWidgetDescription` class and a `helpExpression : IntepretedExpression` attribute the the `WidgetDescription` _EClass_.

The value of the help expression will *not* be evaluated as part of the widget's rendering.
In the vast majority of form renders, no help text will need to be displayed for any widget.
Computing them eagerly would incur both an additional runtime cost to each render of each form _and_ a network transfer cost for text values which will never be visible.

Instead, the evaluation of a "help expression" will be done on-demand through a new GraphQL Query:

```
type FormDescription implements RepresentationDescription {
helpText(widgetId: ID!): String
}
```

Although the actual help expression is not evaluated at render-time, we will still compute a `hasHelpText: Boolean` field for each widget.
This will be used by the frontend to know if it should display a visual hint/icon to indicate that a help text is available for the widget.

There will be no caching of the help expression's value.
Each GraphQL Query invocation will trigger the evaluation of the expression.

On the frontend, the Form representation will fetch the `hasHelpText` for all widgets, and use that to know if some help text is available on a given widget.
When help text is available, a "?" icon (`HelpOutline`) will be added to the right of the widget's label text.
If the user hovers the mouse on the icon, the frontend will issue the GraphQL Query mentioned above to retrieve the proper help text, and display it inside a tooltip.

== Status

Accepted.

== Consequences

A similar feature could be added to all our representations (diagram elements, tree items...), but this is out of scope for the moment.
Binary file added doc/images/Widget_Help_Tooltip.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
@@ -0,0 +1,25 @@
/*******************************************************************************
* Copyright (c) 2023 Obeo.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.sirius.components.collaborative.forms.dto;

import java.util.UUID;

import org.eclipse.sirius.components.collaborative.forms.api.IFormInput;

/**
* Represents a request for the help text of a widget.
*
* @author pcdavid
*/
public record HelpTextRequestInput(UUID id, String editingContextId, String representationId, String widgetId) implements IFormInput {
}
@@ -0,0 +1,25 @@
/*******************************************************************************
* Copyright (c) 2023 Obeo.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.sirius.components.collaborative.forms.dto;

import java.util.UUID;

import org.eclipse.sirius.components.core.api.IPayload;

/**
* The payload to return the help text for a widget.
*
* @author pcdavid
*/
public record HelpTextSuccessPayload(UUID id, String text) implements IPayload {
}
@@ -0,0 +1,92 @@
/*******************************************************************************
* Copyright (c) 2022, 2023 Obeo.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.sirius.components.collaborative.forms.handlers;

import java.util.Objects;
import java.util.Optional;

import org.eclipse.sirius.components.collaborative.api.ChangeDescription;
import org.eclipse.sirius.components.collaborative.api.ChangeKind;
import org.eclipse.sirius.components.collaborative.api.Monitoring;
import org.eclipse.sirius.components.collaborative.forms.api.IFormEventHandler;
import org.eclipse.sirius.components.collaborative.forms.api.IFormInput;
import org.eclipse.sirius.components.collaborative.forms.api.IFormQueryService;
import org.eclipse.sirius.components.collaborative.forms.dto.CompletionRequestInput;
import org.eclipse.sirius.components.collaborative.forms.dto.HelpTextRequestInput;
import org.eclipse.sirius.components.collaborative.forms.dto.HelpTextSuccessPayload;
import org.eclipse.sirius.components.collaborative.forms.messages.ICollaborativeFormMessageService;
import org.eclipse.sirius.components.core.api.ErrorPayload;
import org.eclipse.sirius.components.core.api.IEditingContext;
import org.eclipse.sirius.components.core.api.IPayload;
import org.eclipse.sirius.components.forms.AbstractWidget;
import org.eclipse.sirius.components.forms.Form;
import org.springframework.stereotype.Service;

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import reactor.core.publisher.Sinks.Many;
import reactor.core.publisher.Sinks.One;

/**
* The handler for computing the help text for a widget.
*
* @author pcdavid
*/
@Service
public class HelpTextRequestEventHandler implements IFormEventHandler {
private final IFormQueryService formQueryService;

private final ICollaborativeFormMessageService messageService;

private final Counter counter;

public HelpTextRequestEventHandler(IFormQueryService formQueryService, ICollaborativeFormMessageService messageService, MeterRegistry meterRegistry) {
this.formQueryService = Objects.requireNonNull(formQueryService);
this.messageService = Objects.requireNonNull(messageService);
this.counter = Counter.builder(Monitoring.EVENT_HANDLER).tag(Monitoring.NAME, this.getClass().getSimpleName()).register(meterRegistry);
}

@Override
public boolean canHandle(IFormInput formInput) {
return formInput instanceof HelpTextRequestInput;
}

@Override
public void handle(One<IPayload> payloadSink, Many<ChangeDescription> changeDescriptionSink, IEditingContext editingContext, Form form, IFormInput formInput) {
this.counter.increment();
String message = this.messageService.invalidInput(formInput.getClass().getSimpleName(), CompletionRequestInput.class.getSimpleName());
IPayload payload = new ErrorPayload(formInput.id(), message);
ChangeDescription changeDescription = new ChangeDescription(ChangeKind.NOTHING, formInput.representationId(), formInput);

if (formInput instanceof HelpTextRequestInput input) {
Optional<AbstractWidget> optionalWidget = this.formQueryService.findWidget(form, input.widgetId());
if (optionalWidget.isPresent()) {
String helpText = this.computeHelpText(optionalWidget.get()).orElse("");
payload = new HelpTextSuccessPayload(formInput.id(), helpText);
}
}

changeDescriptionSink.tryEmitNext(changeDescription);
payloadSink.tryEmitValue(payload);
}

public Optional<String> computeHelpText(AbstractWidget widget) {
var optionalHelpTextProvider = Optional.ofNullable(widget.getHelpTextProvider());
if (optionalHelpTextProvider.isPresent()) {
String result = optionalHelpTextProvider.get().get();
return Optional.ofNullable(result);
} else {
return Optional.empty();
}
}
}

0 comments on commit 351349f

Please sign in to comment.