enhancement
major
minor
defect
critical
major
minor
minor
#30218
ModificationListener: no supported way to report to the user what a Modification changed, once the commit succeeded
Problem
A tl:ModificationListener can change further data within a commit (its tl:Modification runs before the commit checks), whichever code made the triggering change (a view form, a table edit, an import in Java). Such implicit changes should be reported to the user who saved, but only after the commit succeeded, and in the React view layer.
There is no supported hook for this:
- A Modification runs before the commit checks, so a message shown there would also appear for a commit that is refused afterwards.
- The only "after a successful commit, in the committing thread" callback is tl:Committable, reached by casting the knowledge base to tl:CommitHandler (addCommittable, then complete(CommitContext)).
- From there, the only way to reach the user is InfoService.showInfo/showWarning. The view layer's own ViewMessages needs a React context that is not available in the commit, and its documentation warns that InfoService fails in React windows. A plain text fragment (Fragments.text) does reach the window's snackbar.
Example
E-Home (haumacher/ehome #111): renaming the id of a rule parameter rewrites every expression naming it in the same commit (a ModificationListener). Afterwards the user should see "3 expressions adjusted to the new id." and, for a parameter, a warning that a value set on the controller is lost. Implemented with Committable plus InfoService as described above (de.haumacher.ehome.runtime.IdRenames).
Wish
A supported way for model-level code (a ModificationListener or a Modification) to queue a message for the user of the current interaction, shown only when the commit succeeded, in both the React view layer and classic layouts.
Lösung
- After-commit action: an AfterCommitAction runs in the committing thread after the outermost transaction committed successfully, when the transaction is completely finished (it may start a transaction of its own). It is dropped when the transaction is rolled back or its commit fails, e.g. by a veto of a CommitChecker (model constraints). A failing action is logged and does not affect the commit or the other actions.
- Information about the commit: AfterCommitAction.execute(UpdateEvent changes) receives the UpdateEvent of the committed revision - the same event the UpdateListener`s get: the revision number (`getCommitNumber()), the created, updated and deleted objects and the ChangeSet (getChanges()), including the changes the Modification`s made during the commit. An action registered in a nested transaction receives the event of the outermost commit. A commit without changes creates no revision; its actions run with `null.
- It is registered on an explicit handle CommitActions that offers nothing but this registration - no lookup of a thread-current transaction:
- Transaction is such a handle, for code holding the transaction,
- a ModificationListener receives the handle of the transaction being committed as an additional parameter of createModification and notifyUpcomingDeletion, so its Modification can register an action.
- Committable is split: it keeps only the role of a participant in the commit of a further storage (prepare, prepareDelete, commit, rollback). complete(CommitContext) is removed together with its empty implementations; actions after a successful commit use AfterCommitAction.
- Reaching the user: an after-commit action reports through InfoService.showInfo/showWarning/showError(ResKey summary, ResKey detail), which reaches the message area of a classic window and the snackbar of a React window. A test sending a command through the ReactServlet confirms the React path; the outdated warning in ViewMessages that InfoService fails in React windows is removed.
- The developer article view-layer/commands describes in the section "Reporting implicit changes after the commit" how model-level code reports implicit changes after the commit.
The declarative change handler on a type or attribute (instead of a globally registered ModificationListener) is #30225.
Migration
- ModificationListener.createModification(KnowledgeBase, CommitActions, ...) and ModificationListener.notifyUpcomingDeletion(KnowledgeBase, CommitActions, KnowledgeItem) get the handle of the committed transaction as an additional second parameter. Implementations add the parameter.
- Committable.complete(CommitContext) is removed. An implementation that overrides it moves this code to an AfterCommitAction (registered on the Transaction, or in a ModificationListener on the CommitActions handle it receives; the action receives the UpdateEvent of the commit) and drops the @Override method.