pgkiln 0.43.0 → 0.44.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/100_generated_labels.sql +381 -0
- package/db/migrations/101_theme_choice.sql +15 -0
- package/docs/apex-feature-parity.md +3 -3
- package/docs/guide/03-builder.md +15 -8
- package/docs/guide/04-pages-and-regions.md +10 -3
- package/docs/guide/09-reference.md +1 -0
- package/docs/guide/14-globalization.md +5 -1
- package/package.json +1 -1
- package/public/app.css +23 -1
- package/public/app.js +6 -1
- package/src/builder/appsheets.ts +53 -13
- package/src/builder/appwizard.ts +5 -3
- package/src/i18n/ar.ts +2 -0
- package/src/i18n/cs.ts +2 -0
- package/src/i18n/da.ts +2 -0
- package/src/i18n/de.ts +2 -0
- package/src/i18n/el.ts +2 -0
- package/src/i18n/es.ts +2 -0
- package/src/i18n/fi.ts +2 -0
- package/src/i18n/fr.ts +2 -0
- package/src/i18n/he.ts +2 -0
- package/src/i18n/it.ts +2 -0
- package/src/i18n/ja.ts +2 -0
- package/src/i18n/ko.ts +2 -0
- package/src/i18n/nb.ts +2 -0
- package/src/i18n/pl.ts +2 -0
- package/src/i18n/pt.ts +2 -0
- package/src/i18n/ru.ts +2 -0
- package/src/i18n/sv.ts +2 -0
- package/src/i18n/tr.ts +2 -0
- package/src/i18n/uk.ts +2 -0
- package/src/i18n/zh.ts +2 -0
- package/src/i18n.ts +4 -0
- package/src/runtime/account.ts +5 -5
- package/src/runtime/devbar.ts +16 -13
- package/src/runtime/facets.ts +34 -3
- package/src/runtime/locale.ts +4 -1
- package/src/runtime/render.ts +7 -2
- package/src/runtime/report.ts +14 -9
- package/src/runtime/routes.ts +5 -4
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
-- =====================================================================
|
|
2
|
+
-- 100: generated reports and grids read the same (sprint 78; parity
|
|
3
|
+
-- handoff GEN-2, GEN-1, GEN-7)
|
|
4
|
+
--
|
|
5
|
+
-- - A faceted search page's report shows one column per foreign key: the
|
|
6
|
+
-- parent's display value (Manager → KING) under the key's label. The key
|
|
7
|
+
-- itself is hidden (config.hidden: not shown, not in Actions › Columns,
|
|
8
|
+
-- still in the query for filters and links). Before, both showed, so the
|
|
9
|
+
-- report had two "Manager" and two "Department" headings.
|
|
10
|
+
-- - Interactive grids (the grid page, both grids of a master-detail page)
|
|
11
|
+
-- get headings from meta.wizard_column_label (Deptno → Department Number)
|
|
12
|
+
-- and the number format masks of meta.wizard_mask, like the reports.
|
|
13
|
+
-- - The faceted search report and a map's list report get the format masks
|
|
14
|
+
-- too, so a salary reads 2,850.00 in the report, the grid and the form.
|
|
15
|
+
-- - A faceted search report's rows start with a pencil that opens the form
|
|
16
|
+
-- (link.icon), like a report and form page; the key is plain text.
|
|
17
|
+
--
|
|
18
|
+
-- meta.generate_page is 097's with those changes. Pages generated before
|
|
19
|
+
-- keep their configuration.
|
|
20
|
+
-- =====================================================================
|
|
21
|
+
|
|
22
|
+
-- a grid's headings and number formats: {"headings": {...}, "formats": {...}}
|
|
23
|
+
create or replace function meta.wizard_grid_labels(p_table regclass) returns jsonb
|
|
24
|
+
language sql stable as $$
|
|
25
|
+
select jsonb_build_object(
|
|
26
|
+
'headings', coalesce(jsonb_object_agg(column_name, meta.wizard_column_label(p_table, column_name)), '{}'),
|
|
27
|
+
'formats', coalesce(jsonb_object_agg(column_name, meta.wizard_mask(type_sql, kind, is_pk or fk_table is not null))
|
|
28
|
+
filter (where meta.wizard_mask(type_sql, kind, is_pk or fk_table is not null) is not null), '{}'))
|
|
29
|
+
from meta.wizard_catalog(p_table)
|
|
30
|
+
where kind not in ('binary', 'geometry')
|
|
31
|
+
$$;
|
|
32
|
+
|
|
33
|
+
create or replace function meta.generate_page(
|
|
34
|
+
p_app text,
|
|
35
|
+
p_kind text,
|
|
36
|
+
p_table regclass,
|
|
37
|
+
p_page int,
|
|
38
|
+
p_options jsonb default '{}'
|
|
39
|
+
) returns int
|
|
40
|
+
language plpgsql as $$
|
|
41
|
+
declare
|
|
42
|
+
v_app meta.app;
|
|
43
|
+
o jsonb;
|
|
44
|
+
v_label text;
|
|
45
|
+
v_flabel text;
|
|
46
|
+
v_icon text;
|
|
47
|
+
v_pk text := meta.wizard_pk(p_table);
|
|
48
|
+
v_page int;
|
|
49
|
+
v_region int;
|
|
50
|
+
v_report int;
|
|
51
|
+
v_form int;
|
|
52
|
+
v_fpage int;
|
|
53
|
+
v_link jsonb;
|
|
54
|
+
v_cols text;
|
|
55
|
+
v_heads jsonb := '{}';
|
|
56
|
+
v_src text;
|
|
57
|
+
v_cfg jsonb;
|
|
58
|
+
v_a text;
|
|
59
|
+
v_b text;
|
|
60
|
+
v_c text;
|
|
61
|
+
v_d text;
|
|
62
|
+
v_kind text;
|
|
63
|
+
v_detail regclass;
|
|
64
|
+
v_dpk text;
|
|
65
|
+
v_item text;
|
|
66
|
+
v_facets jsonb := '[]';
|
|
67
|
+
v_hidden jsonb := '[]';
|
|
68
|
+
v_fmts jsonb := '{}';
|
|
69
|
+
v_mask text;
|
|
70
|
+
v_geo text;
|
|
71
|
+
v_key text;
|
|
72
|
+
v_skip text[] := meta.wizard_sidecars(p_table);
|
|
73
|
+
c record;
|
|
74
|
+
begin
|
|
75
|
+
select * into v_app from meta.app where alias = p_app;
|
|
76
|
+
if v_app.id is null then
|
|
77
|
+
raise exception 'application "%" does not exist', p_app;
|
|
78
|
+
end if;
|
|
79
|
+
if p_kind not in ('report_form', 'grid', 'form', 'cards', 'calendar', 'chart', 'map', 'facets', 'master_detail') then
|
|
80
|
+
raise exception 'unknown page type "%"', p_kind;
|
|
81
|
+
end if;
|
|
82
|
+
-- the application's own data: not pgkiln's or the system's tables
|
|
83
|
+
if (select n.nspname ~ '^pg_' or n.nspname in ('information_schema', 'meta')
|
|
84
|
+
from pg_class rel join pg_namespace n on n.oid = rel.relnamespace where rel.oid = p_table) then
|
|
85
|
+
raise exception 'pages can''t be generated on %', p_table;
|
|
86
|
+
end if;
|
|
87
|
+
if (select relkind from pg_class where oid = p_table) not in ('r', 'p', 'v', 'm') then
|
|
88
|
+
raise exception '% is not a table or view', p_table;
|
|
89
|
+
end if;
|
|
90
|
+
if p_options is null or jsonb_typeof(p_options) <> 'object' then
|
|
91
|
+
p_options := '{}';
|
|
92
|
+
end if;
|
|
93
|
+
-- an option given as JSON null or '' means "none"; a missing one takes the default
|
|
94
|
+
o := meta.wizard_defaults(p_kind, p_table) || p_options;
|
|
95
|
+
v_label := coalesce(nullif(btrim(o ->> 'label'), ''), meta.wizard_table_label(p_table, p_kind <> 'form'));
|
|
96
|
+
-- (097) a form page of a listing is titled with the singular: Employees → Employee
|
|
97
|
+
v_flabel := coalesce(nullif(btrim(o ->> 'form_label'), ''), meta.wizard_table_label(p_table, false));
|
|
98
|
+
v_icon := nullif(o ->> 'icon', '');
|
|
99
|
+
v_fpage := nullif(o ->> 'form_page', '')::int;
|
|
100
|
+
if v_fpage = p_page then
|
|
101
|
+
raise exception 'the form page needs a page number of its own';
|
|
102
|
+
end if;
|
|
103
|
+
|
|
104
|
+
if p_kind = 'report_form' then
|
|
105
|
+
v_page := meta.wizard_report_form(v_app, p_table, p_page, v_fpage, v_label, v_flabel);
|
|
106
|
+
elsif p_kind = 'grid' then
|
|
107
|
+
perform meta.generate_grid(p_app, p_table, p_page, v_label, coalesce(v_icon, 'grid'));
|
|
108
|
+
v_page := (select id from meta.page where app_id = v_app.id and page_no = p_page);
|
|
109
|
+
-- (100) the grid's headings and number formats, like the report's
|
|
110
|
+
update meta.region set config = config || meta.wizard_grid_labels(p_table) where page_id = v_page and type = 'grid';
|
|
111
|
+
return v_page;
|
|
112
|
+
elsif p_kind = 'form' then
|
|
113
|
+
v_page := meta.wizard_form(v_app, p_table, p_page, v_label, nullif(o ->> 'return_page', '')::int, o ->> 'mode' = 'modal',
|
|
114
|
+
case when jsonb_typeof(o -> 'columns') = 'array'
|
|
115
|
+
then (select array_agg(meta.wizard_col(p_table, x, 'form column')) from jsonb_array_elements_text(o -> 'columns') x) end);
|
|
116
|
+
elsif p_kind = 'master_detail' then
|
|
117
|
+
if v_pk is null then
|
|
118
|
+
raise exception 'the master table % needs a single-column primary key', p_table;
|
|
119
|
+
end if;
|
|
120
|
+
if nullif(o ->> 'detail', '') is null then
|
|
121
|
+
raise exception 'no detail table: % has no table with a foreign key to it', p_table;
|
|
122
|
+
end if;
|
|
123
|
+
v_detail := (o ->> 'detail')::regclass;
|
|
124
|
+
v_dpk := meta.wizard_pk(v_detail);
|
|
125
|
+
if v_dpk is null then
|
|
126
|
+
raise exception 'the detail table % needs a single-column primary key', v_detail;
|
|
127
|
+
end if;
|
|
128
|
+
v_a := meta.wizard_col(v_detail, o ->> 'detail_column', 'detail column');
|
|
129
|
+
if v_a is null then
|
|
130
|
+
raise exception 'choose the detail column that refers to the master row';
|
|
131
|
+
end if;
|
|
132
|
+
v_item := meta.wizard_item(p_page, v_pk);
|
|
133
|
+
v_page := meta.wizard_new_page(v_app, p_page, v_label, v_app.home_page);
|
|
134
|
+
insert into meta.region (page_id, seq, title, type, columns, source, table_name, pk_column, config)
|
|
135
|
+
values (v_page, 10, meta.wizard_table_label(p_table), 'grid', 12,
|
|
136
|
+
format(E'select %s\n from %s', (select string_agg(quote_ident(column_name), ', ' order by ordinal)
|
|
137
|
+
from meta.wizard_catalog(p_table) where kind <> 'binary' and not (column_name = any (v_skip))),
|
|
138
|
+
meta.wizard_qname(p_table)),
|
|
139
|
+
meta.wizard_qname(p_table), v_pk,
|
|
140
|
+
jsonb_build_object('page_size', 10, 'columns', meta.wizard_grid_columns(p_table),
|
|
141
|
+
'select_row', jsonb_build_object('column', v_pk, 'item', v_item))
|
|
142
|
+
|| meta.wizard_grid_labels(p_table))
|
|
143
|
+
returning id into v_region;
|
|
144
|
+
insert into meta.item (page_id, region_id, seq, name, label, type)
|
|
145
|
+
values (v_page, v_region, 10, v_item, meta.wizard_column_label(p_table, v_pk), 'hidden');
|
|
146
|
+
insert into meta.region (page_id, seq, title, type, columns, source, table_name, pk_column, config)
|
|
147
|
+
values (v_page, 20, meta.wizard_table_label(v_detail), 'grid', 12,
|
|
148
|
+
format(E'select %s\n from %s\n where %I = :%s::%s',
|
|
149
|
+
(select string_agg(quote_ident(column_name), ', ' order by ordinal)
|
|
150
|
+
from meta.wizard_catalog(v_detail) where kind <> 'binary' and column_name <> v_a
|
|
151
|
+
and not (column_name = any (meta.wizard_sidecars(v_detail)))),
|
|
152
|
+
meta.wizard_qname(v_detail), v_a, v_item, (select type_sql from meta.wizard_catalog(p_table) where column_name = v_pk)),
|
|
153
|
+
meta.wizard_qname(v_detail), v_dpk,
|
|
154
|
+
jsonb_build_object('page_size', 10, 'columns', meta.wizard_grid_columns(v_detail, v_a),
|
|
155
|
+
'master', jsonb_build_object('item', v_item, 'column', v_a))
|
|
156
|
+
|| meta.wizard_grid_labels(v_detail))
|
|
157
|
+
returning id into v_report;
|
|
158
|
+
insert into meta.process (page_id, seq, name, type, region_id) values
|
|
159
|
+
(v_page, 10, 'Save ' || meta.wizard_table_label(p_table), 'grid_dml', v_region),
|
|
160
|
+
(v_page, 20, 'Save ' || meta.wizard_table_label(v_detail), 'grid_dml', v_report);
|
|
161
|
+
else
|
|
162
|
+
-- the listing kinds, with an optional modal form page for a row
|
|
163
|
+
if v_fpage is not null and v_pk is null then
|
|
164
|
+
raise exception 'a form page needs a table with a single-column primary key (% has none)', p_table;
|
|
165
|
+
end if;
|
|
166
|
+
v_page := meta.wizard_new_page(v_app, p_page, v_label, v_app.home_page);
|
|
167
|
+
v_key := case when v_pk is not null then format('t.%I', v_pk) end;
|
|
168
|
+
if v_fpage is not null then
|
|
169
|
+
v_link := jsonb_build_object('page', v_fpage, 'items', jsonb_build_object(meta.wizard_item(v_fpage, v_pk), '#' || v_pk || '#'));
|
|
170
|
+
end if;
|
|
171
|
+
|
|
172
|
+
if p_kind = 'cards' then
|
|
173
|
+
v_a := meta.wizard_col(p_table, o ->> 'title', 'title column');
|
|
174
|
+
v_b := meta.wizard_col(p_table, o ->> 'subtitle', 'subtitle column');
|
|
175
|
+
v_c := meta.wizard_col(p_table, o ->> 'body', 'body column');
|
|
176
|
+
v_d := meta.wizard_col(p_table, o ->> 'badge', 'badge column');
|
|
177
|
+
if v_a is null then
|
|
178
|
+
raise exception 'choose the column for the card titles';
|
|
179
|
+
end if;
|
|
180
|
+
v_src := concat_ws(E',\n ',
|
|
181
|
+
meta.wizard_expr(p_table, v_a) || ' as title',
|
|
182
|
+
meta.wizard_expr(p_table, v_b) || ' as subtitle',
|
|
183
|
+
meta.wizard_expr(p_table, v_c) || ' as body',
|
|
184
|
+
meta.wizard_expr(p_table, v_d) || ' as badge',
|
|
185
|
+
v_key);
|
|
186
|
+
insert into meta.region (page_id, seq, title, type, source, config)
|
|
187
|
+
values (v_page, 10, v_label, 'cards', format(E'select %s\n from %s t\n order by 1', v_src, meta.wizard_qname(p_table)),
|
|
188
|
+
jsonb_strip_nulls(jsonb_build_object('link', v_link)))
|
|
189
|
+
returning id into v_region;
|
|
190
|
+
|
|
191
|
+
elsif p_kind = 'calendar' then
|
|
192
|
+
v_a := meta.wizard_col(p_table, o ->> 'start', 'start column');
|
|
193
|
+
v_b := meta.wizard_col(p_table, o ->> 'end', 'end column');
|
|
194
|
+
v_c := meta.wizard_col(p_table, o ->> 'title', 'title column');
|
|
195
|
+
if v_a is null or (select kind from meta.wizard_catalog(p_table) where column_name = v_a) not in ('date', 'timestamp') then
|
|
196
|
+
raise exception 'a calendar needs a date or timestamp start column';
|
|
197
|
+
end if;
|
|
198
|
+
if v_b is not null and (select kind from meta.wizard_catalog(p_table) where column_name = v_b) not in ('date', 'timestamp') then
|
|
199
|
+
raise exception 'the end column must be a date or timestamp';
|
|
200
|
+
end if;
|
|
201
|
+
v_src := concat_ws(E',\n ',
|
|
202
|
+
format('t.%I as start_date', v_a),
|
|
203
|
+
case when v_b is not null then format('t.%I as end_date', v_b) end,
|
|
204
|
+
coalesce(meta.wizard_expr(p_table, v_c), quote_literal(v_label)) || ' as title',
|
|
205
|
+
v_key);
|
|
206
|
+
v_cfg := jsonb_strip_nulls(jsonb_build_object('link', v_link, 'key', v_pk));
|
|
207
|
+
if v_fpage is not null then
|
|
208
|
+
v_cfg := v_cfg || jsonb_build_object('create', jsonb_build_object('page', v_fpage, 'items',
|
|
209
|
+
jsonb_strip_nulls(jsonb_build_object(meta.wizard_item(v_fpage, v_a), '#start#')
|
|
210
|
+
|| case when v_b is not null then jsonb_build_object(meta.wizard_item(v_fpage, v_b), '#end#') else '{}' end)));
|
|
211
|
+
end if;
|
|
212
|
+
if coalesce((o ->> 'drag')::boolean, false) then
|
|
213
|
+
if v_pk is null then
|
|
214
|
+
raise exception 'drag and drop needs a table with a single-column primary key (% has none)', p_table;
|
|
215
|
+
end if;
|
|
216
|
+
v_cfg := v_cfg || jsonb_build_object('move', format('update %s set %I = :NEW_START::%s%s where %I = :EVENT_ID::%s',
|
|
217
|
+
meta.wizard_qname(p_table), v_a, (select type_sql from meta.wizard_catalog(p_table) where column_name = v_a),
|
|
218
|
+
case when v_b is not null then format(', %I = :NEW_END::%s', v_b, (select type_sql from meta.wizard_catalog(p_table) where column_name = v_b)) else '' end,
|
|
219
|
+
v_pk, (select type_sql from meta.wizard_catalog(p_table) where column_name = v_pk)));
|
|
220
|
+
end if;
|
|
221
|
+
insert into meta.region (page_id, seq, title, type, source, config)
|
|
222
|
+
values (v_page, 10, v_label, 'calendar', format(E'select %s\n from %s t', v_src, meta.wizard_qname(p_table)), v_cfg)
|
|
223
|
+
returning id into v_region;
|
|
224
|
+
|
|
225
|
+
elsif p_kind = 'chart' then
|
|
226
|
+
v_a := meta.wizard_col(p_table, o ->> 'label_column', 'label column');
|
|
227
|
+
v_b := meta.wizard_col(p_table, o ->> 'value_column', 'value column');
|
|
228
|
+
v_kind := coalesce(nullif(o ->> 'chart', ''), 'bar');
|
|
229
|
+
v_c := lower(coalesce(nullif(o ->> 'function', ''), 'count'));
|
|
230
|
+
if v_a is null then
|
|
231
|
+
raise exception 'choose the label column of the chart';
|
|
232
|
+
end if;
|
|
233
|
+
if v_kind not in ('bar', 'column', 'line', 'area', 'donut', 'pie', 'funnel') then
|
|
234
|
+
raise exception 'unknown chart type "%"', v_kind;
|
|
235
|
+
end if;
|
|
236
|
+
if v_c not in ('count', 'sum', 'avg', 'min', 'max') then
|
|
237
|
+
raise exception 'unknown function "%"', v_c;
|
|
238
|
+
end if;
|
|
239
|
+
if v_c <> 'count' and (v_b is null or (select kind from meta.wizard_catalog(p_table) where column_name = v_b) <> 'number') then
|
|
240
|
+
raise exception 'the function % needs a number column', v_c;
|
|
241
|
+
end if;
|
|
242
|
+
v_d := case when v_c = 'count' and v_b is null then 'Count'
|
|
243
|
+
else initcap(case v_c when 'avg' then 'average' when 'min' then 'minimum' when 'max' then 'maximum' else v_c end)
|
|
244
|
+
|| ' of ' || meta.wizard_column_label(p_table, v_b) end;
|
|
245
|
+
-- the label as a derived column, so a foreign key's display value can be grouped;
|
|
246
|
+
-- (097) rows without a value are labelled "(none)" instead of an empty bar
|
|
247
|
+
v_src := meta.wizard_expr(p_table, v_a);
|
|
248
|
+
if exists (select 1 from meta.wizard_catalog(p_table) where column_name = v_a and (kind = 'text' or fk_table is not null) and not (not_null and fk_table is null)) then
|
|
249
|
+
v_src := format('coalesce((%s)::text, %L)', v_src, '(none)');
|
|
250
|
+
end if;
|
|
251
|
+
insert into meta.region (page_id, seq, title, type, source, config)
|
|
252
|
+
values (v_page, 10, v_label, 'chart',
|
|
253
|
+
format(E'select t.%I,\n %s as %I\n from (select %s as %I%s from %s t) t\n group by 1\n order by %s',
|
|
254
|
+
meta.wizard_column_label(p_table, v_a),
|
|
255
|
+
case when v_b is null then 'count(*)' else format('%s(t.%I)', v_c, v_b) end, v_d,
|
|
256
|
+
v_src, meta.wizard_column_label(p_table, v_a),
|
|
257
|
+
case when v_b is null then '' else format(', t.%I', v_b) end,
|
|
258
|
+
meta.wizard_qname(p_table), case when v_kind in ('bar', 'donut', 'pie', 'funnel') then '2 desc' else '1' end),
|
|
259
|
+
jsonb_build_object('kind', v_kind) || case when v_kind in ('bar', 'donut', 'pie', 'funnel') then '{"top": 10}'::jsonb else '{}' end)
|
|
260
|
+
returning id into v_region;
|
|
261
|
+
|
|
262
|
+
elsif p_kind = 'map' then
|
|
263
|
+
v_a := meta.wizard_col(p_table, o ->> 'lat', 'latitude column');
|
|
264
|
+
v_b := meta.wizard_col(p_table, o ->> 'lng', 'longitude column');
|
|
265
|
+
v_c := meta.wizard_col(p_table, o ->> 'location', 'location column');
|
|
266
|
+
if v_c is not null then
|
|
267
|
+
v_kind := (select kind from meta.wizard_catalog(p_table) where column_name = v_c);
|
|
268
|
+
v_src := case v_kind
|
|
269
|
+
when 'geometry' then format('st_y(st_pointonsurface(t.%1$I::geometry)) as lat, st_x(st_pointonsurface(t.%1$I::geometry)) as lng', v_c)
|
|
270
|
+
when 'point' then format('t.%1$I[1] as lat, t.%1$I[0] as lng', v_c)
|
|
271
|
+
when 'text' then format('t.%I as location', v_c)
|
|
272
|
+
else null end;
|
|
273
|
+
-- lines and areas are drawn as shapes too
|
|
274
|
+
if v_kind = 'geometry' then
|
|
275
|
+
v_geo := format('case when geometrytype(t.%1$I::geometry) <> ''POINT'' then st_asgeojson(t.%1$I::geometry) end as geojson', v_c);
|
|
276
|
+
end if;
|
|
277
|
+
if v_src is null then
|
|
278
|
+
raise exception 'the location column must hold "latitude,longitude" text, a point or a PostGIS geometry';
|
|
279
|
+
end if;
|
|
280
|
+
v_d := format('t.%I is not null', v_c);
|
|
281
|
+
elsif v_a is not null and v_b is not null then
|
|
282
|
+
if (select count(*) from meta.wizard_catalog(p_table) where column_name in (v_a, v_b) and kind = 'number') <> 2 then
|
|
283
|
+
raise exception 'latitude and longitude must be number columns';
|
|
284
|
+
end if;
|
|
285
|
+
v_src := format('t.%I as lat, t.%I as lng', v_a, v_b);
|
|
286
|
+
v_d := format('t.%I is not null and t.%I is not null', v_a, v_b);
|
|
287
|
+
else
|
|
288
|
+
raise exception 'a map needs latitude and longitude columns, or a location, point or geometry column';
|
|
289
|
+
end if;
|
|
290
|
+
v_cols := concat_ws(E',\n ', v_src, v_geo,
|
|
291
|
+
meta.wizard_expr(p_table, meta.wizard_col(p_table, o ->> 'title', 'title column')) || ' as title',
|
|
292
|
+
meta.wizard_expr(p_table, meta.wizard_col(p_table, o ->> 'body', 'body column')) || ' as body',
|
|
293
|
+
v_key);
|
|
294
|
+
insert into meta.region (page_id, seq, title, type, source, config)
|
|
295
|
+
values (v_page, 10, v_label, 'map', format(E'select %s\n from %s t\n where %s', v_cols, meta.wizard_qname(p_table), v_d),
|
|
296
|
+
jsonb_strip_nulls(jsonb_build_object('link', v_link)))
|
|
297
|
+
returning id into v_region;
|
|
298
|
+
if coalesce((o ->> 'report')::boolean, true) then
|
|
299
|
+
-- a report of the same rows, filtered by the map area ("Show this area in the list")
|
|
300
|
+
insert into meta.region (page_id, seq, title, type, source, config)
|
|
301
|
+
values (v_page, 20, v_label || ' list', 'report',
|
|
302
|
+
format(E'select %s,\n %s\n from %s t',
|
|
303
|
+
(select string_agg(meta.wizard_expr(p_table, column_name) || ' as ' || quote_ident(column_name), ', ' order by ordinal)
|
|
304
|
+
from meta.wizard_catalog(p_table)
|
|
305
|
+
where kind not in ('binary', 'geometry', 'point') and column_name not in ('lat', 'lng', 'location')
|
|
306
|
+
and not (column_name = any (v_skip))),
|
|
307
|
+
v_src, meta.wizard_qname(p_table)),
|
|
308
|
+
jsonb_strip_nulls(jsonb_build_object('link', v_link || jsonb_build_object('column', v_pk),
|
|
309
|
+
'headings', (select jsonb_object_agg(column_name, meta.wizard_column_label(p_table, column_name)) from meta.wizard_catalog(p_table)),
|
|
310
|
+
'formats', nullif(meta.wizard_grid_labels(p_table) -> 'formats', '{}'))))
|
|
311
|
+
returning id into v_report;
|
|
312
|
+
update meta.region set config = config || jsonb_build_object('report', v_report) where id = v_region;
|
|
313
|
+
end if;
|
|
314
|
+
|
|
315
|
+
elsif p_kind = 'facets' then
|
|
316
|
+
perform meta.wizard_col(p_table, x, 'report column') from jsonb_array_elements_text(coalesce(o -> 'columns', '[]')) x;
|
|
317
|
+
perform meta.wizard_col(p_table, x, 'facet column') from jsonb_array_elements_text(coalesce(o -> 'facets', '[]')) x;
|
|
318
|
+
-- the report: the chosen columns, and the parent's display column next to each foreign key
|
|
319
|
+
v_cols := null;
|
|
320
|
+
for c in
|
|
321
|
+
select w.* from meta.wizard_catalog(p_table) w
|
|
322
|
+
where w.column_name in (select x from jsonb_array_elements_text(coalesce(o -> 'columns', '[]')) x)
|
|
323
|
+
or w.is_pk
|
|
324
|
+
or w.column_name in (select x from jsonb_array_elements_text(coalesce(o -> 'facets', '[]')) x)
|
|
325
|
+
order by w.ordinal
|
|
326
|
+
loop
|
|
327
|
+
continue when c.kind in ('binary', 'geometry');
|
|
328
|
+
v_cols := concat_ws(E',\n ', v_cols, format('t.%I', c.column_name));
|
|
329
|
+
if c.fk_display is not null and c.fk_display <> c.fk_column and not c.is_pk then
|
|
330
|
+
-- (100) one column per foreign key: the parent's display value; the key itself is hidden (still filterable)
|
|
331
|
+
v_cols := concat_ws(E',\n ', v_cols, meta.wizard_expr(p_table, c.column_name) || ' as ' || quote_ident(c.column_name || '_' || c.fk_display));
|
|
332
|
+
v_heads := v_heads || jsonb_build_object(c.column_name || '_' || c.fk_display, meta.wizard_column_label(p_table, c.column_name));
|
|
333
|
+
v_hidden := v_hidden || to_jsonb(c.column_name);
|
|
334
|
+
else
|
|
335
|
+
v_heads := v_heads || jsonb_build_object(c.column_name, meta.wizard_column_label(p_table, c.column_name));
|
|
336
|
+
v_mask := meta.wizard_mask(c.type_sql, c.kind, c.is_pk or c.fk_table is not null);
|
|
337
|
+
if v_mask is not null then
|
|
338
|
+
v_fmts := v_fmts || jsonb_build_object(c.column_name, v_mask);
|
|
339
|
+
end if;
|
|
340
|
+
end if;
|
|
341
|
+
end loop;
|
|
342
|
+
if v_cols is null then
|
|
343
|
+
raise exception 'choose at least one report column';
|
|
344
|
+
end if;
|
|
345
|
+
for c in
|
|
346
|
+
select w.* from jsonb_array_elements_text(coalesce(o -> 'facets', '[]')) with ordinality x(col, n)
|
|
347
|
+
join meta.wizard_catalog(p_table) w on w.column_name = x.col
|
|
348
|
+
order by x.n
|
|
349
|
+
loop
|
|
350
|
+
continue when c.kind in ('binary', 'geometry', 'point', 'other');
|
|
351
|
+
v_facets := v_facets || jsonb_build_array(jsonb_strip_nulls(jsonb_build_object(
|
|
352
|
+
'column', case when c.fk_display is not null and c.fk_display <> c.fk_column then c.column_name || '_' || c.fk_display else c.column_name end,
|
|
353
|
+
'label', meta.wizard_column_label(p_table, c.column_name),
|
|
354
|
+
'type', case when c.kind in ('number', 'date', 'timestamp') and c.fk_table is null then 'range' end,
|
|
355
|
+
'custom', case when c.kind in ('number', 'date', 'timestamp') and c.fk_table is null then true end)));
|
|
356
|
+
end loop;
|
|
357
|
+
insert into meta.region (page_id, seq, title, type, columns, source, config)
|
|
358
|
+
values (v_page, 20, v_label, 'report', 9, format(E'select %s\n from %s t', v_cols, meta.wizard_qname(p_table)),
|
|
359
|
+
jsonb_strip_nulls(jsonb_build_object('link', v_link || jsonb_build_object('column', v_pk, 'icon', true), 'headings', v_heads,
|
|
360
|
+
'hidden', nullif(v_hidden, '[]'), 'formats', nullif(v_fmts, '{}'))))
|
|
361
|
+
returning id into v_region;
|
|
362
|
+
insert into meta.region (page_id, seq, title, type, columns, template, config)
|
|
363
|
+
values (v_page, 10, 'Filters', 'facets', 3, 'collapsible',
|
|
364
|
+
jsonb_build_object('report', v_region, 'search', coalesce((o ->> 'search')::boolean, true), 'facets', v_facets));
|
|
365
|
+
end if;
|
|
366
|
+
|
|
367
|
+
if v_fpage is not null then
|
|
368
|
+
perform meta.wizard_form(v_app, p_table, v_fpage, v_flabel, p_page, true, null);
|
|
369
|
+
insert into meta.button (page_id, region_id, seq, name, label, action, target_page, hot)
|
|
370
|
+
values (v_page, v_region, 10, 'CREATE', 'Create', 'redirect', v_fpage, true);
|
|
371
|
+
end if;
|
|
372
|
+
end if;
|
|
373
|
+
|
|
374
|
+
if coalesce((o ->> 'nav')::boolean, false) then
|
|
375
|
+
perform meta.wizard_nav(v_app, v_label, v_icon, p_page);
|
|
376
|
+
end if;
|
|
377
|
+
return v_page;
|
|
378
|
+
end
|
|
379
|
+
$$;
|
|
380
|
+
|
|
381
|
+
revoke all on function meta.wizard_grid_labels(regclass) from public;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
-- =====================================================================
|
|
2
|
+
-- 101: an application opens in its own light or dark mode until the user
|
|
3
|
+
-- chooses (sprint 79; parity handoff UI-2)
|
|
4
|
+
--
|
|
5
|
+
-- meta.account.theme_pref was 'auto' unless the user chose: at sign-in that
|
|
6
|
+
-- choice replaced the application's mode, so an Iris application (light)
|
|
7
|
+
-- followed the operating system's dark mode for every signed-in user. Null
|
|
8
|
+
-- now means "no choice": the application's mode (meta.app.theme.mode).
|
|
9
|
+
-- The 'auto' that every account had is cleared; a user who chose
|
|
10
|
+
-- Automatic chooses it again from the Appearance menu.
|
|
11
|
+
-- =====================================================================
|
|
12
|
+
alter table meta.account alter column theme_pref drop not null;
|
|
13
|
+
alter table meta.account alter column theme_pref set default null;
|
|
14
|
+
update meta.account set theme_pref = null where theme_pref = 'auto';
|
|
15
|
+
comment on column meta.account.theme_pref is 'The user''s light/dark choice (auto, light, dark); null: the application''s mode (101).';
|
|
@@ -7,7 +7,7 @@ open item and open an issue or pull request; see [CONTRIBUTING.md](../CONTRIBUTI
|
|
|
7
7
|
Legend: ✅ available · 🟡 partial (see notes) · ❌ not yet · ➖ not planned (a deliberate choice,
|
|
8
8
|
or better served by the PostgreSQL ecosystem; see the notes and [extensions](guide/15-extensions.md))
|
|
9
9
|
|
|
10
|
-
Last reviewed: 2026-10-14 (sprint 77: the Page Designer's layout and tree by display position, button positions, the tree's context menu with Duplicate and Copy to other page, Show Common / Show All, Go to group and the Template Options dialog; display positions, button positions and side-column page templates at runtime; sprint 76: the interactive report's menus and pagination, date items in the date format; sprint 75: the SQL Workshop's Object Browser and tabs; sprint 74: the builder's information architecture: Shared Components grid, Settings tabs, application home search and tasks; sprint 73: the Create Application wizard on one page with page types and features; sprint 72: the runtime chrome compared with APEX 26.2's: the developer toolbar, the Iris look, the navigation bar's Help menu and Install button; sprint 71: generated applications compared with APEX 26.2's: labels, foreign key names in reports, file columns, format masks, the Home page and the dashboard; sprint 70: treemap, sunburst and timeline charts; sprint 69: dragging tasks on a Gantt chart; sprint 68: the Data Reporter's welcome page, Find my workspace; sprint 67: REST source catalogs from OpenAPI documents, a workspace's export and import (two rows added); sprint 66: a blueprint from an existing application; sprint 65: facet charts; sprint 64: approval vacation rules and expiration policies; sprint 63: range, box plot and stock charts; sprint 62: workspace preferences (SQL Workshop and RESTful services per workspace), extension links, Manage backups across applications, SQL Workshop utilities with object reports, About Database and a database monitor; sprint 61: the builder's screens compared with APEX 26.1: the workspace sign-in page, the Administration menu with workspace message, environment banner and utilization, the SQL Workshop home, the App Gallery, application groups and cross application reports (four rows added); sprint 60: isolated workspaces with their own database logins, row-level security on pgkiln's tables and workspace database roles, workspace administrators; sprint 59: the Page Designer's help pane, Quick edit from the developer toolbar; sprint 58: page groups; sprint 57: region templates hero, title bar, buttons container, inline dialog and drawer, wizard with wizard progress, the carousel; sprint 56: Generate DDL and Schema Comparison in the SQL Workshop, `pgkiln ddl` and `pgkiln compare`; sprint 55: an editable login page with a Login region and the Minimal page template, Forgot password through the application's mail service; sprint 54: interactive grid views, the single row view, group by and chart; sprint 53: application settings, user preferences and items kept per user, dynamic translations, `meta.data_export` and data export downloads, display image and geocoded address items; sprint 52: every dynamic action event and action, Ajax callbacks, an error handling function, the session timeout warning; sprint 51: checked imports, application backups, availability and Run only, feedback, type-specific attribute help; sprint 50: lost update detection, warn on unsaved changes, the URL and search regions with search configurations, the developer toolbar with a session state viewer, and e-mail through SMTP with OAuth2, Microsoft Graph, Amazon SES and HTTP APIs; import checks, application availability, property help and e-mail compared again; 2026-10-06: pgkiln 0.31.0, in progress: static application files and the Execute JavaScript dynamic action; region, item, dynamic action and process plug-ins; conditional and dynamic theme styles; XML and Excel in `meta.parse_data`, zips in SQL, `meta.v_boolean`; file items in S3-compatible object storage; map layers by the visible area and as vector tiles; a graphical query builder; tenants for workflows and tasks; application files as YAML and region static IDs; Lucide icons and icon modifiers; PWA push notifications; 0.30.0: 136 icons and an icon picker; an axe-core accessibility audit; cropping pictures; session sharing between applications; twenty-two built-in languages; 0.29.0: workspaces; the Iris base style; drawers; built-in template components including the metric card; Theme Roller dark-mode colours, live preview and template options on items and report columns; report selection across pages; instance settings; fourteen built-in languages; 0.28.0: AI with Claude and OpenAI; 0.27.0: Data Reporter; sample data generator; create application from several sheets, pasted data or existing tables; 0.26.0: working copies with a three-way merge; theme, library and boilerplate applications with subscriptions; 0.25.0: Gantt, pyramid and polar charts; map layers, clustering and server-side spatial filters; REST data sources that write back from forms and grids and synchronise into tables, OAuth2 password and refresh-token grants; debug messages per request and an install/upgrade log; `meta.web_request()` and `meta.parse_data()`; Theme Roller style variants and template options; 0.24.0: automations with several actions, error handling per row and runs from SQL; invoke-API workflow steps; Unload Data in the Data Workshop; create page wizards for cards, calendar, chart, map, faceted search, form and master-detail; create an application from a file; 0.23.0: interactive grid aggregates, frozen/moved/resized columns, layouts and saved grid reports, master-detail, row actions and copy/paste; download processes, execution chains in the background, workflow processes, branches to a function's URL or another app, the Dialog Closed event; number format masks, automatic time zone, runtime messages in German, French and Spanish; SQL scripts, Quick SQL, a query builder, XML loading and data load definitions; custom authentication, lists, page and application locks with developer comments, supporting objects; 0.22.0: keyset paging, report PDFs and REST collections streamed from a cursor, database-account authentication; 0.21.0: HTTP-header authentication behind a reverse proxy; 0.20.0: a popup list of values with server-side search; 0.19.0: row-range pagination, maximum row counts and row limits, lazy loading and caching of regions, streamed CSV/Excel downloads; 0.18.0: calendar week/day/list views with drag and drop, bubble/gauge/funnel/radar charts and drill-down, smart filters, a region display selector and more facet types, computations, conditional branches, build options, menu buttons, REST data sources and web credentials, rich text/Markdown, rating, combobox, date range and QR code items; 0.17.0: an App Builder home, workspace dashboard and utilities like APEX's; 0.16.0: a Page Designer with drag-and-drop layout, a code editor with autocomplete, template components and plug-ins, parallel branches and versions of workflows, the `pgkiln` command line with one file per component; 0.15.0: drop and paste files, heat maps, filtering a report by the map area; 0.14.0: stacked, combo, scatter and pie charts, several files per upload item; 0.13.0: map and tree regions; 0.12.0: REST modules in the builder; 0.11.0: workflows and Progressive Web Apps; 0.10.0: LDAP, SAML, "Keep me signed in", document templates, JSON loading, approvals and the task list; pgkiln installs no application, HR is an example).
|
|
10
|
+
Last reviewed: 2026-10-14 (sprint 79: generated dashboards with sums, donuts and lines, leaving out charts that say nothing; applications open in their own light or dark mode until the user chooses, per application; a user menu with Appearance and Sign in when signed out; the developer toolbar's App and Page texts and debug state; the report search's column scope in the magnifier; sprint 78: generated faceted search reports with one column per foreign key, the faceted search's total row count, Show all and Yes/No values, one search field, friendly headings and number formats in generated grids and master-detail pages, friendly names in the Create Application wizard's page list; sprint 77: the Page Designer's layout and tree by display position, button positions, the tree's context menu with Duplicate and Copy to other page, Show Common / Show All, Go to group and the Template Options dialog; display positions, button positions and side-column page templates at runtime; sprint 76: the interactive report's menus and pagination, date items in the date format; sprint 75: the SQL Workshop's Object Browser and tabs; sprint 74: the builder's information architecture: Shared Components grid, Settings tabs, application home search and tasks; sprint 73: the Create Application wizard on one page with page types and features; sprint 72: the runtime chrome compared with APEX 26.2's: the developer toolbar, the Iris look, the navigation bar's Help menu and Install button; sprint 71: generated applications compared with APEX 26.2's: labels, foreign key names in reports, file columns, format masks, the Home page and the dashboard; sprint 70: treemap, sunburst and timeline charts; sprint 69: dragging tasks on a Gantt chart; sprint 68: the Data Reporter's welcome page, Find my workspace; sprint 67: REST source catalogs from OpenAPI documents, a workspace's export and import (two rows added); sprint 66: a blueprint from an existing application; sprint 65: facet charts; sprint 64: approval vacation rules and expiration policies; sprint 63: range, box plot and stock charts; sprint 62: workspace preferences (SQL Workshop and RESTful services per workspace), extension links, Manage backups across applications, SQL Workshop utilities with object reports, About Database and a database monitor; sprint 61: the builder's screens compared with APEX 26.1: the workspace sign-in page, the Administration menu with workspace message, environment banner and utilization, the SQL Workshop home, the App Gallery, application groups and cross application reports (four rows added); sprint 60: isolated workspaces with their own database logins, row-level security on pgkiln's tables and workspace database roles, workspace administrators; sprint 59: the Page Designer's help pane, Quick edit from the developer toolbar; sprint 58: page groups; sprint 57: region templates hero, title bar, buttons container, inline dialog and drawer, wizard with wizard progress, the carousel; sprint 56: Generate DDL and Schema Comparison in the SQL Workshop, `pgkiln ddl` and `pgkiln compare`; sprint 55: an editable login page with a Login region and the Minimal page template, Forgot password through the application's mail service; sprint 54: interactive grid views, the single row view, group by and chart; sprint 53: application settings, user preferences and items kept per user, dynamic translations, `meta.data_export` and data export downloads, display image and geocoded address items; sprint 52: every dynamic action event and action, Ajax callbacks, an error handling function, the session timeout warning; sprint 51: checked imports, application backups, availability and Run only, feedback, type-specific attribute help; sprint 50: lost update detection, warn on unsaved changes, the URL and search regions with search configurations, the developer toolbar with a session state viewer, and e-mail through SMTP with OAuth2, Microsoft Graph, Amazon SES and HTTP APIs; import checks, application availability, property help and e-mail compared again; 2026-10-06: pgkiln 0.31.0, in progress: static application files and the Execute JavaScript dynamic action; region, item, dynamic action and process plug-ins; conditional and dynamic theme styles; XML and Excel in `meta.parse_data`, zips in SQL, `meta.v_boolean`; file items in S3-compatible object storage; map layers by the visible area and as vector tiles; a graphical query builder; tenants for workflows and tasks; application files as YAML and region static IDs; Lucide icons and icon modifiers; PWA push notifications; 0.30.0: 136 icons and an icon picker; an axe-core accessibility audit; cropping pictures; session sharing between applications; twenty-two built-in languages; 0.29.0: workspaces; the Iris base style; drawers; built-in template components including the metric card; Theme Roller dark-mode colours, live preview and template options on items and report columns; report selection across pages; instance settings; fourteen built-in languages; 0.28.0: AI with Claude and OpenAI; 0.27.0: Data Reporter; sample data generator; create application from several sheets, pasted data or existing tables; 0.26.0: working copies with a three-way merge; theme, library and boilerplate applications with subscriptions; 0.25.0: Gantt, pyramid and polar charts; map layers, clustering and server-side spatial filters; REST data sources that write back from forms and grids and synchronise into tables, OAuth2 password and refresh-token grants; debug messages per request and an install/upgrade log; `meta.web_request()` and `meta.parse_data()`; Theme Roller style variants and template options; 0.24.0: automations with several actions, error handling per row and runs from SQL; invoke-API workflow steps; Unload Data in the Data Workshop; create page wizards for cards, calendar, chart, map, faceted search, form and master-detail; create an application from a file; 0.23.0: interactive grid aggregates, frozen/moved/resized columns, layouts and saved grid reports, master-detail, row actions and copy/paste; download processes, execution chains in the background, workflow processes, branches to a function's URL or another app, the Dialog Closed event; number format masks, automatic time zone, runtime messages in German, French and Spanish; SQL scripts, Quick SQL, a query builder, XML loading and data load definitions; custom authentication, lists, page and application locks with developer comments, supporting objects; 0.22.0: keyset paging, report PDFs and REST collections streamed from a cursor, database-account authentication; 0.21.0: HTTP-header authentication behind a reverse proxy; 0.20.0: a popup list of values with server-side search; 0.19.0: row-range pagination, maximum row counts and row limits, lazy loading and caching of regions, streamed CSV/Excel downloads; 0.18.0: calendar week/day/list views with drag and drop, bubble/gauge/funnel/radar charts and drill-down, smart filters, a region display selector and more facet types, computations, conditional branches, build options, menu buttons, REST data sources and web credentials, rich text/Markdown, rating, combobox, date range and QR code items; 0.17.0: an App Builder home, workspace dashboard and utilities like APEX's; 0.16.0: a Page Designer with drag-and-drop layout, a code editor with autocomplete, template components and plug-ins, parallel branches and versions of workflows, the `pgkiln` command line with one file per component; 0.15.0: drop and paste files, heat maps, filtering a report by the map area; 0.14.0: stacked, combo, scatter and pie charts, several files per upload item; 0.13.0: map and tree regions; 0.12.0: REST modules in the builder; 0.11.0: workflows and Progressive Web Apps; 0.10.0: LDAP, SAML, "Keep me signed in", document templates, JSON loading, approvals and the task list; pgkiln installs no application, HR is an example).
|
|
11
11
|
|
|
12
12
|
## At a glance
|
|
13
13
|
|
|
@@ -68,7 +68,7 @@ two, REST source catalogs and the workspace export, with their features.)
|
|
|
68
68
|
| Charts | ✅ | Bar, column, stacked, line, area, combination (columns + lines), scatter, bubble, donut, pie, gauge, funnel, radar, **Gantt** (start/end, progress, milestones, dependencies, a today line, a time axis from hours to years), **pyramid** (area-proportional segments, or two series back to back) and **polar** (polar area); (sprint 63) **range** (floating columns from low to high), **box plot** (from one value per row or five statistics) and **stock** (candlesticks from open, high, low and close); multi-series, tooltips, data table, (sprint 71) a value axis with gridlines on horizontal bars and top-N with an *Other* bar (`"top": 10`), palette checked for colour-vision deficiency; drill-down links on chart marks (checksummed, only to pages the user may open). Drawn on the server as SVG without inline styles. (sprint 69) **Gantt editing**: drag a task or its end, or the arrow keys, runs the region's SQL as the app's role. (sprint 70) **treemap** (squarified, two levels shown), **sunburst** (up to four rings) from `node_id` / `parent_id` columns, and **timeline** (lanes by a `series` column, points and periods, overlaps in rows). **Missing:** other Oracle JET types (e.g. picto, NBox, Sankey) |
|
|
69
69
|
| Cards and metric cards (26.1 metric card template) | ✅ | Cards from SQL, with a KPI "metric" style |
|
|
70
70
|
| Calendar | ✅ | Month, week, day and list views (agenda list on phones); create on click (a checksummed link with the slot); drag and drop to move events through the developer's move SQL, run as the app's role (CSRF, a *who may drag* authorization, only events the user sees). Works without JavaScript; dragging adds on top ([chapter 4](guide/04-pages-and-regions.md#calendar)) |
|
|
71
|
-
| Faceted search | ✅ | Checkbox, range (predefined, or a custom from/to on numbers and dates), star rating and search facets with live counts that honour the other facets; exclude on checkbox facets (26.1). Values travel as query parameters, never as SQL text. (sprint 65) **Facet charts**: a bar or pie chart of a facet's counts, each mark filtering on its value |
|
|
71
|
+
| Faceted search | ✅ | Checkbox, range (predefined, or a custom from/to on numbers and dates), star rating and search facets with live counts that honour the other facets; exclude on checkbox facets (26.1). Values travel as query parameters, never as SQL text. (sprint 65) **Facet charts**: a bar or pie chart of a facet's counts, each mark filtering on its value. (sprint 78) The **total row count**, *Show all* after seven values, booleans as Yes/No, one search field (the report's own is left out). No per-facet menu (sort by count or name) |
|
|
72
72
|
| Smart filters | ✅ | One search field with filter chips and suggestions over a report, the same facet types; URL-based, works without JavaScript ([chapter 4](guide/04-pages-and-regions.md#smart_filters)) |
|
|
73
73
|
| Static content, dynamic content | ✅ | `static` (HTML with substitutions) and `dynamic` (a SELECT returning HTML) |
|
|
74
74
|
| Breadcrumb | ✅ | Automatic, from the page's breadcrumb parent |
|
|
@@ -219,7 +219,7 @@ two, REST source catalogs and the workspace export, with their features.)
|
|
|
219
219
|
| Workspace message, environment banner, workspace utilization, preferences and extension links (Manage Service) | ✅ | (sprint 61) An **Administration** menu in the icon rail; administrators and workspace administrators set a **workspace message** (on the App Builder home) and an **environment banner** (a coloured label on every builder page of the workspace), and see **workspace utilization** (applications, pages, page views, users, static files, schema storage) ([chapter 3](guide/03-builder.md#workspace-message-and-environment-banner)). (sprint 62) **Workspace preferences**: the SQL Workshop and the applications' RESTful services (REST modules) on or off per workspace; **extension links** in an Extensions menu of the rail ([chapter 3](guide/03-builder.md#workspace-preferences)). **Missing:** the account login controls of APEX's preferences (pgkiln's password policy and sign-in throttling are installation-wide) |
|
|
220
220
|
| Instance administration, install/upgrade logs (26.1) | ✅ | Versioned migrations (`npm run db:migrate`), every run logged; Workspace utilities → **Installation** (administrators): server version, install and upgrade runs with errors, applied migrations, and warnings for migrations the database misses or the server doesn't know ([chapter 3](guide/03-builder.md)). Workspace utilities → **Instance settings**: session length and sign-in throttling (stored in the database, over the environment variables) and an overview of the server's configuration without secrets; a server whose database lacks migrations says so (503) or applies them (`MIGRATE_ON_START`). Other settings stay environment variables |
|
|
221
221
|
| Debug messages | ✅ | Debug levels 1–9 per app; `meta.debug(level, text)` and `meta.debug_enabled(level)` from application SQL (APEX_DEBUG); timed entries per request (page steps, regions, processes, branches, errors, SQL notices); Activity → **Debug messages** viewer per page view with filters and the slowest step highlighted; retention 1–90 days; password item values are never recorded ([chapter 6](guide/06-processing.md)). (sprint 50) Debug for **one session** from the developer toolbar, the application staying out of debug. **Missing:** debug entries for background jobs and REST requests |
|
|
222
|
-
| Developer toolbar, session state viewer | ✅ | (sprint 50) Developers signed in to the builder see a toolbar on the pages of their applications in the same browser: App Builder, the application, **Edit page** (Page Designer), **Session** (the session state viewer: user, roles, session, language, time zone, debug level, this page's items, application items and other pages' items; passwords never shown; clear the page's items) **Quick edit** (sprint 59: click a region, item or button to open it in the Page Designer) and **Debug** for this session only. A separate random token (cookie path `/a`, HttpOnly) of a live builder session of a developer of the application's workspace ([chapter 3](guide/03-builder.md#developer-toolbar)). (sprint 72) A compact bar of icons with tooltips like APEX's, centred at the bottom, **Customize** (the Theme Roller) and **Info**, hidden to a slim tab when not used, docked at the bottom, top, left or right; the page keeps room for it |
|
|
222
|
+
| Developer toolbar, session state viewer | ✅ | (sprint 50) Developers signed in to the builder see a toolbar on the pages of their applications in the same browser: App Builder, the application, **Edit page** (Page Designer), **Session** (the session state viewer: user, roles, session, language, time zone, debug level, this page's items, application items and other pages' items; passwords never shown; clear the page's items) **Quick edit** (sprint 59: click a region, item or button to open it in the Page Designer) and **Debug** for this session only. A separate random token (cookie path `/a`, HttpOnly) of a live builder session of a developer of the application's workspace ([chapter 3](guide/03-builder.md#developer-toolbar)). (sprint 72) A compact bar of icons with tooltips like APEX's, centred at the bottom, **Customize** (the Theme Roller) and **Info**, hidden to a slim tab when not used, docked at the bottom, top, left or right; the page keeps room for it (sprint 79) Each tool with a short text like APEX's (*App 12*, *Page 3*, *Debug off*), *Icons only* as a setting |
|
|
223
223
|
| Feedback (users send feedback from a page, developers answer) | ✅ | (sprint 51) With *Users can send feedback* on, the navigation bar's **Feedback** link opens a page for a comment, idea or problem about the page (optional rating), with the user's earlier feedback and the developers' answers; developers read, filter, set a status and answer under Application → Feedback ([chapter 3](guide/03-builder.md#feedback)). No screenshots, no e-mail to developers |
|
|
224
224
|
| Automatic application backups, page groups | ✅ | (sprint 51) **Backups**: before every import that replaces an application (CLI, MCP, working copy merge, restore), daily when it changed, and by hand; the newest 25 (configurable) are kept; download and restore from the builder ([chapter 3](guide/03-builder.md#backups)); (sprint 62) Workspace utilities → **Manage backups** across the workspace's applications, with a backup of all of them at once. (sprint 58) **Page groups**: Shared Components → Page groups, a page's *Page group*, the page list by group with a filter ([chapter 3](guide/03-builder.md#page-groups)) |
|
|
225
225
|
| Application availability (Available, Developers only, Restricted access, Unavailable; build status *Run only*) | ✅ | (sprint 51) Available, available to developers only (a builder session in the same browser), restricted to named users, or unavailable with a message, for pages, file downloads, REST modules and PostgREST; build status **Run only** refuses builder changes as if the application were locked, while `pgkiln import --replace` still deploys. Installation settings: not exported, kept by an import ([chapter 3](guide/03-builder.md#availability-and-build-status)) |
|
package/docs/guide/03-builder.md
CHANGED
|
@@ -371,15 +371,20 @@ The wizards (every *Create application* flow and the page wizards) make pages th
|
|
|
371
371
|
forms and dialog titles the singular (*Employee*).
|
|
372
372
|
- **Reports** show a foreign key's parent by its display column (a name, title or label, else its first
|
|
373
373
|
text column): *Manager* shows KING, *Department* shows SALES. The key itself stays in the query,
|
|
374
|
-
hidden, so filters can use it
|
|
374
|
+
hidden, so filters can use it; a report never has two columns with the same heading (also a faceted
|
|
375
|
+
search report, whose facets filter on the display column). A row starts with a pencil that opens the form.
|
|
376
|
+
- **Interactive grids** (the grid page and both grids of a master-detail page) use the same labels as
|
|
377
|
+
headings (*Department Number*, *Hire Date*); a foreign key is a select list of the parent's names.
|
|
375
378
|
- **Files**: a `bytea` column becomes one file upload item that also fills the columns that go with it
|
|
376
379
|
(`<col>_name`/`_filename`, `<col>_mime`/`_mime_type`, `<col>_size`, `<col>_updated`; or `filename`
|
|
377
380
|
and `mime_type` next to the only `bytea` column). Reports leave these columns out. Columns named like
|
|
378
381
|
*photo*, *image* or *logo* accept pictures only.
|
|
379
382
|
- **Numbers** get a format mask with group separators (and the scale's decimals for `numeric(p,s)`),
|
|
380
|
-
in reports and forms alike, in the application's language (*2,850.00*); keys get none.
|
|
381
|
-
- The **Dashboard** comes right after *Home* in the menu; its charts are titled *Employees by Manager
|
|
382
|
-
|
|
383
|
+
in reports, read-only grid cells and forms alike, in the application's language (*2,850.00*); keys get none.
|
|
384
|
+
- The **Dashboard** comes right after *Home* in the menu; its charts are titled *Employees by Manager*
|
|
385
|
+
(a count) or *Salary by Department* (the sum of an amount column: salary, total, price, cost, value…),
|
|
386
|
+
a donut for six groups or fewer, a line for a date, else a sorted bar; a grouping that says nothing (one
|
|
387
|
+
group, or every group the same size) is left out. Rows without a value are labelled *(none)*, and bar charts show the ten largest values (the rest is
|
|
383
388
|
one *Other* bar). Tables with row-level security are left out (the application's role may see none of
|
|
384
389
|
their rows); the result page says which.
|
|
385
390
|
- The **Home** page has a hero with the application's icon and name, and a card per page of the menu
|
|
@@ -493,7 +498,7 @@ step, which keeps the table and type.
|
|
|
493
498
|
| **Calendar** | A [calendar](04-pages-and-regions.md#calendar) region (month, week, day and list views) | Start: a date or timestamp column named like *start*, *begin* or *…_date* (not *created*/*updated*); end: one named like *end* or *until*; title: a name column, else the first foreign key's name |
|
|
494
499
|
| **Chart** | A [chart](04-pages-and-regions.md#chart) region: bar, column, line, area, donut, pie or funnel of the row count, or the sum, average, minimum or maximum of a number column, per label | Label: the first foreign key (its parent's name), else a text column that isn't unique |
|
|
495
500
|
| **Map** | A [map](04-pages-and-regions.md#map) region with popups and, if ticked, an interactive report of the same rows that the map filters (*Show this area in the list*) | Position: a PostGIS geometry or geography (markers, and lines and areas as shapes), a `point`, `lat`/`lng`-like number columns, or a *location* text column with `latitude,longitude` |
|
|
496
|
-
| **Faceted search** | An interactive report (9 columns wide) and a [facets](04-pages-and-regions.md#facets-faceted-search) panel (3 columns, collapsible) with a search field | Up to six facets: foreign keys (values of the parent's name, which the report shows
|
|
501
|
+
| **Faceted search** | An interactive report (9 columns wide) and a [facets](04-pages-and-regions.md#facets-faceted-search) panel (3 columns, collapsible) with a search field | Up to six facets: foreign keys (values of the parent's name, which the report shows instead of the key), booleans, text columns with few values, dates and numbers as ranges with *from*/*to* |
|
|
497
502
|
| **Master detail** | One page with a grid of the table and an editable grid of the selected row's details below it (the grids' [master-detail](04-pages-and-regions.md#grid-interactive-grid)): a hidden item for the selection, both grids with a grid DML process; new detail rows get the master's key | The detail table: a table with a foreign key to this table's primary key |
|
|
498
503
|
|
|
499
504
|
Cards, Calendar, Map and Faceted search take an optional **Form page** number: it adds a modal
|
|
@@ -708,11 +713,13 @@ Chapters 4–7 describe every component type and property.
|
|
|
708
713
|
|
|
709
714
|
While you are signed in to the builder, the pages of your applications show the **developer
|
|
710
715
|
toolbar** when you run them in the same browser (APEX: the Runtime Developer Toolbar): a compact bar
|
|
711
|
-
|
|
716
|
+
centred at the bottom, each tool an icon with a short text (*Home*, *App 12*, *Page 3*, *Session*,
|
|
717
|
+
*Debug off*, *Quick edit*, *Customize*, *Info*) and a tooltip; on narrow screens, docked at the left or
|
|
718
|
+
right, or with *Icons only* in Settings, just the icons. It isn't shown to anyone else, nor inside
|
|
712
719
|
dialogs. The page keeps room for it, so it never covers the menu's last entry or a report's pager.
|
|
713
720
|
After a few seconds without use it shrinks to a slim tab at the edge; point at it or Tab to it to
|
|
714
|
-
bring it back. **Settings** (the last icon) docks it at the bottom, top, left or right
|
|
715
|
-
hiding off (kept in your browser). It has:
|
|
721
|
+
bring it back. **Settings** (the last icon) docks it at the bottom, top, left or right, turns the
|
|
722
|
+
hiding off and shows *Icons only* (kept in your browser). It has:
|
|
716
723
|
|
|
717
724
|
| Entry | Does |
|
|
718
725
|
|---|---|
|
|
@@ -222,7 +222,8 @@ select e.empno, e.ename, e.job, d.dname as department, e.sal
|
|
|
222
222
|
|
|
223
223
|
Features for end users:
|
|
224
224
|
|
|
225
|
-
- **Search** across all columns, or in one column chosen
|
|
225
|
+
- **Search** across all columns, or in one column chosen under the magnifier at the start of the search field
|
|
226
|
+
(it shows the column's name once one is chosen); Enter searches (without JavaScript, a *Go* button)
|
|
226
227
|
(Enter searches; *Go* too).
|
|
227
228
|
- **Column heading menu** (sprint 76; with JavaScript): clicking a heading opens a menu with *Sort ascending*,
|
|
228
229
|
*Sort descending*, *Hide column*, *Control break* and the column's **values** with their counts (with the current
|
|
@@ -807,7 +808,9 @@ link is the way to change its dates.
|
|
|
807
808
|
A panel of filters, each with a live count, that filters a **report region on the same page**.
|
|
808
809
|
Counts take the search and all *other* facets into account. Three kinds of facet:
|
|
809
810
|
|
|
810
|
-
- **checkbox** (the default): the most frequent values of a column, each with a checkbox
|
|
811
|
+
- **checkbox** (the default): the most frequent values of a column, each with a checkbox; after the seventh
|
|
812
|
+
the rest sit under *Show all (n)* (open when one of them is chosen), and a boolean's values read *Yes* and
|
|
813
|
+
*No*. With
|
|
811
814
|
`"exclude": true` the facet gets an *Exclude the selected values* switch: the chosen values are
|
|
812
815
|
then left out instead (rows where the column is empty stay).
|
|
813
816
|
- **range**: a number or date column in ranges, as radio buttons ("Any", then each range). A
|
|
@@ -817,7 +820,11 @@ Counts take the search and all *other* facets into account. Three kinds of facet
|
|
|
817
820
|
- **star**: a rating column as "5 stars and up", "4 stars and up", … down to 1 (`max`, default 5).
|
|
818
821
|
|
|
819
822
|
With `"search": true` the panel starts with a search field: the same search as the report's own
|
|
820
|
-
(`r<id>_q`, the row as text contains the term).
|
|
823
|
+
(`r<id>_q`, the row as text contains the term). The report then shows no search field of its own (its
|
|
824
|
+
*Actions* menu stays), so the page has one.
|
|
825
|
+
|
|
826
|
+
The panel starts with the number of rows the search and the facets leave (*Total row count 14*,
|
|
827
|
+
APEX's *Total Row Count*).
|
|
821
828
|
|
|
822
829
|
**Facet charts** (APEX: facet charts): with `"chart": "bar"` or `"chart": "pie"` a facet shows its counts as a chart,
|
|
823
830
|
collapsed under its heading (*Chart*). Each bar or slice, and each row of the chart's data table, links to the page
|
|
@@ -281,6 +281,7 @@ Run these as the owner (in the SQL Workshop, `psql` or migrations):
|
|
|
281
281
|
| `meta.wizard_catalog(table)` | The columns as the wizards see them: kind (`text`, `number`, `date`, `timestamp`, `boolean`, `point`, `geometry`, `binary`, `other`), key, unique, foreign key and the parent's display column |
|
|
282
282
|
| `meta.wizard_label(name)`, `meta.wizard_table_label(table, plural default true)`, `meta.wizard_column_label(table, column)` | (097) The labels of the wizards: `hiredate` → *Hire Date*, `hr.emp` → *Employees* / *Employee*, `deptno` → *Department*; short comments win ([what the generated pages look like](03-builder.md#what-the-generated-pages-look-like)) |
|
|
283
283
|
| `meta.wizard_report(table, columns default null)` | (097) A report on a table as the wizards make it: `{"source", "hidden", "headings", "formats"}` (foreign keys joined to their parent's display column) |
|
|
284
|
+
| `meta.wizard_grid_labels(table)` | (100) An interactive grid's `{"headings", "formats"}` as the wizards make them: the labels of `meta.wizard_column_label` and the number format masks (keys get none) |
|
|
284
285
|
| `meta.app_activity(days default 30)` | (098) The current application's page views, sign-ins and errors of the last days (1 to 366, at most 10,000 rows, no IP addresses), for a signed-in user with the `admin` role of that application; others get an error. The Activity page of the Create Application wizard reads it |
|
|
285
286
|
| `meta.wizard_files(table)` | (097) The `bytea` columns and the columns holding their file name, mime type, size and change time |
|
|
286
287
|
| `meta.export_app(alias)` | The application as JSON (`pgkiln/2` format) |
|
|
@@ -210,7 +210,11 @@ Under **Settings → Theme**, *Theme style* is **Automatic** (follow the device,
|
|
|
210
210
|
**Light** or **Dark**. With **Users may choose light or dark** (on by default, like APEX's
|
|
211
211
|
*Enable End Users to Choose Theme Style*), the user menu has an *Appearance* switch and My account
|
|
212
212
|
has the same choice. The choice is saved on the account, so it applies in every application that
|
|
213
|
-
allows it.
|
|
213
|
+
allows it. Until a user chooses, an application opens in its own *Theme style* (a new Iris application
|
|
214
|
+
light, also when the device is in dark mode); *Automatic* follows the device only when the application
|
|
215
|
+
or the user chose it. A user who isn't signed in has the *Appearance* switch in the user menu (with
|
|
216
|
+
*Sign in*), kept in a cookie of that application only (`pgkiln_mode`, path `/a/<alias>`), so a choice in
|
|
217
|
+
one application doesn't change another's.
|
|
214
218
|
|
|
215
219
|
## Base styles: Iris, Lavender and Standard
|
|
216
220
|
|