pgkiln 0.82.0 → 0.84.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/153_component_events.sql +21 -0
- package/db/migrations/154_notifications.sql +186 -0
- package/docs/apex-feature-parity.md +4 -3
- package/docs/guide/03-builder.md +10 -1
- package/docs/guide/04-pages-and-regions.md +44 -1
- package/docs/guide/06-processing.md +22 -1
- package/docs/guide/07-dynamic-actions.md +28 -0
- package/docs/guide/09-reference.md +15 -0
- package/docs/guide/11-from-apex.md +3 -1
- package/docs/guide/12-development.md +16 -1
- package/package.json +1 -1
- package/public/app.css +31 -0
- package/public/app.js +163 -4
- package/public/builder.css +25 -2
- package/public/data-modeler.js +8 -5
- package/public/sql-monitor.js +5 -2
- package/public/sw.js +5 -1
- package/src/app.ts +2 -0
- package/src/builder/ai-builder.ts +50 -46
- package/src/builder/api.ts +49 -49
- package/src/builder/autorest.ts +92 -87
- package/src/builder/changelog.ts +39 -37
- package/src/builder/code-editor.ts +1 -1
- package/src/builder/components.ts +13 -8
- package/src/builder/data-modeler.ts +89 -84
- package/src/builder/data-security.ts +39 -37
- package/src/builder/dataload.ts +69 -65
- package/src/builder/datapump.ts +108 -97
- package/src/builder/ddl-tools.ts +38 -34
- package/src/builder/document-view.ts +11 -11
- package/src/builder/i18n/de.ts +1635 -0
- package/src/builder/i18n/en.ts +1635 -0
- package/src/builder/i18n/es.ts +1635 -0
- package/src/builder/i18n/fr.ts +1635 -0
- package/src/builder/i18n/it.ts +1635 -0
- package/src/builder/i18n/ja.ts +1635 -0
- package/src/builder/i18n/ko.ts +1635 -0
- package/src/builder/i18n/nl.ts +1635 -0
- package/src/builder/i18n/pt.ts +1635 -0
- package/src/builder/i18n/zh-cn.ts +1635 -0
- package/src/builder/i18n/zh-tw.ts +1635 -0
- package/src/builder/i18n.ts +18 -0
- package/src/builder/notification-settings.ts +27 -0
- package/src/builder/object-actions.ts +18 -16
- package/src/builder/object-browser.ts +2 -2
- package/src/builder/querybuilder.ts +42 -36
- package/src/builder/quicksql.ts +16 -18
- package/src/builder/region-settings.ts +201 -199
- package/src/builder/report-settings.ts +64 -60
- package/src/builder/rest-modules.ts +145 -143
- package/src/builder/routes.ts +3 -0
- package/src/builder/sampledata.ts +59 -52
- package/src/builder/scheduling.ts +180 -159
- package/src/builder/scripts.ts +71 -65
- package/src/builder/sql-charts.ts +36 -30
- package/src/builder/sql-monitor.ts +95 -89
- package/src/builder/sql-utilities.ts +47 -40
- package/src/builder/top-sql.ts +21 -17
- package/src/builder/unload.ts +48 -44
- package/src/builder/worksheet.ts +3 -3
- package/src/cli/replace.ts +2 -0
- package/src/cron-schedule.ts +47 -17
- package/src/i18n/ar.ts +9 -0
- package/src/i18n/cs.ts +9 -0
- package/src/i18n/da.ts +9 -0
- package/src/i18n/de.ts +9 -0
- package/src/i18n/el.ts +9 -0
- package/src/i18n/es.ts +9 -0
- package/src/i18n/fi.ts +9 -0
- package/src/i18n/fr.ts +9 -0
- package/src/i18n/he.ts +9 -0
- package/src/i18n/it.ts +9 -0
- package/src/i18n/ja.ts +9 -0
- package/src/i18n/ko.ts +9 -0
- package/src/i18n/nb.ts +9 -0
- package/src/i18n/pl.ts +9 -0
- package/src/i18n/pt.ts +9 -0
- package/src/i18n/ru.ts +9 -0
- package/src/i18n/sv.ts +9 -0
- package/src/i18n/tr.ts +9 -0
- package/src/i18n/uk.ts +9 -0
- package/src/i18n/zh.ts +9 -0
- package/src/i18n.ts +18 -0
- package/src/metadata.ts +11 -3
- package/src/runtime/calendar.ts +3 -2
- package/src/runtime/context.ts +2 -0
- package/src/runtime/engine.ts +2 -1
- package/src/runtime/feedback.ts +1 -1
- package/src/runtime/grid.ts +1 -0
- package/src/runtime/lists.ts +43 -11
- package/src/runtime/maps.ts +7 -4
- package/src/runtime/notifications.ts +237 -0
- package/src/runtime/processes.ts +45 -1
- package/src/runtime/regions.ts +11 -15
- package/src/runtime/render.ts +18 -4
- package/src/runtime/routes.ts +16 -4
- package/src/runtime/signin.ts +2 -1
- package/src/runtime/tree.ts +4 -3
- package/src/session.ts +13 -4
|
@@ -11,6 +11,7 @@ import { heading } from '../runtime/items.ts';
|
|
|
11
11
|
import { COLUMN_OPTIONS } from '../runtime/template-options.ts';
|
|
12
12
|
import type { Session } from '../session.ts';
|
|
13
13
|
import { back, BASE, csrf, developer, flash, type Req } from './ui.ts';
|
|
14
|
+
import { scopedTx as tx, tr, withTranslator } from './i18n.ts';
|
|
14
15
|
|
|
15
16
|
// Page designer → a report region → Report settings: the most used keys of
|
|
16
17
|
// the region's "config" JSON as a form (columns, link, paging, PDF, saved
|
|
@@ -84,7 +85,10 @@ const columnOption = (cfg: ReportConfig, name: string) => {
|
|
|
84
85
|
};
|
|
85
86
|
|
|
86
87
|
/** The Report settings form of a report region. */
|
|
87
|
-
|
|
88
|
+
/** (sprint 162) in the developer's builder language: the helpers below use the request's scoped translator */
|
|
89
|
+
export const reportSettingsForm = (pageId: number, appId: number, r: { id: number; source: string | null; config: ReportConfig }, s: Session) => withTranslator(tr(s), () => reportSettingsFormInner(pageId, appId, r, s));
|
|
90
|
+
|
|
91
|
+
async function reportSettingsFormInner(pageId: number, appId: number, r: { id: number; source: string | null; config: ReportConfig }, s: Session) {
|
|
88
92
|
const cfg = r.config ?? {};
|
|
89
93
|
const cols = await reportColumns(appId, await designSql(appId, r));
|
|
90
94
|
const [layouts, schemes, pages, items] = await Promise.all([
|
|
@@ -107,85 +111,85 @@ export async function reportSettingsForm(pageId: number, appId: number, r: { id:
|
|
|
107
111
|
const printed = cfg.pdf?.columns?.length ? new Set(cfg.pdf.columns.map((c) => c.toLowerCase())) : null;
|
|
108
112
|
|
|
109
113
|
const columnRows = all.map((n, i) => html`<tr>
|
|
110
|
-
<td data-label="
|
|
111
|
-
<td data-label="
|
|
112
|
-
<td data-label="
|
|
113
|
-
<td data-label="
|
|
114
|
-
<td data-label="
|
|
115
|
-
<td data-label="
|
|
116
|
-
<td data-label="
|
|
114
|
+
<td data-label="${tx('rpt.column')}"><code>${n}</code>${known.has(n) ? '' : html` <span class="tag tag-error">${tx('rs.not_in_query')}</span>`}<input type="hidden" name="col_${i}" value="${n}"></td>
|
|
115
|
+
<td data-label="${tx('rpt.heading')}"><input name="heading_${i}" value="${cfg.headings?.[n] ?? ''}" placeholder="${heading(n)}" aria-label="${tx('rpt.aria_heading_of', { name: n })}"></td>
|
|
116
|
+
<td data-label="${tx('rpt.format_mask')}"><input name="fmt_${i}" value="${cfg.formats?.[n] ?? ''}" placeholder="e.g. 999G990D00" aria-label="${tx('rpt.aria_format_mask_of', { name: n })}" class="u-mw10"></td>
|
|
117
|
+
<td data-label="${tx('rpt.display')}"><select name="opt_${i}" aria-label="${tx('rpt.aria_display_of', { name: n })}">${opt('', tx('rpt.normal'), columnOption(cfg, n))}${COLUMN_OPTIONS.map((o) => opt(o.cls, o.label, columnOption(cfg, n)))}</select></td>
|
|
118
|
+
<td data-label="${tx('rpt.shown')}"><input type="checkbox" name="shown_${i}" value="true"${hidden.has(n.toLowerCase()) ? '' : raw(' checked')} aria-label="${tx('rpt.aria_show', { name: n })}"></td>
|
|
119
|
+
<td data-label="${tx('rpt.in_pdf')}"><input type="checkbox" name="print_${i}" value="true"${(printed ? printed.has(n.toLowerCase()) : !hidden.has(n.toLowerCase())) ? raw(' checked') : ''} aria-label="${tx('rpt.aria_print', { name: n })}"></td>
|
|
120
|
+
<td data-label="${tx('rpt.pdf_width_mm')}"><input name="width_${i}" type="number" min="0" max="500" value="${cfg.pdf?.widths?.[n] ?? ''}" aria-label="${tx('rpt.aria_pdf_width_of', { name: n })}" class="u-mw6"></td>
|
|
117
121
|
</tr>`);
|
|
118
122
|
|
|
119
123
|
const linkItems = linkItemsText(cfg.link?.items);
|
|
120
|
-
return html`<h3 class="u-mt15"
|
|
121
|
-
<p class="muted u-mt0"
|
|
124
|
+
return html`<h3 class="u-mt15">${tx('rpt.report_settings')}</h3>
|
|
125
|
+
<p class="muted u-mt0">${tx('rpt.these_fields_write_the_region')}</p>
|
|
122
126
|
${'error' in cols ? html`<div class="alert alert-error" role="alert">The columns could not be read: ${cols.error}</div>` : ''}
|
|
123
127
|
<form method="post" action="${BASE}/pages/${pageId}/region/${r.id}/report-settings" class="component-form">${csrf(s)}
|
|
124
128
|
<input type="hidden" name="n" value="${all.length}">
|
|
125
|
-
<fieldset class="prop-group"><legend
|
|
126
|
-
<div class="field"><label class="label" for="${id('page_size')}"
|
|
129
|
+
<fieldset class="prop-group"><legend>${tx('rpt.behaviour')}</legend><div class="form-grid">
|
|
130
|
+
<div class="field"><label class="label" for="${id('page_size')}">${tx('rpt.rows_per_page')}</label>
|
|
127
131
|
<select id="${id('page_size')}" name="page_size">${PAGE_SIZES.map((n) => opt(n, n, cfg.page_size ?? 15))}</select></div>
|
|
128
|
-
<div class="field"><label class="label" for="${id('mobile')}"
|
|
129
|
-
<select id="${id('mobile')}" name="mobile">${opt('reflow', '
|
|
130
|
-
<div class="field" data-wide><label class="label" for="${id('empty')}"
|
|
131
|
-
<input id="${id('empty')}" name="empty" value="${cfg.empty ?? ''}" placeholder="
|
|
132
|
-
${check('searchable', '
|
|
133
|
-
${check('interactive', '
|
|
134
|
-
${check('sortable', '
|
|
135
|
-
${check('saved_reports', '
|
|
136
|
-
<div class="field"><label class="label" for="${id('public')}"
|
|
137
|
-
<select id="${id('public')}" name="public_reports">${opt('', '
|
|
132
|
+
<div class="field"><label class="label" for="${id('mobile')}">${tx('rpt.on_phones')}</label>
|
|
133
|
+
<select id="${id('mobile')}" name="mobile">${opt('reflow', tx('rpt.cards_reflow'), cfg.mobile ?? 'reflow')}${opt('scroll', tx('rpt.scrolling_table'), cfg.mobile)}</select></div>
|
|
134
|
+
<div class="field" data-wide><label class="label" for="${id('empty')}">${tx('rpt.text_when_there_are_no')}</label>
|
|
135
|
+
<input id="${id('empty')}" name="empty" value="${cfg.empty ?? ''}" placeholder="${tx('rpt.no_data_found')}"></div>
|
|
136
|
+
${check('searchable', tx('rpt.search_box'), cfg.searchable !== false)}
|
|
137
|
+
${check('interactive', tx('rpt.actions_menu_filters_break_aggregates'), cfg.interactive !== false)}
|
|
138
|
+
${check('sortable', tx('rpt.sortable'), cfg.sortable !== false)}
|
|
139
|
+
${check('saved_reports', tx('rpt.users_may_save_reports'), cfg.saved_reports !== false)}
|
|
140
|
+
<div class="field"><label class="label" for="${id('public')}">${tx('rpt.who_may_save_public_reports')}</label>
|
|
141
|
+
<select id="${id('public')}" name="public_reports">${opt('', tx('rpt.nobody'), cfg.public_reports)}${schemes.rows.map((x) => opt(x.name, x.name, cfg.public_reports))}</select></div>
|
|
138
142
|
</div></fieldset>
|
|
139
|
-
<fieldset class="prop-group"><legend
|
|
140
|
-
<div class="field"><label class="label" for="${id('pagination')}"
|
|
141
|
-
<select id="${id('pagination')}" name="pagination">${opt('', '
|
|
142
|
-
<small class="help"
|
|
143
|
-
<div class="field"><label class="label" for="${id('keyset')}"
|
|
143
|
+
<fieldset class="prop-group"><legend>${tx('rpt.large_tables')}</legend><div class="form-grid">
|
|
144
|
+
<div class="field"><label class="label" for="${id('pagination')}">${tx('rpt.pagination')}</label>
|
|
145
|
+
<select id="${id('pagination')}" name="pagination">${opt('', tx('rpt.rows_x_y_of_z'), cfg.pagination)}${opt('range', tx('rpt.row_ranges_x_y_no'), cfg.pagination)}</select>
|
|
146
|
+
<small class="help">${tx('rpt.row_ranges_skip_counting_the')}</small></div>
|
|
147
|
+
<div class="field"><label class="label" for="${id('keyset')}">${tx('rpt.keyset_columns')}</label>
|
|
144
148
|
<input id="${id('keyset')}" name="keyset" value="${(cfg.keyset ?? []).join(', ')}" placeholder="e.g. id">
|
|
145
|
-
<small class="help"
|
|
146
|
-
<div class="field"><label class="label" for="${id('max_rows')}"
|
|
149
|
+
<small class="help">${tx('rpt.with_row_ranges_columns_that')}</small></div>
|
|
150
|
+
<div class="field"><label class="label" for="${id('max_rows')}">${tx('rpt.maximum_row_count')}</label>
|
|
147
151
|
<input id="${id('max_rows')}" name="max_rows" type="number" min="1" max="1000000" value="${cfg.max_rows ?? ''}" placeholder="no maximum">
|
|
148
|
-
<small class="help"
|
|
149
|
-
${check('lazy', '
|
|
150
|
-
<div class="field"><label class="label" for="${id('cache_scope')}"
|
|
151
|
-
<select id="${id('cache_scope')}" name="cache_scope">${opt('', '
|
|
152
|
-
<div class="field"><label class="label" for="${id('cache_seconds')}"
|
|
152
|
+
<small class="help">${tx('rpt.the_report_and_its_downloads')}</small></div>
|
|
153
|
+
${check('lazy', tx('rpt.load_after_the_page_shows'), cfg.lazy === true)}
|
|
154
|
+
<div class="field"><label class="label" for="${id('cache_scope')}">${tx('rpt.cache')}</label>
|
|
155
|
+
<select id="${id('cache_scope')}" name="cache_scope">${opt('', tx('rpt.no_cache'), cfg.cache?.scope)}${opt('user', tx('rpt.per_user'), cfg.cache?.scope)}${opt('session', tx('rpt.per_session'), cfg.cache?.scope)}${opt('all', tx('rpt.for_all_users_with_the'), cfg.cache?.scope)}</select></div>
|
|
156
|
+
<div class="field"><label class="label" for="${id('cache_seconds')}">${tx('rpt.cache_for_seconds')}</label>
|
|
153
157
|
<input id="${id('cache_seconds')}" name="cache_seconds" type="number" min="1" max="86400" value="${cfg.cache?.seconds ?? ''}" placeholder="300">
|
|
154
|
-
<small class="help"
|
|
158
|
+
<small class="help">${tx('rpt.a_submit_of_the_page')}</small></div>
|
|
155
159
|
</div></fieldset>
|
|
156
|
-
<fieldset class="prop-group"><legend
|
|
157
|
-
<div class="field" data-wide><label class="label" for="${id('json_columns')}"
|
|
160
|
+
<fieldset class="prop-group"><legend>${tx('rpt.json_path_columns')}</legend><div class="form-grid">
|
|
161
|
+
<div class="field" data-wide><label class="label" for="${id('json_columns')}">${tx('rpt.columns_read_from_a_json')}</label>
|
|
158
162
|
<input id="${id('json_columns')}" name="json_columns" value="${jsonColumnsText(cfg.json_columns)}" placeholder="city=profile:address.city; first_skill=profile:skills.0.name; since=profile:since:date" class="code">
|
|
159
163
|
<small class="help">name=column:path[:type]; separate them with ";". A path is keys separated by dots, a number picks an element of an array. Types: ${JSON_TYPES.join(', ')} (text when left out); a value that doesn't read as the type shows empty. They are columns like the others: search, filters, sorting, headings and downloads. To edit documents, use a JSON source (Shared Components).</small></div>
|
|
160
164
|
</div></fieldset>
|
|
161
|
-
<fieldset class="prop-group"><legend
|
|
165
|
+
<fieldset class="prop-group"><legend>${tx('rpt.columns')}</legend>
|
|
162
166
|
${all.length
|
|
163
|
-
? html`<div class="table-wrap"><table class="report report-reflow"><thead><tr><th
|
|
164
|
-
: html`<p class="muted"
|
|
165
|
-
<small class="help"
|
|
167
|
+
? html`<div class="table-wrap"><table class="report report-reflow"><thead><tr><th>${tx('rpt.column')}</th><th>${tx('rpt.heading')}</th><th>${tx('rpt.format_mask')}</th><th>${tx('rpt.display')}</th><th>${tx('rpt.shown')}</th><th>${tx('rpt.in_pdf')}</th><th>${tx('rpt.pdf_width_mm')}</th></tr></thead><tbody>${columnRows}</tbody></table></div>`
|
|
168
|
+
: html`<p class="muted">${tx('rpt.no_columns_yet')}</p>`}
|
|
169
|
+
<small class="help">${tx('rpt.format_masks_numbers_like_999g999g990d00')}</small>
|
|
166
170
|
</fieldset>
|
|
167
|
-
<fieldset class="prop-group"><legend
|
|
168
|
-
<div class="field"><label class="label" for="${id('link_column')}"
|
|
169
|
-
<select id="${id('link_column')}" name="link_column">${opt('', '
|
|
170
|
-
<div class="field"><label class="label" for="${id('link_page')}"
|
|
171
|
-
<select id="${id('link_page')}" name="link_page">${opt('', '
|
|
172
|
-
<div class="field" data-wide><label class="label" for="${id('link_items')}"
|
|
171
|
+
<fieldset class="prop-group"><legend>${tx('rpt.link')}</legend><div class="form-grid">
|
|
172
|
+
<div class="field"><label class="label" for="${id('link_column')}">${tx('rpt.link_column')}</label>
|
|
173
|
+
<select id="${id('link_column')}" name="link_column">${opt('', tx('rpt.no_link'), cfg.link?.column)}${all.map((n) => opt(n, n, cfg.link?.column))}</select></div>
|
|
174
|
+
<div class="field"><label class="label" for="${id('link_page')}">${tx('rpt.to_page')}</label>
|
|
175
|
+
<select id="${id('link_page')}" name="link_page">${opt('', tx('rpt.choose'), cfg.link?.page)}${pages.rows.map((p) => opt(String(p.page_no), `${p.page_no}. ${p.name}`, cfg.link?.page))}</select></div>
|
|
176
|
+
<div class="field" data-wide><label class="label" for="${id('link_items')}">${tx('rpt.set_items')}</label>
|
|
173
177
|
<input id="${id('link_items')}" name="link_items" value="${linkItems}" placeholder="P3_ID=#id#">
|
|
174
|
-
<small class="help"
|
|
178
|
+
<small class="help">${tx('rpt.item_column_comma_separated_column')}</small></div>
|
|
175
179
|
</div></fieldset>
|
|
176
|
-
<fieldset class="prop-group"><legend
|
|
177
|
-
<div class="field"><label class="label" for="${id('sel_column')}"
|
|
178
|
-
<select id="${id('sel_column')}" name="sel_column">${opt('', '
|
|
179
|
-
<div class="field"><label class="label" for="${id('sel_item')}"
|
|
180
|
-
<select id="${id('sel_item')}" name="sel_item">${opt('', '
|
|
181
|
-
<small class="help"
|
|
180
|
+
<fieldset class="prop-group"><legend>${tx('rpt.row_selection')}</legend><div class="form-grid">
|
|
181
|
+
<div class="field"><label class="label" for="${id('sel_column')}">${tx('rpt.value_column')}</label>
|
|
182
|
+
<select id="${id('sel_column')}" name="sel_column">${opt('', tx('rpt.no_row_selection'), cfg.selection?.column)}${all.map((n) => opt(n, n, cfg.selection?.column))}</select></div>
|
|
183
|
+
<div class="field"><label class="label" for="${id('sel_item')}">${tx('rpt.into_item')}</label>
|
|
184
|
+
<select id="${id('sel_item')}" name="sel_item">${opt('', tx('rpt.choose'), cfg.selection?.item)}${items.rows.map((x) => opt(x.name, `${x.name} (${x.type})`, cfg.selection?.item))}</select>
|
|
185
|
+
<small class="help">${tx('rpt.a_checkbox_per_row_on')}</small></div>
|
|
182
186
|
</div></fieldset>
|
|
183
|
-
<fieldset class="prop-group"><legend
|
|
184
|
-
<div class="field"><label class="label" for="${id('layout')}"
|
|
187
|
+
<fieldset class="prop-group"><legend>${tx('rpt.pdf')}</legend><div class="form-grid">
|
|
188
|
+
<div class="field"><label class="label" for="${id('layout')}">${tx('rpt.report_layout')}</label>
|
|
185
189
|
<select id="${id('layout')}" name="pdf_layout">${opt('', layouts.rows.some((l) => l.is_default) ? '- the default layout -' : '- built-in -', cfg.pdf?.layout)}${layouts.rows.map((l) => opt(l.name, l.name + (l.is_default ? ' (default)' : ''), cfg.pdf?.layout))}</select>
|
|
186
|
-
<small class="help"
|
|
190
|
+
<small class="help">${tx('rpt.layouts_are_under_shared_components')}</small></div>
|
|
187
191
|
</div></fieldset>
|
|
188
|
-
<div class="buttons"><button class="btn btn-hot"
|
|
192
|
+
<div class="buttons"><button class="btn btn-hot">${tx('rpt.save_report_settings')}</button></div>
|
|
189
193
|
</form>`;
|
|
190
194
|
}
|
|
191
195
|
|
|
@@ -295,8 +299,8 @@ export async function reportSettingsRoutes(app: FastifyInstance) {
|
|
|
295
299
|
);
|
|
296
300
|
await owner.query('update meta.region set config = $2 where id = $1', [r.id, JSON.stringify(config)]);
|
|
297
301
|
const bad = formatProblems((req.body ?? {}) as Record<string, string | undefined>);
|
|
298
|
-
if (bad.length) flash(s,
|
|
299
|
-
else flash(s, '
|
|
302
|
+
if (bad.length) flash(s, tr(s)('rpt.saved_without', { masks: bad.join('; ') }), 'error');
|
|
303
|
+
else flash(s, tr(s)('rpt.saved'));
|
|
300
304
|
return back(reply, s, `${BASE}/pages/${pid}?c=region-${rid}`);
|
|
301
305
|
});
|
|
302
306
|
}
|