pgkiln 0.55.0 → 0.56.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SECURITY.md +1 -0
- package/docs/apex-feature-parity.md +3 -3
- package/docs/guide/03-builder.md +8 -2
- package/docs/guide/04-pages-and-regions.md +26 -0
- package/examples/gallery/README.md +4 -0
- package/examples/gallery/gallery.json +8 -0
- package/examples/gallery/samples/sample-approvals.sql +153 -0
- package/examples/gallery/samples/sample-data-loading.sql +142 -0
- package/examples/gallery/samples/sample-files.sql +160 -0
- package/examples/gallery/samples/sample-maps.sql +175 -0
- package/examples/gallery/samples/sample-reporting.sql +4 -3
- package/package.json +1 -1
- package/src/builder/region-help.ts +1 -1
- package/src/builder/region-settings.ts +3 -1
- package/src/i18n/ar.ts +1 -0
- package/src/i18n/cs.ts +1 -0
- package/src/i18n/da.ts +1 -0
- package/src/i18n/de.ts +1 -0
- package/src/i18n/el.ts +1 -0
- package/src/i18n/es.ts +1 -0
- package/src/i18n/fi.ts +1 -0
- package/src/i18n/fr.ts +1 -0
- package/src/i18n/he.ts +1 -0
- package/src/i18n/it.ts +1 -0
- package/src/i18n/ja.ts +1 -0
- package/src/i18n/ko.ts +1 -0
- package/src/i18n/nb.ts +1 -0
- package/src/i18n/pl.ts +1 -0
- package/src/i18n/pt.ts +1 -0
- package/src/i18n/ru.ts +1 -0
- package/src/i18n/sv.ts +1 -0
- package/src/i18n/tr.ts +1 -0
- package/src/i18n/uk.ts +1 -0
- package/src/i18n/zh.ts +1 -0
- package/src/i18n.ts +2 -0
- package/src/runtime/ai-filter.ts +22 -6
- package/src/runtime/ask-facets.ts +72 -0
- package/src/runtime/ir-plan.ts +17 -3
- package/src/runtime/smart-filters.ts +36 -2
package/src/runtime/ir-plan.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { REPORT_CHART_KINDS } from './charts.ts';
|
|
2
2
|
import { ComputeError, computeNameOk, computeSql } from './compute.ts';
|
|
3
|
+
import { facetFilters, facetKeys, facetParamNames, type FacetDef } from './facet-state.ts';
|
|
3
4
|
import { AGGREGATES, HIGHLIGHT_COLORS, MAX_COMPUTATIONS, OPERATORS } from './report.ts';
|
|
4
5
|
|
|
5
6
|
// (sprint 112) What "Ask in your own words" may do to an interactive report
|
|
@@ -15,7 +16,8 @@ import { AGGREGATES, HIGHLIGHT_COLORS, MAX_COMPUTATIONS, OPERATORS } from './rep
|
|
|
15
16
|
//
|
|
16
17
|
// The settings in force are shown as chips (chipsOf): each one is a URL
|
|
17
18
|
// parameter value of the report, so a chip can be edited or removed
|
|
18
|
-
// (editChip, removeChip) and nothing else is stored.
|
|
19
|
+
// (editChip, removeChip) and nothing else is stored. (sprint 122) The page's
|
|
20
|
+
// facets on the report are chips too (Remove only: the facet region edits them).
|
|
19
21
|
|
|
20
22
|
export type Kind = 'number' | 'date' | 'text' | 'boolean';
|
|
21
23
|
|
|
@@ -243,12 +245,14 @@ export type Chip =
|
|
|
243
245
|
| { kind: 'chart'; key: string; type: string; label: string; fn: string; value: string }
|
|
244
246
|
| { kind: 'hide'; key: string; column: string }
|
|
245
247
|
| { kind: 'area'; key: string }
|
|
246
|
-
| { kind: 'near'; key: string }
|
|
248
|
+
| { kind: 'near'; key: string }
|
|
249
|
+
| { kind: 'facet'; key: string; column: string; value: string; exclude: boolean }
|
|
250
|
+
| { kind: 'facetrange'; key: string; column: string; from: string; to: string; custom: boolean; def: FacetDef };
|
|
247
251
|
|
|
248
252
|
const MAX_CHIPS = 60;
|
|
249
253
|
|
|
250
254
|
/** The report's settings in the URL as chips (what reportState() reads, in the same limits). */
|
|
251
|
-
export function chipsOf(params: URLSearchParams, rid: number): Chip[] {
|
|
255
|
+
export function chipsOf(params: URLSearchParams, rid: number, facets?: Map<string, FacetDef>): Chip[] {
|
|
252
256
|
const k = (x: string) => `r${rid}_${x}`;
|
|
253
257
|
const out: Chip[] = [];
|
|
254
258
|
const q = (params.get(k('q')) ?? '').trim();
|
|
@@ -287,6 +291,9 @@ export function chipsOf(params: URLSearchParams, rid: number): Chip[] {
|
|
|
287
291
|
for (const x of params.getAll(k('x')).filter(Boolean).slice(0, 100)) out.push({ kind: 'hide', key: `x:${x}`, column: x });
|
|
288
292
|
if (params.get(k('bb'))) out.push({ kind: 'area', key: 'bb' });
|
|
289
293
|
if (params.get(k('near'))) out.push({ kind: 'near', key: 'near' });
|
|
294
|
+
for (const f of facets ? facetFilters(params, rid, facets) : [])
|
|
295
|
+
if (f.kind === 'values') for (const value of f.values) out.push({ kind: 'facet', key: `fx:${f.column}:${value}`, column: f.column, value, exclude: f.exclude });
|
|
296
|
+
else out.push({ kind: 'facetrange', key: `fr:${f.column}:${f.from}|${f.to}`, column: f.column, from: f.from, to: f.to, custom: f.custom, def: facets!.get(f.column)! });
|
|
290
297
|
return out.slice(0, MAX_CHIPS);
|
|
291
298
|
}
|
|
292
299
|
|
|
@@ -334,6 +341,13 @@ export function removeChip(params: URLSearchParams, rid: number, chip: Chip) {
|
|
|
334
341
|
case 'hide': dropValue(params, k('x'), chip.column); break;
|
|
335
342
|
case 'area': params.delete(k('bb')); break;
|
|
336
343
|
case 'near': params.delete(k('near')); break;
|
|
344
|
+
case 'facet': {
|
|
345
|
+
const name = facetKeys(rid).values + chip.column;
|
|
346
|
+
dropValue(params, name, chip.value);
|
|
347
|
+
if (!params.getAll(name).length) params.delete(facetKeys(rid).exclude + chip.column);
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
case 'facetrange': for (const n of facetParamNames(rid, chip.column)) params.delete(n); break;
|
|
337
351
|
}
|
|
338
352
|
params.delete(k('p'));
|
|
339
353
|
params.delete(k('k'));
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { appDateReader } from './items.ts';
|
|
2
|
+
import { valueLookup } from './ai-filter.ts';
|
|
3
|
+
import { applyFacetSettings, facetSettings } from './ask-facets.ts';
|
|
4
|
+
import { parseQuestion } from './ir-phrases.ts';
|
|
5
|
+
import { checkPlan, type Column } from './ir-plan.ts';
|
|
6
|
+
import { headingOf, isNumeric } from './report.ts';
|
|
2
7
|
import { html, type Raw } from '../html.ts';
|
|
3
8
|
import { icon } from '../icons.ts';
|
|
4
9
|
import type { Region } from '../metadata.ts';
|
|
5
|
-
import { publicError, type PageContext } from './context.ts';
|
|
10
|
+
import { dbg, publicError, type PageContext } from './context.ts';
|
|
6
11
|
import { regionUrl } from './report.ts';
|
|
7
12
|
import { describeFacetFilter, facetDefs, facetFilters, facetKeys, facetParamNames, rangeLabel, rangeValue, type FacetDef, type FacetFilter } from './facet-state.ts';
|
|
8
13
|
import { facetLabel, facetSource, isFacetSource, rangeCounts, valueCounts } from './facets.ts';
|
|
@@ -14,6 +19,12 @@ import { facetLabel, facetSource, isFacetSource, rangeCounts, valueCounts } from
|
|
|
14
19
|
// works without JavaScript and can be bookmarked.
|
|
15
20
|
// config: {"report": <report region id>, "suggestions": 3, "placeholder": "Search or filter…",
|
|
16
21
|
// "facets": [{"column": "job"}, {"column": "sal", "type": "range", "ranges": [...]}]}
|
|
22
|
+
//
|
|
23
|
+
// (sprint 122, WS-5; APEX 26.2) With "ask": true the typed text is also read
|
|
24
|
+
// as a phrase ("clerks in sales with salary between 1000 and 3000", in the
|
|
25
|
+
// application's language: ir-phrases.ts, no AI service): what this region's
|
|
26
|
+
// facets can hold (ask-facets.ts) is offered as the first suggestion, which
|
|
27
|
+
// sets those facets at once. Values are looked up in the report's own rows.
|
|
17
28
|
|
|
18
29
|
const MAX_SUGGESTIONS = 10;
|
|
19
30
|
|
|
@@ -110,6 +121,28 @@ export async function renderSmartFilters(ctx: PageContext, r: Region): Promise<R
|
|
|
110
121
|
}
|
|
111
122
|
}
|
|
112
123
|
|
|
124
|
+
// (sprint 122) the search term read as a phrase: one suggestion that sets the facets it names
|
|
125
|
+
let understood: Raw | '' = '';
|
|
126
|
+
if (r.config.ask === true && fs.search && ctx.client)
|
|
127
|
+
try {
|
|
128
|
+
const columns: Column[] = defs.map((f) => {
|
|
129
|
+
const oid = fs.cols.get(f.column)!;
|
|
130
|
+
return { name: f.column, label: f.label ?? headingOf(report, f.column, ctx.locale.tr), kind: isNumeric(oid) ? 'number' : [1082, 1114, 1184].includes(oid) ? 'date' : oid === 16 ? 'boolean' : 'text', pos: 0 };
|
|
131
|
+
});
|
|
132
|
+
const parsed = await parseQuestion(fs.search, columns, ctx.locale.lang, valueLookup(ctx, columns, fs.src));
|
|
133
|
+
const settings = facetSettings(checkPlan(parsed.plan, columns), new Map(defs.map((f) => [f.column, f])), columns);
|
|
134
|
+
// a single value the matching suggestions already offer needs no second chip
|
|
135
|
+
const single = settings.length === 1 && settings[0].kind === 'values' && settings[0].values.length === 1 && !settings[0].exclude;
|
|
136
|
+
dbg(ctx, 6, 'smart', () => `"${fs.search}" as facets: ${JSON.stringify(settings)}; not understood: ${JSON.stringify(parsed.left)}`);
|
|
137
|
+
if (settings.length && !(single && !parsed.left.length && suggestions.some((x) => x.f.column === settings[0].column)))
|
|
138
|
+
understood = html`<div class="sf-suggestions sf-understood">
|
|
139
|
+
<span class="sf-suggest-title" id="${formId}_u">${t('smart.understood')}</span>
|
|
140
|
+
<ul class="sf-chips" aria-labelledby="${formId}_u"><li><a class="chip chip-suggest" href="${regionUrl(ctx, report, (p) => { applyFacetSettings(p, report.id, settings); p.delete(qName); })}">${settings.map((x, i) => html`${i ? ' · ' : ''}${describeFacetFilter(x, facetLabel(ctx, defs.find((d) => d.column === x.column)!), t, defs.find((d) => d.column === x.column))}`)}</a></li></ul>
|
|
141
|
+
</div>`;
|
|
142
|
+
} catch (e) {
|
|
143
|
+
dbg(ctx, 4, 'smart', () => `"${fs.search}" not read as a phrase: ${(e as Error).message}`);
|
|
144
|
+
}
|
|
145
|
+
|
|
113
146
|
// GET form: the search term replaces the old one; every other parameter stays
|
|
114
147
|
const keep = [...ctx.params.entries()].filter(([name]) => ![qName, page, 'clear', 'cs'].includes(name));
|
|
115
148
|
ctx.detached.push(
|
|
@@ -128,12 +161,13 @@ export async function renderSmartFilters(ctx: PageContext, r: Region): Promise<R
|
|
|
128
161
|
? html`<a class="sf-clear" href="${regionUrl(ctx, report, (p) => { p.delete(qName); p.delete(page); for (const f of defs) for (const n of facetParamNames(report.id, f.column)) p.delete(n); })}">${t('facets.clear')}</a>`
|
|
129
162
|
: ''}
|
|
130
163
|
</div>
|
|
164
|
+
${understood}
|
|
131
165
|
${suggestions.length
|
|
132
166
|
? html`<div class="sf-suggestions">
|
|
133
167
|
<span class="sf-suggest-title" id="${formId}_s">${fs.search ? t('smart.matching') : t('smart.suggested')}</span>
|
|
134
168
|
<ul class="sf-chips" aria-labelledby="${formId}_s">${suggestions.map((s) => html`<li><a class="chip chip-suggest" href="${s.href}">${s.label} <span class="facet-count">${s.n}</span></a></li>`)}</ul>
|
|
135
169
|
</div>`
|
|
136
|
-
: fs.search && defs.some((f) => f.type === 'checkbox')
|
|
170
|
+
: fs.search && !understood && defs.some((f) => f.type === 'checkbox')
|
|
137
171
|
? html`<p class="muted sf-none">${t('smart.no_match')}</p>`
|
|
138
172
|
: ''}
|
|
139
173
|
${errors}
|