pgkiln 0.50.0 → 0.52.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SECURITY.md +7 -0
- package/db/migrations/119_workspace_home.sql +30 -0
- package/db/migrations/120_datasets.sql +87 -0
- package/db/migrations/121_ai_agents.sql +94 -0
- package/db/migrations/122_team_development.sql +186 -0
- package/db/migrations/123_workflow_outcomes.sql +407 -0
- package/db/migrations/124_component_details.sql +51 -0
- package/docs/apex-feature-parity.md +15 -12
- package/docs/guide/01-installation.md +9 -3
- package/docs/guide/03-builder.md +211 -11
- package/docs/guide/04-pages-and-regions.md +96 -22
- package/docs/guide/05-items.md +7 -1
- package/docs/guide/06-processing.md +123 -8
- package/docs/guide/07-dynamic-actions.md +34 -0
- package/docs/guide/09-reference.md +2 -1
- package/docs/guide/11-from-apex.md +2 -1
- package/docs/guide/12-development.md +70 -11
- package/docs/guide/20-ai-agents.md +4 -2
- package/examples/hr/README.md +2 -0
- package/examples/hr/hr_63_datasets.sql +29 -0
- package/examples/hr/hr_64_ai_agent.sql +97 -0
- package/examples/hr/hr_65_ask_reports.sql +21 -0
- package/examples/hr/hr_66_team_development.sql +58 -0
- package/examples/hr/hr_67_workflow_outcomes.sql +113 -0
- package/examples/hr/hr_68_component_details.sql +96 -0
- package/package.json +1 -1
- package/public/app.css +46 -0
- package/public/app.js +134 -20
- package/public/builder.css +116 -0
- package/public/builder.js +47 -0
- package/src/ai/chat.ts +18 -18
- package/src/ai/service.ts +35 -11
- package/src/ai/types.ts +7 -2
- package/src/app.ts +11 -1
- package/src/builder/advisor.ts +6 -3
- package/src/builder/ai-agents.ts +372 -0
- package/src/builder/ai-builder.ts +3 -3
- package/src/builder/ai.ts +13 -9
- package/src/builder/assistant.ts +22 -7
- package/src/builder/components.ts +38 -9
- package/src/builder/datasets.ts +472 -0
- package/src/builder/designer.ts +8 -3
- package/src/builder/feedback.ts +6 -1
- package/src/builder/home.ts +12 -7
- package/src/builder/instance.ts +2 -0
- package/src/builder/locks.ts +7 -1
- package/src/builder/region-help.ts +2 -2
- package/src/builder/reporter.ts +52 -3
- package/src/builder/routes.ts +7 -4
- package/src/builder/shared.ts +1 -1
- package/src/builder/subscriptions.ts +1 -0
- package/src/builder/team.ts +991 -0
- package/src/builder/ui.ts +8 -5
- package/src/builder/workflow-migrate.ts +210 -0
- package/src/builder/workflows.ts +29 -6
- package/src/builder/workspace-home.ts +285 -0
- package/src/builder/workspace-transfer.ts +1 -1
- package/src/builder/workspaces.ts +7 -4
- package/src/cli/replace.ts +7 -1
- package/src/datasets.ts +367 -0
- package/src/i18n/ar.ts +33 -0
- package/src/i18n/cs.ts +33 -0
- package/src/i18n/da.ts +33 -0
- package/src/i18n/de.ts +33 -0
- package/src/i18n/el.ts +33 -0
- package/src/i18n/es.ts +33 -0
- package/src/i18n/fi.ts +33 -0
- package/src/i18n/fr.ts +33 -0
- package/src/i18n/he.ts +33 -0
- package/src/i18n/it.ts +33 -0
- package/src/i18n/ja.ts +33 -0
- package/src/i18n/ko.ts +33 -0
- package/src/i18n/nb.ts +33 -0
- package/src/i18n/pl.ts +33 -0
- package/src/i18n/pt.ts +33 -0
- package/src/i18n/ru.ts +33 -0
- package/src/i18n/sv.ts +33 -0
- package/src/i18n/tr.ts +33 -0
- package/src/i18n/uk.ts +33 -0
- package/src/i18n/zh.ts +33 -0
- package/src/i18n.ts +66 -0
- package/src/instance.ts +3 -0
- package/src/metadata.ts +9 -3
- package/src/release-check.ts +139 -0
- package/src/reporting-app.ts +101 -0
- package/src/runtime/ai-agents.ts +365 -0
- package/src/runtime/ai-filter.ts +385 -107
- package/src/runtime/assistant.ts +64 -11
- package/src/runtime/context.ts +2 -0
- package/src/runtime/data-reporter.ts +199 -32
- package/src/runtime/ir-phrases.ts +812 -0
- package/src/runtime/ir-plan.ts +449 -0
- package/src/runtime/items.ts +1 -1
- package/src/runtime/regions.ts +6 -2
- package/src/runtime/render.ts +1 -1
- package/src/runtime/report.ts +1 -1
- package/src/runtime/tasks.ts +63 -12
- package/src/session.ts +2 -0
- package/src/subscriptions.ts +2 -1
- package/src/task-events.ts +232 -0
- package/src/team-samples.ts +74 -0
- package/src/team.ts +342 -0
- package/src/workflow.ts +135 -18
- package/src/workspace-export.ts +39 -4
|
@@ -256,7 +256,7 @@ Features for end users:
|
|
|
256
256
|
computed columns and views under a name, and switch between saved reports (below);
|
|
257
257
|
- **rows per page**, **download CSV / Excel / PDF**, **print**, **reset**.
|
|
258
258
|
- Active search, filters, break, aggregates, highlights, computed columns and views appear as
|
|
259
|
-
removable chips. Once a group by, pivot or chart is set, **Report / Group by / Pivot / Chart**
|
|
259
|
+
removable chips (with *Ask in your own words*, as chips with Edit and Remove under the question box). Once a group by, pivot or chart is set, **Report / Group by / Pivot / Chart**
|
|
260
260
|
links switch between the views; all views use the same search, filters and facets.
|
|
261
261
|
- **Paging** with Previous/Next.
|
|
262
262
|
- On phones every row **reflows** into a card with labelled values.
|
|
@@ -267,26 +267,71 @@ and highlight values become literals, columns must exist in the result, operator
|
|
|
267
267
|
functions, chart types and colors come from fixed lists, sort positions are integers, and computed
|
|
268
268
|
column expressions are parsed (below), never pasted into SQL.
|
|
269
269
|
|
|
270
|
-
###
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
The
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
270
|
+
### Ask in your own words (AI interactive reports)
|
|
271
|
+
|
|
272
|
+
An interactive report can take a request in the user's own words (APEX 26.1/26.2: AI interactive
|
|
273
|
+
reports): *"sum of salary by department where job is clerk"*, *"managers hired before 1982, highest
|
|
274
|
+
salary first"*, *"bonus is 10% of salary"*. Set `"ai_filter": {}` in the report's attributes (or
|
|
275
|
+
*Ask in your own words* under the report's settings in the page designer); a question box appears
|
|
276
|
+
above the report.
|
|
277
|
+
|
|
278
|
+
The answer is never SQL: it becomes the report's **own settings**, the same ones the Actions menu
|
|
279
|
+
sets: filters, highlights, a search, a sort, a control break, totals (aggregates), a group by, a
|
|
280
|
+
pivot, a chart and computed columns. They are listed under the box as **chips**; each chip has
|
|
281
|
+
**Edit** (a small form with its column, operator, value, function, color or expression) and
|
|
282
|
+
**Remove**, and **Undo** returns to the settings before the last change (the last five steps, kept
|
|
283
|
+
in the session). A new request refines the current settings: a filter on a column replaces that
|
|
284
|
+
column's filters, a sort, break, group by, pivot or chart replaces the old one, highlights, totals
|
|
285
|
+
and computed columns are added; *"start over"* clears them. The chips are buttons of one form, so
|
|
286
|
+
they work without JavaScript.
|
|
287
|
+
|
|
288
|
+
**Without an AI service** pgkiln reads the request itself with a rule-based parser for common
|
|
289
|
+
phrases (APEX 26.2: works with zero AI). Columns are recognised by name or heading (also without
|
|
290
|
+
spaces, and verbs such as *hired* for `hiredate`); values such as *"only clerks"* or *"in sales"*
|
|
291
|
+
are looked up among the report's own rows (as a query parameter); what isn't understood is said
|
|
292
|
+
in the message, not guessed. Phrases it understands, in English (always), Dutch and German (when
|
|
293
|
+
that is the user's language):
|
|
294
|
+
|
|
295
|
+
| Request | Becomes |
|
|
296
|
+
|---|---|
|
|
297
|
+
| *salary over 3000*, *salary between 1,000 and 3k*, *hired before june 1981*, *hired in 1981*, *name contains "mi"*, *no commission* | filters (`gt`, `ge` + `le`, `lt`, a date range, `contains`, `null`) |
|
|
298
|
+
| *only clerks*, *not clerks*, *in sales*, *active* | a filter on the column that holds the value |
|
|
299
|
+
| *sort by hire date, newest first*, *highest salary first* | the sort |
|
|
300
|
+
| *sum of salary by department*, *count by department and job* | a group by with its function |
|
|
301
|
+
| *total salary*, *average salary* | a totals row |
|
|
302
|
+
| *break by department* | a control break |
|
|
303
|
+
| *pivot sum of salary by department and job* | a pivot |
|
|
304
|
+
| *pie chart of count by department*, *chart of salary by job* | a chart |
|
|
305
|
+
| *highlight salary over 4000 in red* | a highlight |
|
|
306
|
+
| *bonus is 10% of salary*, *yearly = sal * 12* | a computed column |
|
|
307
|
+
| *search for smith* | the search |
|
|
308
|
+
| *start over*, *clear all* | no settings |
|
|
309
|
+
|
|
310
|
+
Dutch (*salaris boven 3000*, *sorteer op datum aflopend*, *som van salaris per afdeling*, *alleen
|
|
311
|
+
klerken*, *bonus is 10% van salaris*) and German (*Gehalt über 3000*, *sortiere nach Datum
|
|
312
|
+
absteigend*, *Summe von Gehalt pro Abteilung*, *nur Angestellte*) work the same way; another
|
|
313
|
+
language adds a phrase table to `src/runtime/ir-phrases.ts`.
|
|
314
|
+
|
|
315
|
+
**With an AI service** (`"ai_filter": {"service": "NAME"}`, an [AI
|
|
316
|
+
service](06-processing.md#generate-text-with-ai) the application may use), the request goes to the
|
|
317
|
+
service with the report's shown columns (names, headings and kinds: text, number, date, boolean),
|
|
318
|
+
its operators and functions and the current settings; the answer is a structured output. When the
|
|
319
|
+
service is disabled or not allowed for the application, the built-in phrases answer instead. The
|
|
320
|
+
request and the column names go to the service's provider; logged in the AI usage log with the
|
|
321
|
+
source `nl2ir`.
|
|
322
|
+
|
|
323
|
+
Either way the answer is checked again before it is applied: only the report's shown columns
|
|
324
|
+
(`hidden` columns are neither offered nor accepted), operators, functions, chart types and colors
|
|
325
|
+
from the report's fixed lists, at most five filters, highlights and totals, short values (which
|
|
326
|
+
become escaped literals or query parameters), and computed expressions that pass the same
|
|
327
|
+
validator as Actions → Compute (below). A chip edit is checked the same way; a form that names a
|
|
328
|
+
hidden column, an unknown operator or a chip that isn't there is refused. The report builds the
|
|
329
|
+
SQL and runs it as the application's role. Signed-in users only, unless `"public": true`.
|
|
286
330
|
|
|
287
331
|
| Key | Meaning |
|
|
288
332
|
|---|---|
|
|
289
|
-
| `ai_filter
|
|
333
|
+
| `ai_filter` | `{}` for the built-in phrases |
|
|
334
|
+
| `ai_filter.service` | An AI service's name (the built-in phrases when it isn't available) |
|
|
290
335
|
| `ai_filter.placeholder` | The question box's placeholder |
|
|
291
336
|
| `ai_filter.public` | Also for users who are not signed in |
|
|
292
337
|
|
|
@@ -1186,9 +1231,12 @@ Attributes (written by the region's **Settings**):
|
|
|
1186
1231
|
|
|
1187
1232
|
| Key | Default | Meaning |
|
|
1188
1233
|
|---|---|---|
|
|
1189
|
-
| `sources` | | `[{"id": "orders", "label": "Orders", "description": "…", "schema": "sales", "table": "order_v", "columns": [{"name": "total", "label": "Total", "format": "FML999G990D00"}]}]` |
|
|
1234
|
+
| `sources` | | `[{"id": "orders", "label": "Orders", "description": "…", "schema": "sales", "table": "order_v", "columns": [{"name": "total", "label": "Total", "format": "FML999G990D00"}]}]`; (sprint 110) or a dataset: `{"id": "sales", "dataset": "sales"}` (label and description optional) |
|
|
1190
1235
|
| `sharing` | `true` | `false`: reports stay private |
|
|
1191
1236
|
| `share_authz` | | An authorization scheme: only users that pass it may share reports |
|
|
1237
|
+
| `edit_authz` | | (sprint 110) An authorization scheme: only users that pass it build, change, save and delete reports; the others only run shared ones |
|
|
1238
|
+
| `admin_authz` | | (sprint 110) An authorization scheme: users that pass it unpublish and delete anyone's reports |
|
|
1239
|
+
| `publishing` | `false` | (sprint 110) `true`: shared reports are *published*: a stable URL, Publish / Unpublish buttons, pins |
|
|
1192
1240
|
| `page_size` | `25` | Rows per page of a detail report (5 to 200) |
|
|
1193
1241
|
| `welcome` | `true` | `false`: the compact start (a list of sources and *Start*) instead of the welcome page |
|
|
1194
1242
|
| `welcome_title`, `welcome_text` | | The welcome page's greeting instead of pgkiln's (translated like other application texts) |
|
|
@@ -1200,6 +1248,27 @@ region when an application is replaced. The HR example's page 36 (*My reports*)
|
|
|
1200
1248
|
(a view without the user name and photo columns) and leave requests (row level security: an
|
|
1201
1249
|
employee sees only their own), with King's shared report *Salary by department*.
|
|
1202
1250
|
|
|
1251
|
+
#### Datasets and reporting applications
|
|
1252
|
+
|
|
1253
|
+
(sprint 110) A source may be a **dataset** of the workspace ([App Builder → Data
|
|
1254
|
+
Reporter](03-builder.md#data-reporter-datasets-and-reporting-applications)): tables and views of one schema with
|
|
1255
|
+
their joins, the offered columns with labels, format masks and descriptions (shown under the column in the editor),
|
|
1256
|
+
and a row filter. The region's Settings add one under *Add a data source → Or a dataset of the workspace*. The
|
|
1257
|
+
runtime reads the dataset when the page is drawn and builds its SELECT from the definition (identifiers quoted,
|
|
1258
|
+
values escaped literals, operators from the whitelist), and the report runs on it as a subquery, as the
|
|
1259
|
+
application's database role. A dataset that was deleted disappears from the region; a column that no longer exists
|
|
1260
|
+
makes the report show an error.
|
|
1261
|
+
|
|
1262
|
+
With **`publishing`**, shared reports are *published*: an editor saves a report privately and **Publishes** it
|
|
1263
|
+
(or ticks *Publish for all users* when saving), and **Unpublishes** it again. A published report has a **stable
|
|
1264
|
+
URL**, `/a/<alias>/reports/<id>`, shown on the report: it opens the report on its page (signing in first), so it
|
|
1265
|
+
can be bookmarked or sent. Every signed-in user may **Pin** a published report: pinned reports come first on the
|
|
1266
|
+
welcome page (a [preference](06-processing.md) of the user, `reporter.pins.<region>`). Users that don't pass
|
|
1267
|
+
`edit_authz` see only the published reports: no *New report* cards, no editor (report parameters in the URL are
|
|
1268
|
+
ignored), no saving. Users that pass `admin_authz` unpublish and delete anyone's reports. The HR example's dataset
|
|
1269
|
+
`hr` (`examples/hr/hr_63_datasets.sql`: employees with their department, active ones only) is the one a reporting
|
|
1270
|
+
application is made from in the guide's chapter 3.
|
|
1271
|
+
|
|
1203
1272
|
### `form_designer` (Form Designer)
|
|
1204
1273
|
|
|
1205
1274
|
A **Form Designer** region (sprint 85; pgkiln's own, APEX has no counterpart) lets the application's
|
|
@@ -1273,8 +1342,12 @@ forms *Departments* and *Book a meeting*.
|
|
|
1273
1342
|
### `ai_assistant` (AI assistant)
|
|
1274
1343
|
|
|
1275
1344
|
An **AI assistant** region (APEX: AI Assistant, AI agents and tools) is a chat with an
|
|
1276
|
-
[AI service](06-processing.md#generate-text-with-ai) (Claude
|
|
1277
|
-
configured and allowed for the application.
|
|
1345
|
+
[AI service](06-processing.md#generate-text-with-ai) (Claude, OpenAI, Gemini or an OpenAI-compatible
|
|
1346
|
+
endpoint) that an administrator configured and allowed for the application. Either the region names an
|
|
1347
|
+
**[AI agent](03-builder.md#ai-agents)** of the application (`"agent": "SALES_AGENT"`: the agent sets the
|
|
1348
|
+
service, system prompt, reasoning effort, tools, steps and output limit, and its authorization scheme
|
|
1349
|
+
decides who may use the region; tools may also be PL/pgSQL functions, JavaScript actions and links to
|
|
1350
|
+
pages), or the developer gives the region a system prompt and, optionally,
|
|
1278
1351
|
**context queries** and **tools**; the model then answers questions about the application's data,
|
|
1279
1352
|
calling the tools when it needs to, like an agent. It works without JavaScript (the message is
|
|
1280
1353
|
posted and the page comes back with the answer); with JavaScript the Send button shows that the
|
|
@@ -1320,6 +1393,7 @@ Attributes (written by the region's **Settings**; they travel with the applicati
|
|
|
1320
1393
|
|
|
1321
1394
|
| Key | Default | Meaning |
|
|
1322
1395
|
|---|---|---|
|
|
1396
|
+
| `agent` | | (121) An [AI agent](03-builder.md#ai-agents) of the application; then leave out `service`, `system`, `tools` and `max_rounds` |
|
|
1323
1397
|
| `service` | | The AI service's name |
|
|
1324
1398
|
| `system` | | The system prompt (`&ITEM.` substitutions as delimited data) |
|
|
1325
1399
|
| `welcome` | | The first message shown (translatable) |
|
|
@@ -1333,7 +1407,7 @@ Attributes (written by the region's **Settings**; they travel with the applicati
|
|
|
1333
1407
|
|
|
1334
1408
|
The HR example's page 38 (*HR assistant*) has an assistant with a context query over `hr.policy`
|
|
1335
1409
|
and four tools (the user's own leave, colleagues, departments, and *request_leave*, which files a
|
|
1336
|
-
leave request), next to a staff list with [*Ask in your own words*](#
|
|
1410
|
+
leave request), next to a staff list with [*Ask in your own words*](#ask-in-your-own-words-ai-interactive-reports).
|
|
1337
1411
|
|
|
1338
1412
|
### Template components
|
|
1339
1413
|
|
package/docs/guide/05-items.md
CHANGED
|
@@ -217,6 +217,11 @@ item's config. A combobox is free text and isn't checked.
|
|
|
217
217
|
A `popup_lov` item shows the chosen value's display text and a **Search** button. The button
|
|
218
218
|
opens a dialog with a search field: the server searches the item's own list of values (case
|
|
219
219
|
insensitive, on the display column and any extra columns) and answers one page at a time.
|
|
220
|
+
**The next page loads as the list scrolls** (APEX: infinite scrolling): when the end of the list
|
|
221
|
+
comes into view the dialog fetches the next `page_size` rows and adds them, until the list of
|
|
222
|
+
values has no more, so a list of thousands of rows scrolls to its end without a *More* button. A
|
|
223
|
+
new search starts over at the first page. A browser without `IntersectionObserver` pages with
|
|
224
|
+
‹ and *More* instead.
|
|
220
225
|
|
|
221
226
|
- **Several columns**: the first column is the display value, the second the return value, and
|
|
222
227
|
any further columns are shown in the dialog (and searched), for example:
|
|
@@ -233,7 +238,8 @@ insensitive, on the display column and any extra columns) and answers one page a
|
|
|
233
238
|
parameter (never SQL text), only for an item the user may see and change on a page they may
|
|
234
239
|
open. A submitted value must be one the list of values returns, or the page shows an error.
|
|
235
240
|
|
|
236
|
-
The HR example's page 26 (*Pick an employee*) has one with name, job and department columns
|
|
241
|
+
The HR example's page 26 (*Pick an employee*) has one with name, job and department columns;
|
|
242
|
+
page 47 (*Component details*) one over 6,000 rows, 100 a page.
|
|
237
243
|
|
|
238
244
|
## Cascading lists of values
|
|
239
245
|
|
|
@@ -28,13 +28,14 @@ simply closes the dialog.
|
|
|
28
28
|
### Menu buttons and badges
|
|
29
29
|
|
|
30
30
|
A button with action `menu` opens a dropdown (an HTML `<details>` element, so it works without
|
|
31
|
-
JavaScript). `menu` is a JSON array of at most 20 entries; each is
|
|
32
|
-
the application
|
|
31
|
+
JavaScript). `menu` is a JSON array of at most 20 entries; each is a **link** to a page of
|
|
32
|
+
the application, a **submit request** or an **action** that runs dynamic actions:
|
|
33
33
|
|
|
34
34
|
```json
|
|
35
35
|
[{"label": "Leave requests", "page": 6, "icon": "list"},
|
|
36
36
|
{"label": "Details", "page": 3, "items": {"P3_EMPNO": "&P22_EMPNO."}},
|
|
37
|
-
{"label": "Start over", "request": "RESET", "confirm": "Clear the form?", "authz": "ADMIN"}
|
|
37
|
+
{"label": "Start over", "request": "RESET", "confirm": "Clear the form?", "authz": "ADMIN"},
|
|
38
|
+
{"label": "Export", "action": "EXPORT", "icon": "download"}]
|
|
38
39
|
```
|
|
39
40
|
|
|
40
41
|
| Key | Meaning |
|
|
@@ -42,6 +43,7 @@ the application or a **submit request**:
|
|
|
42
43
|
| `label` | The entry's text (1–100 characters) |
|
|
43
44
|
| `page`, `items` | A link: the page and the items to set (signed with a checksum, like any link) |
|
|
44
45
|
| `request` | A submit: the page is submitted with this request, as if a button of that name was pressed. Validations, processes and branches with `when_button` see it |
|
|
46
|
+
| `action` | Runs the [dynamic actions of event `menu_entry`](07-dynamic-actions.md#menu-entry-chosen) with this name (capitals, digits and `_`) as trigger (APEX: Menu entry chosen) |
|
|
45
47
|
| `confirm` | A confirmation question first |
|
|
46
48
|
| `authz` | The entry only exists for users who pass this authorization scheme |
|
|
47
49
|
| `icon` | An [icon](09-reference.md#icons) name |
|
|
@@ -852,6 +854,9 @@ application ask someone to approve, reject or complete something, and act on the
|
|
|
852
854
|
| When overdue, Maximum renewals, Escalation role | The expiration policy ([below](#overdue-tasks-expire-renew-escalate)); empty: overdue tasks stay open |
|
|
853
855
|
| Details page, Details item | The subject links to this page, setting the item to the record key |
|
|
854
856
|
| On completion (SQL) | Runs as the application's role when the task is approved, rejected or completed (below) |
|
|
857
|
+
| Outcomes | Custom outcomes instead of Approve / Reject ([below](#custom-outcomes)) |
|
|
858
|
+
| Events, When the completion SQL fails | Actions when a task is completed, expires or its completion SQL fails ([below](#task-events-and-notifications)) |
|
|
859
|
+
| Notifications | E-mail templates pgkiln sends when a task is created, completed or expires |
|
|
855
860
|
|
|
856
861
|
**2. Application SQL creates tasks**, for example in the process that saves a request or in a
|
|
857
862
|
trigger:
|
|
@@ -903,6 +908,62 @@ When a record is decided somewhere else (for example with buttons on its own pag
|
|
|
903
908
|
open tasks so they don't linger: `select meta.close_tasks('EXPENSE_APPROVAL', :P5_ID::text,
|
|
904
909
|
'approved')` (no completion SQL runs).
|
|
905
910
|
|
|
911
|
+
<a id="custom-outcomes"></a>**Custom outcomes** (APEX 26.2: task outcomes). A task definition may list its own outcomes
|
|
912
|
+
instead of Approve / Reject (or Complete):
|
|
913
|
+
|
|
914
|
+
```json
|
|
915
|
+
[{"name": "YES", "label": "Yes", "colour": "success"},
|
|
916
|
+
{"name": "NO", "label": "No", "colour": "danger"},
|
|
917
|
+
{"name": "UNDECIDED", "label": "Undecided", "colour": "warning"}]
|
|
918
|
+
```
|
|
919
|
+
|
|
920
|
+
The task list then shows one button per outcome, with its label and colour (`default`, `primary`, `success`,
|
|
921
|
+
`danger` or `warning`: a fixed list, so a definition can't add other classes). The outcome is stored in lower case
|
|
922
|
+
(`meta.tasks.outcome = 'yes'`), `:OUTCOME` in the completion SQL is the name (`YES`), and the history shows the label.
|
|
923
|
+
A task ends only with an outcome of its definition: `meta.complete_task` refuses any other (also Approve / Reject
|
|
924
|
+
on a definition with its own outcomes), and so does `meta.close_tasks(name, detail_pk, outcome)`. A
|
|
925
|
+
[workflow](#workflows) branches on them in a task step's `next` (`{"yes": "PAY", "no": "END"}`) or a switch case
|
|
926
|
+
`{"outcome": "UNDECIDED", "next": "ASK_AGAIN"}`. Names are letters, digits and underscores (at most 30, at most 10
|
|
927
|
+
outcomes); `cancelled`, `expired` and `default` are reserved.
|
|
928
|
+
|
|
929
|
+
<a id="task-events-and-notifications"></a>**Task events and notifications** (APEX 26.2: Human Task events). A
|
|
930
|
+
definition's *Events* are actions on what happens to its tasks:
|
|
931
|
+
|
|
932
|
+
```json
|
|
933
|
+
[{"on": "complete", "type": "sql", "code": "select expenses.log_decision(:DETAIL_PK::int, :OUTCOME, :APPROVER)"},
|
|
934
|
+
{"on": "expire", "type": "mail", "to": "select email from staff where role = 'manager'", "template": "TASK_EXPIRED"},
|
|
935
|
+
{"on": "fail", "type": "workflow", "workflow": "FIX_REQUEST", "vars": {"REASON": "&ERROR."}}]
|
|
936
|
+
```
|
|
937
|
+
|
|
938
|
+
| `on` | When |
|
|
939
|
+
|---|---|
|
|
940
|
+
| `complete` | The task was completed, with any outcome (also by `meta.close_tasks` with an outcome) |
|
|
941
|
+
| `expire` | The task expired ([below](#overdue-tasks-expire-renew-escalate)) |
|
|
942
|
+
| `fail` | The definition's completion SQL raised an error |
|
|
943
|
+
|
|
944
|
+
| `type` | Does | Fields |
|
|
945
|
+
|---|---|---|
|
|
946
|
+
| `sql` | Runs statements as the application's role | `code` |
|
|
947
|
+
| `mail` | Queues an e-mail through [`meta.send_mail_template`](#e-mail-templates) or [`meta.send_mail`](#sending-e-mail) | `to` (a SELECT returning addresses, or addresses with `&NAME.`), `template`, or `subject` and `body` with `#NAME#` placeholders |
|
|
948
|
+
| `workflow` | Starts a workflow with the task's record key and parameters | `workflow`, `vars` (variable → text, `&NAME.` allowed) |
|
|
949
|
+
|
|
950
|
+
Binds (and `#NAME#` placeholders): the task's parameters, `:TASK_ID`, `:DETAIL_PK`, `:SUBJECT`, `:OUTCOME`,
|
|
951
|
+
`:APPROVER`, `:INITIATOR`, `:EVENT`, and for `fail` `:ERROR` and `:COMMENT`. *Complete* and *expire* events run
|
|
952
|
+
right after the commit: a trigger queues them (`meta.task_hook`, closed to applications) and the server runs each
|
|
953
|
+
in a transaction of its own, as the application's role with the person who completed the task (or the initiator) as
|
|
954
|
+
`meta.app_user()` and the task's tenant; one that fails is tried three times and keeps its error on the
|
|
955
|
+
`meta.task_hook` row. *Fail* events run in the request that completed the task: the completion SQL's effect is
|
|
956
|
+
undone, the failure is in the task's history, the actions run, and **When the completion SQL fails** says what
|
|
957
|
+
happens to the task: `reopen` (the default) undoes the decision so the task stays open, `complete` keeps it completed
|
|
958
|
+
without the SQL's effect. The user sees the error either way. Without fail events or a recovery, an error undoes the
|
|
959
|
+
decision as before.
|
|
960
|
+
|
|
961
|
+
**Notifications** are e-mails pgkiln sends itself, from the application's
|
|
962
|
+
[e-mail templates](#e-mail-templates): `{"create": "TASK_CREATED", "complete": "TASK_DONE", "expire": "TASK_EXPIRED"}`.
|
|
963
|
+
*create* goes to the potential owners (the named users, the actual owner and the application's users with one of
|
|
964
|
+
the owner roles), *complete* to the initiator, *expire* to both, at the e-mail address of their account. The
|
|
965
|
+
placeholders are those of the events. They need a mail service, like `meta.send_mail`.
|
|
966
|
+
|
|
906
967
|
#### Overdue tasks: expire, renew, escalate
|
|
907
968
|
|
|
908
969
|
A task definition with *Due in* can say what happens to a task that is still open after its due date (APEX:
|
|
@@ -930,9 +991,9 @@ workflow whose task is cancelled.
|
|
|
930
991
|
| `meta.task_events` | View: the history and comments of those tasks |
|
|
931
992
|
|
|
932
993
|
The task tables themselves are closed to the application's role: rights are checked by these
|
|
933
|
-
functions and views, for every call. To tell owners by e-mail,
|
|
934
|
-
[`meta.send_mail`](#sending-e-mail) from the task
|
|
935
|
-
reminds owners of overdue tasks; or put a task list on the home page.
|
|
994
|
+
functions and views, for every call. To tell owners by e-mail, use the definition's
|
|
995
|
+
[notifications](#task-events-and-notifications), call [`meta.send_mail`](#sending-e-mail) from the task
|
|
996
|
+
definition's SQL or from an automation that reminds owners of overdue tasks; or put a task list on the home page.
|
|
936
997
|
|
|
937
998
|
**Example:** in the HR sample application, a leave request creates a `LEAVE_APPROVAL` task for the
|
|
938
999
|
employee's manager; *My tasks* (page 14) approves or rejects it through `hr.decide_leave`, the same
|
|
@@ -954,9 +1015,15 @@ steps; the builder checks them and draws the flow.
|
|
|
954
1015
|
| `invoke_api` | Calls a REST data source or a URL on the server; values of the response become variables ([below](#invoke-api-steps)) | `source` + `params`, or `url`, `method`, `credential`, `body`; `variables`, `status_variable`, `response_variable`, `timeout`, `next` |
|
|
955
1016
|
| `parallel` | Starts a branch at each of its steps; they run side by side | `branches` (the first step of each, at least two), `join` |
|
|
956
1017
|
| `join` | Where the branches of a `parallel` step meet | `wait_for` (`all`, the default, or `any`), `next` |
|
|
1018
|
+
| `send_mail` | Queues an e-mail ([below](#send-e-mail-steps)) | `to`, `template` (or `subject` and `body`), `cc`, `next` |
|
|
1019
|
+
| `draft` | A placeholder while modelling: only in a development version ([below](#draft-steps)) | `note`, `next` |
|
|
957
1020
|
| `end` | Ends the workflow | |
|
|
958
1021
|
|
|
959
1022
|
`next` is optional: the following step in the list (after the last one, the workflow is complete).
|
|
1023
|
+
A task step's `next` per outcome names the outcomes of its task definition, [custom ones](#custom-outcomes) too,
|
|
1024
|
+
plus `cancelled`, `expired` and `default`. A `switch` case is either `{"when": "SQL condition", "next": …}` or
|
|
1025
|
+
`{"outcome": "YES", "next": …}`, which compares the outcome of the last task (`:TASK_OUTCOME`); the builder checks
|
|
1026
|
+
that a task of the workflow can end with it.
|
|
960
1027
|
|
|
961
1028
|
```json
|
|
962
1029
|
[{"name": "CHECK", "type": "switch", "cases": [{"when": ":AMOUNT::numeric > 1000", "next": "DIRECTOR"}], "otherwise": "MANAGER"},
|
|
@@ -976,7 +1043,7 @@ select meta.start_workflow('EXPENSE', :P5_ID, jsonb_build_object('AMOUNT', :P5_A
|
|
|
976
1043
|
|
|
977
1044
|
The variables (upper case) are binds in every step, with `:DETAIL_PK`, `:WORKFLOW_ID` and
|
|
978
1045
|
`:INITIATOR`; after a task step also `:TASK_OUTCOME` (`APPROVED`, `REJECTED`, `COMPLETED`,
|
|
979
|
-
`CANCELLED`) and `:TASK_APPROVER`. The title may use `&VAR.`.
|
|
1046
|
+
`CANCELLED`, `EXPIRED` or a custom outcome such as `YES`) and `:TASK_APPROVER`. The title may use `&VAR.`.
|
|
980
1047
|
|
|
981
1048
|
**How it runs.** The pgkiln server runs workflows: right after they start or a task of theirs
|
|
982
1049
|
ends (`NOTIFY`), and it checks for waits that are over every few seconds (`WORKFLOW_INTERVAL_S`,
|
|
@@ -1008,6 +1075,29 @@ background runs with the tenant of the session that queued it. Filter your own t
|
|
|
1008
1075
|
(`where tenant_id = meta.tenant_id()`, or a row level security policy). Use row level security in
|
|
1009
1076
|
the tenant's tables as well: the tenant is a value the application sets, not a database boundary.
|
|
1010
1077
|
|
|
1078
|
+
### Send e-mail steps
|
|
1079
|
+
|
|
1080
|
+
A `send_mail` step (APEX 26.1: the *Send E-Mail* activity) queues an e-mail through the application's mail service,
|
|
1081
|
+
as the application's role, like [`meta.send_mail`](#sending-e-mail) in a `sql` step:
|
|
1082
|
+
|
|
1083
|
+
```json
|
|
1084
|
+
{"name": "TELL", "type": "send_mail", "to": "select email from staff where id = :DETAIL_PK::int", "template": "REQUEST_DONE"}
|
|
1085
|
+
{"name": "NOTE", "type": "send_mail", "to": "desk@example.com, &MANAGER_EMAIL.", "subject": "Request #TITLE# done", "body": "Decided by #TASK_APPROVER#."}
|
|
1086
|
+
```
|
|
1087
|
+
|
|
1088
|
+
`to` is a SELECT returning addresses, or addresses with `&VAR.` substitutions; `cc` likewise addresses. With a
|
|
1089
|
+
`template` ([e-mail templates](#e-mail-templates)), or a `subject` and a plain-text `body`, the placeholders `#NAME#`
|
|
1090
|
+
are the variables, `DETAIL_PK`, `WORKFLOW_ID` and `INITIATOR`. No recipient: the step goes on (the history says
|
|
1091
|
+
so). A mail that can't be queued (no mail service, an address that isn't one, the daily limit) faults the step.
|
|
1092
|
+
The history records the mail's id; it is sent after the step's transaction commits.
|
|
1093
|
+
|
|
1094
|
+
<a id="draft-steps"></a>**Draft steps** (APEX 26.2: Draft activities). A step of type `draft` stands for something
|
|
1095
|
+
not modelled yet (`{"name": "BUDGET_CHECK", "type": "draft", "note": "check the budget", "next": "APPROVE"}`).
|
|
1096
|
+
It may be in a development version only: the builder shows a warning and doesn't offer **Activate** while a version
|
|
1097
|
+
has one, `meta.activate_workflow_version` refuses it, and the database refuses draft steps in a definition's active
|
|
1098
|
+
steps (so a new definition can't have them either: create it, then a new version to model with drafts). The diagram
|
|
1099
|
+
draws them dashed. An instance can never reach one; if it did (steps changed with SQL), the step faults.
|
|
1100
|
+
|
|
1011
1101
|
### Invoke API steps
|
|
1012
1102
|
|
|
1013
1103
|
An `invoke_api` step (APEX: the *Invoke API* activity) calls a web service: a
|
|
@@ -1086,6 +1176,28 @@ steps with problems), and the active one becomes inactive. **Discard** throws a
|
|
|
1086
1176
|
away. The Versions table shows each version with its instance count and diagram; the Instances
|
|
1087
1177
|
table shows which version each instance runs and where it is.
|
|
1088
1178
|
|
|
1179
|
+
<a id="migrating-instances"></a>**Migrating running instances** (APEX 26.2: in-flight workflow migration). Running
|
|
1180
|
+
instances (active, waiting or faulted) of an older version can move to a newer one: the active version or an inactive
|
|
1181
|
+
version activated after theirs. Under the definition, **Migrate instances…** (shown
|
|
1182
|
+
while there are such instances) opens a page where you choose the version and the instances, and map each step an
|
|
1183
|
+
instance is at to a step of the new version (a step of the same name is proposed). **Preview** shows per instance
|
|
1184
|
+
where it goes or why it can't; **Migrate** then moves them all in one transaction, with their variables, the task
|
|
1185
|
+
they wait for and their wait, logged in each instance's history (*migrated*, `version 1 → 2: APPROVE → APPROVE
|
|
1186
|
+
(by admin)`) and in `meta.workflow_migration`. Nothing moves when one of them can't:
|
|
1187
|
+
|
|
1188
|
+
- a step without a mapping, or mapped to a step the version doesn't have (or a draft);
|
|
1189
|
+
- an instance waiting for a task must go to a task step that creates a task of the same definition;
|
|
1190
|
+
one at a `wait` step to a `wait` step (its wait goes on), one at a join to a join;
|
|
1191
|
+
- an instance inside parallel branches, or calling a web service at that moment: try again later.
|
|
1192
|
+
|
|
1193
|
+
The database function is `meta.migrate_workflows(definition_id, version, ids, mapping, apply, by)` (the owner's,
|
|
1194
|
+
for the builder and scripts; not for applications).
|
|
1195
|
+
|
|
1196
|
+
<a id="diagram-export"></a>**The diagram as a file.** Under the diagram, **Download SVG** downloads the whole model
|
|
1197
|
+
of the version shown (`/builder/apps/<app>/shared/workflow_definition/<id>/diagram.svg?v=<version>`), a standalone
|
|
1198
|
+
SVG with its own colours; **Download PNG** draws that SVG in the browser (with JavaScript; without it the SVG link
|
|
1199
|
+
remains). Step names and labels are escaped, so a definition can't put markup into the file.
|
|
1200
|
+
|
|
1089
1201
|
**The console.** A region of type **`workflows`** lists the workflows the user started (or, with
|
|
1090
1202
|
`"context": "admin"`, those they administer), with their state, the version, the current steps (several with parallel branches), a diagram
|
|
1091
1203
|
and the history. The initiator and administrators can **terminate** a workflow (its open tasks and
|
|
@@ -1102,7 +1214,10 @@ branches are cancelled); administrators can retry a faulted one.
|
|
|
1102
1214
|
created. Its version 2 (part 18) prepares the workplace (the manager's task) and, in a parallel
|
|
1103
1215
|
branch, the access that employees with a salary of 2500 or more need (a switch and an
|
|
1104
1216
|
administrator's task); when both branches are done, the manager is notified (SQL). Version 1, which
|
|
1105
|
-
did this one after the other, is inactive. *My tasks* (page 14) shows the workflows. Part
|
|
1217
|
+
did this one after the other, is inactive. *My tasks* (page 14) shows the workflows. Part 67 adds `SALARY_CHANGE`: a
|
|
1218
|
+
`SALARY_DECISION` task with the outcomes Yes, No and Undecided (the owners get the `SALARY_TASK` e-mail, a complete
|
|
1219
|
+
event notifies the initiator), a switch on the outcome (Undecided asks again), SQL that applies the salary and, in
|
|
1220
|
+
version 2, a `send_mail` step to the employee; version 3 is in development with a draft step. Part 34 adds
|
|
1106
1221
|
`DEPARTMENT_CHECK`, started by *Check in a workflow* on page 23: an `invoke_api` step reads the
|
|
1107
1222
|
department from the example's own REST API (the `DEPARTMENT` source of part 23, so the server must
|
|
1108
1223
|
allow `127.0.0.1:3100`), a `switch` on `HTTP_STATUS`, and SQL that notifies the initiator.
|
|
@@ -37,6 +37,7 @@ your own from a static application file.
|
|
|
37
37
|
| `selection_change` | Rows are selected in a report with [row selection](04-pages-and-regions.md#row-selection), or another master row in a grid; the condition sees the selected values, colon separated | Region ids or static ids |
|
|
38
38
|
| `before_submit` | The page is about to be submitted; a declined `confirm` keeps the page | – |
|
|
39
39
|
| `custom` | JavaScript raised the event: `pgkiln.trigger('ORDER_SAVED', value)` (APEX: custom events, `apex.event.trigger`) | Event names (case-insensitive) |
|
|
40
|
+
| `menu_entry` | An entry `{"label": …, "action": "NAME"}` of a [menu button](06-processing.md#menu-buttons-and-badges) was chosen (APEX: Menu entry chosen); the condition sees the entry's name ([below](#menu-entry-chosen)) | Entry names, or menu button names (any of their entries) |
|
|
40
41
|
|
|
41
42
|
**The actions of one event and trigger run one after the other, in sequence order** (APEX: the true
|
|
42
43
|
actions of a dynamic action), each waiting for the one before (a server action for its answer). A
|
|
@@ -79,11 +80,44 @@ actions of a dynamic action), each waiting for the one before (a server action f
|
|
|
79
80
|
| `print` | browser | The browser's print dialog |
|
|
80
81
|
| `current_position` | browser | The device's position as `latitude,longitude` (5 decimals) into the first affected item |
|
|
81
82
|
| `share` | browser | The device's share sheet with the page's title, `message` and address; where there is none, the address is copied |
|
|
83
|
+
| `wait_popup` | browser | Shows a modal "please wait" popup with `message` (default *Please wait…*) until `remove_wait_popup` or the next page (APEX: Show Wait Popup); [below](#wait-popup) |
|
|
84
|
+
| `remove_wait_popup` | browser | Removes the wait popup (APEX: Remove Wait Popup) |
|
|
82
85
|
|
|
83
86
|
Server-side actions first store `items_to_submit` in session state, run as the application's
|
|
84
87
|
database role like everything else, and check the page's and the dynamic action's authorization.
|
|
85
88
|
The SQL itself never reaches the browser.
|
|
86
89
|
|
|
90
|
+
## Wait popup
|
|
91
|
+
|
|
92
|
+
`wait_popup` covers the page with a small dialog (a spinner and `message`) while something slow
|
|
93
|
+
runs, so the user doesn't click twice; `remove_wait_popup` takes it away. Because the actions of
|
|
94
|
+
one event run in sequence, each waiting for the one before, two patterns cover most cases:
|
|
95
|
+
|
|
96
|
+
| Sequence | Event | Action | |
|
|
97
|
+
|---|---|---|---|
|
|
98
|
+
| 10 | `click` on `RECALC` (a submit button) | `wait_popup` | *Recalculating…*: the popup stays until the next page arrives |
|
|
99
|
+
| 10 | `click` on `CHECK` | `wait_popup` | *Checking…* |
|
|
100
|
+
| 20 | `click` on `CHECK` | `execute_sql` | the Ajax call (also `set_value`, `refresh_region`, `download`, a plug-in) |
|
|
101
|
+
| 30 | `click` on `CHECK` | `remove_wait_popup` | |
|
|
102
|
+
|
|
103
|
+
`before_submit` → `wait_popup` shows it for every submit of the page. A submit the browser refuses
|
|
104
|
+
(a required field is empty) removes it again, and so does going back to the page in the browser.
|
|
105
|
+
The popup is a modal `<dialog>`: the page below can't be clicked or focused, Escape doesn't close
|
|
106
|
+
it, its text is a status message that screen readers read out, and the page's `<main>` is
|
|
107
|
+
`aria-busy` while it shows. It follows the theme (light and dark) and needs no inline styles or
|
|
108
|
+
scripts. From JavaScript: `const done = pgkiln.waitPopup('Saving…'); … done();` (or
|
|
109
|
+
`pgkiln.removeWaitPopup()`).
|
|
110
|
+
|
|
111
|
+
## Menu entry chosen
|
|
112
|
+
|
|
113
|
+
A [menu button](06-processing.md#menu-buttons-and-badges)'s entry can run dynamic actions instead
|
|
114
|
+
of linking or submitting: `{"label": "Export", "action": "EXPORT"}`. Dynamic actions with event
|
|
115
|
+
`menu_entry` and trigger `EXPORT` run when it is chosen (the menu closes first); a trigger with the
|
|
116
|
+
menu button's name runs for every action entry of that menu, and a client-side condition tests the
|
|
117
|
+
entry's name (`equals` `EXPORT`). Without JavaScript such an entry does nothing, like a button with
|
|
118
|
+
action `da`. The HR example's page 47 has an *Actions* menu whose *Count employees* entry shows a
|
|
119
|
+
wait popup around an Ajax call.
|
|
120
|
+
|
|
87
121
|
## Ajax callbacks
|
|
88
122
|
|
|
89
123
|
A page process with point **ajax** (APEX: an On Demand process) runs only when JavaScript calls it by
|
|
@@ -481,9 +481,10 @@ navigation entries and application processes have the same `build_option` column
|
|
|
481
481
|
| `push_key` | The VAPID key pair of an application with [push notifications](17-mobile.md#push-notifications): `public_key`, `private_key` (encrypted with `PGKILN_SECRET_KEY`). Not exported | no |
|
|
482
482
|
| `push_subscription` | Devices with notifications on: `app_id`, `username`, `endpoint` (the push service URL), the device's keys `p256dh` and `auth`, `user_agent`, `created_at`, `last_sent_at`, `failures`. Not exported | no (through `meta.has_push_subscription`) |
|
|
483
483
|
| `push_message` | [Push notifications from SQL](#push-notifications-from-sql): `app_id`, `username`, `title`, `body`, `url`, `tag`, `urgency`, `ttl_s`, `status`, `attempts`, `devices`, `delivered`, `message`, `requested_by`, times. Kept 7 days, not exported | no |
|
|
484
|
-
| `ai_service` | [AI services](06-processing.md#generate-text-with-ai) of the installation: `name`, `provider` (`anthropic`, `openai`), `model`, `effort`, `refusal_fallback`, `max_tokens`, `timeout_s`, `base_url`, `api_key_enc` (encrypted with `PGKILN_SECRET_KEY`, write-only), `enabled`. Not exported | no |
|
|
484
|
+
| `ai_service` | [AI services](06-processing.md#generate-text-with-ai) of the installation: `name`, `provider` (`anthropic`, `openai`, `gemini`, `openai_compatible`: 121), `model`, `effort`, `refusal_fallback`, `max_tokens`, `timeout_s`, `base_url`, `api_key_enc` (encrypted with `PGKILN_SECRET_KEY`, write-only), `enabled`. Not exported | no |
|
|
485
485
|
| `app_ai_service` | Which applications may use which AI service, with daily limits `max_requests` and `max_tokens` (null: no limit). Not exported | no (through `meta.ai_available`) |
|
|
486
486
|
| `ai_usage` | One row per AI request: `at`, `app_id`, `page_no`, `username`, `service`, `provider`, `model`, `source`, `input_tokens`, `output_tokens`, `duration_ms`, `status`, `message` (an error class, never prompt or answer text). Not exported | no |
|
|
487
|
+
| `ai_tool_call` | (121) Every tool call of an [AI assistant or agent](03-builder.md#ai-agents): `at`, `app_id`, `page_no`, `region_id`, `username`, `agent`, `tool`, `type`, `arguments` (the model's, cut to 4 KB), `status` (`ok`, `error`, `refused`), `duration_ms`, `message`. Kept 30 days, not exported | no |
|
|
487
488
|
| `ai_conversation` | [AI assistant](04-pages-and-regions.md#ai_assistant-ai-assistant) conversations: one per session and region (`messages`: the provider's history, `turns`: what the page shows); deleted with the session. Not exported | no |
|
|
488
489
|
| `ai_table_note` | Descriptions of tables and columns for models ([App Builder AI](03-builder.md#app-builder-ai)): `schema_name`, `table_name`, `column_name` (`''` for the table), `note`. Installation data, not exported | no |
|
|
489
490
|
| `blueprint` | Saved [blueprints](03-builder.md#creating-an-application-from-a-blueprint): `name`, `spec` (the JSON), `app_id` (the application last created from it), `created_by`. Builder data, not exported | no |
|
|
@@ -22,10 +22,11 @@ What's missing is listed in the [feature parity matrix](../apex-feature-parity.m
|
|
|
22
22
|
| Instance administration: install/upgrade log | Workspace utilities → Installation (administrators) |
|
|
23
23
|
| `APEX_WEB_SERVICE.make_rest_request`, `g_status_code` | `meta.web_request(url, method, body, headers, credential)` or `meta.web_request_source(source, params)`, then `meta.web_response(id)`. **Asynchronous**: the server makes the request after the page process that queued it, or after the commit ([chapter 9](09-reference.md#web-requests-from-sql)) |
|
|
24
24
|
| Workspace → Generative AI services; *Generate Text with AI* process and dynamic action; `APEX_AI.generate` | Workspace utilities → AI services (Claude or OpenAI; administrators); the `ai_generate` process and dynamic action, with structured outputs into items ([chapter 6](06-processing.md#generate-text-with-ai)); `meta.ai_generate(service, prompt, system, schema)` then `meta.ai_result(id)` ([chapter 9](09-reference.md#ai-requests-from-sql)) |
|
|
25
|
-
| AI Assistant region; AI agents with tools; natural language to interactive report
|
|
25
|
+
| AI Assistant region; AI agents with tools; AI interactive reports (natural language to interactive report) | The `ai_assistant` region: a chat with context queries (RAG over your queries) and tools (SQL as the app role with bound arguments, REST data sources) ([chapter 4](04-pages-and-regions.md#ai_assistant-ai-assistant)); `"ai_filter"` on a report: *Ask in your own words*, the request becomes the report's settings as editable chips, with or without an AI service ([chapter 4](04-pages-and-regions.md#ask-in-your-own-words-ai-interactive-reports)) |
|
|
26
26
|
| App Builder / SQL Workshop AI Assistant (create pages, generate and explain SQL); describing tables for AI | The App Builder's AI service: *Create pages with AI* (proposals you confirm), SQL Workshop → AI (SQL from a question, shown not run; explain a query or error), *Describe tables* (`meta.ai_table_note`, optionally `COMMENT ON`) ([chapter 3](03-builder.md#app-builder-ai)) |
|
|
27
27
|
| Blueprints (spec-driven development) | Create → From a blueprint: a JSON blueprint (tables, pages, menu, sample data), optionally drafted by AI, reviewed, then created in one transaction ([chapter 3](03-builder.md#creating-an-application-from-a-blueprint)) |
|
|
28
28
|
| `APEX_DATA_PARSER.parse`, `get_columns` | `meta.parse_data(blob, …)` (rows with `cols` and `data`), `meta.parse_data_columns(blob, …)`; CSV/TSV and JSON, not Excel or XML ([chapter 9](09-reference.md#parsing-files-in-sql)) |
|
|
29
|
+
| Team Development (issues, labels, milestones, templates, feedback) | Builder → Team Development (`/builder/team`): issues per workspace with labels in groups, milestones, assignees, application and page links, Markdown comments with @mentions shown in the builder, history, saved views and a board; a set-up wizard with sample labels, templates and milestones; feedback and developer comments become issues. No e-mail and no GitHub synchronisation ([chapter 3](03-builder.md#team-development)) |
|
|
29
30
|
| Workspace users (APEX accounts), Application Access Control | `meta.account` (Builder → Users), `meta.app_access` (Access control) |
|
|
30
31
|
| Page access protection "Arguments must have checksum" | `protection = 'checksum'` (the default) |
|
|
31
32
|
| Automatic row processing (DML) | Process type `form_dml` |
|