Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ Client `.java` under `/registry/public/<project>/...` is synchronized by `JavaSy

A single `app.intent` YAML file at a project root is the source of truth one altitude above the model files. **The intent is an authoring artifact, not a runtime artifact** — like the `.edm` it has an editor and an explicit Generate, and (like the `.edm`) it has **no synchronizer**. Double-clicking any `*.intent` file opens the Intent Editor (`components/ui/editor-intent`): editable YAML left, live read-only diagram right (mxGraph ER + per-process flowcharts, the same engine the EDM/schema/mapping modelers use), validation inline; the Generate button runs six generators that write `.edm`/`.model`, `.bpmn`, `.form`, `.report`, `.roles` and `.csvim`/`.csv` **into the developer's workspace project at the project root** (the layout of real-world Dirigible application projects) — nothing touches the registry until normal publish, after which the per-artefact synchronizers bring the runtime live as for any project. Services: `POST /services/ide/intent/parse` and `POST /services/ide/intent/generate`. A third editor pane is a **Claude AI assistant** (`POST /services/ide/intent/agent`): it proposes the complete updated `app.intent` via the Anthropic API (key server-side in `DirigibleConfig.INTENT_AI_*`, never sent to the browser), the editor shows a Monaco diff, and Accept merges it into the buffer — the agent never writes disk or runs Generate. Developed on PR [#6017](https://github.com/eclipse-dirigible/dirigible/pull/6017).

**Detailed guide:** [`components/engine/engine-intent/CLAUDE.md`](components/engine/engine-intent/CLAUDE.md). Read it before changing anything under that module — it covers the editor-first architecture and altitude contract (model files only, never code), the YAML schema and its semantics (integer-only primary keys, `composition: true` to-one = DEPENDENT master-detail while `required` alone is just a NOT NULL FK, PascalCase property names with UPPER_SNAKE columns, decision `then`/`else`, intent-prefixed table names via `IntentNaming`), the `writeModelFile`-only write surface with the stale-output scrub, the wrong turns already made (wrong altitude, template-output paths, registry-relative vs repository-absolute paths, the `JsonHelper` Gson pitfall, **and the synchronizer-based first incarnation — do not reintroduce it**), and the follow-up list (chaining model-to-code via `.gen` descriptors, `/custom/` escape hatch). Process triggers (`trigger: { onCreate: <Entity> }`) are wired: the EDM adds a `ProcessId` field + a `triggers` collection to the `.model`, and the `template-application-events-java` template generates a `gen/events/<Process>Trigger.java` listener that starts the process on create. `IntentEngineIT` is the HTTP-only end-to-end test (~1 minute, no sync cycles). The editor's diagram pane is **mxGraph** (replacing Mermaid, which had unfixable light/dark theming bugs) with a fixed brand-colour palette that reads on both themes — see the module guide's "Intent Editor diagram = mxGraph" section before touching `editor-intent/js/editor.js`.
**Detailed guide:** [`components/engine/engine-intent/CLAUDE.md`](components/engine/engine-intent/CLAUDE.md). Read it before changing anything under that module — it covers the editor-first architecture and altitude contract (model files only, never code), the YAML schema and its semantics (integer-only primary keys, `composition: true` to-one = DEPENDENT master-detail while `required` alone is just a NOT NULL FK, PascalCase property names with UPPER_SNAKE columns, decision `then`/`else`, intent-prefixed table names via `IntentNaming`), the `writeModelFile`-only write surface with the stale-output scrub, the wrong turns already made (wrong altitude, template-output paths, registry-relative vs repository-absolute paths, the `JsonHelper` Gson pitfall, **and the synchronizer-based first incarnation — do not reintroduce it**), and the follow-up list (chaining model-to-code via `.gen` descriptors, `/custom/` escape hatch). Process triggers (`trigger: { onCreate: <Entity> }`) are wired: the EDM adds a `ProcessId` field + a `triggers` collection to the `.model`, and the `template-application-events-java` template generates a `gen/events/<Process>Trigger.java` listener that starts the process on create. That persisted `ProcessId` is in turn **consumed by the generated entity-view UI**: a shared `ProcessTasks` module (`components/resources/resources-dashboard/.../dashboard/services/process-tasks.js`) surfaces the record's actionable BPM user tasks inline via an `<entity-process-tasks>` directive (correlating `entity.ProcessId === task.processInstanceId`), wired into every generated view gated on a `hasProcess` flag; the task form completes via the permission-checked `/services/inbox/tasks/{id}` and self-closes (#6074). `IntentEngineIT` is the HTTP-only end-to-end test (~1 minute, no sync cycles). The editor's diagram pane is **mxGraph** (replacing Mermaid, which had unfixable light/dark theming bugs) with a fixed brand-colour palette that reads on both themes — see the module guide's "Intent Editor diagram = mxGraph" section before touching `editor-intent/js/editor.js`.

**The general platform line this enshrines:** authoring artifacts (`.edm`, `.model`, `.form`, `.report`, `.intent`) get **workspace editors + an explicit Generate**; only runtime artifacts (`.roles`, `.bpmn`, `.csvim`, `.table`, jobs, listeners, …) get **synchronizers**. Applying the synchronizer hammer to an authoring artifact generates into the registry where no modeler, Projects view, or template can use it — that mistake was made once and reverted; the inventory of synchronizers (grep `extends BaseSynchronizer`) deliberately contains no authoring formats.

Expand Down
1 change: 1 addition & 0 deletions components/engine/engine-intent/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@ Implemented and generating annotated client-Java off the shared `EventBinding` /
- Reports rewritten to the Dirigible `.report` shape with a materialised SQL `query` (was empty), `relation.field` -> `INNER JOIN`, `filter` -> qualified `WHERE`, and default-role `security`. Covered by `IntentEngineIT` (aggregate + join/filter reports).
- Cross-artefact PascalCase: the `.form` control `model`/`id` bind to the PascalCase EDM property name; a bare to-one relation report dimension auto-joins and shows the target's `name`-like field instead of the raw FK id.
- Process triggers (`trigger: { onCreate: <Entity> }`) fully wired in Java: validated by the parser; the new `template-application-events-java` ("Application - Glue Code - Java") template generates a `gen/events/<Process>Trigger.java` self-describing `MessageHandler` that starts the process on the entity's create event; the Java DAO template publishes that event. The EDM keeps only the persisted `ProcessId` column (`EdmIntentGenerator`). Covered by `IntentEngineIT` end-to-end (verified live: create → trigger → process start → ProcessId written back).
- **`ProcessId` consumed by the generated entity-view UI** (in-context BPM task surfacing). The `ProcessId` the trigger writes back is read by the generated views: a shared `ProcessTasks` AngularJS module (`components/resources/resources-dashboard/.../dashboard/services/process-tasks.js` — service + `<entity-process-tasks>` directive) fetches the current user's Inbox tasks once, buckets them by `processInstanceId`, and a record shows its actionable tasks inline by matching `entity.ProcessId === task.processInstanceId`. Wired into every generated view (`list`, `manage`, `master-list`/`master-manage` `detail` and `main-details`) gated on a `hasProcess` flag that `parameterUtils.js` sets when an entity has a `ProcessId` property — so non-process entities generate unchanged. The generated task form (`FormIntentGenerator`) completes via the per-task **permission-checked** `/services/inbox/tasks/{id}` (not the role-guarded `/services/bpm/bpm-processes/tasks/{id}`, which blocks candidate-group users) and self-closes on completion via both `DialogHub.closeWindow()` (dialog/inbox) and `window.close()` (standalone window). (#6074 + refinements #6075.)
- **Process glue externalized to `<intent>.glue`** (the precedent: `.report`/`.form` were lifted out of the EDM). The `triggers` + `resolvers` collections live in `.glue` (`GlueIntentGenerator`), NOT the `.model` - the EDM describes entities, the BPMN describes flow, neither owns "who starts a process / how its context is populated". The Glue-Code template binds to `extension: "glue"`; `generateUtils.js` has `triggers` + `resolvers` collection cases. (Supersedes the older "triggers in the .model" wiring.)
- **Decision resolvers (`relation.field`):** a decision condition like `book.price > 500` referencing a one-hop to-one relation of the trigger entity gets a `${JavaTask}` resolver service task inserted before the gateway and the condition rewritten to the resolved variable (`book_price`); the `gen/events/Resolve<Relation><Field>.java` `JavaDelegate` (generated from `.glue`) loads the related entity at the decision and sets the variable. `ProcessResolverSupport` + `IntentEntities` (shared perspective/PK resolution). Rewrite happens on a copy of the step list so the glue generator still sees the original path.
- **`<intent>.settings`** (`IntentSettings`, loaded/scaffolded by `IntentGenerationService.loadOrScaffoldSettings`): developer-owned, scaffolded once then preserved (not scrubbed). Holds the `generation` recipe (template id + parameters per model type), per-artefact `overrides` (`{triggers|resolvers|forms}.<name>.generate=false` -> skip and reuse a hand-written one), and `userTasks.candidateGroupsExtra` (defaults to `ADMINISTRATOR`, appended to every user-task `candidateGroups`). Loaded into `IntentGenerationContext` before generators run; honored by the Glue/Form/BPMN generators. The Generate endpoint returns a `codeGenerations` plan from the recipe + written files, and the **editor's Generate chains model->code** by replaying it through `generate.mjs`. Cross-module tenant-context fix: the Java `@Listener` dispatch (`ListenerClassConsumer`) now runs in the message's tenant context.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,16 @@
* carries a {@code callback} like {@code onApproveClicked()} wired in the {@code code} block to
* complete the current BPM user task: the Inbox/Process perspective opens the form with
* {@code ?taskId=&processInstanceId=}, and the handler POSTs {@code COMPLETE} to
* {@code /services/bpm/bpm-processes/tasks/<taskId>} with the action name and the form model as
* process variables (so a downstream gateway can branch on the action). On success the handler
* closes its host via both {@code DialogHub.closeWindow()} and {@code window.close()} - the former
* closes the dialog when the form is opened from an entity view, the latter a standalone
* (script-opened) window; each is a harmless no-op where it does not apply, including the Inbox's
* inline iframe (which clears its own pane on its refresh cycle). Forms opened outside a task
* report the missing {@code taskId} instead of failing silently. Business logic beyond completing
* the task belongs in a hand-written form override under {@code custom/}.
* {@code /services/inbox/tasks/<taskId>} (the per-task permission-checked Inbox endpoint, so a
* candidate-group user - not only ADMINISTRATOR/DEVELOPER/OPERATOR - can complete) with the action
* name and the form model as process variables (so a downstream gateway can branch on the action).
* On success the handler closes its host via both {@code DialogHub.closeWindow()} and
* {@code window.close()} - the former closes the dialog when the form is opened from an entity
* view, the latter a standalone (script-opened) window; each is a harmless no-op where it does not
* apply, including the Inbox's inline iframe (which clears its own pane on its refresh cycle).
* Forms opened outside a task report the missing {@code taskId} instead of failing silently.
* Business logic beyond completing the task belongs in a hand-written form override under
* {@code custom/}.
*
* <p>
* Idempotent: identical input always produces byte-identical output.
Expand Down Expand Up @@ -157,7 +159,7 @@ function __completeTask(action) {
__notifications.show({ type: 'negative', title: 'Cannot submit', description: 'This form was not opened from a task (no taskId).' });
return;
}
$http.post('/services/bpm/bpm-processes/tasks/' + __taskId, {
$http.post('/services/inbox/tasks/' + __taskId, {
action: 'COMPLETE',
data: Object.assign({ action: action }, $scope.model || {})
}).then(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ angular.module('ProcessTasks', ['platformLocale'])
scope: { entity: '<' },
template: `<bk-popover ng-if="tasks().length">
<bk-popover-control>
<bk-button compact="true" glyph="sap-icon--inbox" state="transparent" label="{{tasks().length}}" aria-label="{{ariaLabel}}"></bk-button>
<bk-button compact="true" glyph="sap-icon--inbox" state="transparent" label="{{label()}}" aria-label="{{ariaLabel}}"></bk-button>
</bk-popover-control>
<bk-popover-body align="bottom-right">
<bk-menu no-backdrop="true" no-shadow="true">
Expand All @@ -130,6 +130,12 @@ angular.module('ProcessTasks', ['platformLocale'])
link: (scope) => {
scope.ariaLabel = LocaleService.t('dashboard.processTasks.pending', {}, 'Pending tasks');
scope.tasks = () => ProcessTasks.getTasks(scope.entity);
// Surface the current step inline: a single actionable task shows its name (answers
// "why is there a task here?" at a glance), several collapse to a count.
scope.label = () => {
const open = scope.tasks();
return open.length === 1 ? open[0].name : LocaleService.t('dashboard.processTasks.count', { count: open.length }, open.length + ' tasks');
};
scope.openTask = (task) => ProcessTasks.openTask(task);
ProcessTasks.ensureLoaded().then(() => scope.$applyAsync());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#set($dollar = '$')
angular.module('page', ['blimpKit', 'platformView', 'platformLocale'#if($hasProcess), 'ProcessTasks'#end]).controller('PageController', ($scope, Extensions, LocaleService) => {
angular.module('page', ['blimpKit', 'platformView', 'platformLocale'#if($hasProcess), 'ProcessTasks'#end]).controller('PageController', ($scope, Extensions, LocaleService#if($hasProcess), ProcessTasks#end) => {
const Dialogs = new DialogHub();
$scope.entity = {};

Expand Down Expand Up @@ -48,6 +48,9 @@ angular.module('page', ['blimpKit', 'platformView', 'platformLocale'#if($hasProc
#end
#end
});
#if($hasProcess)
ProcessTasks.refresh();
#end
}});
//-----------------Events-------------------//

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ angular.module('page', ['blimpKit', 'platformView', 'platformLocale', 'EntitySer
.config(["EntityServiceProvider", (EntityServiceProvider) => {
EntityServiceProvider.baseUrl = '/services/java/${projectName}/gen/${javaGenFolderName}/api/${javaPerspectiveName}/${name}Controller';
}])
.controller('PageController', ($scope, ${dollar}http, Extensions, LocaleService, EntityService) => {
.controller('PageController', ($scope, ${dollar}http, Extensions, LocaleService, EntityService#if($hasProcess), ProcessTasks#end) => {
const Dialogs = new DialogHub();
const Notifications = new NotificationHub();
let description = 'Description';
Expand Down Expand Up @@ -76,6 +76,9 @@ angular.module('page', ['blimpKit', 'platformView', 'platformLocale', 'EntitySer
#end
$scope.action = 'select';
});
#if($hasProcess)
ProcessTasks.refresh();
#end
}});
Dialogs.addMessageListener({ topic: '${projectName}.${perspectiveName}.${name}.createEntity', handler: (data) => {
$scope.$evalAsync(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -943,8 +943,12 @@ private void assertForm() {
// HTML-escaped by Gson - ' becomes \\u0027, = becomes \\u003d - so match escape-free substrings;
// the form-builder un-escapes the code when it injects it into the controller.)
assertTrue(body.contains("__completeTask("), "the action buttons should complete the task");
assertTrue(body.contains("/services/bpm/bpm-processes/tasks/") && body.contains("COMPLETE"),
"the form should complete the task via the platform BPM task API");
assertTrue(body.contains("/services/inbox/tasks/") && body.contains("COMPLETE"),
"the form should complete the task via the per-task permission-checked Inbox endpoint");
assertFalse(body.contains("/services/bpm/bpm-processes/tasks/"),
"the form must not use the role-guarded BPM endpoint, which would block candidate-group users");
assertTrue(body.contains("closeWindow(") && body.contains("window.close("),
"on completion the form should close its host (dialog via closeWindow, standalone via window.close)");
assertFalse(body.contains("TODO: wire"), "the action handlers must no longer be TODO stubs");
}

Expand Down
Loading