enhancement
major
minor
major
minor
minor
#29679
TL Views: a command refused by its executability rule is reported with the title "Interner Fehler"
Problem
A command request for a command whose executability rule refuses it (e.g. a click posted to react-api/command for a button hidden by <authenticated-only/>, in an anonymous session) is correctly not executed. The toast reporting it, though, is titled "Interner Fehler" ("internal error"), with the text "Der Befehl kann derzeit nicht ausgeführt werden.". Seen in the TL Development app, snapshot build 42.
A refused command is an expected outcome (a stale button, a concurrent state change, a session that lost its login), not an internal error. The title alarms the user and suggests a defect of the application.
Expected
A refusal by an executability rule is shown as a notice or warning with the rule's reason. "Interner Fehler" is reserved for unexpected exceptions.
Lösung
Ein abgelehnter Befehl erscheint in der React-UI jetzt als Warnung mit dem Titel "Kommando nicht ausführbar" und dem Grund der ablehnenden Ausführbarkeitsregel als Detailzeile (z.B. "Kein Bezugsobjekt gewählt"). Die generischen Gründe "nicht ausführbar"/"ausgeblendet" wiederholen nur den Titel und werden nicht zusätzlich angezeigt. Das gilt einheitlich für jeden Weg, auf dem ein Befehl ausgelöst wird: Button, Menü, Kontextmenü, Sidebar, Dashboard-Kachel, Upload, Formularbefehl, Aktivierung einer Tabellen- oder Baumzeile, Wertabsendung eines Eingabefelds.
- Ursache: Die Ablehnungsstellen lieferten HandlerResult.error(...) ohne Titel; HandlerResult.getErrorTitle() fällt dann auf INTERNAL_ERROR ("Interner Fehler") zurück. Außerdem zeigte CommandErrors.show jedes Ergebnis als Fehler an, der Grund der Regel ging verloren, und mehrere Wege (Kontextmenü, Dashboard-Kachel, Upload) prüften die Ausführbarkeit selbst oder gar nicht und verwarfen Ablehnung und Ergebnis.
- HandlerResult.notExecutable(ExecutableState): fehlgeschlagenes Ergebnis mit Schwere WARNING, Titel "Kommando nicht ausführbar" und dem Grund des Zustands als Meldung.
- CommandErrors.show zeigt ein Ergebnis gemäß seiner Schwere als Info, Warnung oder Fehler an.
- Das CommandModel ist die einzige Instanz, die über die Ausführung entscheidet: CommandModel.executeCommand(ReactContext) prüft getExecutableState() und lehnt mit notExecutable ab; die Wirkung des Befehls liefert eine Implementierung über perform(ReactContext). Ebenso UploadCommandModel.upload(...) mit dem Hook uploadFiles(...).
- CommandModel#getExecutableState() liefert den Zustand der Regeln samt Grund (ViewCommandModel, FormCommandModel; Wrapper delegieren). ViewCommandModel wertet die Regeln bei der Ausführung für die aktuelle Eingabe neu aus.
- Alle Aufrufer geben das Ergebnis des Befehls an den Command-Handler zurück, sodass es angezeigt wird: der Auswahl-Handler von ReactMenuControl und ContextMenuOpener liefert ein HandlerResult, ebenso Dashboard-Aktivierung, Upload und das Absenden eines Eingabefelds (ReactFormFieldControl). Ein fehlgeschlagener übernommener Wert eines Eingabefelds wird direkt angezeigt.
- Kontextmenü und Dialog-Verwaltung eines Fensters melden ihre Ergebnisse an die Snackbar des Fensters (bisher fehlte ihnen die Fehlersenke, sodass Ergebnisse nur geloggt wurden).
- <authenticated-only/> liefert für den anonymen Benutzer den Grund "Diese Funktion erfordert eine Anmeldung."
- Die Schlüssel com.top_logic.layout.react.I18NConstants.ERROR_COMMAND_NOT_EXECUTABLE und com.top_logic.layout.view.I18NConstants.ERROR_FORM_COMMAND_NOT_EXECUTABLE entfallen.
Die klassische Layout-UI (CommandDispatcher) ist unverändert.
Migration
Nur relevant für Anwendungen mit eigenem Java-Code für die React-UI:
- Eine eigene Implementierung von com.top_logic.layout.react.control.button.CommandModel implementiert perform(ReactContext) statt executeCommand(ReactContext) (Methode umbenennen). executeCommand prüft die Ausführbarkeit und darf nicht mehr überschrieben werden.
- Eine eigene Implementierung von UploadCommandModel implementiert uploadFiles(...) (liefert ein HandlerResult) statt upload(...).
- Der Auswahl-Handler von ReactMenuControl (Konstruktor, setSelectHandler) ist eine Function<String, HandlerResult> statt eines Consumer<String>; der Submit-Listener von ReactFormFieldControl eine Function<Object, HandlerResult>. ReactMenuControl.MenuEntry trägt statt des Flags disabled einen ExecutableState.