enhancement
major
minor
major
minor
major
#29575
TL Views: UI-Inspektor – Zustand eines Elements der React-Oberfläche einsehen (z. B. durch Zugriffsrechte ausgeblendete Tabellenzeilen) und als Skript-Zusicherung aufzeichnen
Kontext
Die React-Oberfläche (.view.xml-Ebene, tl-layout-view / tl-layout-react) berechnet die Zeilen einer <table> mit einem TL-Script-Ausdruck. Das Ergebnis jeder TL-Script-Ausführung wird vom Framework um die Objekte bereinigt, auf die der angemeldete Benutzer kein Leserecht hat (QueryExecutor.executeWith, SearchExpression.filterSecurity). Das ist gewollt: Eine View muss keine eigene Rechteprüfung einbauen, um keine unzugänglichen Objekte anzuzeigen.
Für den Entwickler einer View ist dieser Filter aber unsichtbar. Eine Tabelle über tl.accounts:Person, deren Zeilen eine Rolle ohne Leserecht auf Person betrachtet, ist schlicht leer – ohne Log-Eintrag, Hinweis oder Markierung. „Keine Zeilen“ und „keine Rechte“ sind nicht zu unterscheiden; das kostet bei jeder neuen Anwendung einen Analyse-Durchlauf (Ticket #29565, Arbeitsliste der Expenses-Anwendung). Ein Hinweis in der produktiven Oberfläche oder im Log kommt nicht in Frage: Eine Anwendung, die sich auf das Filtern verlässt, würde ihn überall erzeugen, und die Zahl ausgeblendeter Objekte wäre selbst eine Information, die dem Benutzer nicht zusteht.
Die klassische Oberfläche hatte für solche Fragen den GUI-Inspektor: nur in der Entwicklung freigeschaltet, zeigt er zu einem angeklickten Element dessen internen Zustand und zeichnet daraus Zusicherungen für den Skript-Rekorder auf. Die React-Oberfläche hat bereits die Datenseite dafür – die Headless-Projektion des Control-Baums (ScriptingTreeProjector, ScriptingNodeView: Adresse, Rolle, Name, Zustand, Aktionen jedes Controls) und den Zusicherungsschritt AssertCommand, der beim Abspielen einen Teil dieses Zustands prüft –, aber keine Oberfläche dazu.
Erweiterung
UI-Inspektor für die React-Oberfläche. Ein Entwickler öffnet aus der Anwendung heraus das Inspektor-Fenster (wie den Skript-Rekorder ein Seitenfenster), klickt in der Anwendung ein Element an und sieht im Inspektor:
- Adresse, Rolle, Name und React-Modul des Controls; „Übergeordnetes Element“ steigt zum umschließenden Element auf, „Aktualisieren“ zeigt den aktuellen Zustand desselben Elements,
- den projizierten Zustand als Tabelle Pfad/Wert (verschachtelte Zustände als Pfade mit Punkten, z. B. diagnostics.hiddenByAccess.count) – derselbe Zustand, den die Headless-Schnittstelle liefert und den ein Zusicherungsschritt prüft,
- die Aktionen, die das Element anbietet, mit ihren Argumentschemata.
Zu einer Tabelle gehört dabei die Diagnose ihrer Zeilenberechnung: wie viele Zeilen der Sicherheitsfilter wegen fehlender Leserechte entfernt hat, aufgeschlüsselt nach Typ (diagnostics.hiddenByAccess.count, diagnostics.hiddenByAccess.byType.<Typ>), neben der Zahl der angezeigten Zeilen (totalRowCount). Die Frage aus #29565 beantwortet der Inspektor damit direkt, in der Sitzung der Testrolle, ohne dass die produktive Anzeige oder das Log etwas davon zeigt.
Zusicherungen aufzeichnen. Läuft der Skript-Rekorder, zeichnet „Zusicherung aufzeichnen“ die in der Zustandstabelle markierten Einträge als Zusicherungsschritt auf. So entsteht z. B. die Zusicherung „diese Tabelle zeigt 3 Zeilen und keine ist durch Rechte ausgeblendet“ ohne Handarbeit an der Skriptdatei; das Abspielen des Schritts prüft die Einträge als Teilmenge des dann aktuellen Zustands.
Freischaltung. Der Inspektor ist wie der klassische verfügbar, wenn die Debug-Konfiguration das Inspizieren erlaubt (DebuggingConfig.inspectEnabled, Alias tl_enable_debug) oder der Skript-Rekorder eingeschaltet ist; es ist ein Entwicklerschalter, keine Berechtigung, damit auch die Sitzung einer Testrolle den Inspektor öffnen kann. In einer produktiven Anwendung ist er nicht erreichbar.
Lösung
Sicherheitsfilter meldet, was er entfernt (tl-model-search). SecurityFilterReport zählt die entfernten Fachobjekte (Gesamtzahl, Anzahl je TLStructuredType). SearchExpression.filterSecurity erhält Varianten mit Report; ein Aufrufer hängt ihn an den EvalContext (setSecurityReport), QueryExecutor.executeWith(EvalContext, Args) befüllt ihn. Ohne Report ändert sich nichts – kein Log, keine Kosten; bei abgeschalteter Sicherheit wird nichts gezählt. Die JavaDoc von QueryExecutor und AccessLike.lookupValue beschreibt die Asymmetrie: Das Ergebnis einer Ausführung wird gefiltert, der Wert einer Referenz (get()) nicht, weil eine Referenz keine Abfrage ist; das Lesen der Attribute eines unzugänglichen Objekts wird beim Attributzugriff selbst verweigert.
Diagnosezustand von Controls (tl-layout-react). ReactControl.putDiagnostic(key, value) / diagnostics(): ein serverseitiger Zustand, der in der Headless-Projektion (scriptingScalarState, Schlüssel ReactControl.DIAGNOSTICS = diagnostics) erscheint, aber getrennt vom Client-Zustand gehalten wird und den Browser nie erreicht. AssertCommand.mismatchingKeys vergleicht verschachtelte Zustände als Teilmenge und meldet Abweichungen als Pfad mit Punkten (AssertCommand.PATH_SEPARATOR, valueAt), so dass eine Zusicherung einzelne Diagnoseeinträge prüft, ohne an anderen zu scheitern.
Tabelle (tl-layout-view). TableElement führt die Zeilenabfrage – beim Aufbau wie bei jedem Refresh, auch in der editierbaren Variante – mit angehängtem SecurityFilterReport aus und legt das Ergebnis als Diagnose hiddenByAccess = {count, byType} am Tabellen-Control ab (TableElement.DIAGNOSTIC_HIDDEN_BY_ACCESS); wurde nichts entfernt, gibt es keinen Eintrag. <list>, <tree> und <object-list> können denselben Weg gehen, tun es hier aber nicht.
Element-Auswahl im Browser (tl-layout-react, Paket com.top_logic.layout.react.window). Die Auswahl per Klick des View-Designers ist zu einem allgemeinen ElementPicker mit einer Art PickKind (view | control) verallgemeinert: ElementPicker.start(requester, targetWindow, kind, callback) versetzt das Zielfenster in den Auswahlmodus (PickEvent über SSE, Client element-picker.ts); im Modus control meldet der Client die Id des innersten eingehängten Controls unter dem Klick, der Endpunkt react-api/pick löst sie über die SSEUpdateQueue des Fensters auf und liefert PickResult.ControlPicked(windowName, queue, control), im Modus view wie bisher den View-Pfad. Der Callback läuft unter der Sub-Session des anfragenden Fensters, so dass dessen Kanäle direkt gesetzt werden können. SelectViewCommand des Designers ist ein dünner Aufrufer mit PickKind.VIEW. ReactContext.getOpenerWindowName() liefert einem Seitenfenster das Fenster, das es geöffnet hat.
Inspektor-Fenster (tl-layout-view, Paket com.top_logic.layout.view.inspector, View ui-inspector.view.xml). Kanäle node (InspectedNode: Fenster, Adresse, ScriptingNodeView) und selection (markierte Zustandspfade). Aktionen SelectElementAction (startet die Auswahl im öffnenden Fenster, projiziert das getroffene Control – bei einem nicht adressierbaren Control das innerste adressierbare umschließende – und setzt den Kanal), InspectNodeAction (mode=PARENT|REFRESH), RecordAssertionAction (baut aus den markierten Pfaden die Teilmenge des Zustands, InspectedNode.stateSubset, und zeichnet sie über ScriptRecorder.recordAssertion im Rekorder des öffnenden Fensters auf; ohne laufende Aufzeichnung nur ein Hinweis). InspectedNodeElement setzt vorhandene Controls zusammen (Textzeilen, Zustandstabelle mit Mehrfachauswahl, Aktionstabelle) und aktualisiert sie bei Kanaländerung in place. ScriptRecorder.recordAssertion ist der eine Ort, an dem ein Zusicherungsschritt entsteht; der Agent-Endpunkt (AgentServlet) verwendet ihn ebenfalls.
Freischaltung. Ausführbarkeitsregel <inspect-enabled/> (com.top_logic.layout.view.command.InspectEnabled), die GuiInspectorUtil.isGuiInspectorEnabled() auswertet.
Demo, Dokumentation, Tests. Die React-Demo-Anwendung öffnet den Inspektor („Untersuchen“) neben dem Skript-Rekorder aus der App-Leiste (<open-window view="ui-inspector.view.xml"> mit <inspect-enabled/>; das Inspizieren ist in der Demo-Konfiguration bereits eingeschaltet). Die Kontenliste der Kachel-Demo (tl.accounts:Person) zeigt für ein Konto ohne Administrationsrechte die durch Rechte ausgeblendeten Zeilen in der Diagnose. Abschnitt „UI inspector, diagnostics and assertions“ in docs/faq/react-view-layer.md. Tests: TestSecurityFilterReport (tl-model-search), TestScriptingSession (Diagnose in der Projektion, nicht im Client-Zustand; verschachtelte Teilmengen), TestElementPicker (tl-layout-react), TestInspectedNode (tl-layout-view).
Nicht Teil dieses Tickets: ein Hinweis auf ausgeblendete Zeilen in der produktiven Anzeige; eine Änderung am Verhalten von get(); die Diagnose für <list>, <tree> und <object-list>. Beobachtung: Die Projektion einer Tabelle, deren Zeilen ein inzwischen gelöschtes Objekt enthalten, scheitert in TableViewControl.scriptingRows mit DeletedObjectAccess (Verhalten der Headless-Projektion, das der Inspektor sichtbar macht).