pgkiln 0.51.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 +5 -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 +12 -10
- package/docs/guide/01-installation.md +7 -3
- package/docs/guide/03-builder.md +121 -7
- package/docs/guide/04-pages-and-regions.md +71 -21
- 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 +44 -8
- package/docs/guide/20-ai-agents.md +4 -2
- package/examples/hr/README.md +2 -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 +40 -0
- package/public/app.js +134 -20
- package/public/builder.css +83 -1
- 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 +6 -0
- 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/designer.ts +8 -3
- package/src/builder/feedback.ts +6 -1
- package/src/builder/locks.ts +7 -1
- package/src/builder/region-help.ts +2 -2
- package/src/builder/routes.ts +4 -1
- package/src/builder/shared.ts +1 -1
- package/src/builder/team.ts +991 -0
- package/src/builder/ui.ts +2 -1
- package/src/builder/workflow-migrate.ts +210 -0
- package/src/builder/workflows.ts +29 -6
- package/src/builder/workspace-home.ts +2 -0
- package/src/builder/workspace-transfer.ts +1 -1
- package/src/cli/replace.ts +7 -1
- package/src/i18n/ar.ts +17 -0
- package/src/i18n/cs.ts +17 -0
- package/src/i18n/da.ts +17 -0
- package/src/i18n/de.ts +17 -0
- package/src/i18n/el.ts +17 -0
- package/src/i18n/es.ts +17 -0
- package/src/i18n/fi.ts +17 -0
- package/src/i18n/fr.ts +17 -0
- package/src/i18n/he.ts +17 -0
- package/src/i18n/it.ts +17 -0
- package/src/i18n/ja.ts +17 -0
- package/src/i18n/ko.ts +17 -0
- package/src/i18n/nb.ts +17 -0
- package/src/i18n/pl.ts +17 -0
- package/src/i18n/pt.ts +17 -0
- package/src/i18n/ru.ts +17 -0
- package/src/i18n/sv.ts +17 -0
- package/src/i18n/tr.ts +17 -0
- package/src/i18n/uk.ts +17 -0
- package/src/i18n/zh.ts +17 -0
- package/src/i18n.ts +34 -0
- package/src/instance.ts +2 -0
- package/src/metadata.ts +9 -3
- 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/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/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 +16 -1
|
@@ -182,6 +182,8 @@ which is read at startup; real environment variables take precedence.
|
|
|
182
182
|
| `PDF_FONT`, `PDF_FONT_BOLD` | *(none)* | TrueType fonts for report PDFs, for text beyond Western European (e.g. `DejaVuSans.ttf`) |
|
|
183
183
|
| `ANTHROPIC_API_KEY` | *(none)* | Default API key of [AI services](03-builder.md#ai-services) with the provider Claude that have no key of their own |
|
|
184
184
|
| `OPENAI_API_KEY` | *(none)* | Default API key of AI services with the provider OpenAI that have no key of their own |
|
|
185
|
+
| `GEMINI_API_KEY` | *(none)* | (sprint 111) Default API key of AI services with the provider Gemini that have no key of their own |
|
|
186
|
+
| `OPENAI_COMPATIBLE_API_KEY` | *(none)* | (sprint 111) Default API key of OpenAI-compatible AI services (Ollama, vLLM, Azure OpenAI) that have no key of their own; without either, no key is sent (`none`) |
|
|
185
187
|
| `PGKILN_SECRET_KEY` | *(none)* | Encrypts the secrets of web credentials and the API keys of AI services; at least 32 characters (e.g. `openssl rand -base64 32`). Keep it outside the database; changing it means entering the secrets again ([chapter 19](19-rest-data-sources.md)) |
|
|
186
188
|
| `PGKILN_REST_ALLOWED_HOSTS` | *(none: no outgoing calls)* | Hosts REST data sources and `invoke_api` may call: `api.example.com`, `*.example.com`, `host:8443`, `*` (any public host) ([chapter 19](19-rest-data-sources.md#server-configuration-and-the-allow-list)) |
|
|
187
189
|
| `PGKILN_REST_PRIVATE_HOSTS` | *(none)* | Hosts that may resolve to private, loopback or link-local addresses (also allows them) |
|
|
@@ -264,9 +266,11 @@ stops the container at once with a message saying so.
|
|
|
264
266
|
| `PGKILN_IMAGE` | `pgkiln/pgkiln:latest` | The image to run, e.g. a fixed version (`pgkiln/pgkiln:0.31.0`) or `ghcr.io/pgkiln/pgkiln` |
|
|
265
267
|
|
|
266
268
|
**Upgrading:** with the published image: `docker compose pull`, then `docker compose up -d` (with a
|
|
267
|
-
fixed version in `PGKILN_IMAGE`, change it first)
|
|
268
|
-
`docker compose up -d --build`. The new
|
|
269
|
-
before it starts serving.
|
|
269
|
+
fixed version in `PGKILN_IMAGE`, change it first); `up -d` replaces the container when the image changed, so
|
|
270
|
+
`--force-recreate` isn't needed. Built from a checkout: `git pull`, then `docker compose up -d --build`. The new
|
|
271
|
+
container migrates the database before it starts serving. A `compose.yaml` from pgkiln 0.51.0 or older has
|
|
272
|
+
`pull_policy: missing` under `pgkiln`, which makes `docker compose pull` skip an image that is already present:
|
|
273
|
+
delete that line (or take the current `deploy/compose.yaml`) before the first upgrade this way. Back up first (`docker compose exec db pg_dump -U pgkiln pgkiln > backup.sql`).
|
|
270
274
|
Keep `PGKILN_SECRET_KEY`: without it, stored secrets (web credentials, AI keys, push keys) can't be decrypted.
|
|
271
275
|
|
|
272
276
|
## Upgrading
|
package/docs/guide/03-builder.md
CHANGED
|
@@ -158,6 +158,7 @@ applications' files. Choose what goes in:
|
|
|
158
158
|
| REST source catalogs | Every [catalog](19-rest-data-sources.md#rest-source-catalogs) with its services |
|
|
159
159
|
| SQL scripts, blueprints, data generators | The SQL Workshop's saved work |
|
|
160
160
|
| Data Reporter datasets | Every [dataset](#data-reporter-datasets-and-reporting-applications) (sprint 110) |
|
|
161
|
+
| Team Development | [Label groups, labels, milestones, templates and issues](#team-development) with their comments and history; an issue's application by alias (sprint 113) |
|
|
161
162
|
|
|
162
163
|
Never in the file: the workspace's name, its [isolation](#isolated-workspaces) and database roles, passwords and
|
|
163
164
|
secrets, script runs and other history. **Applications are exported on their own** (an application's *Export*).
|
|
@@ -275,7 +276,8 @@ JavaScript. The [default-password banner](#signing-in-and-developer-accounts) st
|
|
|
275
276
|
- **System message**: the pgkiln version the server runs and, when the [release check](#the-release-check) is on and
|
|
276
277
|
a newer release is out, **pgkiln X is available** with a link to its release notes and how to upgrade (back up,
|
|
277
278
|
install the new version, `pgkiln migrate`, restart).
|
|
278
|
-
- **A side column**: About, **
|
|
279
|
+
- **A side column**: About, **Team Development** ([open issues](#team-development), those assigned to you, unseen
|
|
280
|
+
mentions of you, or *Set up Team Development* before it is set up), **Learn more** (this guide, the [tutorial](10-tutorial.md),
|
|
279
281
|
[Coming from APEX](11-from-apex.md), the changelog and the discussions on GitHub) and Resources.
|
|
280
282
|
|
|
281
283
|
### The release check
|
|
@@ -753,6 +755,10 @@ author or an administrator deletes it. The page list counts each page's comments
|
|
|
753
755
|
another number keeps its lock and comments. Locks and comments belong to this installation and
|
|
754
756
|
are not exported.
|
|
755
757
|
|
|
758
|
+
**Make an issue** under a comment turns it into a [Team Development](#team-development) issue about the same page;
|
|
759
|
+
the comment then links to its issue. A page with open issues shows them above the designer's tabs (*1 open issue:
|
|
760
|
+
…*, with *New issue* for that page), and the application's page list tags such pages (*2 issues*).
|
|
761
|
+
|
|
756
762
|
### Arranging the layout
|
|
757
763
|
|
|
758
764
|
Regions are placed in sequence order; a region's *column span* (1–12) sets its width, and a
|
|
@@ -840,7 +846,7 @@ out, other keys are kept, columns the query no longer returns stay listed so you
|
|
|
840
846
|
| `smart_filters` | The same per-column facets, the suggestions per facet and the search field's placeholder |
|
|
841
847
|
| `display_selector` | Tabs or a select list, "Show all", remember the choice; per other region of the page: in a tab and the tab name (saved in that region's settings) |
|
|
842
848
|
| `ai_assistant` | The AI service (those the application may use), system prompt, welcome text, placeholder, error message, tool rounds per question, questions per conversation, public or not, and the context queries and tools as JSON (checked when saving: query shapes, parameter types, REST data sources and authorization schemes that exist) ([AI assistant](04-pages-and-regions.md#ai_assistant-ai-assistant)) |
|
|
843
|
-
| `report` (also) | *Ask in your own words (AI)*: the AI service and placeholder of
|
|
849
|
+
| `report` (also) | *Ask in your own words (AI)*: off, the built-in phrases or an AI service, and the placeholder of [Ask in your own words](04-pages-and-regions.md#ask-in-your-own-words-ai-interactive-reports) |
|
|
844
850
|
| `data_reporter` | Data sources (a table or view each: static id, label, description, offered columns with labels and format masks; or (sprint 110) a dataset of the workspace), who may share, build and moderate reports, publishing, rows per page ([Data Reporter](04-pages-and-regions.md#data_reporter-data-reporter)) |
|
|
845
851
|
| `form_designer` | Who may design forms (an authorization scheme), data sources (a table or view each: static id, label, description, key column, may add / change / delete rows, offered columns with labels and lists of values), rows per page ([Form Designer](04-pages-and-regions.md#form_designer-form-designer)) |
|
|
846
852
|
|
|
@@ -958,6 +964,48 @@ user, page, rating and browser), set its status (*Open*, *In progress*, *Done*,
|
|
|
958
964
|
an answer the user sees. Answering is allowed while the application is locked or Run only. The
|
|
959
965
|
setting travels with the export; the feedback itself belongs to the installation.
|
|
960
966
|
|
|
967
|
+
**Make an issue** turns an entry into a [Team Development](#team-development) issue of the application's workspace
|
|
968
|
+
(title from its first line, the feedback quoted, linked to its page, labelled *Bug* for a problem and *Feature* for
|
|
969
|
+
an idea when the workspace has those labels); open feedback becomes *In progress*, and the entry links to its issue.
|
|
970
|
+
|
|
971
|
+
## Team Development
|
|
972
|
+
|
|
973
|
+
(sprint 113, migration 122; APEX: Team Development) An issue tracker for the developers of a
|
|
974
|
+
[workspace](#workspaces): **Team Development** on the icon rail (`/builder/team`) and a block in the
|
|
975
|
+
[Workspace home](#workspace-home)'s side column with the number of open issues. Everything is of the current
|
|
976
|
+
workspace and works without JavaScript.
|
|
977
|
+
|
|
978
|
+
- **Set up** installs sample data, like APEX's set-up wizard: the label groups *Type* (Bug, Feature, Task) and
|
|
979
|
+
*Priority* (High, Medium, Low), issue templates (*Bug report*, *Feature request*, *Task*), canned responses and the
|
|
980
|
+
milestones *1.0*, *1.1* and *Backlog*. What the workspace has already (by name) is kept, so it can run again.
|
|
981
|
+
The samples are in English; other languages are added in `src/team-samples.ts`.
|
|
982
|
+
- **Issues** have a title, a description in Markdown (rendered safely: raw HTML is shown as text, links are http,
|
|
983
|
+
https, mailto or relative), *Open* or *Closed*, labels, a milestone, assignees (the workspace's developers and the
|
|
984
|
+
installation's administrators), and optionally an application of the workspace and one of its pages. Comments are
|
|
985
|
+
Markdown too; *Comment and close* does both. **@name** mentions a developer of the workspace: they see it under
|
|
986
|
+
*Mentions of you* (and on the Workspace home) until they open the issue or mark them seen; no mail is sent. Every
|
|
987
|
+
change is in the issue's history (who changed the title, labels, milestone, assignees, link or status, and when).
|
|
988
|
+
An issue is deleted by its author or a workspace administrator.
|
|
989
|
+
- **The list** filters by status, label, milestone, assignee (*Me*, *Nobody*), application and a search of the title
|
|
990
|
+
and text (or `#12` for issue 12). **Save view** keeps the filters under a name, for you only.
|
|
991
|
+
- **The board** shows the issues as columns of cards, by the labels of one label group (a card moved to *High*
|
|
992
|
+
loses *Medium*; *None of these* removes the group's labels) or by milestone. Each card has a *Move* form, so it
|
|
993
|
+
works without dragging.
|
|
994
|
+
- **Labels** belong to label groups and have a colour from a fixed palette (red, orange, yellow, green, teal, blue,
|
|
995
|
+
purple, gray). **Milestones** have a due date and are open or closed, with the share of their issues closed.
|
|
996
|
+
**Templates** are issue templates (title, labels, text: *Start from a template* on *New issue*) and canned
|
|
997
|
+
responses (*Canned response* → *Insert* above an issue's comment field). Any developer of the workspace adds and
|
|
998
|
+
changes them; only its administrators delete labels, label groups and milestones.
|
|
999
|
+
- Issues come from [feedback](#feedback) and [developer comments](#page-locks-and-comments) with **Make an issue**,
|
|
1000
|
+
linked both ways. The page designer shows a page's open issues and App Home counts them per page (*Open issues* in
|
|
1001
|
+
its Tasks).
|
|
1002
|
+
|
|
1003
|
+
Issues and the rest are workspace data: they are not part of an application's export (an in-place import keeps them)
|
|
1004
|
+
but travel in the workspace's [Export and import](#export-and-import) (*Team Development*): labels, milestones and
|
|
1005
|
+
templates match by name, an issue by its title and creation time; assignees are kept when they are developers of the
|
|
1006
|
+
importing workspace and the application link when it has an application with that alias. GitHub synchronisation is
|
|
1007
|
+
not part of it.
|
|
1008
|
+
|
|
961
1009
|
## Working copies
|
|
962
1010
|
|
|
963
1011
|
APEX's working copies: a second application to change in isolation, then merge back. On an
|
|
@@ -1024,8 +1072,8 @@ is the earlier page: every component in one rail, with **Access control** next t
|
|
|
1024
1072
|
| **Shortcuts** | Named texts: `"NAME"` in static content, help texts and confirm messages ([chapter 2](02-concepts.md#shortcuts-name)) |
|
|
1025
1073
|
| **Application computations** | Computations of (application) items for every page: at sign-in, before every page, after every submit ([chapter 6](06-processing.md#application-computations)) |
|
|
1026
1074
|
| **REST modules** | REST endpoints (method, path, SQL) served by pgkiln, with an OpenAPI description ([chapter 13](13-rest-api.md#rest-modules-in-the-builder)) |
|
|
1027
|
-
| **Workflows** | Multi-step processes of tasks, SQL, web service calls (invoke API), decisions, waits and parallel branches, with versions and a diagram ([chapter 6](06-processing.md#workflows)) |
|
|
1028
|
-
| **Task definitions** | Approvals and action tasks: subject, owners, administrators, due date, the SQL that runs on completion ([chapter 6](06-processing.md#approvals-and-the-task-list)) |
|
|
1075
|
+
| **Workflows** | Multi-step processes of tasks, SQL, web service calls (invoke API), e-mail, decisions, waits and parallel branches, with versions, draft steps, migration of running instances and a diagram (SVG / PNG download) ([chapter 6](06-processing.md#workflows)) |
|
|
1076
|
+
| **Task definitions** | Approvals and action tasks: subject, owners, administrators, due date, custom outcomes, the SQL that runs on completion, events and e-mail notifications ([chapter 6](06-processing.md#approvals-and-the-task-list)) |
|
|
1029
1077
|
| **Document templates** | Letters, invoices and other PDFs filled from a query ([chapter 16](16-files.md#document-templates)), with a preview |
|
|
1030
1078
|
| **Build options** | Include / exclude switches for features; pages and their components name one in their *Build option* property, and *Used in* lists them ([chapter 6](06-processing.md#build-options)) |
|
|
1031
1079
|
| **Web credentials** | How the application signs in to web services (basic, API key header, bearer token, OAuth2 client credentials); the secret is write-only and encrypted ([chapter 19](19-rest-data-sources.md#web-credentials)) |
|
|
@@ -1293,7 +1341,12 @@ Workspace utilities → **AI services** (administrators only; APEX: *Generative
|
|
|
1293
1341
|
large language model services of this installation. A service has:
|
|
1294
1342
|
|
|
1295
1343
|
- a **name** (upper case, e.g. `CLAUDE`) by which processes, dynamic actions and SQL refer to it;
|
|
1296
|
-
- a **provider**: *Claude (Anthropic)* or *OpenAI*, called through their official SDKs;
|
|
1344
|
+
- a **provider**: *Claude (Anthropic)* or *OpenAI*, called through their official SDKs; *Gemini*
|
|
1345
|
+
(Google, through its OpenAI-compatible endpoint `https://generativelanguage.googleapis.com/v1beta/openai/`,
|
|
1346
|
+
with the `openai` SDK); or *OpenAI-compatible*: any endpoint that speaks OpenAI's Chat Completions,
|
|
1347
|
+
such as Ollama (`http://localhost:11434/v1`), vLLM, Azure OpenAI (`https://<resource>.openai.azure.com/openai/v1/`)
|
|
1348
|
+
or a gateway. An OpenAI-compatible service needs a base URL; its key may stay empty for endpoints
|
|
1349
|
+
without keys (a local Ollama);
|
|
1297
1350
|
- a **model**, exactly as the provider names it (new Claude services start with `claude-opus-5-5`);
|
|
1298
1351
|
pgkiln never changes or downgrades it;
|
|
1299
1352
|
- for Claude, the **effort** (how deeply the model thinks; current Claude models always think, lower
|
|
@@ -1305,8 +1358,9 @@ large language model services of this installation. A service has:
|
|
|
1305
1358
|
- an optional **base URL** for a gateway or proxy that speaks the provider's API (calls to the AI
|
|
1306
1359
|
service don't go through `PGKILN_REST_ALLOWED_HOSTS`: only administrators set this URL);
|
|
1307
1360
|
- the **API key**: stored encrypted with `PGKILN_SECRET_KEY`, write-only (never shown again, never
|
|
1308
|
-
exported or logged, not readable by applications). Without one the
|
|
1309
|
-
`OPENAI_API_KEY`
|
|
1361
|
+
exported or logged, not readable by applications, never sent to the browser). Without one the
|
|
1362
|
+
server's `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY` or `OPENAI_COMPATIBLE_API_KEY`
|
|
1363
|
+
(by provider) is used.
|
|
1310
1364
|
|
|
1311
1365
|
On a service's page, **Applications** sets which applications may use it, each with optional daily
|
|
1312
1366
|
limits (requests and input + output tokens per UTC day; a request over a limit fails with a message
|
|
@@ -1314,11 +1368,71 @@ and is logged as *limited*), and **Test** sends a prompt (logged as a builder te
|
|
|
1314
1368
|
on the list page for all services, the usage log: the last requests and the totals per application
|
|
1315
1369
|
and service of the last 30 days.
|
|
1316
1370
|
|
|
1371
|
+
An interactive report's *Ask in your own words* doesn't need a service: without one (or when the
|
|
1372
|
+
service is disabled or not allowed for the application) pgkiln reads the request with its built-in
|
|
1373
|
+
phrases ([chapter 4](04-pages-and-regions.md#ask-in-your-own-words-ai-interactive-reports)).
|
|
1374
|
+
|
|
1317
1375
|
**Prompts go to the provider.** Whatever a prompt contains, including the item values substituted
|
|
1318
1376
|
into it, is sent to the chosen provider (or the gateway of the base URL). Choose providers and
|
|
1319
1377
|
models that your organisation allows for that data. See
|
|
1320
1378
|
[Generate text with AI](06-processing.md#generate-text-with-ai).
|
|
1321
1379
|
|
|
1380
|
+
## AI agents
|
|
1381
|
+
|
|
1382
|
+
Shared Components → Generative AI → **AI Agents** (APEX 26.1 / 26.2: AI Agents and AI Tools) holds an
|
|
1383
|
+
application's agents and the tools they may use. An agent reasons over a user's request in an
|
|
1384
|
+
[AI assistant region](04-pages-and-regions.md#ai_assistant-ai-assistant) and acts **only through the
|
|
1385
|
+
tools you approve**; pgkiln prepares the context, runs each tool call itself under the checks below,
|
|
1386
|
+
and composes the conversation.
|
|
1387
|
+
|
|
1388
|
+
An **agent** has:
|
|
1389
|
+
|
|
1390
|
+
- a **name** (upper case, e.g. `SALES_AGENT`) that regions refer to, and a description;
|
|
1391
|
+
- an **AI service** the application may use, and a **system prompt** (`&ITEM.` is replaced by the item's
|
|
1392
|
+
value as delimited data, as in assistant regions);
|
|
1393
|
+
- a **reasoning effort**: *low*, *medium* or *high*, or empty for the service's own. For Claude it is
|
|
1394
|
+
the output effort (thinking depth, `output_config.effort`); for OpenAI, Gemini and OpenAI-compatible
|
|
1395
|
+
services it is sent as `reasoning_effort` (leave it empty for models without reasoning: they refuse it);
|
|
1396
|
+
- the **tools** it may use (ticked from the application's tools);
|
|
1397
|
+
- **maximum steps** (rounds of tool calls per question, 1–20, default 5) and **maximum output tokens**
|
|
1398
|
+
per call (never above the service's maximum);
|
|
1399
|
+
- an **authorization scheme**: other users see *You may not use this assistant* instead of the message
|
|
1400
|
+
box, and their questions are refused before anything is sent to the AI service.
|
|
1401
|
+
|
|
1402
|
+
A **tool** has a name the model calls it by, a description that tells the model what it does and when
|
|
1403
|
+
to use it, optional **parameters** (JSON: `{"DNAME": {"type": "string", "description": "…", "optional": true}}`;
|
|
1404
|
+
types string, integer, number, boolean, date, `enum` for a list of texts), an optional authorization
|
|
1405
|
+
scheme (only users who pass it are offered the tool) and one of these types:
|
|
1406
|
+
|
|
1407
|
+
| Type | What runs | Checks |
|
|
1408
|
+
|---|---|---|
|
|
1409
|
+
| **SQL query** | one `SELECT` (or `WITH`, `VALUES`, `TABLE`) with the parameters as `:NAME` bind values, plus `:APP_USER` and items | as the application's database role (grants and row level security apply), in a **read-only transaction** (functions it calls can't write either), with a time limit (1–60 s, default 15) and at most *maximum rows* rows (default 50) back to the model |
|
|
1410
|
+
| **PL/pgSQL function** | `schema.function` called with named arguments: each parameter name in lower case is an argument name (`P_EMPNO` → `p_empno => …`) | as the application's role; it **may change data** (committed); same time limit and rows |
|
|
1411
|
+
| **REST data source** | a REST data source of the application; the parameters become its parameters | as in [assistant regions](04-pages-and-regions.md#ai_assistant-ai-assistant) |
|
|
1412
|
+
| **JavaScript** | nothing on the server: after the answer the browser calls the function registered with `pgkiln.actions.register(action)` (with `{value: arguments}`) and the dynamic actions of the **custom event** of that name (the arguments as JSON) | once, right after the answer; the arguments are escaped data in the page |
|
|
1413
|
+
| **Navigate** | the model picks one of the tool's **pages**, a link text and item values of that page; pgkiln builds the URL with the item values' **checksum** (bound to application, page and user) and shows the link under the answer | only pages the user may open are offered (the page's authorization scheme and sign-in); only items of that page; the model's values are never substituted (`&ITEM.`); nothing opens the link by itself |
|
|
1414
|
+
|
|
1415
|
+
The model's arguments are checked against the parameters before a tool runs; they are bind values
|
|
1416
|
+
(escaped literals), never SQL text, and can't replace the server's own names (`:APP_USER`, `:APP_ID`, …).
|
|
1417
|
+
Tool results go back to the model as data, with the instruction never to follow instructions inside them.
|
|
1418
|
+
|
|
1419
|
+
**Every tool call is logged** with the arguments the model gave, its status (*ok*, *error*, or *refused*
|
|
1420
|
+
for a page the user may not open) and duration: in the page's debug messages (level 4) and under
|
|
1421
|
+
Activity → **AI usage** → *Tool calls* (`meta.ai_tool_call`, kept 30 days; arguments may hold user data,
|
|
1422
|
+
only developers see them). The token usage of each call to the model is logged as before.
|
|
1423
|
+
|
|
1424
|
+
Agents and tools are kept in the application (`meta.app.ai_agents`, `meta.app.ai_tools`) and **exported
|
|
1425
|
+
with it**; an export made before they existed imports with none. The forms check each one before
|
|
1426
|
+
saving (a SQL tool must be a query, a function `schema.name`, a navigate tool's pages must exist); the
|
|
1427
|
+
runtime checks them again and shows *This assistant is not set up yet* for a broken one. Renaming a
|
|
1428
|
+
tool keeps it in its agents; a tool an agent uses can't be deleted; renaming an agent renames it in
|
|
1429
|
+
the regions that use it.
|
|
1430
|
+
|
|
1431
|
+
The HR example (`examples/hr/hr_64_ai_agent.sql`) has an agent `HR_AGENT` on page 46: ask *Who in Sales
|
|
1432
|
+
earns most?* and it calls the SQL tool `top_earners` as the signed-in user's role, then offers
|
|
1433
|
+
*Open BLAKE's record*, a link to the employee form (page 3) with a valid checksum, only to users who
|
|
1434
|
+
may open that page.
|
|
1435
|
+
|
|
1322
1436
|
## App Builder AI
|
|
1323
1437
|
|
|
1324
1438
|
The App Builder uses one AI service (APEX: AI Assistant in App Builder and SQL Workshop). An
|
|
@@ -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
|
|
|
@@ -1297,8 +1342,12 @@ forms *Departments* and *Book a meeting*.
|
|
|
1297
1342
|
### `ai_assistant` (AI assistant)
|
|
1298
1343
|
|
|
1299
1344
|
An **AI assistant** region (APEX: AI Assistant, AI agents and tools) is a chat with an
|
|
1300
|
-
[AI service](06-processing.md#generate-text-with-ai) (Claude
|
|
1301
|
-
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,
|
|
1302
1351
|
**context queries** and **tools**; the model then answers questions about the application's data,
|
|
1303
1352
|
calling the tools when it needs to, like an agent. It works without JavaScript (the message is
|
|
1304
1353
|
posted and the page comes back with the answer); with JavaScript the Send button shows that the
|
|
@@ -1344,6 +1393,7 @@ Attributes (written by the region's **Settings**; they travel with the applicati
|
|
|
1344
1393
|
|
|
1345
1394
|
| Key | Default | Meaning |
|
|
1346
1395
|
|---|---|---|
|
|
1396
|
+
| `agent` | | (121) An [AI agent](03-builder.md#ai-agents) of the application; then leave out `service`, `system`, `tools` and `max_rounds` |
|
|
1347
1397
|
| `service` | | The AI service's name |
|
|
1348
1398
|
| `system` | | The system prompt (`&ITEM.` substitutions as delimited data) |
|
|
1349
1399
|
| `welcome` | | The first message shown (translatable) |
|
|
@@ -1357,7 +1407,7 @@ Attributes (written by the region's **Settings**; they travel with the applicati
|
|
|
1357
1407
|
|
|
1358
1408
|
The HR example's page 38 (*HR assistant*) has an assistant with a context query over `hr.policy`
|
|
1359
1409
|
and four tools (the user's own leave, colleagues, departments, and *request_leave*, which files a
|
|
1360
|
-
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).
|
|
1361
1411
|
|
|
1362
1412
|
### Template components
|
|
1363
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.
|