pgkiln 0.85.0 → 0.86.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/db/migrations/155_mail_available.sql +23 -0
- package/docs/guide/04-pages-and-regions.md +1 -1
- package/docs/guide/06-processing.md +7 -3
- package/docs/guide/07-dynamic-actions.md +3 -3
- package/docs/guide/09-reference.md +1 -0
- package/docs/guide/12-development.md +4 -0
- package/examples/gallery/README.md +23 -6
- package/examples/gallery/gallery.json +4 -0
- package/examples/gallery/samples/sample-approvals.sql +102 -11
- package/examples/gallery/samples/sample-calendar.sql +40 -5
- package/examples/gallery/samples/sample-dynamic-actions.sql +237 -9
- package/examples/gallery/samples/sample-grids.sql +35 -3
- package/examples/gallery/samples/sample-maps.sql +65 -7
- package/examples/gallery/samples/sample-processes.sql +466 -0
- package/examples/gallery/samples/sample-validations.sql +407 -0
- package/examples/gallery/samples/ux-patterns.sql +154 -14
- package/examples/hr/README.md +2 -0
- package/examples/hr/hr_80_notifications.sql +125 -0
- package/examples/hr/hr_81_vector_search_type.sql +33 -0
- package/package.json +1 -1
- package/public/app.css +8 -1
- package/public/app.js +28 -9
- package/src/runtime/engine.ts +6 -3
- package/src/runtime/lists.ts +2 -2
- package/src/runtime/push.ts +7 -1
- package/src/runtime/regions.ts +9 -4
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
-- 155: meta.mail_available() (sprint 166)
|
|
2
|
+
--
|
|
3
|
+
-- A Send E-Mail process (and meta.send_mail) raises an error when the application has
|
|
4
|
+
-- no mail service, which rolls back the whole submit; until now application code had
|
|
5
|
+
-- no way to ask first (meta.mail_caller is internal). meta.mail_available() answers
|
|
6
|
+
-- whether the current application may send e-mail: it is called from application code
|
|
7
|
+
-- (meta.app_id() set), it has an enabled mail service, and that service has a From
|
|
8
|
+
-- address (the application's own or the service's default). Use it as a process's
|
|
9
|
+
-- condition: condition_type sql, condition_expr meta.mail_available(). It says nothing
|
|
10
|
+
-- about other applications and never raises. Found by the App Gallery's Sample Processes.
|
|
11
|
+
|
|
12
|
+
create function meta.mail_available() returns boolean
|
|
13
|
+
language sql stable security definer set search_path = meta, pg_catalog as $$
|
|
14
|
+
select meta.app_id() is not null and exists (
|
|
15
|
+
select 1
|
|
16
|
+
from meta.mail_service s
|
|
17
|
+
left join meta.app_mail_service x on x.app_id = meta.app_id() and x.service_id = s.id
|
|
18
|
+
where s.id = meta.mail_service_for_app(meta.app_id()) and s.enabled
|
|
19
|
+
and coalesce(x.from_address, nullif(s.settings->>'default_from', '')) is not null)
|
|
20
|
+
$$;
|
|
21
|
+
revoke all on function meta.mail_available() from public;
|
|
22
|
+
grant execute on function meta.mail_available() to public;
|
|
23
|
+
comment on function meta.mail_available() is 'Whether the current application may send e-mail: an enabled mail service with a From address (155)';
|
|
@@ -104,7 +104,7 @@ Region **templates**:
|
|
|
104
104
|
|---|---|
|
|
105
105
|
| `standard` | A card with a header (title and buttons) and a body |
|
|
106
106
|
| `plain` | No frame and no header: content only (good for KPI cards and intro text) |
|
|
107
|
-
| `collapsible` | A standard card whose body can be collapsed by clicking the header |
|
|
107
|
+
| `collapsible` | A standard card whose body can be collapsed by clicking the header; its header buttons stay visible while it is collapsed |
|
|
108
108
|
| `hero` | A large banner: an icon (`{"icon": "users"}` in the attributes), the title, the text and the buttons (APEX: Hero) |
|
|
109
109
|
| `title_bar` | The title and buttons on one line over the content, without a card (APEX: Title Bar) |
|
|
110
110
|
| `buttons_container` | The buttons only (after any text), without a header (APEX: Buttons Container) |
|
|
@@ -113,7 +113,7 @@ Plus: every **required** editable item must have a value.
|
|
|
113
113
|
| Property | Meaning |
|
|
114
114
|
|---|---|
|
|
115
115
|
| `item_name` | The message is shown next to this item (otherwise at the top of the page) |
|
|
116
|
-
| `message` | The error message |
|
|
116
|
+
| `message` | The error message; (sprint 166) `&ITEM.` substitutions are filled in with the submitted values (`The name &P3_NAME. is taken.`) |
|
|
117
117
|
| `when_button` | Only validate for this request. A `DELETE` request runs only the validations with `when_button` `DELETE` (e.g. "approved requests can't be deleted"), not the item checks |
|
|
118
118
|
| `condition_type`, `condition_expr`, `condition_value` | Only validate when the [server-side condition](#server-side-conditions) holds, e.g. `request_in` `SAVE,CREATE` (sprint 154) |
|
|
119
119
|
| `authz` | Only validate for users the authorization scheme lets through (sprint 155) |
|
|
@@ -154,7 +154,7 @@ two combine well; see error handling below.
|
|
|
154
154
|
| `parent_process` | The name of a `chain` process on the page: this process then runs only inside that chain |
|
|
155
155
|
| `region_id` | The form or grid region, for `form_dml` / `grid_dml` |
|
|
156
156
|
| `config` | Settings of a `data_load`, `invoke_api`, `download`, `chain`, `workflow`, `send_push` or `plugin` process (JSON) |
|
|
157
|
-
| `success_message` | Shown after the redirect; messages of several processes are joined |
|
|
157
|
+
| `success_message` | Shown after the redirect; messages of several processes are joined. (sprint 166) `&ITEM.` substitutions are filled in with the values after the process (`Order &P3_ID. created.`) |
|
|
158
158
|
| `authz` | Skipped when the user isn't authorized |
|
|
159
159
|
| `seq` | Order |
|
|
160
160
|
|
|
@@ -398,6 +398,10 @@ or with an [e-mail template](#e-mail-templates):
|
|
|
398
398
|
{"to": "&P5_EMAIL.", "template": "ORDER_SHIPPED", "placeholders": {"NAME": "&P5_NAME.", "ORDER_ID": "&P5_ID."}}
|
|
399
399
|
```
|
|
400
400
|
|
|
401
|
+
Without a mail service the process raises an error and the submit is rolled back. When the mail is
|
|
402
|
+
optional, give the process the condition `sql` `meta.mail_available()` (sprint 166): it runs only when
|
|
403
|
+
the application has an enabled mail service with a From address.
|
|
404
|
+
|
|
401
405
|
`to`, `cc`, `bcc` (several addresses separated by commas), `from`, `reply_to`, `subject` and `body`
|
|
402
406
|
take `&ITEM.` substitutions as they are; in `body_html` the item values are HTML-escaped.
|
|
403
407
|
`attachments` is a SELECT (binds allowed) with the columns `content` (bytea), `filename` and `mime`,
|
|
@@ -448,7 +452,7 @@ the process's attributes (`config`), checked when it is saved and again when it
|
|
|
448
452
|
|---|---|---|---|
|
|
449
453
|
| `close_dialog` | Close Dialog | `{"items": ["P5_ID"]}` | Closes the modal page after the submit (a modal page closes after a submit anyway; this one also hands the items' values to the page below, where items of the same name take them before its [Dialog Closed](07-dynamic-actions.md#dialog-closed) actions run) |
|
|
450
454
|
| `clear_session_state` | Clear Session State | `{"items": ["P3_NAME"], "pages": [3, 4], "all": false}` | Empties the items, every `P<n>_` item of the pages, or all page and application items |
|
|
451
|
-
| `reset_pagination` | Reset Pagination | – |
|
|
455
|
+
| `reset_pagination` | Reset Pagination | – | An interactive grid saved from a later page starts on its first page again (a report starts on its first page after any submit anyway: its page number is in the URL) |
|
|
452
456
|
| `user_preferences` | User Preferences | `{"action": "set", "preference": "DEFAULT_DEPT", "item": "P1_DEPT"}`, or `"reset"` | Stores (or removes) a [user preference](#user-preferences); nothing for the public user |
|
|
453
457
|
| `server_geocoding` | Server Side Geocoding | `{"address_item": "P3_ADDRESS", "coordinates_item": "P3_LOCATION", "country": "nl"}` | The installation's geocoder (`GEOCODER_URL`); the first place's `lat,lng` goes into the coordinates item |
|
|
454
458
|
| `print_report` | Print Report | `{"document": "EMPLOYEE_SHEET"}` | A [document template](16-files.md#document-templates) or report query, sent as the answer to the submit |
|
|
@@ -91,7 +91,7 @@ items, in the browser. A tree of departments that filters an employee report, wi
|
|
|
91
91
|
|---|---|---|
|
|
92
92
|
| `show` / `hide` | browser | Show or hide the affected items/region. **With a condition, the opposite happens when the condition is false**, so one dynamic action gives "show commission only for salesmen". Also applied when the page loads (and already on the server, so there's no flicker) |
|
|
93
93
|
| `enable` / `disable` | browser | Enable or disable the controls, reversed when the condition is false |
|
|
94
|
-
| `set_value` | server | Runs `code` (a SELECT); its first row's columns set the affected items, in order |
|
|
94
|
+
| `set_value` | server | Runs `code` (a SELECT); its first row's columns set the affected items, in order. The affected items must be on the page: a `hidden` item isn't rendered (its value stays on the server, safe from tampering), so set a text or display item, or use a computation |
|
|
95
95
|
| `execute_sql` | server | Runs `code`; returned columns named like items set those items |
|
|
96
96
|
| `refresh_region` | server | Re-renders `affected_region_id` with the current session state (e.g. a report filtered by the changed item) |
|
|
97
97
|
| `refresh_item` | server | Re-renders the affected items (e.g. a list whose LOV depends on another item) |
|
|
@@ -119,10 +119,10 @@ items, in the browser. A tree of departments that filters an employee report, wi
|
|
|
119
119
|
| `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) |
|
|
120
120
|
| `remove_wait_popup` | browser | Removes the wait popup (APEX: Remove Wait Popup) |
|
|
121
121
|
| `cancel_event` | browser | Stops the actions after it, and the event's own effect: a button doesn't submit or follow its link, a typed key (`key_down`, `key_press`) stays out of the field, a refresh doesn't happen (APEX: Cancel Event). Sprint 156 |
|
|
122
|
-
| `set_style` | browser | Sets CSS properties on the affected items or region: `{"styles": {"color": "#b42318", "font-weight": "bold"}}` in the attributes (APEX: Set Style). Properties from a fixed list (colours, font, text, border, opacity, visibility, padding, margin, width); values without `url()` or `var()`, checked when saved and again before they reach the page |
|
|
122
|
+
| `set_style` | browser | Sets CSS properties on the affected items (their field: input, select list or text area; (sprint 166) before, the item's wrapper) or region: `{"styles": {"color": "#b42318", "font-weight": "bold"}}` in the attributes (APEX: Set Style). Properties from a fixed list (colours, font, text, border, opacity, visibility, padding, margin, width); values without `url()` or `var()`, checked when saved and again before they reach the page |
|
|
123
123
|
| `collapse_tree` / `expand_tree` | browser | Closes / opens every node of the affected tree region (APEX: Collapse Tree, Expand Tree) |
|
|
124
124
|
| `show_ai_assistant` | browser | Opens the affected AI assistant region (or the inline dialog it sits in) and puts the cursor in its question (APEX: Show AI Assistant) |
|
|
125
|
-
| `trigger_geocoding` | browser | The affected geocoded address item looks its text up, as its *Check address* button does (APEX: Trigger Geocoding) |
|
|
125
|
+
| `trigger_geocoding` | browser | The affected geocoded address item looks its text up, as its *Check address* button does (APEX: Trigger Geocoding); (sprint 166) not when its text is the address just chosen from the list, so a `change` action on the address item itself doesn't reopen it |
|
|
126
126
|
| `open_report_dialog` | browser | Opens a dialog of the affected interactive report: `code` is `filter`, `columns`, `sort`, `highlight`, `compute`, `aggregate`, `chart`, `group`, `pivot`, `break`, `rows` or `saved` (APEX: Invoke Interactive Report Dialog) |
|
|
127
127
|
|
|
128
128
|
Server-side actions first store `items_to_submit` in session state, run as the application's
|
|
@@ -111,6 +111,7 @@ service (an administrator chooses it under Workspace utilities → Mail services
|
|
|
111
111
|
| `meta.send_mail_template(p_template, p_placeholders jsonb, p_to, p_cc, p_bcc, p_from, p_reply_to)` | The same with an [e-mail template](06-processing.md#e-mail-templates) and its placeholder values (APEX_MAIL.SEND with `p_template_static_id`) |
|
|
112
112
|
| `meta.add_mail_attachment(p_mail_id, p_content bytea, p_filename, p_mime)` | Adds a file to a mail queued **in the same transaction** (APEX_MAIL.ADD_ATTACHMENT); at most 20 files and 20 MB per mail, 10 MB per file |
|
|
113
113
|
| `meta.mail_status(p_mail_id)` | `queued`, `sending`, `sent`, `error` or `cancelled`; NULL for another application's mail |
|
|
114
|
+
| `meta.mail_available()` | (155) Whether the application may send e-mail now: it has an enabled mail service with a From address. Without one, `meta.send_mail` and a Send E-Mail process raise an error (rolling back the submit), so use it as their condition when mail is optional |
|
|
114
115
|
|
|
115
116
|
The mail is sent by the server **after the transaction commits**. Over the application's limit per
|
|
116
117
|
day `meta.send_mail` raises an error. Application roles can't read the queue; the owner can:
|
|
@@ -15,6 +15,7 @@ scripts/migrate.ts migration/seed runner (src/migrate.ts does the work)
|
|
|
15
15
|
website/ the website pgkiln.vargar.eu (VitePress): landing page + this guide, synced from docs/ at build time;
|
|
16
16
|
cd website && npm install && npm run dev; scripts/deploy.sh publishes it (see website/deploy/)
|
|
17
17
|
scripts/screenshots.ts the README's screenshots in docs/images/ (npm run screenshots, HR example)
|
|
18
|
+
scripts/check-sample.ts (sprint 166) one App Gallery sample: install, sign in as demo, render every page, report errors (--keep)
|
|
18
19
|
scripts/docker-start.ts container entry point: checks secrets, migrates (locked), sets role and admin passwords, starts the server
|
|
19
20
|
deploy/ compose.yaml and .env.example for Docker (Dockerfile at the root; chapter 1)
|
|
20
21
|
bin/pgkiln.js the `pgkiln` command line (runs src/cli/main.ts with tsx)
|
|
@@ -651,6 +652,9 @@ test/
|
|
|
651
652
|
gallery.test.ts the App Gallery: every starter and utility created from its review and every sample app installed, with all
|
|
652
653
|
their pages rendering; sample apps (admins), the search, a failing script leaves nothing
|
|
653
654
|
gallery-samples.ts the gallery's shipped apps for the tests: SAMPLES (schema and role of each script), BLUEPRINTS, drop/install
|
|
655
|
+
sample-coverage.test.ts (sprint 166) every feature in a sample app: each value of the components' catalogues (check constraints
|
|
656
|
+
listed in sample-coverage.sql, and meta.condition_ok's condition types) is used by HR or a gallery
|
|
657
|
+
sample, or exempted there with a reason
|
|
654
658
|
builder-apex-screens.test.ts sprint 61: sign-in with a workspace, message and banner, utilization, SQL Workshop home, groups, reports
|
|
655
659
|
workspace-isolation.test.ts isolated workspaces (090): logins, RLS on meta, app_id, roles, SQL Workshop, scripts, imports
|
|
656
660
|
ai.test.ts AI services: Generate text with AI (text, structured outputs, errors, limits, keys), its dynamic action,
|
|
@@ -19,21 +19,38 @@ The builder's **App Gallery** (`/builder/gallery`) lists what `gallery.json` nam
|
|
|
19
19
|
|---|---|---|
|
|
20
20
|
| `sample-charts.sql` | Sample Charts (`/a/sample-charts`) | the 22 chart types, a page per question, a drill-down to a report |
|
|
21
21
|
| `sample-cards.sql` | Sample Cards | cards with a form, KPI tiles, lists as cards, built-in template components |
|
|
22
|
-
| `sample-calendar.sql` | Sample Calendar | month, week, day and list views, create on click, drag and drop through a function |
|
|
23
|
-
| `sample-grids.sql` | Sample Interactive Grids | an editable grid, select lists, totals, frozen column, row menu, master-detail, views |
|
|
24
|
-
| `sample-dynamic-actions.sql` | Sample Dynamic Actions | show/hide/enable, set value, refresh, messages, wait popup, classes, inline dialog |
|
|
22
|
+
| `sample-calendar.sql` | Sample Calendar | month, week, day and list views, create on click, drag and drop through a function, the calendar's dynamic action events (a chosen day, the view) |
|
|
23
|
+
| `sample-grids.sql` | Sample Interactive Grids | an editable grid, select lists, totals, frozen column, row menu, master-detail, views, a new row's defaults (`grid_row_init`) and a message after saving (`grid_save`) |
|
|
24
|
+
| `sample-dynamic-actions.sql` | Sample Dynamic Actions | show/hide/enable, set value, refresh, messages, wait popup, classes, inline dialog, and every other event (keyboard, mouse, focus, before submit, page unload, resize, scroll, refreshes, a tree, facets) and action (styles, disable, clear, submit, download, print, share, push subscribe, dialogs, trees, report dialogs, the AI assistant) with client-side conditions |
|
|
25
25
|
| `sample-reporting.sql` | Sample Reporting | interactive report, faceted search, smart filters, a classic report with column options |
|
|
26
|
-
| `sample-maps.sql` | Sample Maps | markers and clusters, layers with GeoJSON and a heat map, a map filtering a list by distance, a location item |
|
|
27
|
-
| `sample-approvals.sql` | Sample Approvals | a workflow started by a form, an approval task and the task list, the workflow console (accounts demo and approver) |
|
|
26
|
+
| `sample-maps.sql` | Sample Maps | markers and clusters, layers with GeoJSON and a heat map, a map filtering a list by distance, a location item, the map's events (a clicked marker, the zoom), the current position, geocoding an address and the Display Map item |
|
|
27
|
+
| `sample-approvals.sql` | Sample Approvals | a workflow started by a form, an approval task and the task list, the workflow console and diagram, the notification bell (`send_notification`, `meta.notify`), a navigation menu with badges from SQL and live badges, a `timer` dynamic action (accounts demo and approver) |
|
|
28
|
+
| `sample-validations.sql` | Sample Validations | every validation type and error location, every server-side condition type |
|
|
29
|
+
| `sample-processes.sql` | Sample Processes | every process type, page and application computations, branches, application processes and Ajax callbacks, report queries, shortcuts |
|
|
28
30
|
| `sample-data-loading.sql` | Sample Data Loading | `data_load` processes: by column name and merged, through a data load definition, skipping rows with errors |
|
|
29
31
|
| `sample-files.sql` | Sample Files | a file in the record's columns, several files in a child table, a photo cropped before upload |
|
|
30
|
-
| `ux-patterns.sql` | UX Pattern Catalog | region templates and options, buttons, items and form layouts, content and page patterns |
|
|
32
|
+
| `ux-patterns.sql` | UX Pattern Catalog | region templates and options, buttons and every button position, items (autocomplete, image upload, multiselect, percent graph, URL) and form layouts, content and page patterns, display positions, the minimal and right-side-column page templates, URL and sign-in regions |
|
|
31
33
|
|
|
32
34
|
Each script creates its own schema (`sample_charts`, …), a role (`app_sample_charts`, …) granted to the runtime, the
|
|
33
35
|
application and one account, **demo** / **demo**. Without the builder:
|
|
34
36
|
`psql "$DATABASE_URL" -1 -f examples/gallery/samples/sample-charts.sql`. To remove one, delete the application in the
|
|
35
37
|
App Builder, then `drop schema sample_charts cascade; drop role app_sample_charts;`.
|
|
36
38
|
|
|
39
|
+
## Every feature in a sample app
|
|
40
|
+
|
|
41
|
+
Every catalogued value of the application components (region, item, process, validation and
|
|
42
|
+
computation types, dynamic action events, actions and conditions, server-side condition types,
|
|
43
|
+
button positions, page templates, …) is used by HR or one of these samples: `test/sample-coverage.test.ts`
|
|
44
|
+
fails for a value nobody uses, unless it is exempted there with the reason it can't be shown (an
|
|
45
|
+
authentication scheme that needs an outside service). `test/gallery.test.ts` installs each sample and
|
|
46
|
+
renders every page as its demo account, so what a sample shows also works. A new feature therefore
|
|
47
|
+
comes with a sample that uses it. While you write one:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npx tsx scripts/check-sample.ts examples/gallery/samples/sample-processes.sql # install, render every page, remove
|
|
51
|
+
npx tsx scripts/check-sample.ts examples/gallery/samples/sample-processes.sql --keep # and leave it installed
|
|
52
|
+
```
|
|
53
|
+
|
|
37
54
|
A sample script follows the pattern of the HR example: plain SQL that inserts `meta.*` rows, using
|
|
38
55
|
`meta.generate_page()` for wizard-made pages and two temporary helpers (`pg_temp.page_id`, `pg_temp.region_id`).
|
|
39
56
|
Every page starts with a text that says how it is made.
|
|
@@ -20,6 +20,10 @@
|
|
|
20
20
|
"description": "Maps of Dutch museums without PostGIS: markers with popups and clustering, several layers (a route and an area as GeoJSON, a heat map you switch on), a map that filters the list beside it by distance, and a form with a location item. Demo account demo / demo."},
|
|
21
21
|
{"key": "sample-approvals", "title": "Sample Approvals", "alias": "sample-approvals", "icon": "list-checks", "file": "samples/sample-approvals.sql",
|
|
22
22
|
"description": "Purchase requests that start a workflow: approved by itself below 1,000, otherwise an approval task for the role approver, decided under My tasks; the workflow console with its diagram and history. Demo accounts demo / demo (asks) and approver / approver (decides)."},
|
|
23
|
+
{"key": "sample-validations", "title": "Sample Validations", "alias": "sample-validations", "icon": "shield", "file": "samples/sample-validations.sql",
|
|
24
|
+
"description": "Every validation type with its error shown next to the field, at the top or both, and every server-side condition type deciding whether a region, item, button or process is there, each page saying which one it shows. Demo account demo / demo."},
|
|
25
|
+
{"key": "sample-processes", "title": "Sample Processes", "alias": "sample-processes", "icon": "settings", "file": "samples/sample-processes.sql",
|
|
26
|
+
"description": "What runs when a page is shown and submitted: every process type, page and application computations, branches, application processes and Ajax callbacks, report queries in six formats and shortcuts, each page saying how it is made. Demo account demo / demo."},
|
|
23
27
|
{"key": "sample-data-loading", "title": "Sample Data Loading", "alias": "sample-data-loading", "icon": "file-up", "file": "samples/sample-data-loading.sql",
|
|
24
28
|
"description": "Loading CSV, Excel and JSON files into a table from a page: merged by the key, through a data load definition that maps other headings and a date format, and skipping rows with errors. Example files to download included. Demo account demo / demo."},
|
|
25
29
|
{"key": "sample-files", "title": "Sample Files", "alias": "sample-files", "icon": "files", "file": "samples/sample-files.sql",
|
|
@@ -7,7 +7,18 @@
|
|
|
7
7
|
-- * from 1,000 it asks for approval: a PURCHASE_APPROVAL task for the
|
|
8
8
|
-- users with the role approver, decided under My tasks; the task's
|
|
9
9
|
-- action records the decision on the request
|
|
10
|
-
-- * the console lists the workflows with their diagram and history
|
|
10
|
+
-- * the console lists the workflows with their diagram and history; the
|
|
11
|
+
-- request's form draws its workflow (a workflow_diagram region)
|
|
12
|
+
-- * on the request's form the approver claims or releases the task, and
|
|
13
|
+
-- who asked can ask again after a rejection (the declarative process
|
|
14
|
+
-- types human_task_manage and human_task_create)
|
|
15
|
+
-- * the notification bell: a request of 1,000 or more notifies the
|
|
16
|
+
-- approver (a send_notification process), a decision notifies who asked
|
|
17
|
+
-- (meta.notify in the decision); opening one goes to the request
|
|
18
|
+
-- * the navigation menu is a list whose entries have badges from SQL
|
|
19
|
+
-- (your pending requests, the tasks waiting for you), refreshed every
|
|
20
|
+
-- 15 seconds by the live badges; a timer dynamic action refreshes
|
|
21
|
+
-- "Waiting for me"
|
|
11
22
|
--
|
|
12
23
|
-- Two accounts: demo / demo asks, approver / approver decides (nobody
|
|
13
24
|
-- approves their own request).
|
|
@@ -50,11 +61,25 @@ create trigger purchase_defaults before insert on sample_approvals.purchase
|
|
|
50
61
|
-- the decision, from the approval task's action (by the approver) or the workflow's SQL step
|
|
51
62
|
-- (which runs as the user who started the workflow, so it says who decided: 'workflow')
|
|
52
63
|
create function sample_approvals.decide(p_id int, p_outcome text, p_note text, p_by text default null) returns void language plpgsql as $$
|
|
64
|
+
declare
|
|
65
|
+
p sample_approvals.purchase;
|
|
53
66
|
begin
|
|
54
67
|
update sample_approvals.purchase
|
|
55
68
|
set status = case lower(p_outcome) when 'approved' then 'APPROVED' when 'rejected' then 'REJECTED' else status end,
|
|
56
69
|
decided_by = coalesce(p_by, meta.app_user()), note = nullif(p_note, '')
|
|
57
|
-
where id = p_id and status = 'PENDING'
|
|
70
|
+
where id = p_id and status = 'PENDING'
|
|
71
|
+
returning * into p;
|
|
72
|
+
-- the bell of who asked: the decision, opening the request (page 3); a notification
|
|
73
|
+
-- that can't be added (notifications turned off) never stops the decision
|
|
74
|
+
if p.status in ('APPROVED', 'REJECTED') and p.requested_by is not null and meta.app_id() is not null then
|
|
75
|
+
begin
|
|
76
|
+
perform meta.notify(p.requested_by, format('Purchase %s: %s', lower(p.status), p.title),
|
|
77
|
+
coalesce(p.note, format('Decided by %s', p.decided_by)),
|
|
78
|
+
3, jsonb_build_object('P3_ID', p.id), case p.status when 'APPROVED' then 'check' else 'x' end);
|
|
79
|
+
exception when others then
|
|
80
|
+
raise warning 'sample_approvals.decide: no notification: %', sqlerrm;
|
|
81
|
+
end;
|
|
82
|
+
end if;
|
|
58
83
|
end
|
|
59
84
|
$$;
|
|
60
85
|
|
|
@@ -73,8 +98,8 @@ grant select, insert, update, delete on sample_approvals.purchase to app_sample_
|
|
|
73
98
|
grant execute on function sample_approvals.decide(int, text, text, text) to app_sample_approvals;
|
|
74
99
|
|
|
75
100
|
-- 3. The application ----------------------------------------------------------
|
|
76
|
-
insert into meta.app (alias, name, home_page, authentication, db_role)
|
|
77
|
-
values ('sample-approvals', 'Sample Approvals', 1, 'app_users', 'app_sample_approvals');
|
|
101
|
+
insert into meta.app (alias, name, home_page, authentication, db_role, notifications, live_badges_s, nav_list)
|
|
102
|
+
values ('sample-approvals', 'Sample Approvals', 1, 'app_users', 'app_sample_approvals', true, 15, 'APPROVAL_MENU');
|
|
78
103
|
|
|
79
104
|
insert into meta.app_user (app_id, username, password_hash, roles)
|
|
80
105
|
select a.id, u.name, meta.hash_password(u.name), u.roles
|
|
@@ -116,13 +141,29 @@ insert into meta.process (page_id, seq, name, type, code, when_button)
|
|
|
116
141
|
select pg_temp.page_id('sample-approvals', 3), 15, 'Start the approval', 'sql',
|
|
117
142
|
$c$select meta.start_workflow('PURCHASE', :P3_ID, jsonb_build_object('TITLE', :P3_TITLE, 'AMOUNT', :P3_AMOUNT))$c$, 'CREATE';
|
|
118
143
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
144
|
+
-- from 1,000 the approver gets a notification on the bell (the declarative process type
|
|
145
|
+
-- send_notification; an application with several approvers would call meta.notify for each)
|
|
146
|
+
insert into meta.process (page_id, seq, name, type, when_button, condition_type, condition_expr, config)
|
|
147
|
+
select pg_temp.page_id('sample-approvals', 3), 16, 'Tell the approver', 'send_notification', 'CREATE',
|
|
148
|
+
'sql', ':P3_AMOUNT::numeric >= 1000',
|
|
149
|
+
'{"to": "approver", "title": "Purchase to approve: &P3_TITLE.", "body": "&P3_AMOUNT. from &P3_SUPPLIER., asked by &APP_USER.", "page": 4, "icon": "check"}'::jsonb;
|
|
123
150
|
|
|
124
151
|
insert into meta.list (app_id, name, type, description)
|
|
125
|
-
select id,
|
|
152
|
+
select id, l.name, 'static', l.description from meta.app, (values
|
|
153
|
+
('APPROVAL_TOPICS', 'The pages of Sample Approvals'),
|
|
154
|
+
('APPROVAL_MENU', 'The navigation menu, with badges from SQL')) as l (name, description)
|
|
155
|
+
where alias = 'sample-approvals';
|
|
156
|
+
|
|
157
|
+
-- the navigation menu (app.nav_list): badges from SQL, refreshed by the live badges
|
|
158
|
+
insert into meta.list_entry (app_id, list_name, seq, label, icon, target_page, badge_query)
|
|
159
|
+
select a.id, 'APPROVAL_MENU', e.seq, e.label, e.icon, e.page, e.badge_query
|
|
160
|
+
from meta.app a, (values
|
|
161
|
+
(10, 'Home', 'home', 1, null),
|
|
162
|
+
(20, 'Purchases', 'list', 2, 'select nullif(count(*), 0) from sample_approvals.purchase where status = ''PENDING'' and requested_by = meta.app_user()'),
|
|
163
|
+
(30, 'My tasks', 'check', 4, 'select nullif(count(*), 0) from meta.tasks where app_id = meta.app_id() and may_act and state in (''unassigned'', ''assigned'')'),
|
|
164
|
+
(40, 'Workflows', 'route', 5, null)
|
|
165
|
+
) as e (seq, label, icon, page, badge_query)
|
|
166
|
+
where a.alias = 'sample-approvals';
|
|
126
167
|
|
|
127
168
|
insert into meta.list_entry (app_id, list_name, seq, label, icon, target_page, description)
|
|
128
169
|
select a.id, 'APPROVAL_TOPICS', e.seq, e.label, e.icon, e.page, e.description
|
|
@@ -137,11 +178,11 @@ insert into meta.region (page_id, seq, title, type, columns, template, source, c
|
|
|
137
178
|
select pg_temp.page_id('sample-approvals', r.page), r.seq, r.title, r.type, 12, r.template, r.source, r.config::jsonb
|
|
138
179
|
from (values
|
|
139
180
|
(1, 10, 'Sample Approvals', 'static', 'hero',
|
|
140
|
-
'<p>Signed in as <strong>demo</strong>, ask for a purchase under <em>Purchases</em>. Below 1,000 the workflow approves it at once; from 1,000 it creates an approval task for the role <code>approver</code>. Sign in as <strong>approver</strong> / approver and decide under <em>My tasks</em>: the task''s action writes the decision on the request, and the workflow ends.</p>',
|
|
181
|
+
'<p>Signed in as <strong>demo</strong>, ask for a purchase under <em>Purchases</em>. Below 1,000 the workflow approves it at once; from 1,000 it creates an approval task for the role <code>approver</code>. Sign in as <strong>approver</strong> / approver and decide under <em>My tasks</em>: the task''s action writes the decision on the request, and the workflow ends.</p><p>The bell in the header shows notifications: the approver gets one for each request of 1,000 or more (a <code>send_notification</code> process), and who asked gets the decision (<code>meta.notify</code>). The menu''s badges come from SQL and refresh every 15 seconds without a reload: keep both accounts open in two browsers and watch them change.</p>',
|
|
141
182
|
'{"icon": "check"}'),
|
|
142
183
|
(1, 20, 'Topics', 'list', 'plain', null, '{"list": "APPROVAL_TOPICS", "template": "cards"}'),
|
|
143
184
|
(2, 5, 'About this page', 'static', 'plain',
|
|
144
|
-
'<p class="lead">The form''s Create button runs the wizard''s row process, then <code>select meta.start_workflow(''PURCHASE'', :P3_ID, …)</code>. A trigger fills in who asked and the status from the session, so the form can''t set them.</p>', '{}'),
|
|
185
|
+
'<p class="lead">The form''s Create button runs the wizard''s row process, then <code>select meta.start_workflow(''PURCHASE'', :P3_ID, …)</code>. A trigger fills in who asked and the status from the session, so the form can''t set them.</p><p>Open a request: below the form a <code>workflow_diagram</code> region draws its workflow, the steps it went through and where it is now. While its approval task is open, the approver gets <strong>Claim task</strong> (and then <strong>Release task</strong>): processes of type <code>human_task_manage</code>. After a rejection, who asked gets <strong>Ask again</strong>: the request, changed or not, is pending again and a process of type <code>human_task_create</code> creates a new <code>PURCHASE_APPROVAL</code> task, outside the workflow, which has ended.</p>', '{}'),
|
|
145
186
|
(4, 5, 'About this page', 'static', 'plain',
|
|
146
187
|
'<p class="lead">Regions of type <code>tasks</code>: what waits for the signed-in user (as owner, here the role <code>approver</code>), and what they asked for. Approve and Reject run the task definition''s action with <code>:DETAIL_PK</code>, <code>:OUTCOME</code> and <code>:COMMENT</code>.</p>', '{}'),
|
|
147
188
|
(4, 10, 'Waiting for me', 'tasks', 'standard', null, '{"empty": "Nothing waits for you. Sign in as approver to decide on requests of 1,000 or more."}'),
|
|
@@ -151,3 +192,53 @@ select pg_temp.page_id('sample-approvals', r.page), r.seq, r.title, r.type, 12,
|
|
|
151
192
|
(5, 10, 'Workflows I started', 'workflows', 'standard', null, '{"completed": true}'),
|
|
152
193
|
(5, 20, 'All purchase workflows (approvers)', 'workflows', 'collapsible', null, '{"context": "admin", "completed": true}')
|
|
153
194
|
) as r (page, seq, title, type, template, source, config);
|
|
195
|
+
|
|
196
|
+
-- a timer dynamic action: "Waiting for me" refreshes every 30 seconds while the page is visible
|
|
197
|
+
insert into meta.dynamic_action (page_id, seq, name, event, trigger_element, action, affected_region_id)
|
|
198
|
+
select pg_temp.page_id('sample-approvals', 4), 10, 'Refresh what waits', 'timer', '30', 'refresh_region', r.id
|
|
199
|
+
from meta.region r where r.page_id = pg_temp.page_id('sample-approvals', 4) and r.title = 'Waiting for me';
|
|
200
|
+
|
|
201
|
+
-- page 3, the request's form (also the task's details page): its open approval task and its workflow,
|
|
202
|
+
-- computed when the page is shown
|
|
203
|
+
insert into meta.item (page_id, region_id, seq, name, type)
|
|
204
|
+
select r.page_id, r.id, i.seq, i.name, 'hidden'
|
|
205
|
+
from meta.region r, (values (200, 'P3_TASK_ID'), (210, 'P3_WORKFLOW_ID')) as i (seq, name)
|
|
206
|
+
where r.page_id = pg_temp.page_id('sample-approvals', 3) and r.type = 'form';
|
|
207
|
+
|
|
208
|
+
insert into meta.computation (page_id, seq, item_name, point, type, expression)
|
|
209
|
+
values
|
|
210
|
+
(pg_temp.page_id('sample-approvals', 3), 10, 'P3_TASK_ID', 'before_header', 'sql_query',
|
|
211
|
+
$q$select id::text from meta.tasks where app_id = meta.app_id() and definition = 'PURCHASE_APPROVAL' and detail_pk = :P3_ID and state in ('unassigned', 'assigned') order by id desc limit 1$q$),
|
|
212
|
+
(pg_temp.page_id('sample-approvals', 3), 20, 'P3_WORKFLOW_ID', 'before_header', 'sql_query',
|
|
213
|
+
$q$select id::text from meta.workflows where app_id = meta.app_id() and name = 'PURCHASE' and detail_pk = :P3_ID order by id desc limit 1$q$);
|
|
214
|
+
|
|
215
|
+
-- the workflow of the request: a Workflow Diagram region (shown to who started it and its administrators)
|
|
216
|
+
insert into meta.region (page_id, seq, title, type, columns, template, config, condition_type, condition)
|
|
217
|
+
values (pg_temp.page_id('sample-approvals', 3), 20, 'Its workflow', 'workflow_diagram', 12, 'standard',
|
|
218
|
+
'{"instance_item": "P3_WORKFLOW_ID", "empty": "You can''t see this request''s workflow: only who asked and the approvers can."}',
|
|
219
|
+
'item_not_null', 'P3_WORKFLOW_ID');
|
|
220
|
+
|
|
221
|
+
-- the open task: Claim (a potential owner, while nobody has it) and Release (its owner);
|
|
222
|
+
-- after a rejection who asked can Ask again: the request (as changed on the form) pending again, and a new approval task
|
|
223
|
+
insert into meta.button (page_id, region_id, seq, name, label, action, condition_type, condition)
|
|
224
|
+
select r.page_id, r.id, b.seq, b.name, b.label, 'submit', 'exists', b.cond
|
|
225
|
+
from meta.region r, (values
|
|
226
|
+
(50, 'CLAIM', 'Claim task', $c$select 1 from meta.tasks where id = nullif(:P3_TASK_ID, '')::bigint and may_claim$c$),
|
|
227
|
+
(60, 'RELEASE', 'Release task', $c$select 1 from meta.tasks where id = nullif(:P3_TASK_ID, '')::bigint and may_release$c$),
|
|
228
|
+
(70, 'ASK_AGAIN', 'Ask again', $c$select 1 from sample_approvals.purchase where id = :P3_ID::int and status = 'REJECTED' and requested_by = meta.app_user()$c$)
|
|
229
|
+
) as b (seq, name, label, cond)
|
|
230
|
+
where r.page_id = pg_temp.page_id('sample-approvals', 3) and r.type = 'form';
|
|
231
|
+
|
|
232
|
+
insert into meta.process (page_id, seq, name, type, code, when_button, config, success_message)
|
|
233
|
+
values
|
|
234
|
+
(pg_temp.page_id('sample-approvals', 3), 30, 'Claim the task', 'human_task_manage', null, 'CLAIM',
|
|
235
|
+
'{"action": "claim", "task": "&P3_TASK_ID."}', 'The task is yours: approve or reject it under My tasks.'),
|
|
236
|
+
(pg_temp.page_id('sample-approvals', 3), 40, 'Release the task', 'human_task_manage', null, 'RELEASE',
|
|
237
|
+
'{"action": "release", "task": "&P3_TASK_ID.", "comment": "Released from the request''s page"}', 'The task is back with every approver.'),
|
|
238
|
+
(pg_temp.page_id('sample-approvals', 3), 50, 'Pending again', 'sql',
|
|
239
|
+
$c$update sample_approvals.purchase
|
|
240
|
+
set title = :P3_TITLE, supplier = :P3_SUPPLIER, amount = :P3_AMOUNT::numeric, status = 'PENDING', decided_by = null, note = null
|
|
241
|
+
where id = :P3_ID::int and status = 'REJECTED' and requested_by = meta.app_user()$c$, 'ASK_AGAIN', '{}', null),
|
|
242
|
+
(pg_temp.page_id('sample-approvals', 3), 51, 'Ask for approval again', 'human_task_create', null, 'ASK_AGAIN',
|
|
243
|
+
'{"definition": "PURCHASE_APPROVAL", "detail_pk": "&P3_ID.", "parameters": {"TITLE": "&P3_TITLE.", "AMOUNT": "&P3_AMOUNT."}, "id_item": "P3_TASK_ID"}',
|
|
244
|
+
'Asked again: the approvers have a new task.');
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
--
|
|
4
4
|
-- The calendar region: month, week, day and list views, all-day and timed
|
|
5
5
|
-- events, events over several days, create on click, drag and drop through
|
|
6
|
-
-- a PL/pgSQL function with its own rule,
|
|
6
|
+
-- a PL/pgSQL function with its own rule, a calendar filtered by an item, and
|
|
7
|
+
-- dynamic actions on a day chosen and on another view.
|
|
7
8
|
-- The events are placed around today, so there is always something to see.
|
|
8
9
|
--
|
|
9
10
|
-- Installed by an administrator from the App Gallery, or with
|
|
@@ -79,7 +80,7 @@ select id, 'demo', meta.hash_password('demo'), '{}' from meta.app where alias =
|
|
|
79
80
|
insert into meta.page (app_id, page_no, name, title, parent_page)
|
|
80
81
|
select a.id, p.no, p.name, p.name, p.parent
|
|
81
82
|
from meta.app a, (values
|
|
82
|
-
(1, 'Sample Calendar', null), (2, 'Month', 1), (4, 'Week planner', 1), (5, 'Agenda', 1), (6, 'By category', 1)
|
|
83
|
+
(1, 'Sample Calendar', null), (2, 'Month', 1), (4, 'Week planner', 1), (5, 'Agenda', 1), (6, 'By category', 1), (7, 'Calendar events', 1)
|
|
83
84
|
) as p (no, name, parent)
|
|
84
85
|
where a.alias = 'sample-calendar';
|
|
85
86
|
|
|
@@ -96,7 +97,7 @@ insert into meta.nav_entry (app_id, seq, label, icon, target_page)
|
|
|
96
97
|
select a.id, n.seq, n.label, n.icon, n.page
|
|
97
98
|
from meta.app a, (values
|
|
98
99
|
(10, 'Home', 'home', 1), (20, 'Month', 'calendar', 2), (30, 'Week planner', 'clock', 4),
|
|
99
|
-
(40, 'Agenda', 'list', 5), (50, 'By category', 'filter', 6)
|
|
100
|
+
(40, 'Agenda', 'list', 5), (50, 'By category', 'filter', 6), (60, 'Calendar events', 'bolt', 7)
|
|
100
101
|
) as n (seq, label, icon, page)
|
|
101
102
|
where a.alias = 'sample-calendar';
|
|
102
103
|
|
|
@@ -109,7 +110,8 @@ select a.id, 'CALENDAR_TOPICS', e.seq, e.label, e.icon, e.page, e.description
|
|
|
109
110
|
(10, 'Month', 'calendar', 2, 'All four views; click a day to add an event, drag an event to move it.'),
|
|
110
111
|
(20, 'Week planner', 'clock', 4, 'Week and day views from 7:00 to 19:00, opened on the week.'),
|
|
111
112
|
(30, 'Agenda', 'list', 5, 'The list view only: a month of events per day.'),
|
|
112
|
-
(40, 'By category', 'filter', 6, 'A calendar whose query uses a page item, refreshed when it changes.')
|
|
113
|
+
(40, 'By category', 'filter', 6, 'A calendar whose query uses a page item, refreshed when it changes.'),
|
|
114
|
+
(50, 'Calendar events', 'bolt', 7, 'Dynamic actions on the calendar''s own events: a day chosen, another view or period.')
|
|
113
115
|
) as e (seq, label, icon, page, description)
|
|
114
116
|
where a.alias = 'sample-calendar';
|
|
115
117
|
|
|
@@ -159,7 +161,19 @@ select pg_temp.page_id('sample-calendar', r.page), r.seq, r.title, r.type, r.col
|
|
|
159
161
|
(6, 10, 'Filter', 'static', 12, 'standard', null, '{}', 'filter'),
|
|
160
162
|
(6, 20, 'Events of the category', 'calendar', 12, 'standard',
|
|
161
163
|
E'select starts as start_date, ends as end_date, title, id\n from sample_calendar.event\n where :P6_CATEGORY is null or category = :P6_CATEGORY',
|
|
162
|
-
'{"views": ["month", "list"], "link": {"page": 3, "items": {"P3_ID": "#id#"}}}', 'category-calendar')
|
|
164
|
+
'{"views": ["month", "list"], "link": {"page": 3, "items": {"P3_ID": "#id#"}}}', 'category-calendar'),
|
|
165
|
+
|
|
166
|
+
-- 7 Calendar events
|
|
167
|
+
(7, 5, 'About this page', 'static', 12, 'plain',
|
|
168
|
+
'<p class="lead">Dynamic actions can listen to the calendar itself (the trigger is its static id <code>planner</code>). <strong>Date Select</strong> (<code>calendar_date_select</code>) runs when you click an empty day or hour: a <em>Set Value</em> without SQL puts the chosen date into <code>P7_DAY</code> and the report beside it is refreshed with that day''s events. This calendar has no <code>create</code> link, so a click only chooses (with one, a <em>Cancel Event</em> on this event would keep the form closed). <strong>View Change</strong> (<code>calendar_view_change</code>) runs on the page that comes back from another view or period: the view (<code>month</code>, <code>week</code>, <code>day</code> or <code>list</code>) goes into <code>P7_VIEW</code>, and the day view adds a hint.</p>',
|
|
169
|
+
'{}', null),
|
|
170
|
+
(7, 10, 'Planner', 'calendar', 8, 'standard',
|
|
171
|
+
E'select starts as start_date, ends as end_date, title, id\n from sample_calendar.event',
|
|
172
|
+
'{"views": ["month", "week", "day", "list"], "link": {"page": 3, "items": {"P3_ID": "#id#"}}}', 'planner'),
|
|
173
|
+
(7, 20, 'Chosen day', 'static', 4, 'standard', null, '{}', 'chosen'),
|
|
174
|
+
(7, 30, 'Events of the day', 'classic_report', 4, 'standard',
|
|
175
|
+
E'select to_char(starts, ''HH24:MI'') as "Time", title as "Event", category as "Category"\n from sample_calendar.event\n where :P7_DAY is not null\n and starts::date <= left(:P7_DAY, 10)::date and coalesce(ends, starts)::date >= left(:P7_DAY, 10)::date\n order by starts',
|
|
176
|
+
'{"empty": "Click a day in the calendar."}', 'day-events')
|
|
163
177
|
) as r (page, seq, title, type, cols, template, source, config, sid);
|
|
164
178
|
|
|
165
179
|
insert into meta.item (page_id, region_id, seq, name, label, type, lov, config)
|
|
@@ -170,3 +184,24 @@ select pg_temp.page_id('sample-calendar', 6), r.id, 10, 'P6_CATEGORY', 'Category
|
|
|
170
184
|
insert into meta.dynamic_action (page_id, seq, name, event, trigger_element, action, affected_region_id, items_to_submit)
|
|
171
185
|
select pg_temp.page_id('sample-calendar', 6), 10, 'Refresh the calendar', 'change', 'P6_CATEGORY', 'refresh_region', r.id, 'P6_CATEGORY'
|
|
172
186
|
from meta.region r where r.page_id = pg_temp.page_id('sample-calendar', 6) and r.static_id = 'category-calendar';
|
|
187
|
+
|
|
188
|
+
-- page 7: dynamic actions on the calendar's own events (trigger: its static id)
|
|
189
|
+
insert into meta.item (page_id, region_id, seq, name, label, type, help, config)
|
|
190
|
+
select pg_temp.page_id('sample-calendar', 7), r.id, i.seq, i.name, i.label, i.type, i.help, '{"warn_unsaved": false}'
|
|
191
|
+
from meta.region r, (values
|
|
192
|
+
(10, 'P7_DAY', 'Chosen day', 'text', 'Set by the calendar_date_select dynamic action: YYYY-MM-DD, or YYYY-MM-DDTHH:MM for an hour.'),
|
|
193
|
+
(20, 'P7_VIEW', 'Calendar view', 'display', 'Set by the calendar_view_change dynamic action.')
|
|
194
|
+
) as i (seq, name, label, type, help)
|
|
195
|
+
where r.page_id = pg_temp.page_id('sample-calendar', 7) and r.static_id = 'chosen';
|
|
196
|
+
|
|
197
|
+
insert into meta.dynamic_action (page_id, seq, name, event, trigger_element, condition_type, condition_value, action,
|
|
198
|
+
affected_items, affected_region_id, items_to_submit, message)
|
|
199
|
+
select pg_temp.page_id('sample-calendar', 7), d.seq, d.name, d.event, 'planner', d.ctype, d.cvalue, d.action, d.items,
|
|
200
|
+
(select id from meta.region where page_id = pg_temp.page_id('sample-calendar', 7) and static_id = d.region), d.submit, d.message
|
|
201
|
+
from (values
|
|
202
|
+
(10, 'The chosen day into P7_DAY', 'calendar_date_select', null, null, 'set_value', 'P7_DAY', null, null, null),
|
|
203
|
+
(20, 'Its events', 'calendar_date_select', null, null, 'refresh_region', null, 'day-events', 'P7_DAY', null),
|
|
204
|
+
(40, 'The view into P7_VIEW', 'calendar_view_change', null, null, 'set_value', 'P7_VIEW', null, null, null),
|
|
205
|
+
(50, 'A hint for the day view', 'calendar_view_change', 'equals', 'day', 'show_success', null, null, null,
|
|
206
|
+
'The day view: click an hour to see the events of that day.')
|
|
207
|
+
) as d (seq, name, event, ctype, cvalue, action, items, region, submit, message);
|