pgkiln 0.53.0 → 0.54.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.
@@ -0,0 +1,157 @@
1
+ -- =====================================================================
2
+ -- Sample app "Sample Cards" (App Gallery → Sample Apps)
3
+ --
4
+ -- Cards in all the forms pgkiln has: a cards region with title, subtitle,
5
+ -- body, badge and icon that opens a form; KPI tiles (style metric); a list
6
+ -- region as cards, badges, links and tabs; and the built-in template
7
+ -- components (media list, metric cards, badges, avatars) as cards.
8
+ --
9
+ -- Installed by an administrator from the App Gallery, or with
10
+ -- psql "$DATABASE_URL" -1 -f examples/gallery/samples/sample-cards.sql
11
+ -- then open /a/sample-cards and sign in as demo / demo.
12
+ -- =====================================================================
13
+
14
+ create or replace function pg_temp.page_id(p_alias text, p_page int) returns int language sql stable as
15
+ $$ select p.id from meta.page p join meta.app a on a.id = p.app_id where a.alias = p_alias and p.page_no = p_page $$;
16
+
17
+ -- 1. Schema and data ---------------------------------------------------------
18
+ create schema sample_cards;
19
+
20
+ create table sample_cards.product (
21
+ id int generated always as identity primary key,
22
+ name text not null unique,
23
+ category text not null check (category in ('Furniture', 'Lighting', 'Accessories', 'Plants')),
24
+ price numeric(10, 2) not null check (price >= 0),
25
+ stock int not null default 0 check (stock >= 0),
26
+ rating int check (rating between 1 and 5),
27
+ icon text not null default 'box',
28
+ description text
29
+ );
30
+
31
+ insert into sample_cards.product (name, category, price, stock, rating, icon, description) values
32
+ ('Oak desk', 'Furniture', 640, 12, 5, 'briefcase', 'A solid oak desk, 160 by 80 cm, with a cable tray.'),
33
+ ('Task chair', 'Furniture', 290, 3, 4, 'user', 'Adjustable seat, arms and lumbar support.'),
34
+ ('Bookcase', 'Furniture', 210, 0, 4, 'book', 'Five shelves of birch plywood.'),
35
+ ('Desk lamp', 'Lighting', 79, 25, 5, 'lightbulb', 'Warm LED light with a dimmer.'),
36
+ ('Floor lamp', 'Lighting', 149, 7, 3, 'sun', 'A reading light that bends where you need it.'),
37
+ ('Monitor arm', 'Accessories', 95, 40, 4, 'tool', 'Holds one screen up to 32 inches.'),
38
+ ('Desk mat', 'Accessories', 29, 60, 4, 'layers', 'Felt, 90 by 40 cm.'),
39
+ ('Cable box', 'Accessories', 24, 2, 3, 'box', 'Hides a power strip and its cables.'),
40
+ ('Fiddle-leaf fig', 'Plants', 59, 9, 5, 'leaf', 'Large leaves; likes bright, indirect light.'),
41
+ ('Snake plant', 'Plants', 35, 18, 4, 'leaf', 'Thrives on neglect.');
42
+
43
+ -- 2. The application's database role ----------------------------------------
44
+ do $$ begin
45
+ if not exists (select 1 from pg_roles where rolname = 'app_sample_cards') then create role app_sample_cards nologin; end if;
46
+ end $$;
47
+ grant app_sample_cards to pgkiln_runtime;
48
+ grant usage on schema sample_cards to app_sample_cards;
49
+ grant select, insert, update, delete on all tables in schema sample_cards to app_sample_cards;
50
+
51
+ -- 3. The application ----------------------------------------------------------
52
+ insert into meta.app (alias, name, home_page, authentication, db_role)
53
+ values ('sample-cards', 'Sample Cards', 1, 'app_users', 'app_sample_cards');
54
+
55
+ insert into meta.app_user (app_id, username, password_hash, roles)
56
+ select id, 'demo', meta.hash_password('demo'), '{}' from meta.app where alias = 'sample-cards';
57
+
58
+ insert into meta.page (app_id, page_no, name, title, parent_page)
59
+ select a.id, p.no, p.name, p.name, p.parent
60
+ from meta.app a, (values
61
+ (1, 'Sample Cards', null), (2, 'Cards', 1), (4, 'KPI tiles', 1), (5, 'Lists as cards', 1), (6, 'Template components', 1)
62
+ ) as p (no, name, parent)
63
+ where a.alias = 'sample-cards';
64
+
65
+ -- page 3: the product form, a modal dialog opened from the cards (the Form wizard)
66
+ select meta.generate_page('sample-cards', 'form', 'sample_cards.product', 3,
67
+ '{"label": "Product", "mode": "modal", "return_page": 2, "nav": false}');
68
+ update meta.page set parent_page = 2 where id = pg_temp.page_id('sample-cards', 3);
69
+ update meta.item set type = 'select', lov = 'STATIC:Furniture,Lighting,Accessories,Plants'
70
+ where page_id = pg_temp.page_id('sample-cards', 3) and name = 'P3_CATEGORY';
71
+ update meta.item set type = 'rating'
72
+ where page_id = pg_temp.page_id('sample-cards', 3) and name = 'P3_RATING';
73
+
74
+ insert into meta.nav_entry (app_id, seq, label, icon, target_page)
75
+ select a.id, n.seq, n.label, n.icon, n.page
76
+ from meta.app a, (values
77
+ (10, 'Home', 'home', 1), (20, 'Cards', 'layers', 2), (30, 'KPI tiles', 'dashboard', 4),
78
+ (40, 'Lists as cards', 'list', 5), (50, 'Template components', 'grid', 6)
79
+ ) as n (seq, label, icon, page)
80
+ where a.alias = 'sample-cards';
81
+
82
+ insert into meta.list (app_id, name, type, description, query)
83
+ select a.id, l.name, l.type, l.description, l.query
84
+ from meta.app a, (values
85
+ ('CARD_TOPICS', 'static', 'The pages of Sample Cards', null),
86
+ ('CATEGORIES', 'sql', 'The categories with the number of products, from a query',
87
+ E'select category as label, 2 as page, null::jsonb as items,\n case category when ''Furniture'' then ''briefcase'' when ''Lighting'' then ''lightbulb'' when ''Plants'' then ''leaf'' else ''box'' end as icon,\n count(*)::text as badge, count(*) filter (where stock = 0) || '' out of stock'' as description\n from sample_cards.product group by category order by category')
88
+ ) as l (name, type, description, query)
89
+ where a.alias = 'sample-cards';
90
+
91
+ insert into meta.list_entry (app_id, list_name, seq, label, icon, target_page, description)
92
+ select a.id, 'CARD_TOPICS', e.seq, e.label, e.icon, e.page, e.description
93
+ from meta.app a, (values
94
+ (10, 'Cards', 'layers', 2, 'A cards region: title, subtitle, body, badge and icon from the query, each card a link to the form.'),
95
+ (20, 'KPI tiles', 'dashboard', 4, 'Key figures as tiles: the style metric, a format mask and an empty text.'),
96
+ (30, 'Lists as cards', 'list', 5, 'A list (Shared Components) shown as cards, badges, links or tabs.'),
97
+ (40, 'Template components', 'grid', 6, 'Media list, metric cards, badges and avatars: the built-in template components.')
98
+ ) as e (seq, label, icon, page, description)
99
+ where a.alias = 'sample-cards';
100
+
101
+ insert into meta.region (page_id, seq, title, type, columns, template, source, config)
102
+ select pg_temp.page_id('sample-cards', r.page), r.seq, r.title, r.type, r.cols, r.template, r.source, r.config::jsonb
103
+ from (values
104
+ -- 1 Home
105
+ (1, 10, 'Sample Cards', 'static', 12, 'hero',
106
+ '<p>Cards show rows as tiles instead of table rows: good for a few dozen things with a name, a picture or icon and a key figure. pgkiln has a cards region, KPI tiles, lists shown as cards and template components; this application shows each on a page of its own.</p>',
107
+ '{"icon": "layers"}'),
108
+ (1, 20, 'Topics', 'list', 12, 'plain', null, '{"list": "CARD_TOPICS", "template": "cards"}'),
109
+
110
+ -- 2 Cards
111
+ (2, 5, 'About this page', 'static', 12, 'plain',
112
+ '<p class="lead">A <strong>cards</strong> region reads the columns <code>title</code>, <code>subtitle</code>, <code>body</code>, <code>badge</code> and <code>icon</code> of its query; other columns can be used in its link. Here every card opens the product form as a dialog (the attribute <code>link</code>), and the badge shows the price with a format mask.</p>',
113
+ '{}'),
114
+ (2, 10, 'Products', 'cards', 12, 'plain',
115
+ E'select name as title, category || '' · '' || stock || '' in stock'' as subtitle, description as body,\n price as badge, icon, id\n from sample_cards.product order by category, name',
116
+ '{"link": {"page": 3, "items": {"P3_ID": "#id#"}}, "formats": {"badge": "FML999G990D00"}, "empty": "No products yet."}'),
117
+
118
+ -- 4 KPI tiles
119
+ (4, 5, 'About this page', 'static', 12, 'plain',
120
+ '<p class="lead">With <code>{"style": "metric"}</code> a cards region shows KPI tiles: the <code>badge</code> as a big value and the <code>title</code> as its label. A query with no rows shows the region''s <code>empty</code> text instead.</p>',
121
+ '{}'),
122
+ (4, 10, 'Key figures', 'cards', 12, 'plain',
123
+ E'select ''Products'' as title, count(*)::text as badge, ''box'' as icon from sample_cards.product\nunion all\nselect ''Stock value'', to_char(sum(price * stock), ''FM999G999G990''), ''coins'' from sample_cards.product\nunion all\nselect ''Average rating'', to_char(avg(rating), ''FM0D0''), ''star'' from sample_cards.product\nunion all\nselect ''Out of stock'', count(*) filter (where stock = 0)::text, ''alert'' from sample_cards.product',
124
+ '{"style": "metric"}'),
125
+ (4, 20, 'Stock per category', 'cards', 12, 'standard',
126
+ E'select category as title, sum(stock)::text as badge,\n case category when ''Furniture'' then ''briefcase'' when ''Lighting'' then ''lightbulb'' when ''Plants'' then ''leaf'' else ''box'' end as icon\n from sample_cards.product group by category order by category',
127
+ '{"style": "metric"}'),
128
+ (4, 30, 'Products to reorder today', 'cards', 12, 'standard',
129
+ E'select name as title, stock || '' left'' as subtitle, ''alert'' as icon from sample_cards.product where stock < 0',
130
+ '{"empty": "Nothing to reorder: every product has stock. (This region''s query finds no rows, so it shows its empty text.)"}'),
131
+
132
+ -- 5 Lists as cards
133
+ (5, 5, 'About this page', 'static', 12, 'plain',
134
+ '<p class="lead">A <strong>list</strong> region shows a list from Shared Components → Lists with a template: <code>cards</code>, <code>badges</code>, <code>links</code> or <code>tabs</code>. The list here comes from a query (a dynamic list): a label, a page, an icon, a badge and a description per row.</p>',
135
+ '{}'),
136
+ (5, 10, 'Categories as cards', 'list', 12, 'standard', null, '{"list": "CATEGORIES", "template": "cards"}'),
137
+ (5, 20, 'Categories as badges', 'list', 6, 'standard', null, '{"list": "CATEGORIES", "template": "badges"}'),
138
+ (5, 30, 'Categories as links', 'list', 6, 'standard', null, '{"list": "CATEGORIES", "template": "links"}'),
139
+ (5, 40, 'Categories as tabs', 'list', 12, 'standard', null, '{"list": "CATEGORIES", "template": "tabs"}'),
140
+
141
+ -- 6 Template components
142
+ (6, 5, 'About this page', 'static', 12, 'plain',
143
+ '<p class="lead">A <strong>template component</strong> region draws each row of its query through a component; shown as <code>multiple</code>, the rows go into the component''s group. These are the built-in components every application has: <code>ut_metric_card</code>, <code>ut_media_list</code>, <code>ut_badge</code> and <code>ut_avatar</code>.</p>',
144
+ '{}'),
145
+ (6, 10, 'Metric cards', 'template_component', 12, 'standard',
146
+ E'select category as label, sum(stock)::text as value, ''in stock'' as unit,\n count(*) || '' products'' as change, case when min(stock) = 0 then ''down'' else ''up'' end as trend, ''up'' as good\n from sample_cards.product group by category order by category',
147
+ '{"component": "ut_metric_card", "display": "multiple"}'),
148
+ (6, 20, 'Media list', 'template_component', 6, 'standard',
149
+ E'select id, name as title, description, upper(left(name, 2)) as initials,\n to_char(price, ''FM999G990D00'') as badge, case when stock = 0 then ''danger'' when stock < 5 then ''warning'' else ''success'' end as state\n from sample_cards.product order by name limit 6',
150
+ '{"component": "ut_media_list", "display": "multiple", "link": {"page": 3, "items": {"P3_ID": "#id#"}}}'),
151
+ (6, 30, 'Badges', 'template_component', 6, 'standard',
152
+ E'select name as label, case when stock = 0 then ''danger'' when stock < 5 then ''warning'' else ''success'' end as state\n from sample_cards.product order by name',
153
+ '{"component": "ut_badge", "display": "multiple"}'),
154
+ (6, 40, 'Avatars', 'template_component', 12, 'standard',
155
+ E'select name, upper(left(name, 2)) as initials from sample_cards.product order by name',
156
+ '{"component": "ut_avatar", "display": "multiple", "attributes": {"SIZE": "medium"}}')
157
+ ) as r (page, seq, title, type, cols, template, source, config);
@@ -0,0 +1,265 @@
1
+ -- =====================================================================
2
+ -- Sample app "Sample Charts" (App Gallery → Sample Apps)
3
+ --
4
+ -- Every chart type of the chart region, a page per kind of question
5
+ -- (comparing categories, change over time, parts of a whole, relations and
6
+ -- spread, plans) and a drill-down from a chart to a report. Each page starts
7
+ -- with a text that says how the query shapes the chart.
8
+ --
9
+ -- Installed by an administrator from the App Gallery, or with
10
+ -- psql "$DATABASE_URL" -1 -f examples/gallery/samples/sample-charts.sql
11
+ -- then open /a/sample-charts and sign in as demo / demo.
12
+ -- =====================================================================
13
+
14
+ -- helpers of this script (temporary: they go with the session)
15
+ create or replace function pg_temp.page_id(p_alias text, p_page int) returns int language sql stable as
16
+ $$ select p.id from meta.page p join meta.app a on a.id = p.app_id where a.alias = p_alias and p.page_no = p_page $$;
17
+
18
+ -- 1. Schema and data ---------------------------------------------------------
19
+ create schema sample_charts;
20
+
21
+ create table sample_charts.sale (
22
+ id int generated always as identity primary key,
23
+ sold_on date not null,
24
+ region text not null,
25
+ product text not null,
26
+ channel text not null,
27
+ units int not null,
28
+ amount numeric(12, 2) not null
29
+ );
30
+
31
+ -- twelve months of sales: four regions, three products, three channels
32
+ insert into sample_charts.sale (sold_on, region, product, channel, units, amount)
33
+ select (date_trunc('month', current_date) - make_interval(months => m))::date + ((r * 7 + p * 3) % 27)::int,
34
+ g.region, q.product, (array['Web', 'Store', 'Partner'])[1 + (m + r + p) % 3],
35
+ x.units, x.units * (array[640, 290, 95])[p]
36
+ from generate_series(0, 11) m,
37
+ unnest(array['North', 'South', 'East', 'West']) with ordinality as g (region, r),
38
+ unnest(array['Standing desk', 'Office chair', 'Monitor arm']) with ordinality as q (product, p),
39
+ lateral (select (5 + (m * 7 + r * 11 + p * 13) % 23)::int as units) x;
40
+
41
+ create table sample_charts.share_price (
42
+ day date primary key,
43
+ open numeric(8, 2) not null,
44
+ high numeric(8, 2) not null,
45
+ low numeric(8, 2) not null,
46
+ close numeric(8, 2) not null
47
+ );
48
+
49
+ insert into sample_charts.share_price (day, open, high, low, close)
50
+ select current_date - 30 + d, o, greatest(o, c) + 1.5, least(o, c) - 1.2, c
51
+ from generate_series(1, 30) d,
52
+ lateral (select round((100 + 8 * sin((d - 1) / 4.0) + (d - 1) * 0.3)::numeric, 2) as o,
53
+ round((100 + 8 * sin(d / 4.0) + d * 0.3)::numeric, 2) as c) v;
54
+
55
+ create table sample_charts.pipeline (
56
+ seq int primary key,
57
+ stage text not null,
58
+ deals int not null
59
+ );
60
+
61
+ insert into sample_charts.pipeline values (1, 'Leads', 240), (2, 'Qualified', 130), (3, 'Proposal', 64), (4, 'Negotiation', 31), (5, 'Won', 18);
62
+
63
+ create table sample_charts.budget (
64
+ node_id int primary key,
65
+ parent_id int references sample_charts.budget,
66
+ label text not null,
67
+ amount numeric(10, 2) not null default 0
68
+ );
69
+
70
+ insert into sample_charts.budget values
71
+ (1, null, 'Operations', 0), (2, null, 'Marketing', 0), (3, null, 'Engineering', 0),
72
+ (11, 1, 'Rent', 40), (12, 1, 'Utilities', 12),
73
+ (21, 2, 'Events', 18), (22, 2, 'Online ads', 25), (23, 2, 'Print', 6),
74
+ (31, 3, 'Salaries', 120), (32, 3, 'Cloud', 30), (33, 3, 'Tools', 14);
75
+
76
+ create table sample_charts.project_task (
77
+ id int primary key,
78
+ name text not null,
79
+ team text not null,
80
+ starts date not null,
81
+ ends date,
82
+ progress int not null default 0,
83
+ depends_on int[] not null default '{}'
84
+ );
85
+
86
+ insert into sample_charts.project_task values
87
+ (1, 'Kick-off', 'Product', current_date - 20, null, 100, '{}'),
88
+ (2, 'Requirements', 'Product', current_date - 19, current_date - 8, 100, '{1}'),
89
+ (3, 'Design', 'Product', current_date - 9, current_date + 3, 70, '{2}'),
90
+ (4, 'Build', 'Engineering', current_date + 1, current_date + 25, 10, '{3}'),
91
+ (5, 'Test', 'Quality', current_date + 18, current_date + 32, 0, '{4}'),
92
+ (6, 'Launch', 'Product', current_date + 33, null, 0, '{5}');
93
+
94
+ -- 2. The application's database role ----------------------------------------
95
+ do $$ begin
96
+ if not exists (select 1 from pg_roles where rolname = 'app_sample_charts') then create role app_sample_charts nologin; end if;
97
+ end $$;
98
+ grant app_sample_charts to pgkiln_runtime;
99
+ grant usage on schema sample_charts to app_sample_charts;
100
+ grant select on all tables in schema sample_charts to app_sample_charts;
101
+
102
+ -- 3. The application ----------------------------------------------------------
103
+ insert into meta.app (alias, name, home_page, authentication, db_role)
104
+ values ('sample-charts', 'Sample Charts', 1, 'app_users', 'app_sample_charts');
105
+
106
+ insert into meta.app_user (app_id, username, password_hash, roles)
107
+ select id, 'demo', meta.hash_password('demo'), '{}' from meta.app where alias = 'sample-charts';
108
+
109
+ insert into meta.page (app_id, page_no, name, title, parent_page)
110
+ select a.id, p.no, p.name, p.name, p.parent
111
+ from meta.app a, (values
112
+ (1, 'Sample Charts', null), (2, 'Comparing categories', 1), (3, 'Change over time', 1), (4, 'Parts of a whole', 1),
113
+ (5, 'Relations and spread', 1), (6, 'Plans and timelines', 1), (7, 'Drill-down', 1), (8, 'Sales by region', 7)
114
+ ) as p (no, name, parent)
115
+ where a.alias = 'sample-charts';
116
+
117
+ insert into meta.nav_entry (app_id, seq, label, icon, target_page)
118
+ select a.id, n.seq, n.label, n.icon, n.page
119
+ from meta.app a, (values
120
+ (10, 'Home', 'home', 1), (20, 'Comparing categories', 'chart', 2), (30, 'Change over time', 'line-chart', 3),
121
+ (40, 'Parts of a whole', 'pie-chart', 4), (50, 'Relations and spread', 'activity', 5), (60, 'Plans and timelines', 'calendar', 6),
122
+ (70, 'Drill-down', 'filter', 7)
123
+ ) as n (seq, label, icon, page)
124
+ where a.alias = 'sample-charts';
125
+
126
+ -- the home page's list of topics
127
+ insert into meta.list (app_id, name, type, description)
128
+ select id, 'CHART_TOPICS', 'static', 'The pages of Sample Charts (cards on the home page)' from meta.app where alias = 'sample-charts';
129
+
130
+ insert into meta.list_entry (app_id, list_name, seq, label, icon, target_page, description)
131
+ select a.id, 'CHART_TOPICS', e.seq, e.label, e.icon, e.page, e.description
132
+ from meta.app a, (values
133
+ (10, 'Comparing categories', 'chart', 2, 'Bar, column, stacked and combination charts.'),
134
+ (20, 'Change over time', 'line-chart', 3, 'Line, area, range and stock charts.'),
135
+ (30, 'Parts of a whole', 'pie-chart', 4, 'Pie, donut, funnel, pyramid, treemap and sunburst charts.'),
136
+ (40, 'Relations and spread', 'activity', 5, 'Scatter, bubble, box plot, radar, polar and gauge charts.'),
137
+ (50, 'Plans and timelines', 'calendar', 6, 'Gantt and timeline charts.'),
138
+ (60, 'Drill-down', 'filter', 7, 'A chart whose columns open a report of their rows.')
139
+ ) as e (seq, label, icon, page, description)
140
+ where a.alias = 'sample-charts';
141
+
142
+ insert into meta.region (page_id, seq, title, type, columns, template, source, config)
143
+ select pg_temp.page_id('sample-charts', r.page), r.seq, r.title, r.type, r.cols, r.template, r.source, r.config::jsonb
144
+ from (values
145
+ -- 1 Home
146
+ (1, 10, 'Sample Charts', 'static', 12, 'hero',
147
+ '<p>A chart region draws a SELECT: the first column is the label, every further number column a series, named by its alias. The attribute <code>kind</code> chooses one of 22 chart types. Every chart has tooltips, a legend for several series and a <em>Data table</em> toggle, the accessible alternative to the picture.</p>',
148
+ '{"icon": "chart"}'),
149
+ (1, 20, 'Topics', 'list', 12, 'plain', null, '{"list": "CHART_TOPICS", "template": "cards"}'),
150
+ (1, 30, 'The data', 'static', 12, 'standard',
151
+ '<p>The schema <code>sample_charts</code> holds a year of sales (<code>sale</code>: region, product, channel, units, amount), a month of share prices, a sales pipeline, a budget in two levels and a small project plan. The queries of the charts are in the Page Designer: open a region and look at its Source and its attributes.</p>',
152
+ '{}'),
153
+
154
+ -- 2 Comparing categories
155
+ (2, 5, 'About these charts', 'static', 12, 'plain',
156
+ '<p class="lead">Compare categories with bars and columns. <strong>bar</strong> draws horizontal bars, good for long labels; <strong>column</strong> vertical columns; <strong>stacked</strong> stacks the series of a label on top of each other; <strong>combo</strong> draws the first series as columns and the others as lines over them.</p>',
157
+ '{}'),
158
+ (2, 10, 'Units sold per product and channel (bar)', 'chart', 6, 'standard',
159
+ E'select product || '' via '' || lower(channel) as product, sum(units) as "Units"\n from sample_charts.sale group by product, channel order by 2 desc',
160
+ '{"kind": "bar"}'),
161
+ (2, 20, 'Sales per region (column)', 'chart', 6, 'standard',
162
+ E'select region, sum(amount) as "Sales"\n from sample_charts.sale group by region order by region',
163
+ '{"kind": "column", "format_mask": "999G999G990"}'),
164
+ (2, 30, 'Sales per region and product (stacked)', 'chart', 6, 'standard',
165
+ E'select region,\n sum(amount) filter (where product = ''Standing desk'') as "Standing desk",\n sum(amount) filter (where product = ''Office chair'') as "Office chair",\n sum(amount) filter (where product = ''Monitor arm'') as "Monitor arm"\n from sample_charts.sale group by region order by region',
166
+ '{"kind": "stacked", "format_mask": "999G999G990"}'),
167
+ (2, 40, 'Sales against the target (combo)', 'chart', 6, 'standard',
168
+ E'select region, sum(amount) as "Sales", 200000 as "Target"\n from sample_charts.sale group by region order by region',
169
+ '{"kind": "combo", "format_mask": "999G999G990"}'),
170
+
171
+ -- 3 Change over time
172
+ (3, 5, 'About these charts', 'static', 12, 'plain',
173
+ '<p class="lead">Show change over time with the label column in order (months, days). <strong>line</strong> and <strong>area</strong> draw one line per series; <strong>range</strong> a floating column from a low to a high column; <strong>stock</strong> a candle per row from the open, high, low and close columns.</p>',
174
+ '{}'),
175
+ (3, 10, 'Monthly sales per region (line)', 'chart', 12, 'standard',
176
+ E'select to_char(date_trunc(''month'', sold_on), ''YYYY-MM'') as month,\n sum(amount) filter (where region = ''North'') as "North",\n sum(amount) filter (where region = ''South'') as "South",\n sum(amount) filter (where region = ''East'') as "East",\n sum(amount) filter (where region = ''West'') as "West"\n from sample_charts.sale group by 1 order by 1',
177
+ '{"kind": "line", "format_mask": "999G999G990"}'),
178
+ (3, 20, 'Sales so far this year (area)', 'chart', 6, 'standard',
179
+ E'select month, sum(total) over (order by month) as "Cumulative sales"\n from (select to_char(date_trunc(''month'', sold_on), ''YYYY-MM'') as month, sum(amount) as total\n from sample_charts.sale group by 1) m\n order by month',
180
+ '{"kind": "area", "format_mask": "999G999G990"}'),
181
+ (3, 30, 'Smallest and largest sale per region (range)', 'chart', 6, 'standard',
182
+ E'select region, min(amount) as "Smallest", max(amount) as "Largest"\n from sample_charts.sale group by region order by region',
183
+ '{"kind": "range", "format_mask": "999G999G990"}'),
184
+ (3, 40, 'Share price over the last month (stock)', 'chart', 12, 'standard',
185
+ E'select to_char(day, ''DD Mon'') as day, open as "Open", high as "High", low as "Low", close as "Close"\n from sample_charts.share_price order by sample_charts.share_price.day',
186
+ '{"kind": "stock"}'),
187
+
188
+ -- 4 Parts of a whole
189
+ (4, 5, 'About these charts', 'static', 12, 'plain',
190
+ '<p class="lead">Show how a total divides. <strong>pie</strong> and <strong>donut</strong> use the first series (up to six slices, the rest folds into Other); <strong>funnel</strong> shows the stages of a process in the order of the query; <strong>pyramid</strong> with two series compares two groups back to back; <strong>treemap</strong> and <strong>sunburst</strong> show a hierarchy from the columns <code>node_id</code> and <code>parent_id</code>.</p>',
191
+ '{}'),
192
+ (4, 10, 'Sales per channel (pie)', 'chart', 6, 'standard',
193
+ E'select channel, sum(amount) as "Sales"\n from sample_charts.sale group by channel order by 2 desc',
194
+ '{"kind": "pie", "format_mask": "999G999G990"}'),
195
+ (4, 20, 'Units per product (donut)', 'chart', 6, 'standard',
196
+ E'select product, sum(units) as "Units"\n from sample_charts.sale group by product order by 2 desc',
197
+ '{"kind": "donut"}'),
198
+ (4, 30, 'Sales pipeline (funnel)', 'chart', 6, 'standard',
199
+ E'select stage, deals as "Deals" from sample_charts.pipeline order by seq',
200
+ '{"kind": "funnel"}'),
201
+ (4, 40, 'Units per product, North against South (pyramid)', 'chart', 6, 'standard',
202
+ E'select product,\n sum(units) filter (where region = ''North'') as "North",\n sum(units) filter (where region = ''South'') as "South"\n from sample_charts.sale group by product order by product',
203
+ '{"kind": "pyramid"}'),
204
+ (4, 50, 'Budget (treemap)', 'chart', 6, 'standard',
205
+ E'select label, amount as "Budget", node_id, parent_id from sample_charts.budget order by node_id',
206
+ '{"kind": "treemap"}'),
207
+ (4, 60, 'Budget (sunburst)', 'chart', 6, 'standard',
208
+ E'select label, amount as "Budget", node_id, parent_id from sample_charts.budget order by node_id',
209
+ '{"kind": "sunburst"}'),
210
+
211
+ -- 5 Relations and spread
212
+ (5, 5, 'About these charts', 'static', 12, 'plain',
213
+ '<p class="lead">Look at numbers against each other. <strong>scatter</strong> takes a number as its first column (the x axis); <strong>bubble</strong> a label, then x, y and size; <strong>boxplot</strong> one row per observation and computes the quartiles per label; <strong>radar</strong> an axis per row; <strong>polar</strong> a sector per row; <strong>gauge</strong> a dial per row with thresholds in its attributes.</p>',
214
+ '{}'),
215
+ (5, 10, 'Units against amount per sale (scatter)', 'chart', 6, 'standard',
216
+ E'select units as "Units", amount as "Amount" from sample_charts.sale order by units',
217
+ '{"kind": "scatter"}'),
218
+ (5, 20, 'Products per region: units, average sale and number of sales (bubble)', 'chart', 6, 'standard',
219
+ E'select region || '' '' || lower(product) as product, sum(units) as "Units", round(avg(amount)) as "Average sale", count(*) as "Sales"\n from sample_charts.sale group by region, product order by 1',
220
+ '{"kind": "bubble"}'),
221
+ (5, 30, 'Spread of the sales per region (box plot)', 'chart', 6, 'standard',
222
+ E'select region, amount as "Sale" from sample_charts.sale order by region',
223
+ '{"kind": "boxplot"}'),
224
+ (5, 40, 'Units per region and product (radar)', 'chart', 6, 'standard',
225
+ E'select region,\n sum(units) filter (where product = ''Standing desk'') as "Standing desk",\n sum(units) filter (where product = ''Office chair'') as "Office chair",\n sum(units) filter (where product = ''Monitor arm'') as "Monitor arm"\n from sample_charts.sale group by region order by region',
226
+ '{"kind": "radar"}'),
227
+ (5, 50, 'Sales per month (polar)', 'chart', 6, 'standard',
228
+ E'select to_char(date_trunc(''month'', sold_on), ''Mon'') as month, sum(amount) as "Sales"\n from sample_charts.sale group by date_trunc(''month'', sold_on) order by date_trunc(''month'', sold_on)',
229
+ '{"kind": "polar", "format_mask": "999G999G990"}'),
230
+ (5, 60, 'Target reached per region, % (gauge)', 'chart', 6, 'standard',
231
+ E'select region, round(100.0 * sum(amount) / 200000) as "Target reached"\n from sample_charts.sale group by region order by region',
232
+ '{"kind": "gauge", "gauge": {"min": 0, "max": 120, "warning": 90, "critical": 75}}'),
233
+
234
+ -- 6 Plans and timelines
235
+ (6, 5, 'About these charts', 'static', 12, 'plain',
236
+ '<p class="lead">Show plans. <strong>gantt</strong> takes a label, a start and an end (no end: a milestone), and optional columns <code>progress</code>, <code>task_id</code> and <code>depends_on</code>; <strong>timeline</strong> a label, a start and an end, with a lane per value of the column <code>series</code>.</p>',
237
+ '{}'),
238
+ (6, 10, 'Project plan (Gantt)', 'chart', 12, 'standard',
239
+ E'select name as "Task", starts as "Starts", ends as "Ends", progress, id as task_id,\n array_to_string(depends_on, '','') as depends_on\n from sample_charts.project_task order by starts, id',
240
+ '{"kind": "gantt"}'),
241
+ (6, 20, 'Who works when (timeline)', 'chart', 12, 'standard',
242
+ E'select name as "Task", starts as "Starts", ends as "Ends", team as series\n from sample_charts.project_task order by starts, id',
243
+ '{"kind": "timeline"}'),
244
+
245
+ -- 7 Drill-down
246
+ (7, 5, 'About this page', 'static', 12, 'plain',
247
+ '<p class="lead">With the attribute <code>link</code> every column of the chart is a link: <code>#region#</code> is replaced by the value of the clicked row. The link carries a checksum, like every link pgkiln makes. Columns that only the link uses are not drawn. From the keyboard, the data table has the same links.</p>',
248
+ '{}'),
249
+ (7, 10, 'Sales per region: click a column', 'chart', 12, 'standard',
250
+ E'select region, sum(amount) as "Sales"\n from sample_charts.sale group by region order by region',
251
+ '{"kind": "column", "format_mask": "999G999G990", "link": {"page": 8, "items": {"P8_REGION": "#region#"}}}'),
252
+
253
+ -- 8 Sales by region (the drill-down's target)
254
+ (8, 10, 'Sales', 'report', 12, 'standard',
255
+ E'select sold_on, region, product, channel, units, amount\n from sample_charts.sale\n where :P8_REGION is null or region = :P8_REGION\n order by sold_on desc, id',
256
+ '{"headings": {"sold_on": "Sold on"}, "formats": {"amount": "999G999G990D00"}}'),
257
+ (8, 20, 'Per product', 'chart', 12, 'standard',
258
+ E'select product, sum(amount) as "Sales"\n from sample_charts.sale where :P8_REGION is null or region = :P8_REGION\n group by product order by 2 desc',
259
+ '{"kind": "bar", "format_mask": "999G999G990"}')
260
+ ) as r (page, seq, title, type, cols, template, source, config);
261
+
262
+ insert into meta.item (page_id, region_id, seq, name, label, type, lov, config)
263
+ select pg_temp.page_id('sample-charts', 8), null, 10, 'P8_REGION', 'Region', 'select',
264
+ 'select distinct region as d, region as r from sample_charts.sale order by 1',
265
+ '{"submit_on_change": true, "null_label": "All regions"}';
@@ -0,0 +1,177 @@
1
+ -- =====================================================================
2
+ -- Sample app "Sample Dynamic Actions" (App Gallery → Sample Apps)
3
+ --
4
+ -- Behaviour in the browser without JavaScript of your own:
5
+ -- page 2 show, hide, enable and disable items on a condition;
6
+ -- page 3 values from the server (set value, execute SQL) and a report
7
+ -- refreshed when an item changes;
8
+ -- page 4 messages, confirmations, a wait popup, classes, item errors,
9
+ -- an inline dialog and actions on page load and key release.
10
+ --
11
+ -- Installed by an administrator from the App Gallery, or with
12
+ -- psql "$DATABASE_URL" -1 -f examples/gallery/samples/sample-dynamic-actions.sql
13
+ -- then open /a/sample-dynamic-actions and sign in as demo / demo.
14
+ -- =====================================================================
15
+
16
+ create or replace function pg_temp.page_id(p_alias text, p_page int) returns int language sql stable as
17
+ $$ select p.id from meta.page p join meta.app a on a.id = p.app_id where a.alias = p_alias and p.page_no = p_page $$;
18
+ create or replace function pg_temp.region_id(p_alias text, p_page int, p_static text) returns int language sql stable as
19
+ $$ select r.id from meta.region r where r.page_id = pg_temp.page_id(p_alias, p_page) and r.static_id = p_static $$;
20
+
21
+ -- 1. Schema and data ---------------------------------------------------------
22
+ create schema sample_da;
23
+
24
+ create table sample_da.product (
25
+ id int generated always as identity primary key,
26
+ name text not null unique,
27
+ category text not null,
28
+ price numeric(10, 2) not null
29
+ );
30
+
31
+ insert into sample_da.product (name, category, price) values
32
+ ('Notebook', 'Paper', 3.50), ('Sticky notes', 'Paper', 2.25), ('Printer paper', 'Paper', 6.95),
33
+ ('Ballpoint', 'Pens', 1.20), ('Fountain pen', 'Pens', 24.00), ('Marker set', 'Pens', 8.40),
34
+ ('Stapler', 'Desk', 12.50), ('Desk organiser', 'Desk', 18.00), ('Scissors', 'Desk', 6.75);
35
+
36
+ -- 2. The application's database role ----------------------------------------
37
+ do $$ begin
38
+ if not exists (select 1 from pg_roles where rolname = 'app_sample_da') then create role app_sample_da nologin; end if;
39
+ end $$;
40
+ grant app_sample_da to pgkiln_runtime;
41
+ grant usage on schema sample_da to app_sample_da;
42
+ grant select on all tables in schema sample_da to app_sample_da;
43
+
44
+ -- 3. The application ----------------------------------------------------------
45
+ insert into meta.app (alias, name, home_page, authentication, db_role)
46
+ values ('sample-dynamic-actions', 'Sample Dynamic Actions', 1, 'app_users', 'app_sample_da');
47
+
48
+ insert into meta.app_user (app_id, username, password_hash, roles)
49
+ select id, 'demo', meta.hash_password('demo'), '{}' from meta.app where alias = 'sample-dynamic-actions';
50
+
51
+ insert into meta.page (app_id, page_no, name, title, parent_page)
52
+ select a.id, p.no, p.name, p.name, p.parent
53
+ from meta.app a, (values
54
+ (1, 'Sample Dynamic Actions', null), (2, 'Show, hide, enable', 1), (3, 'Values from the server', 1), (4, 'Messages and effects', 1)
55
+ ) as p (no, name, parent)
56
+ where a.alias = 'sample-dynamic-actions';
57
+
58
+ insert into meta.nav_entry (app_id, seq, label, icon, target_page)
59
+ select a.id, n.seq, n.label, n.icon, n.page
60
+ from meta.app a, (values
61
+ (10, 'Home', 'home', 1), (20, 'Show, hide, enable', 'eye', 2), (30, 'Values from the server', 'refresh', 3), (40, 'Messages and effects', 'bell', 4)
62
+ ) as n (seq, label, icon, page)
63
+ where a.alias = 'sample-dynamic-actions';
64
+
65
+ insert into meta.list (app_id, name, type, description)
66
+ select id, 'DA_TOPICS', 'static', 'The pages of Sample Dynamic Actions' from meta.app where alias = 'sample-dynamic-actions';
67
+
68
+ insert into meta.list_entry (app_id, list_name, seq, label, icon, target_page, description)
69
+ select a.id, 'DA_TOPICS', e.seq, e.label, e.icon, e.page, e.description
70
+ from meta.app a, (values
71
+ (10, 'Show, hide, enable', 'eye', 2, 'Items that appear, disappear or switch on depending on another item.'),
72
+ (20, 'Values from the server', 'refresh', 3, 'SQL fills in items and refreshes a report, without reloading the page.'),
73
+ (30, 'Messages and effects', 'bell', 4, 'Alerts, confirmations, a wait popup, classes, item errors and an inline dialog.')
74
+ ) as e (seq, label, icon, page, description)
75
+ where a.alias = 'sample-dynamic-actions';
76
+
77
+ insert into meta.region (page_id, seq, title, type, columns, template, source, config, static_id)
78
+ select pg_temp.page_id('sample-dynamic-actions', r.page), r.seq, r.title, r.type, r.cols, r.template, r.source, r.config::jsonb, r.sid
79
+ from (values
80
+ -- 1 Home
81
+ (1, 10, 'Sample Dynamic Actions', 'static', 12, 'hero',
82
+ '<p>A dynamic action is an <strong>event</strong> (an item changes, a button is clicked, the page loads), an optional <strong>condition</strong> on the item''s value and an <strong>action</strong>: show or hide, set a value with SQL, refresh a region, show a message. The actions of one event run in sequence. They need JavaScript; without it the pages still work, without these conveniences.</p>',
83
+ '{"icon": "bolt"}', null),
84
+ (1, 20, 'Topics', 'list', 12, 'plain', null, '{"list": "DA_TOPICS", "template": "cards"}', null),
85
+
86
+ -- 2 Show, hide, enable
87
+ (2, 5, 'About this page', 'static', 12, 'plain',
88
+ '<p class="lead"><strong>Show</strong> with a condition shows the company fields when <em>Customer type</em> equals Company, and hides them otherwise: one dynamic action does both, also when the page loads (already on the server, so nothing flickers). <strong>Enable</strong> works the same way for the e-mail address.</p>',
89
+ '{}', null),
90
+ (2, 10, 'Customer', 'static', 6, 'standard', null, '{}', 'customer'),
91
+ (2, 20, 'Newsletter', 'static', 6, 'standard', null, '{}', 'newsletter'),
92
+
93
+ -- 3 Values from the server
94
+ (3, 5, 'About this page', 'static', 12, 'plain',
95
+ '<p class="lead"><strong>Set value</strong> runs a SELECT on the server with the items it submits and puts the columns of its first row into the affected items: choosing a product fills in its price, changing the quantity the total. <strong>Refresh region</strong> draws the report again with the chosen category. The SQL never reaches the browser.</p>',
96
+ '{}', null),
97
+ (3, 10, 'Order line', 'static', 6, 'standard', null, '{}', 'order-line'),
98
+ (3, 20, 'Filter', 'static', 6, 'standard', null, '{}', 'filter'),
99
+ (3, 30, 'Products', 'report', 12, 'standard',
100
+ E'select name, category, price from sample_da.product\n where :P3_CATEGORY is null or category = :P3_CATEGORY\n order by category, name',
101
+ '{"formats": {"price": "999G990D00"}}', 'products'),
102
+
103
+ -- 4 Messages and effects
104
+ (4, 5, 'About this page', 'static', 12, 'plain',
105
+ '<p class="lead">Each button has the action <em>Defined by dynamic action</em>. <strong>Confirm</strong> stops the actions after it when you answer No; <strong>Wait popup</strong> covers the page while an Ajax call runs; <strong>Add class</strong> and <strong>Remove class</strong> change a region''s look; <strong>Show error</strong> puts a message under an item; <strong>Open region</strong> opens an inline dialog. Typing in the name runs a <em>Key release</em> action, and the page load puts the cursor in it.</p>',
106
+ '{}', null),
107
+ (4, 10, 'Try the actions', 'static', 12, 'buttons_container', null, '{}', 'buttons'),
108
+ (4, 20, 'Greeting', 'static', 6, 'standard', null, '{}', 'greeting'),
109
+ (4, 30, 'A region to highlight', 'static', 6, 'standard',
110
+ '<p>The buttons <em>Highlight</em> and <em>Mute</em> add the theme''s classes <code>is-highlight</code> and <code>is-muted</code> to this region; <em>Reset</em> removes them.</p>',
111
+ '{}', 'target'),
112
+ (4, 40, 'An inline dialog', 'static', 12, 'inline_dialog',
113
+ '<p>This region has the template <em>Inline dialog</em>: hidden until a dynamic action (or a link to its id) opens it. Escape, the close button or a click beside it closes it.</p>',
114
+ '{}', 'dialog')
115
+ ) as r (page, seq, title, type, cols, template, source, config, sid);
116
+
117
+ insert into meta.item (page_id, region_id, seq, name, label, type, lov, default_value, help, config)
118
+ select pg_temp.page_id('sample-dynamic-actions', i.page), pg_temp.region_id('sample-dynamic-actions', i.page, i.region),
119
+ i.seq, i.name, i.label, i.type, i.lov, i.def, i.help, i.config::jsonb
120
+ from (values
121
+ (2, 'customer', 10, 'P2_TYPE', 'Customer type', 'radio', 'STATIC:Person;PERSON,Company;COMPANY', 'PERSON', null, '{}'),
122
+ (2, 'customer', 20, 'P2_NAME', 'Name', 'text', null, null, null, '{}'),
123
+ (2, 'customer', 30, 'P2_COMPANY', 'Company name', 'text', null, null, 'Shown only for companies.', '{}'),
124
+ (2, 'customer', 40, 'P2_VAT', 'VAT number', 'text', null, null, 'Shown only for companies.', '{}'),
125
+ (2, 'newsletter', 10, 'P2_NEWSLETTER', 'Send me the newsletter', 'radio', 'STATIC:Yes;YES,No;NO', 'NO', null, '{}'),
126
+ (2, 'newsletter', 20, 'P2_EMAIL', 'E-mail address', 'email', null, null, 'Enabled only when you want the newsletter.', '{}'),
127
+ (3, 'order-line', 10, 'P3_PRODUCT', 'Product', 'select', 'select name, id from sample_da.product order by name', null, null, '{"warn_unsaved": false}'),
128
+ (3, 'order-line', 20, 'P3_PRICE', 'Price', 'number', null, null, 'Set by the server when you choose a product.', '{"warn_unsaved": false}'),
129
+ (3, 'order-line', 30, 'P3_QTY', 'Quantity', 'number', null, '1', null, '{"warn_unsaved": false}'),
130
+ (3, 'order-line', 40, 'P3_TOTAL', 'Total', 'number', null, null, 'Price times quantity, computed by the server.', '{"warn_unsaved": false}'),
131
+ (3, 'filter', 10, 'P3_CATEGORY', 'Category', 'select', 'select distinct category as d, category as r from sample_da.product order by 1', null,
132
+ 'The report below is refreshed when this changes.', '{"warn_unsaved": false, "null_label": "All categories"}'),
133
+ (4, 'greeting', 10, 'P4_NAME', 'Your name', 'text', null, null, 'Type: the greeting follows each key you release.', '{"warn_unsaved": false}'),
134
+ (4, 'greeting', 20, 'P4_GREETING', 'Greeting', 'text', null, null, null, '{"warn_unsaved": false}')
135
+ ) as i (page, region, seq, name, label, type, lov, def, help, config);
136
+
137
+ insert into meta.button (page_id, region_id, seq, name, label, action, hot)
138
+ select pg_temp.page_id('sample-dynamic-actions', 4), pg_temp.region_id('sample-dynamic-actions', 4, 'buttons'), b.seq, b.name, b.label, 'da', b.hot
139
+ from (values
140
+ (10, 'ALERT', 'Alert', false), (20, 'CONFIRM', 'Confirm, then a message', true), (30, 'WAIT', 'Wait popup', false),
141
+ (40, 'HIGHLIGHT', 'Highlight', false), (50, 'MUTE', 'Mute', false), (60, 'RESET', 'Reset', false),
142
+ (70, 'ERROR', 'Show an item error', false), (80, 'CLEAR', 'Clear the errors', false), (90, 'DIALOG', 'Open the inline dialog', false)
143
+ ) as b (seq, name, label, hot);
144
+
145
+ insert into meta.dynamic_action (page_id, seq, name, event, trigger_element, condition_type, condition_value,
146
+ action, affected_items, affected_region_id, code, items_to_submit, message, css_classes)
147
+ select pg_temp.page_id('sample-dynamic-actions', d.page), d.seq, d.name, d.event, d.trig, d.ctype, d.cvalue,
148
+ d.action, d.items, pg_temp.region_id('sample-dynamic-actions', d.page, d.region), d.code, d.submit, d.message, d.classes
149
+ from (values
150
+ -- page 2
151
+ (2, 10, 'Company fields for companies', 'change', 'P2_TYPE', 'equals', 'COMPANY', 'show', 'P2_COMPANY,P2_VAT', null, null, null, null, null),
152
+ (2, 20, 'E-mail only with the newsletter', 'change', 'P2_NEWSLETTER', 'equals', 'YES', 'enable', 'P2_EMAIL', null, null, null, null, null),
153
+ -- page 3
154
+ (3, 10, 'The product''s price', 'change', 'P3_PRODUCT', null, null, 'set_value', 'P3_PRICE,P3_TOTAL', null,
155
+ 'select price, price * coalesce(nullif(:P3_QTY, '''')::numeric, 1) from sample_da.product where id = nullif(:P3_PRODUCT, '''')::int',
156
+ 'P3_PRODUCT,P3_QTY', null, null),
157
+ (3, 20, 'The total', 'change', 'P3_QTY', null, null, 'set_value', 'P3_TOTAL', null,
158
+ 'select round(coalesce(nullif(:P3_PRICE, '''')::numeric, 0) * coalesce(nullif(:P3_QTY, '''')::numeric, 0), 2)',
159
+ 'P3_PRICE,P3_QTY', null, null),
160
+ (3, 30, 'Refresh the products', 'change', 'P3_CATEGORY', null, null, 'refresh_region', null, 'products', null, 'P3_CATEGORY', null, null),
161
+ -- page 4
162
+ (4, 10, 'Put the cursor in the name', 'load', null, null, null, 'set_focus', 'P4_NAME', null, null, null, null, null),
163
+ (4, 20, 'Greet while typing', 'key_release', 'P4_NAME', null, null, 'set_value', 'P4_GREETING', null,
164
+ 'select case when coalesce(:P4_NAME, '''') = '''' then '''' else ''Hello, '' || :P4_NAME || ''!'' end', 'P4_NAME', null, null),
165
+ (4, 30, 'An alert', 'click', 'ALERT', null, null, 'alert', null, null, null, null, 'This is an alert: the browser waits until you close it.', null),
166
+ (4, 40, 'Ask first', 'click', 'CONFIRM', null, null, 'confirm', null, null, null, null, 'Show a success message?', null),
167
+ (4, 50, 'Then the message', 'click', 'CONFIRM', null, null, 'show_success', null, null, null, null, 'You said yes: this is a success message.', null),
168
+ (4, 60, 'Wait popup', 'click', 'WAIT', null, null, 'wait_popup', null, null, null, null, 'Working on it…', null),
169
+ (4, 70, 'A slow call', 'click', 'WAIT', null, null, 'execute_sql', null, null, 'select pg_sleep(1.5)', null, null, null),
170
+ (4, 80, 'Done waiting', 'click', 'WAIT', null, null, 'remove_wait_popup', null, null, null, null, null, null),
171
+ (4, 90, 'Highlight the region', 'click', 'HIGHLIGHT', null, null, 'add_class', null, 'target', null, null, null, 'is-highlight'),
172
+ (4, 100, 'Mute the region', 'click', 'MUTE', null, null, 'add_class', null, 'target', null, null, null, 'is-muted'),
173
+ (4, 110, 'Reset the region', 'click', 'RESET', null, null, 'remove_class', null, 'target', null, null, null, 'is-highlight is-muted'),
174
+ (4, 120, 'An item error', 'click', 'ERROR', null, null, 'show_error', 'P4_NAME', null, null, null, 'This is how an error under an item looks.', null),
175
+ (4, 130, 'Clear the errors', 'click', 'CLEAR', null, null, 'clear_errors', null, null, null, null, null, null),
176
+ (4, 140, 'Open the dialog', 'click', 'DIALOG', null, null, 'open_region', null, 'dialog', null, null, null, null)
177
+ ) as d (page, seq, name, event, trig, ctype, cvalue, action, items, region, code, submit, message, classes);