@firestitch/report 18.0.16 → 18.0.18
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/app/reports/data/report.data.d.ts +1 -0
- package/app/reports/dialogs/entity-functions/entity-functions.component.d.ts +3 -2
- package/app/reports/dialogs/global-settings/components/semantic-database/semantic-database.component.d.ts +32 -10
- package/app/reports/interfaces/report.interface.d.ts +18 -2
- package/app/reports/views/reports/fs-ai-reports.component.d.ts +2 -1
- package/esm2022/app/reports/data/report.data.mjs +11 -1
- package/esm2022/app/reports/dialogs/entity-functions/entity-functions.component.mjs +16 -8
- package/esm2022/app/reports/dialogs/global-settings/components/semantic-database/semantic-database.component.mjs +94 -35
- package/esm2022/app/reports/export/report-pdf.service.mjs +5 -4
- package/esm2022/app/reports/export/report-pptx.service.mjs +5 -4
- package/esm2022/app/reports/interfaces/report.interface.mjs +8 -1
- package/esm2022/app/reports/views/reports/fs-ai-reports.component.mjs +34 -3
- package/fesm2022/{firestitch-report-echarts-CO50nSof.mjs → firestitch-report-echarts-CMOBB-Er.mjs} +2 -2
- package/fesm2022/{firestitch-report-echarts-CO50nSof.mjs.map → firestitch-report-echarts-CMOBB-Er.mjs.map} +1 -1
- package/fesm2022/{firestitch-report-firestitch-report-DlxhJJhU.mjs → firestitch-report-firestitch-report-DwgbMhwr.mjs} +172 -56
- package/fesm2022/firestitch-report-firestitch-report-DwgbMhwr.mjs.map +1 -0
- package/fesm2022/firestitch-report.mjs +1 -1
- package/package.json +1 -1
- package/fesm2022/firestitch-report-firestitch-report-DlxhJJhU.mjs.map +0 -1
|
@@ -116,12 +116,22 @@ class ReportData {
|
|
|
116
116
|
...config,
|
|
117
117
|
});
|
|
118
118
|
}
|
|
119
|
+
// Soft-delete: the report leaves the listing but keeps its pages and
|
|
120
|
+
// components, so undelete() brings back the same document.
|
|
119
121
|
delete(reportId, config = {}) {
|
|
120
122
|
return this._api.delete(this._path(`${reportId}`), {}, {
|
|
121
123
|
key: 'report',
|
|
122
124
|
...config,
|
|
123
125
|
});
|
|
124
126
|
}
|
|
127
|
+
// Restore a soft-deleted report through its own action endpoint — the report
|
|
128
|
+
// PUT does not accept `state`.
|
|
129
|
+
undelete(reportId, config = {}) {
|
|
130
|
+
return this._api.post(this._path(`${reportId}/undelete`), {}, {
|
|
131
|
+
key: 'report',
|
|
132
|
+
...config,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
125
135
|
// The assembled structure: pages → components (+ their filters) and the
|
|
126
136
|
// report's filter groups.
|
|
127
137
|
get(reportId, config = {}) {
|
|
@@ -2488,6 +2498,13 @@ const FREQUENCY_OPTIONS = [
|
|
|
2488
2498
|
{ value: 'quarter', name: 'Quarter' },
|
|
2489
2499
|
{ value: 'year', name: 'Annually' },
|
|
2490
2500
|
];
|
|
2501
|
+
// Reports are soft-deletable: a deleted report leaves the listing but keeps its
|
|
2502
|
+
// pages and components, and the listing's "Show Deleted" mode restores it.
|
|
2503
|
+
var ReportState;
|
|
2504
|
+
(function (ReportState) {
|
|
2505
|
+
ReportState["Active"] = "active";
|
|
2506
|
+
ReportState["Deleted"] = "deleted";
|
|
2507
|
+
})(ReportState || (ReportState = {}));
|
|
2491
2508
|
|
|
2492
2509
|
// Rows fetched for a list component's export table — enough to fill a
|
|
2493
2510
|
// slide/page table; the cap note covers the rest.
|
|
@@ -2582,7 +2599,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
2582
2599
|
// complete synchronously after setOption.
|
|
2583
2600
|
async function renderChartImage(component, data, width, height, pixelRatio = 3) {
|
|
2584
2601
|
// The same tree-shaken echarts build + 'report' theme the live canvas uses.
|
|
2585
|
-
const echarts = (await import('./firestitch-report-echarts-
|
|
2602
|
+
const echarts = (await import('./firestitch-report-echarts-CMOBB-Er.mjs')).default;
|
|
2586
2603
|
const host = document.createElement('div');
|
|
2587
2604
|
host.style.width = `${width}px`;
|
|
2588
2605
|
host.style.height = `${height}px`;
|
|
@@ -2733,6 +2750,10 @@ class ReportPdfService {
|
|
|
2733
2750
|
doc.text(config.measure.label, x + w / 2, y + h / 2 + 16, { align: 'center' });
|
|
2734
2751
|
}
|
|
2735
2752
|
}
|
|
2753
|
+
// The exported cell must read like the on-screen cell: the SQL already
|
|
2754
|
+
// carries the formatting, so the value goes out verbatim. A raw date is the
|
|
2755
|
+
// one value the client renders (the column stays a RAW timestamp for the
|
|
2756
|
+
// report timezone), and it is rendered here too.
|
|
2736
2757
|
_cell(value, columnFormat) {
|
|
2737
2758
|
if (value === null || value === undefined) {
|
|
2738
2759
|
return '';
|
|
@@ -2741,9 +2762,6 @@ class ReportPdfService {
|
|
|
2741
2762
|
const date = new Date(String(value));
|
|
2742
2763
|
return Number.isNaN(date.getTime()) ? String(value) : format(date, 'MMM d, yyyy');
|
|
2743
2764
|
}
|
|
2744
|
-
if (columnFormat === 'number') {
|
|
2745
|
-
return formatMeasureValue(value, 'number');
|
|
2746
|
-
}
|
|
2747
2765
|
return String(value);
|
|
2748
2766
|
}
|
|
2749
2767
|
_slug(name) {
|
|
@@ -2960,6 +2978,10 @@ class ReportPptxService {
|
|
|
2960
2978
|
valign: 'middle',
|
|
2961
2979
|
});
|
|
2962
2980
|
}
|
|
2981
|
+
// The exported cell must read like the on-screen cell: the SQL already
|
|
2982
|
+
// carries the formatting, so the value goes out verbatim. A raw date is the
|
|
2983
|
+
// one value the client renders (the column stays a RAW timestamp for the
|
|
2984
|
+
// report timezone), and it is rendered here too.
|
|
2963
2985
|
_cell(value, columnFormat) {
|
|
2964
2986
|
if (value === null || value === undefined) {
|
|
2965
2987
|
return '';
|
|
@@ -2968,9 +2990,6 @@ class ReportPptxService {
|
|
|
2968
2990
|
const date = new Date(String(value));
|
|
2969
2991
|
return Number.isNaN(date.getTime()) ? String(value) : format(date, 'MMM d, yyyy');
|
|
2970
2992
|
}
|
|
2971
|
-
if (columnFormat === 'number') {
|
|
2972
|
-
return formatMeasureValue(value, 'number');
|
|
2973
|
-
}
|
|
2974
2993
|
return String(value);
|
|
2975
2994
|
}
|
|
2976
2995
|
_slug(name) {
|
|
@@ -3326,7 +3345,7 @@ class ReportComponent {
|
|
|
3326
3345
|
ReportPdfService,
|
|
3327
3346
|
// Tree-shaken ECharts core + the 'report' house theme, loaded lazily with
|
|
3328
3347
|
// this route's chunk.
|
|
3329
|
-
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-
|
|
3348
|
+
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-CMOBB-Er.mjs').then((module) => module.default) }),
|
|
3330
3349
|
], viewQueries: [{ propertyName: "_split", first: true, predicate: ["split"], descendants: true, static: true }, { propertyName: "_chatPanel", first: true, predicate: ["chatPanel"], descendants: true, read: ElementRef, static: true }], ngImport: i0, template: "<div\n #split\n class=\"report fs-row.align-start\">\n <fs-ai-chat\n #chatPanel\n class=\"chat\"\n style=\"min-height: 500px;\"\n basePath=\"reports\"\n [requestData]=\"{ reportId: selected?.id ?? null }\"\n [introMessage]=\"introMessage\"\n (response)=\"onChatResponse($event)\">\n </fs-ai-chat>\n <div\n class=\"resizer\"\n (pointerdown)=\"onResizeStart($event)\">\n </div>\n <div class=\"viewer fs-flex fs-column\">\n <div class=\"fs-row.align-center.gap-sm\">\n <fs-autocomplete-chips\n class=\"fs-flex\"\n [fetch]=\"fetchReports\"\n [(ngModel)]=\"selected\"\n [disabled]=\"loadingReports\"\n [padless]=\"true\"\n [multiple]=\"false\"\n [fetchOnFocus]=\"true\"\n (ngModelChange)=\"reportChange($event)\"\n placeholder=\"Report\"\n name=\"report\">\n <ng-template\n fsAutocompleteChipsTemplate\n let-object=\"object\">\n {{ object.name }}\n </ng-template>\n <ng-template\n fsAutocompleteChipsStatic\n (click)=\"createReport()\">\n Create Report\n </ng-template>\n </fs-autocomplete-chips>\n @if (report) {\n <fs-menu>\n <ng-template\n fs-menu-item\n (click)=\"reportSettings()\">\n <mat-icon>\n tune\n </mat-icon>\n Report settings\n </ng-template>\n <ng-template\n fs-menu-item\n (click)=\"toggleEditMode()\"\n [disabled]=\"editMode\">\n <mat-icon>\n dashboard_2_edit\n </mat-icon>\n {{ editMode ? 'Done editing layout' : 'Edit layout' }}\n </ng-template>\n <ng-template\n fs-menu-item\n (click)=\"exportPowerpoint()\">\n <mat-icon>\n slideshow\n </mat-icon>\n Export PowerPoint\n </ng-template>\n <ng-template\n fs-menu-item\n (click)=\"exportPdf()\">\n <mat-icon>\n picture_as_pdf\n </mat-icon>\n Export PDF\n </ng-template>\n </fs-menu>\n }\n </div>\n @if (report) {\n <!-- Report-level filters only (the report's actions live in the menu\n above). fs-filter reads its config once at init, so it's keyed on the\n filter signature: when the report-level filter set changes the block\n is recreated and re-reads the rebuilt config. -->\n @if (reportHasFilters) {\n @for (key of [reportFilterKey]; track key) {\n <fs-filter [config]=\"reportFilterConfig\"></fs-filter>\n }\n }\n <app-report-canvas\n [report]=\"report\"\n [editMode]=\"editMode\"\n (componentSettings)=\"componentSettings($event)\"\n (reportChanged)=\"onCanvasReportChanged()\"\n (editDone)=\"toggleEditMode()\">\n </app-report-canvas>\n }\n </div>\n</div>", styles: [".report{height:100%;min-height:500px}.report .chat{display:block;flex:0 0 25%;width:100%;height:100%;min-height:500px;min-width:0;border:none}.report .viewer{display:flex;flex-direction:column;min-width:0;height:100%;overflow:hidden}.report .viewer fs-filter{flex:0 0 auto;margin-top:10px;margin-bottom:0}.report .viewer ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.report .resizer{flex:0 0 11px;align-self:stretch;display:flex;justify-content:center;cursor:col-resize;touch-action:none;-webkit-user-select:none;user-select:none}.report .resizer:before{content:\"\";width:0px;background:#0000001f;transition:width .12s ease,background-color .12s ease}.report .resizer:hover:before{width:3px;background:var(--brand-primary-color)}.report.resizing{cursor:col-resize;-webkit-user-select:none;user-select:none}.report.resizing .resizer:before{width:3px;background:var(--brand-primary-color)}.report.resizing .chat,.report.resizing .viewer{pointer-events:none}::ng-deep body.body-report-reports .mat-mdc-card-content{display:flex;flex-direction:column;box-sizing:border-box}::ng-deep body.body-report-reports .mat-mdc-card-content mat-tab-nav-panel{flex:1;min-height:0}::ng-deep body.body-report-reports .mat-mdc-card-content mat-tab-nav-panel router-outlet+ng-component{height:100%}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: FsAutocompleteChipsModule }, { kind: "component", type: i2$2.FsAutocompleteChipsComponent, selector: "fs-autocomplete-chips", inputs: ["fetch", "appearance", "floatLabel", "readonly", "size", "label", "placeholder", "chipImage", "chipBackground", "chipColor", "chipIcon", "chipIconColor", "chipClass", "chipPadding", "shape", "hint", "allowText", "allowObject", "delay", "minPanelWidth", "maxPanelHeight", "validateText", "removable", "allowClear", "color", "background", "orderable", "padless", "initOnClick", "fetchOnFocus", "multiple", "multipleAdd", "confirm", "disabled", "groupBy", "panelWidth", "panelClass", "compareWith"], outputs: ["selected", "removed", "reordered", "clear", "panelOpened", "panelClosed"] }, { kind: "directive", type: i2$2.FsAutocompleteObjectDirective, selector: "[fsAutocompleteObject],[fsAutocompleteChipsTemplate]" }, { kind: "directive", type: i2$2.FsAutocompleteChipsStaticDirective, selector: "[fsAutocompleteChipsStatic]", inputs: ["show", "disable"], outputs: ["click", "selected"] }, { kind: "ngmodule", type: FsFilterModule }, { kind: "component", type: i2.FilterComponent, selector: "fs-filter", inputs: ["config"], outputs: ["closed", "opened", "ready"] }, { kind: "ngmodule", type: FsMenuModule }, { kind: "component", type: i2$3.FsMenuComponent, selector: "fs-menu", inputs: ["class", "buttonClass", "buttonType", "buttonColor"], outputs: ["opened", "closed"] }, { kind: "directive", type: i2$3.FsMenuItemDirective, selector: "fs-menu-group,[fs-menu-item]" }, { kind: "component", type: FsAiChatComponent, selector: "fs-ai-chat", inputs: ["basePath", "requestData", "introMessage"], outputs: ["response"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: ReportCanvasComponent, selector: "app-report-canvas", inputs: ["report", "editMode"], outputs: ["componentSettings", "reportChanged", "editDone"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3331
3350
|
}
|
|
3332
3351
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ReportComponent, decorators: [{
|
|
@@ -3340,7 +3359,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
3340
3359
|
ReportPdfService,
|
|
3341
3360
|
// Tree-shaken ECharts core + the 'report' house theme, loaded lazily with
|
|
3342
3361
|
// this route's chunk.
|
|
3343
|
-
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-
|
|
3362
|
+
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-CMOBB-Er.mjs').then((module) => module.default) }),
|
|
3344
3363
|
], imports: [
|
|
3345
3364
|
FormsModule,
|
|
3346
3365
|
FsAutocompleteChipsModule,
|
|
@@ -3451,10 +3470,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
3451
3470
|
], template: "<div class=\"fs-column tab-body\">\n <mat-form-field>\n <mat-label>Semantic build concurrency</mat-label>\n <input\n matInput\n type=\"number\"\n [ngModel]=\"concurrency()\"\n (ngModelChange)=\"concurrency.set($event)\"\n name=\"semanticBuildConcurrency\"\n [fsFormRequired]=\"true\"\n [fsFormInteger]=\"true\"\n [fsFormMin]=\"min()\"\n [fsFormMax]=\"max()\">\n <mat-hint>\n How many AI calls the semantic-database build runs at once ({{ min() }}\u2013{{ max() }}).\n Higher is faster but more likely to hit the AI provider's rate limit. Default is 8.\n </mat-hint>\n </mat-form-field>\n</div>\n", styles: [".tab-body{padding-top:16px}mat-form-field{display:block;max-width:340px}\n"] }]
|
|
3452
3471
|
}] });
|
|
3453
3472
|
|
|
3454
|
-
// One group of
|
|
3455
|
-
//
|
|
3456
|
-
// open from the shared per-
|
|
3457
|
-
//
|
|
3473
|
+
// One group of a unit's AI-classified cached code functions — an entity's model
|
|
3474
|
+
// or handler class, or the single group of a standalone code class. Fetched on
|
|
3475
|
+
// open from the shared per-key endpoint (one payload carries every group, each
|
|
3476
|
+
// row tagged with its source) and filtered client-side.
|
|
3458
3477
|
class EntityFunctionsComponent {
|
|
3459
3478
|
// One color per AI classification kind, so a function's nature reads at a glance.
|
|
3460
3479
|
static KIND_COLORS = {
|
|
@@ -3467,15 +3486,23 @@ class EntityFunctionsComponent {
|
|
|
3467
3486
|
};
|
|
3468
3487
|
// Interesting logic first: derived values and rules, then data access, then the rest.
|
|
3469
3488
|
static KIND_ORDER = ['calculation', 'business-rule', 'query', 'mutation', 'accessor', 'other'];
|
|
3489
|
+
// The dialog title per group. A standalone class has no model/handler split,
|
|
3490
|
+
// so its one group is just the code the build read.
|
|
3491
|
+
static SOURCE_LABELS = {
|
|
3492
|
+
model: 'Model functions',
|
|
3493
|
+
handler: 'Handler functions',
|
|
3494
|
+
class: 'Code functions',
|
|
3495
|
+
};
|
|
3470
3496
|
_data = inject(MAT_DIALOG_DATA);
|
|
3471
3497
|
entityKey = this._data.entityKey;
|
|
3472
|
-
sourceLabel = this._data.source
|
|
3498
|
+
sourceLabel = EntityFunctionsComponent.SOURCE_LABELS[this._data.source];
|
|
3473
3499
|
// null until loaded — gates the list behind the skeleton.
|
|
3474
3500
|
entityFunctions = signal(null);
|
|
3475
|
-
// The class this group's functions were discovered from.
|
|
3501
|
+
// The class this group's functions were discovered from. A standalone code
|
|
3502
|
+
// class has no handler, so its group reads the model slot like 'model' does.
|
|
3476
3503
|
discoveredClass = computed(() => {
|
|
3477
3504
|
const loaded = this.entityFunctions();
|
|
3478
|
-
return this._data.source === '
|
|
3505
|
+
return this._data.source === 'handler' ? loaded?.handlerClass : loaded?.modelClass;
|
|
3479
3506
|
});
|
|
3480
3507
|
// This group's functions, ordered by kind priority then method name.
|
|
3481
3508
|
functions = computed(() => this._sortFunctions((this.entityFunctions()?.functions ?? [])
|
|
@@ -3518,13 +3545,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
3518
3545
|
], template: "<fs-dialog>\n <fs-dialog-title>\n {{ entityKey }} \u2014 {{ sourceLabel }}\n </fs-dialog-title>\n <mat-dialog-content>\n <div *fsSkeleton=\"entityFunctions()\">\n @if (entityFunctions(); as fns) {\n @if (functions().length) {\n @if (fns.generatedAt) {\n <div class=\"hint functions-generated\">\n Discovered from {{ discoveredClass() }} ·\n <fs-date\n [date]=\"fns.generatedAt\"\n format=\"date-time\">\n </fs-date>\n </div>\n }\n <div class=\"functions-list\">\n @for (fn of functions(); track fn.class + '::' + fn.method) {\n <div class=\"function-row\">\n <div class=\"function-head\">\n <fs-chip\n size=\"tiny\"\n [backgroundColor]=\"kindColor(fn.kind)\"\n color=\"#ffffff\">\n {{ fn.kind }}\n </fs-chip>\n <code class=\"function-method\">{{ fn.method }}</code>\n </div>\n <div class=\"function-summary\">{{ fn.summary }}</div>\n @if (fn.produces) {\n <div class=\"function-produces hint\">Produces: {{ fn.produces }}</div>\n }\n </div>\n }\n </div>\n } @else {\n <div class=\"hint\">\n No functions cached for this entity yet \u2014 run a build to discover its code.\n </div>\n }\n }\n </div>\n </mat-dialog-content>\n <mat-dialog-actions>\n <button\n type=\"button\"\n mat-button\n color=\"primary\"\n [mat-dialog-close]=\"undefined\">\n Close\n </button>\n </mat-dialog-actions>\n</fs-dialog>\n", styles: [".hint{margin:0;color:#0009;font-size:13px;line-height:1.5}.functions-generated{margin-bottom:10px}.functions-list{display:flex;flex-direction:column}.functions-list .function-row{display:flex;flex-direction:column;gap:3px;padding:10px 0}.functions-list .function-row+.function-row{border-top:1px solid rgba(0,0,0,.08)}.functions-list .function-row .function-head{display:flex;align-items:center;gap:8px}.functions-list .function-row .function-method{font-family:monospace;font-size:13px;font-weight:600}.functions-list .function-row .function-summary{color:#000000bf}.functions-list .function-row .function-produces{padding-left:10px}\n"] }]
|
|
3519
3546
|
}], ctorParameters: () => [] });
|
|
3520
3547
|
|
|
3521
|
-
// The Semantic Database tab: the
|
|
3522
|
-
//
|
|
3523
|
-
//
|
|
3524
|
-
//
|
|
3525
|
-
//
|
|
3526
|
-
//
|
|
3527
|
-
// big to ship with the scoreboard).
|
|
3548
|
+
// The Semantic Database tab: the scoreboard of everything the report AI was
|
|
3549
|
+
// built from. Shows the last build's status, runs (or stops) a build — streamed
|
|
3550
|
+
// into the FsProcess dock so it survives the dialog closing — and drills into
|
|
3551
|
+
// every unit to reveal its shape, inlined enum values, computed fields and its
|
|
3552
|
+
// cached function counts, whose links open the functions dialog (the full
|
|
3553
|
+
// function set is far too big to ship with the scoreboard).
|
|
3528
3554
|
//
|
|
3529
3555
|
// It also reports the other half of the picture: the tables the AI CANNOT see —
|
|
3530
3556
|
// excluded ones (no model) and junctions that bridged nothing. Those are
|
|
@@ -3541,21 +3567,20 @@ class SemanticDatabaseComponent {
|
|
|
3541
3567
|
K: 'Stopped',
|
|
3542
3568
|
F: 'Failed',
|
|
3543
3569
|
};
|
|
3544
|
-
//
|
|
3545
|
-
//
|
|
3546
|
-
//
|
|
3547
|
-
static
|
|
3548
|
-
|
|
3549
|
-
//
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
enumsInlined: 'Enum cases',
|
|
3554
|
-
computed: 'Computed',
|
|
3555
|
-
functions: 'Functions',
|
|
3556
|
-
};
|
|
3570
|
+
// A row that is still owed content says so; a built one says nothing. On a
|
|
3571
|
+
// finished build every stage is done on every row, so chipping them all was
|
|
3572
|
+
// noise that buried the one row that mattered.
|
|
3573
|
+
static PENDING_COLOR = '#b26500';
|
|
3574
|
+
// Class-name suffix stripped for display: `BudgetSummaryModel` reads as
|
|
3575
|
+
// `BudgetSummary`. Only `Model` — stripping `Handler` too would collide
|
|
3576
|
+
// `WorkOrderOneTimeHandler` with `WorkOrderOneTimeModel`, which are two
|
|
3577
|
+
// separate units.
|
|
3578
|
+
static NAME_SUFFIX = 'Model';
|
|
3557
3579
|
// null until loaded — gates the tab behind the skeleton.
|
|
3558
3580
|
status = signal(null);
|
|
3581
|
+
// Free-text filter over the unit list — at a few hundred rows the accordion
|
|
3582
|
+
// is only usable if you can narrow it.
|
|
3583
|
+
filter = signal('');
|
|
3559
3584
|
// True while a build started from this tab is streaming into the dock.
|
|
3560
3585
|
building = signal(false);
|
|
3561
3586
|
buildActive = computed(() => this.building()
|
|
@@ -3572,6 +3597,43 @@ class SemanticDatabaseComponent {
|
|
|
3572
3597
|
// junction, but they were folded into no relationship, so neither they nor
|
|
3573
3598
|
// what they link are reachable.
|
|
3574
3599
|
unlinkedJunctions = computed(() => this._tables().filter((table) => table.classification === 'junction' && !!table.reason));
|
|
3600
|
+
// Everything the build cached, entities and standalone code classes together,
|
|
3601
|
+
// in one list ordered by the name a reader recognizes. Two arrays on the wire
|
|
3602
|
+
// because they are genuinely different shapes; one list on screen because
|
|
3603
|
+
// "what did the build read?" is a single question.
|
|
3604
|
+
units = computed(() => {
|
|
3605
|
+
const detail = this.status()?.detail;
|
|
3606
|
+
if (!detail) {
|
|
3607
|
+
return [];
|
|
3608
|
+
}
|
|
3609
|
+
const entities = (detail.entities ?? []).map((entity) => ({
|
|
3610
|
+
key: entity.key,
|
|
3611
|
+
name: this._displayName(entity.model, entity.key),
|
|
3612
|
+
hasTable: true,
|
|
3613
|
+
entity,
|
|
3614
|
+
model: entity.model,
|
|
3615
|
+
functionCount: entity.functionCount,
|
|
3616
|
+
}));
|
|
3617
|
+
const classes = (detail.classes ?? []).map((unit) => ({
|
|
3618
|
+
key: unit.key,
|
|
3619
|
+
name: this._displayName(unit.model, unit.key),
|
|
3620
|
+
hasTable: false,
|
|
3621
|
+
entity: null,
|
|
3622
|
+
model: unit.model,
|
|
3623
|
+
functionCount: unit.functionCount,
|
|
3624
|
+
}));
|
|
3625
|
+
return [...entities, ...classes]
|
|
3626
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
3627
|
+
});
|
|
3628
|
+
// The filter matches the display name, the class and the entity key, so
|
|
3629
|
+
// hunting by any of the three works.
|
|
3630
|
+
filteredUnits = computed(() => {
|
|
3631
|
+
const filter = this.filter().trim().toLowerCase();
|
|
3632
|
+
if (!filter) {
|
|
3633
|
+
return this.units();
|
|
3634
|
+
}
|
|
3635
|
+
return this.units().filter((unit) => `${unit.name} ${unit.model ?? ''} ${unit.key}`.toLowerCase().includes(filter));
|
|
3636
|
+
});
|
|
3575
3637
|
// The Coverage line. Assembled here because the catalog parts drop out
|
|
3576
3638
|
// entirely on a pre-catalog artifact, which reads badly as template branches.
|
|
3577
3639
|
coverage = computed(() => {
|
|
@@ -3579,7 +3641,7 @@ class SemanticDatabaseComponent {
|
|
|
3579
3641
|
if (!detail) {
|
|
3580
3642
|
return '';
|
|
3581
3643
|
}
|
|
3582
|
-
const parts = [`${detail.entityCount} entities`];
|
|
3644
|
+
const parts = [`${detail.entityCount} entities`, `${detail.classCount} code classes`];
|
|
3583
3645
|
if (this.hasCatalog()) {
|
|
3584
3646
|
parts.push(detail.unlinkedJunctionCount
|
|
3585
3647
|
? `${detail.junctionCount} junctions (${detail.unlinkedJunctionCount} not linked)`
|
|
@@ -3728,38 +3790,62 @@ class SemanticDatabaseComponent {
|
|
|
3728
3790
|
this._message.success('Semantic database build complete.');
|
|
3729
3791
|
}
|
|
3730
3792
|
}
|
|
3731
|
-
|
|
3732
|
-
return
|
|
3733
|
-
|
|
3734
|
-
|
|
3793
|
+
pendingColor() {
|
|
3794
|
+
return SemanticDatabaseComponent.PENDING_COLOR;
|
|
3795
|
+
}
|
|
3796
|
+
/**
|
|
3797
|
+
* The build stages this row is still owed, or [] when it is fully built. A
|
|
3798
|
+
* stopped or interrupted build is the only way to get a non-empty list.
|
|
3799
|
+
*/
|
|
3800
|
+
pendingStages(unit) {
|
|
3801
|
+
const stages = unit.entity?.stages;
|
|
3802
|
+
return stages
|
|
3803
|
+
? Object.keys(stages).filter((stage) => stages[stage] !== 'done')
|
|
3804
|
+
: [];
|
|
3735
3805
|
}
|
|
3736
|
-
|
|
3737
|
-
|
|
3806
|
+
setFilter(value) {
|
|
3807
|
+
this.filter.set(value);
|
|
3738
3808
|
}
|
|
3739
3809
|
/**
|
|
3740
|
-
* Open the child dialog listing one group of
|
|
3741
|
-
*
|
|
3742
|
-
* so the tab never holds function payloads.
|
|
3810
|
+
* Open the child dialog listing one group of a unit's cached functions — an
|
|
3811
|
+
* entity's model or handler class, or a standalone class's code. The dialog
|
|
3812
|
+
* fetches fresh on open, so the tab never holds function payloads.
|
|
3743
3813
|
*/
|
|
3744
|
-
openFunctions(
|
|
3814
|
+
openFunctions(unit, source) {
|
|
3745
3815
|
this._dialog.open(EntityFunctionsComponent, {
|
|
3746
3816
|
width: '700px',
|
|
3747
3817
|
maxWidth: '95vw',
|
|
3748
3818
|
maxHeight: '85vh',
|
|
3749
3819
|
autoFocus: false,
|
|
3750
|
-
data: { entityKey:
|
|
3820
|
+
data: { entityKey: unit.key, source },
|
|
3751
3821
|
});
|
|
3752
3822
|
}
|
|
3753
3823
|
_init() {
|
|
3754
3824
|
this._refreshStatus();
|
|
3755
3825
|
}
|
|
3826
|
+
/**
|
|
3827
|
+
* What a reader recognizes the unit by: its class's short name, minus the
|
|
3828
|
+
* `Model` suffix that every model carries and none of them are distinguished
|
|
3829
|
+
* by. Falls back to the cache key for a unit whose class could not be
|
|
3830
|
+
* resolved, so a row never renders nameless.
|
|
3831
|
+
*/
|
|
3832
|
+
_displayName(model, key) {
|
|
3833
|
+
if (!model) {
|
|
3834
|
+
return key;
|
|
3835
|
+
}
|
|
3836
|
+
const shortName = model.split('\\').pop();
|
|
3837
|
+
return shortName.length > SemanticDatabaseComponent.NAME_SUFFIX.length
|
|
3838
|
+
&& shortName.endsWith(SemanticDatabaseComponent.NAME_SUFFIX)
|
|
3839
|
+
? shortName.slice(0, -SemanticDatabaseComponent.NAME_SUFFIX.length)
|
|
3840
|
+
: shortName;
|
|
3841
|
+
}
|
|
3756
3842
|
_refreshStatus() {
|
|
3757
3843
|
this._reportData.semanticBuildStatus()
|
|
3758
3844
|
.pipe(takeUntilDestroyed(this._destroyRef))
|
|
3759
3845
|
.subscribe((status) => this.status.set(status));
|
|
3760
3846
|
}
|
|
3761
3847
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SemanticDatabaseComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3762
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: SemanticDatabaseComponent, isStandalone: true, selector: "fs-ai-report-global-settings-semantic-database", ngImport: i0, template: "<div class=\"fs-column tab-body\">\n <p class=\"hint\">\n The semantic database is what the report AI uses to understand your data \u2014\n tables, relationships and the calculations in code. Rebuild it after\n significant application updates. The build runs in the background; progress\n appears in the dock at the bottom right and this dialog can be closed while\n it runs.\n </p>\n\n <div *fsSkeleton=\"status()\">\n @if (status(); as buildStatus) {\n <div class=\"build-status\">\n @if (buildStatus.detail.generatedAt) {\n <div class=\"stat\">\n <span class=\"label\">Last built</span>\n <fs-date\n [date]=\"buildStatus.detail.generatedAt\"\n format=\"date-time\">\n </fs-date>\n </div>\n <div class=\"stat\">\n <span class=\"label\">Coverage</span>\n <span>{{ coverage() }}</span>\n </div>\n } @else {\n <fs-message-warning>\n The semantic database has not been built yet \u2014 the report AI can't\n answer data questions until it is. Run the first build.\n </fs-message-warning>\n }\n @if (buildStatus.build) {\n <div class=\"stat\">\n <span class=\"label\">Last build run</span>\n <span>\n {{ buildStateLabel() }}\n @if (buildStatus.build.message) {\n \u2014 {{ buildStatus.build.message }}\n }\n </span>\n </div>\n }\n </div>\n\n <!-- The entity scoreboard: every entity the AI knows about (an entity =\n a table + a model + a handler), its built-vs-needs-building stages,\n and a drill-down into its shape, enum values, computed fields and\n cached function counts \u2014 the counts link into the functions dialog. -->\n @if (buildStatus.detail.entities?.length) {\n <h2>Entities</h2>\n <mat-accordion class=\"entities\">\n @for (entity of buildStatus.detail.entities; track entity.key) {\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <mat-panel-title>{{ entity.key }}</mat-panel-title>\n <mat-panel-description>\n <div class=\"stage-chips\">\n @for (stage of stageKeys; track stage) {\n <fs-chip\n size=\"tiny\"\n [backgroundColor]=\"stageColor(entity.stages[stage])\"\n color=\"#ffffff\"\n [matTooltip]=\"stageTooltip(entity.stages[stage])\">\n {{ stageLabels[stage] }}\n </fs-chip>\n }\n </div>\n </mat-panel-description>\n </mat-expansion-panel-header>\n\n <!-- Deferred: the body only exists once expanded. -->\n <ng-template matExpansionPanelContent>\n <div class=\"entity-detail\">\n <div class=\"stat\">\n <span class=\"label\">Table</span>\n <span>{{ entity.table }}</span>\n </div>\n <div class=\"stat\">\n <span class=\"label\">Columns</span>\n <span>{{ entity.columns }}</span>\n </div>\n <div class=\"stat\">\n <span class=\"label\">Relationships</span>\n <span>{{ entity.relationships }}</span>\n </div>\n @if (entity.enums.length) {\n <div class=\"stat\">\n <span class=\"label\">Enumeration values</span>\n <div class=\"pair-list\">\n @for (enumColumn of entity.enums; track enumColumn.column) {\n <div class=\"pair-row\">\n <span class=\"pair-name\">{{ enumColumn.column }}</span>\n <span>{{ enumColumn.values.join(', ') }}</span>\n </div>\n }\n </div>\n </div>\n }\n @if (entity.computedFields.length) {\n <div class=\"stat\">\n <span class=\"label\">Computed fields</span>\n <span>{{ entity.computedFields.join(', ') }}</span>\n </div>\n }\n <div class=\"stat\">\n <span class=\"label\">Model functions</span>\n @if (entity.modelFunctionCount) {\n <a\n class=\"count-link\"\n (click)=\"openFunctions(entity, 'model')\">\n {{ entity.modelFunctionCount }}\n </a>\n } @else {\n <span>0</span>\n }\n </div>\n <div class=\"stat\">\n <span class=\"label\">Handler functions</span>\n @if (entity.handlerFunctionCount) {\n <a\n class=\"count-link\"\n (click)=\"openFunctions(entity, 'handler')\">\n {{ entity.handlerFunctionCount }}\n </a>\n } @else {\n <span>0</span>\n }\n </div>\n </div>\n </ng-template>\n </mat-expansion-panel>\n }\n </mat-accordion>\n }\n\n <!-- The other half of the picture: tables the AI can't reach. Excluded\n ones have no model; unlinked junctions have a composite primary key\n but were folded into no relationship, so nothing can navigate to\n them. Both are invisible by omission \u2014 no error, just a capability\n that silently isn't there. Collapsed by default: the excluded list is\n long and mostly framework plumbing. -->\n @if (hasCatalog()) {\n <h2>Not in the semantic database</h2>\n <div class=\"subheading\">\n Tables the report AI can't see. Give one a model to promote it to an\n entity, then rebuild.\n </div>\n <mat-accordion class=\"catalog\">\n @if (excludedTables().length) {\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <mat-panel-title>\n Tables excluded ({{ excludedTables().length }})\n </mat-panel-title>\n </mat-expansion-panel-header>\n\n <!-- Deferred: the body only exists once expanded. -->\n <ng-template matExpansionPanelContent>\n <div class=\"pair-list\">\n @for (table of excludedTables(); track table.table) {\n <div class=\"pair-row\">\n <span class=\"pair-name\">{{ table.table }}</span>\n <span>{{ table.reason }}</span>\n </div>\n }\n </div>\n </ng-template>\n </mat-expansion-panel>\n }\n @if (unlinkedJunctions().length) {\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <mat-panel-title>\n Junctions not linked ({{ unlinkedJunctions().length }})\n </mat-panel-title>\n </mat-expansion-panel-header>\n\n <ng-template matExpansionPanelContent>\n <p class=\"small\">\n Link tables the build couldn't fold into a relationship \u2014 so\n neither they nor what they link can be reported on.\n </p>\n <div class=\"pair-list\">\n @for (table of unlinkedJunctions(); track table.table) {\n <div class=\"pair-row\">\n <span class=\"pair-name\">{{ table.table }}</span>\n <span>{{ table.reason }}</span>\n </div>\n }\n </div>\n </ng-template>\n </mat-expansion-panel>\n }\n </mat-accordion>\n }\n }\n </div>\n</div>\n", styles: [".hint{margin:0 0 12px;color:#0009;font-size:13px;line-height:1.5}.stat{display:flex;align-items:baseline;gap:12px}.stat>.label{flex:0 0 150px;white-space:nowrap;color:#0009}.build-status{display:flex;flex-direction:column;gap:8px}.pair-list{display:flex;flex-direction:column;gap:3px}.pair-row{display:flex;gap:12px}.pair-name{flex:0 0 140px;white-space:nowrap;color:#0009}.entities{display:block;padding:3px}.entities mat-panel-description{justify-content:flex-end;margin-right:8px}.entities .stage-chips{display:flex;gap:4px}.entities .entity-detail{display:flex;flex-direction:column;gap:8px}.catalog{display:block;padding:3px}.catalog .pair-name{flex-basis:220px}\n"], dependencies: [{ kind: "ngmodule", type: MatExpansionModule }, { kind: "directive", type: i1$5.MatAccordion, selector: "mat-accordion", inputs: ["hideToggle", "displayMode", "togglePosition"], exportAs: ["matAccordion"] }, { kind: "component", type: i1$5.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i1$5.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i1$5.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "directive", type: i1$5.MatExpansionPanelDescription, selector: "mat-panel-description" }, { kind: "directive", type: i1$5.MatExpansionPanelContent, selector: "ng-template[matExpansionPanelContent]" }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i2$5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: FsChipModule }, { kind: "component", type: i1$4.FsChipComponent, selector: "fs-chip", inputs: ["selectable", "removable", "value", "maxWidth", "width", "backgroundColor", "borderColor", "color", "shape", "outlined", "outlineDash", "icon", "image", "selected", "disabled", "padding", "contrastColor", "size"], outputs: ["selectedToggled", "removed", "click"] }, { kind: "ngmodule", type: FsDateModule }, { kind: "component", type: i2$4.FsDateComponent, selector: "fs-date", inputs: ["date", "format", "timezone"] }, { kind: "ngmodule", type: FsMessageModule }, { kind: "component", type: i4.FsMessageWarningComponent, selector: "fs-message-warning" }, { kind: "ngmodule", type: FsSkeletonModule }, { kind: "directive", type: i4$1.FsSkeletonContentDirective, selector: "[fsSkeleton]", inputs: ["fsSkeleton", "fsSkeletonPattern"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3848
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: SemanticDatabaseComponent, isStandalone: true, selector: "fs-ai-report-global-settings-semantic-database", ngImport: i0, template: "<div class=\"fs-column tab-body\">\n <p class=\"hint\">\n The semantic database is what the report AI uses to understand your data \u2014\n tables, relationships and the calculations in code. Rebuild it after\n significant application updates. The build runs in the background; progress\n appears in the dock at the bottom right and this dialog can be closed while\n it runs.\n </p>\n\n <div *fsSkeleton=\"status()\">\n @if (status(); as buildStatus) {\n <div class=\"build-status\">\n @if (buildStatus.detail.generatedAt) {\n <div class=\"stat\">\n <span class=\"label\">Last built</span>\n <fs-date\n [date]=\"buildStatus.detail.generatedAt\"\n format=\"date-time\">\n </fs-date>\n </div>\n <div class=\"stat\">\n <span class=\"label\">Coverage</span>\n <span>{{ coverage() }}</span>\n </div>\n } @else {\n <fs-message-warning>\n The semantic database has not been built yet \u2014 the report AI can't\n answer data questions until it is. Run the first build.\n </fs-message-warning>\n }\n @if (buildStatus.build) {\n <div class=\"stat\">\n <span class=\"label\">Last build run</span>\n <span>\n {{ buildStateLabel() }}\n @if (buildStatus.build.message) {\n \u2014 {{ buildStatus.build.message }}\n }\n </span>\n </div>\n }\n </div>\n\n <!-- Everything the build read, in one list: entities (a table + a model +\n a handler) and standalone code classes (domain logic with no table).\n The AI reads code from both, so splitting them into two sections\n would hide half of what was built. Each row drills into its shape,\n enum values, computed fields and cached function counts \u2014 the counts\n link into the functions dialog. -->\n @if (units().length) {\n <h2>Included ({{ units().length }})</h2>\n <div class=\"subheading\">\n Everything the report AI was built from. Entities can be queried;\n code classes are logic it reads and translates into SQL.\n </div>\n\n <input\n class=\"unit-filter\"\n type=\"search\"\n placeholder=\"Filter by name, class or key\u2026\"\n [value]=\"filter()\"\n (input)=\"setFilter($any($event.target).value)\">\n\n <mat-accordion class=\"entities\">\n @for (unit of filteredUnits(); track unit.key) {\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <mat-panel-title>{{ unit.name }}</mat-panel-title>\n <mat-panel-description>\n <div class=\"stage-chips\">\n @if (!unit.hasTable) {\n <fs-chip size=\"tiny\" matTooltip=\"Domain logic with no table \u2014 read, never queried\">\n code\n </fs-chip>\n }\n <!-- Silence means built. A chip here means an interrupted\n build left this row owing content. -->\n @for (stage of pendingStages(unit); track stage) {\n <fs-chip\n size=\"tiny\"\n [backgroundColor]=\"pendingColor()\"\n color=\"#ffffff\"\n matTooltip=\"Needs building\">\n {{ stage }}\n </fs-chip>\n }\n </div>\n </mat-panel-description>\n </mat-expansion-panel-header>\n\n <!-- Deferred: the body only exists once expanded. -->\n <ng-template matExpansionPanelContent>\n <div class=\"entity-detail\">\n @if (unit.model) {\n <div class=\"stat\">\n <span class=\"label\">Class</span>\n <span>{{ unit.model }}</span>\n </div>\n }\n @if (unit.entity; as entity) {\n <div class=\"stat\">\n <span class=\"label\">Table</span>\n <span>{{ entity.table }}</span>\n </div>\n <!-- The handle the report AI addresses this entity by in\n describe_entity / compile_query. -->\n <div class=\"stat\">\n <span class=\"label\">Entity key</span>\n <span>{{ entity.key }}</span>\n </div>\n <div class=\"stat\">\n <span class=\"label\">Columns</span>\n <span>{{ entity.columns }}</span>\n </div>\n <div class=\"stat\">\n <span class=\"label\">Relationships</span>\n <span>{{ entity.relationships }}</span>\n </div>\n @if (entity.enums.length) {\n <div class=\"stat\">\n <span class=\"label\">Enumeration values</span>\n <div class=\"pair-list\">\n @for (enumColumn of entity.enums; track enumColumn.column) {\n <div class=\"pair-row\">\n <span class=\"pair-name\">{{ enumColumn.column }}</span>\n <span>{{ enumColumn.values.join(', ') }}</span>\n </div>\n }\n </div>\n </div>\n }\n @if (entity.computedFields.length) {\n <div class=\"stat\">\n <span class=\"label\">Computed fields</span>\n <span>{{ entity.computedFields.join(', ') }}</span>\n </div>\n }\n <div class=\"stat\">\n <span class=\"label\">Model functions</span>\n @if (entity.modelFunctionCount) {\n <a\n class=\"count-link\"\n (click)=\"openFunctions(unit, 'model')\">\n {{ entity.modelFunctionCount }}\n </a>\n } @else {\n <span>0</span>\n }\n </div>\n <div class=\"stat\">\n <span class=\"label\">Handler functions</span>\n @if (entity.handlerFunctionCount) {\n <a\n class=\"count-link\"\n (click)=\"openFunctions(unit, 'handler')\">\n {{ entity.handlerFunctionCount }}\n </a>\n } @else {\n <span>0</span>\n }\n </div>\n } @else {\n <div class=\"stat\">\n <span class=\"label\">Code functions</span>\n @if (unit.functionCount) {\n <a\n class=\"count-link\"\n (click)=\"openFunctions(unit, 'class')\">\n {{ unit.functionCount }}\n </a>\n } @else {\n <span>0</span>\n }\n </div>\n }\n </div>\n </ng-template>\n </mat-expansion-panel>\n }\n </mat-accordion>\n\n @if (!filteredUnits().length) {\n <div class=\"subheading\">Nothing matches \u201C{{ filter() }}\u201D.</div>\n }\n }\n\n <!-- The other half of the picture: tables the AI can't reach. Excluded\n ones have no model; unlinked junctions have a composite primary key\n but were folded into no relationship, so nothing can navigate to\n them. Both are invisible by omission \u2014 no error, just a capability\n that silently isn't there. Collapsed by default: the excluded list is\n long and mostly framework plumbing. -->\n @if (hasCatalog()) {\n <h2>Not in the semantic database</h2>\n <div class=\"subheading\">\n Tables the report AI can't see. Give one a model to promote it to an\n entity, then rebuild.\n </div>\n <mat-accordion class=\"catalog\">\n @if (excludedTables().length) {\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <mat-panel-title>\n Tables excluded ({{ excludedTables().length }})\n </mat-panel-title>\n </mat-expansion-panel-header>\n\n <!-- Deferred: the body only exists once expanded. -->\n <ng-template matExpansionPanelContent>\n <div class=\"pair-list\">\n @for (table of excludedTables(); track table.table) {\n <div class=\"pair-row\">\n <span class=\"pair-name\">{{ table.table }}</span>\n <span>{{ table.reason }}</span>\n </div>\n }\n </div>\n </ng-template>\n </mat-expansion-panel>\n }\n @if (unlinkedJunctions().length) {\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <mat-panel-title>\n Junctions not linked ({{ unlinkedJunctions().length }})\n </mat-panel-title>\n </mat-expansion-panel-header>\n\n <ng-template matExpansionPanelContent>\n <p class=\"small\">\n Link tables the build couldn't fold into a relationship \u2014 so\n neither they nor what they link can be reported on.\n </p>\n <div class=\"pair-list\">\n @for (table of unlinkedJunctions(); track table.table) {\n <div class=\"pair-row\">\n <span class=\"pair-name\">{{ table.table }}</span>\n <span>{{ table.reason }}</span>\n </div>\n }\n </div>\n </ng-template>\n </mat-expansion-panel>\n }\n </mat-accordion>\n }\n }\n </div>\n</div>\n", styles: [".hint{margin:0 0 12px;color:#0009;font-size:13px;line-height:1.5}.stat{display:flex;align-items:baseline;gap:12px}.stat>.label{flex:0 0 150px;white-space:nowrap;color:#0009}.build-status{display:flex;flex-direction:column;gap:8px}.pair-list{display:flex;flex-direction:column;gap:3px}.pair-row{display:flex;gap:12px}.pair-name{flex:0 0 140px;white-space:nowrap;color:#0009}.unit-filter{width:100%;box-sizing:border-box;margin:8px 0 4px;padding:6px 10px;border:1px solid rgba(0,0,0,.2);border-radius:4px;font:inherit}.unit-filter:focus{outline:none;border-color:#00000080}.entities{display:block;padding:3px}.entities mat-panel-description{justify-content:flex-end;margin-right:8px}.entities .stage-chips{display:flex;gap:4px}.entities .entity-detail{display:flex;flex-direction:column;gap:8px}.catalog{display:block;padding:3px}.catalog .pair-name{flex-basis:220px}\n"], dependencies: [{ kind: "ngmodule", type: MatExpansionModule }, { kind: "directive", type: i1$5.MatAccordion, selector: "mat-accordion", inputs: ["hideToggle", "displayMode", "togglePosition"], exportAs: ["matAccordion"] }, { kind: "component", type: i1$5.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i1$5.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i1$5.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "directive", type: i1$5.MatExpansionPanelDescription, selector: "mat-panel-description" }, { kind: "directive", type: i1$5.MatExpansionPanelContent, selector: "ng-template[matExpansionPanelContent]" }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i2$5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: FsChipModule }, { kind: "component", type: i1$4.FsChipComponent, selector: "fs-chip", inputs: ["selectable", "removable", "value", "maxWidth", "width", "backgroundColor", "borderColor", "color", "shape", "outlined", "outlineDash", "icon", "image", "selected", "disabled", "padding", "contrastColor", "size"], outputs: ["selectedToggled", "removed", "click"] }, { kind: "ngmodule", type: FsDateModule }, { kind: "component", type: i2$4.FsDateComponent, selector: "fs-date", inputs: ["date", "format", "timezone"] }, { kind: "ngmodule", type: FsMessageModule }, { kind: "component", type: i4.FsMessageWarningComponent, selector: "fs-message-warning" }, { kind: "ngmodule", type: FsSkeletonModule }, { kind: "directive", type: i4$1.FsSkeletonContentDirective, selector: "[fsSkeleton]", inputs: ["fsSkeleton", "fsSkeletonPattern"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3763
3849
|
}
|
|
3764
3850
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SemanticDatabaseComponent, decorators: [{
|
|
3765
3851
|
type: Component,
|
|
@@ -3770,7 +3856,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
3770
3856
|
FsDateModule,
|
|
3771
3857
|
FsMessageModule,
|
|
3772
3858
|
FsSkeletonModule,
|
|
3773
|
-
], template: "<div class=\"fs-column tab-body\">\n <p class=\"hint\">\n The semantic database is what the report AI uses to understand your data \u2014\n tables, relationships and the calculations in code. Rebuild it after\n significant application updates. The build runs in the background; progress\n appears in the dock at the bottom right and this dialog can be closed while\n it runs.\n </p>\n\n <div *fsSkeleton=\"status()\">\n @if (status(); as buildStatus) {\n <div class=\"build-status\">\n @if (buildStatus.detail.generatedAt) {\n <div class=\"stat\">\n <span class=\"label\">Last built</span>\n <fs-date\n [date]=\"buildStatus.detail.generatedAt\"\n format=\"date-time\">\n </fs-date>\n </div>\n <div class=\"stat\">\n <span class=\"label\">Coverage</span>\n <span>{{ coverage() }}</span>\n </div>\n } @else {\n <fs-message-warning>\n The semantic database has not been built yet \u2014 the report AI can't\n answer data questions until it is. Run the first build.\n </fs-message-warning>\n }\n @if (buildStatus.build) {\n <div class=\"stat\">\n <span class=\"label\">Last build run</span>\n <span>\n {{ buildStateLabel() }}\n @if (buildStatus.build.message) {\n \u2014 {{ buildStatus.build.message }}\n }\n </span>\n </div>\n }\n </div>\n\n <!-- The entity scoreboard: every entity the AI knows about (an entity =\n a table + a model + a handler), its built-vs-needs-building stages,\n and a drill-down into its shape, enum values, computed fields and\n cached function counts \u2014 the counts link into the functions dialog. -->\n @if (buildStatus.detail.entities?.length) {\n <h2>Entities</h2>\n <mat-accordion class=\"entities\">\n @for (entity of buildStatus.detail.entities; track entity.key) {\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <mat-panel-title>{{ entity.key }}</mat-panel-title>\n <mat-panel-description>\n <div class=\"stage-chips\">\n @for (stage of stageKeys; track stage) {\n <fs-chip\n size=\"tiny\"\n [backgroundColor]=\"stageColor(entity.stages[stage])\"\n color=\"#ffffff\"\n [matTooltip]=\"stageTooltip(entity.stages[stage])\">\n {{ stageLabels[stage] }}\n </fs-chip>\n }\n </div>\n </mat-panel-description>\n </mat-expansion-panel-header>\n\n <!-- Deferred: the body only exists once expanded. -->\n <ng-template matExpansionPanelContent>\n <div class=\"entity-detail\">\n <div class=\"stat\">\n <span class=\"label\">Table</span>\n <span>{{ entity.table }}</span>\n </div>\n <div class=\"stat\">\n <span class=\"label\">Columns</span>\n <span>{{ entity.columns }}</span>\n </div>\n <div class=\"stat\">\n <span class=\"label\">Relationships</span>\n <span>{{ entity.relationships }}</span>\n </div>\n @if (entity.enums.length) {\n <div class=\"stat\">\n <span class=\"label\">Enumeration values</span>\n <div class=\"pair-list\">\n @for (enumColumn of entity.enums; track enumColumn.column) {\n <div class=\"pair-row\">\n <span class=\"pair-name\">{{ enumColumn.column }}</span>\n <span>{{ enumColumn.values.join(', ') }}</span>\n </div>\n }\n </div>\n </div>\n }\n @if (entity.computedFields.length) {\n <div class=\"stat\">\n <span class=\"label\">Computed fields</span>\n <span>{{ entity.computedFields.join(', ') }}</span>\n </div>\n }\n <div class=\"stat\">\n <span class=\"label\">Model functions</span>\n @if (entity.modelFunctionCount) {\n <a\n class=\"count-link\"\n (click)=\"openFunctions(entity, 'model')\">\n {{ entity.modelFunctionCount }}\n </a>\n } @else {\n <span>0</span>\n }\n </div>\n <div class=\"stat\">\n <span class=\"label\">Handler functions</span>\n @if (entity.handlerFunctionCount) {\n <a\n class=\"count-link\"\n (click)=\"openFunctions(entity, 'handler')\">\n {{ entity.handlerFunctionCount }}\n </a>\n } @else {\n <span>0</span>\n }\n </div>\n </div>\n </ng-template>\n </mat-expansion-panel>\n }\n </mat-accordion>\n }\n\n <!-- The other half of the picture: tables the AI can't reach. Excluded\n ones have no model; unlinked junctions have a composite primary key\n but were folded into no relationship, so nothing can navigate to\n them. Both are invisible by omission \u2014 no error, just a capability\n that silently isn't there. Collapsed by default: the excluded list is\n long and mostly framework plumbing. -->\n @if (hasCatalog()) {\n <h2>Not in the semantic database</h2>\n <div class=\"subheading\">\n Tables the report AI can't see. Give one a model to promote it to an\n entity, then rebuild.\n </div>\n <mat-accordion class=\"catalog\">\n @if (excludedTables().length) {\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <mat-panel-title>\n Tables excluded ({{ excludedTables().length }})\n </mat-panel-title>\n </mat-expansion-panel-header>\n\n <!-- Deferred: the body only exists once expanded. -->\n <ng-template matExpansionPanelContent>\n <div class=\"pair-list\">\n @for (table of excludedTables(); track table.table) {\n <div class=\"pair-row\">\n <span class=\"pair-name\">{{ table.table }}</span>\n <span>{{ table.reason }}</span>\n </div>\n }\n </div>\n </ng-template>\n </mat-expansion-panel>\n }\n @if (unlinkedJunctions().length) {\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <mat-panel-title>\n Junctions not linked ({{ unlinkedJunctions().length }})\n </mat-panel-title>\n </mat-expansion-panel-header>\n\n <ng-template matExpansionPanelContent>\n <p class=\"small\">\n Link tables the build couldn't fold into a relationship \u2014 so\n neither they nor what they link can be reported on.\n </p>\n <div class=\"pair-list\">\n @for (table of unlinkedJunctions(); track table.table) {\n <div class=\"pair-row\">\n <span class=\"pair-name\">{{ table.table }}</span>\n <span>{{ table.reason }}</span>\n </div>\n }\n </div>\n </ng-template>\n </mat-expansion-panel>\n }\n </mat-accordion>\n }\n }\n </div>\n</div>\n", styles: [".hint{margin:0 0 12px;color:#0009;font-size:13px;line-height:1.5}.stat{display:flex;align-items:baseline;gap:12px}.stat>.label{flex:0 0 150px;white-space:nowrap;color:#0009}.build-status{display:flex;flex-direction:column;gap:8px}.pair-list{display:flex;flex-direction:column;gap:3px}.pair-row{display:flex;gap:12px}.pair-name{flex:0 0 140px;white-space:nowrap;color:#0009}.entities{display:block;padding:3px}.entities mat-panel-description{justify-content:flex-end;margin-right:8px}.entities .stage-chips{display:flex;gap:4px}.entities .entity-detail{display:flex;flex-direction:column;gap:8px}.catalog{display:block;padding:3px}.catalog .pair-name{flex-basis:220px}\n"] }]
|
|
3859
|
+
], template: "<div class=\"fs-column tab-body\">\n <p class=\"hint\">\n The semantic database is what the report AI uses to understand your data \u2014\n tables, relationships and the calculations in code. Rebuild it after\n significant application updates. The build runs in the background; progress\n appears in the dock at the bottom right and this dialog can be closed while\n it runs.\n </p>\n\n <div *fsSkeleton=\"status()\">\n @if (status(); as buildStatus) {\n <div class=\"build-status\">\n @if (buildStatus.detail.generatedAt) {\n <div class=\"stat\">\n <span class=\"label\">Last built</span>\n <fs-date\n [date]=\"buildStatus.detail.generatedAt\"\n format=\"date-time\">\n </fs-date>\n </div>\n <div class=\"stat\">\n <span class=\"label\">Coverage</span>\n <span>{{ coverage() }}</span>\n </div>\n } @else {\n <fs-message-warning>\n The semantic database has not been built yet \u2014 the report AI can't\n answer data questions until it is. Run the first build.\n </fs-message-warning>\n }\n @if (buildStatus.build) {\n <div class=\"stat\">\n <span class=\"label\">Last build run</span>\n <span>\n {{ buildStateLabel() }}\n @if (buildStatus.build.message) {\n \u2014 {{ buildStatus.build.message }}\n }\n </span>\n </div>\n }\n </div>\n\n <!-- Everything the build read, in one list: entities (a table + a model +\n a handler) and standalone code classes (domain logic with no table).\n The AI reads code from both, so splitting them into two sections\n would hide half of what was built. Each row drills into its shape,\n enum values, computed fields and cached function counts \u2014 the counts\n link into the functions dialog. -->\n @if (units().length) {\n <h2>Included ({{ units().length }})</h2>\n <div class=\"subheading\">\n Everything the report AI was built from. Entities can be queried;\n code classes are logic it reads and translates into SQL.\n </div>\n\n <input\n class=\"unit-filter\"\n type=\"search\"\n placeholder=\"Filter by name, class or key\u2026\"\n [value]=\"filter()\"\n (input)=\"setFilter($any($event.target).value)\">\n\n <mat-accordion class=\"entities\">\n @for (unit of filteredUnits(); track unit.key) {\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <mat-panel-title>{{ unit.name }}</mat-panel-title>\n <mat-panel-description>\n <div class=\"stage-chips\">\n @if (!unit.hasTable) {\n <fs-chip size=\"tiny\" matTooltip=\"Domain logic with no table \u2014 read, never queried\">\n code\n </fs-chip>\n }\n <!-- Silence means built. A chip here means an interrupted\n build left this row owing content. -->\n @for (stage of pendingStages(unit); track stage) {\n <fs-chip\n size=\"tiny\"\n [backgroundColor]=\"pendingColor()\"\n color=\"#ffffff\"\n matTooltip=\"Needs building\">\n {{ stage }}\n </fs-chip>\n }\n </div>\n </mat-panel-description>\n </mat-expansion-panel-header>\n\n <!-- Deferred: the body only exists once expanded. -->\n <ng-template matExpansionPanelContent>\n <div class=\"entity-detail\">\n @if (unit.model) {\n <div class=\"stat\">\n <span class=\"label\">Class</span>\n <span>{{ unit.model }}</span>\n </div>\n }\n @if (unit.entity; as entity) {\n <div class=\"stat\">\n <span class=\"label\">Table</span>\n <span>{{ entity.table }}</span>\n </div>\n <!-- The handle the report AI addresses this entity by in\n describe_entity / compile_query. -->\n <div class=\"stat\">\n <span class=\"label\">Entity key</span>\n <span>{{ entity.key }}</span>\n </div>\n <div class=\"stat\">\n <span class=\"label\">Columns</span>\n <span>{{ entity.columns }}</span>\n </div>\n <div class=\"stat\">\n <span class=\"label\">Relationships</span>\n <span>{{ entity.relationships }}</span>\n </div>\n @if (entity.enums.length) {\n <div class=\"stat\">\n <span class=\"label\">Enumeration values</span>\n <div class=\"pair-list\">\n @for (enumColumn of entity.enums; track enumColumn.column) {\n <div class=\"pair-row\">\n <span class=\"pair-name\">{{ enumColumn.column }}</span>\n <span>{{ enumColumn.values.join(', ') }}</span>\n </div>\n }\n </div>\n </div>\n }\n @if (entity.computedFields.length) {\n <div class=\"stat\">\n <span class=\"label\">Computed fields</span>\n <span>{{ entity.computedFields.join(', ') }}</span>\n </div>\n }\n <div class=\"stat\">\n <span class=\"label\">Model functions</span>\n @if (entity.modelFunctionCount) {\n <a\n class=\"count-link\"\n (click)=\"openFunctions(unit, 'model')\">\n {{ entity.modelFunctionCount }}\n </a>\n } @else {\n <span>0</span>\n }\n </div>\n <div class=\"stat\">\n <span class=\"label\">Handler functions</span>\n @if (entity.handlerFunctionCount) {\n <a\n class=\"count-link\"\n (click)=\"openFunctions(unit, 'handler')\">\n {{ entity.handlerFunctionCount }}\n </a>\n } @else {\n <span>0</span>\n }\n </div>\n } @else {\n <div class=\"stat\">\n <span class=\"label\">Code functions</span>\n @if (unit.functionCount) {\n <a\n class=\"count-link\"\n (click)=\"openFunctions(unit, 'class')\">\n {{ unit.functionCount }}\n </a>\n } @else {\n <span>0</span>\n }\n </div>\n }\n </div>\n </ng-template>\n </mat-expansion-panel>\n }\n </mat-accordion>\n\n @if (!filteredUnits().length) {\n <div class=\"subheading\">Nothing matches \u201C{{ filter() }}\u201D.</div>\n }\n }\n\n <!-- The other half of the picture: tables the AI can't reach. Excluded\n ones have no model; unlinked junctions have a composite primary key\n but were folded into no relationship, so nothing can navigate to\n them. Both are invisible by omission \u2014 no error, just a capability\n that silently isn't there. Collapsed by default: the excluded list is\n long and mostly framework plumbing. -->\n @if (hasCatalog()) {\n <h2>Not in the semantic database</h2>\n <div class=\"subheading\">\n Tables the report AI can't see. Give one a model to promote it to an\n entity, then rebuild.\n </div>\n <mat-accordion class=\"catalog\">\n @if (excludedTables().length) {\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <mat-panel-title>\n Tables excluded ({{ excludedTables().length }})\n </mat-panel-title>\n </mat-expansion-panel-header>\n\n <!-- Deferred: the body only exists once expanded. -->\n <ng-template matExpansionPanelContent>\n <div class=\"pair-list\">\n @for (table of excludedTables(); track table.table) {\n <div class=\"pair-row\">\n <span class=\"pair-name\">{{ table.table }}</span>\n <span>{{ table.reason }}</span>\n </div>\n }\n </div>\n </ng-template>\n </mat-expansion-panel>\n }\n @if (unlinkedJunctions().length) {\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <mat-panel-title>\n Junctions not linked ({{ unlinkedJunctions().length }})\n </mat-panel-title>\n </mat-expansion-panel-header>\n\n <ng-template matExpansionPanelContent>\n <p class=\"small\">\n Link tables the build couldn't fold into a relationship \u2014 so\n neither they nor what they link can be reported on.\n </p>\n <div class=\"pair-list\">\n @for (table of unlinkedJunctions(); track table.table) {\n <div class=\"pair-row\">\n <span class=\"pair-name\">{{ table.table }}</span>\n <span>{{ table.reason }}</span>\n </div>\n }\n </div>\n </ng-template>\n </mat-expansion-panel>\n }\n </mat-accordion>\n }\n }\n </div>\n</div>\n", styles: [".hint{margin:0 0 12px;color:#0009;font-size:13px;line-height:1.5}.stat{display:flex;align-items:baseline;gap:12px}.stat>.label{flex:0 0 150px;white-space:nowrap;color:#0009}.build-status{display:flex;flex-direction:column;gap:8px}.pair-list{display:flex;flex-direction:column;gap:3px}.pair-row{display:flex;gap:12px}.pair-name{flex:0 0 140px;white-space:nowrap;color:#0009}.unit-filter{width:100%;box-sizing:border-box;margin:8px 0 4px;padding:6px 10px;border:1px solid rgba(0,0,0,.2);border-radius:4px;font:inherit}.unit-filter:focus{outline:none;border-color:#00000080}.entities{display:block;padding:3px}.entities mat-panel-description{justify-content:flex-end;margin-right:8px}.entities .stage-chips{display:flex;gap:4px}.entities .entity-detail{display:flex;flex-direction:column;gap:8px}.catalog{display:block;padding:3px}.catalog .pair-name{flex-basis:220px}\n"] }]
|
|
3774
3860
|
}], ctorParameters: () => [] });
|
|
3775
3861
|
|
|
3776
3862
|
// Matches the backend cap (ReportsView::INSTRUCTIONS_MAX_LENGTH) — the document
|
|
@@ -3916,6 +4002,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
3916
4002
|
// title, creates the report, then routes to it. The header gear opens the
|
|
3917
4003
|
// global Report Settings dialog (AI knowledge document + semantic-database
|
|
3918
4004
|
// build), which lives in this package. No routing is hard-coded here.
|
|
4005
|
+
//
|
|
4006
|
+
// Reports are soft-deletable: the row kebab deletes, "Show Deleted" flips the
|
|
4007
|
+
// list into deleted mode (the backend hides deleted reports otherwise), and in
|
|
4008
|
+
// that mode fs-list swaps Delete for Restore, which calls the undelete action
|
|
4009
|
+
// endpoint.
|
|
3919
4010
|
class FsAiReportsComponent {
|
|
3920
4011
|
// API base for the reports endpoints; override to mount elsewhere.
|
|
3921
4012
|
basePath = input('reports');
|
|
@@ -3926,6 +4017,9 @@ class FsAiReportsComponent {
|
|
|
3926
4017
|
createPattern = input(':id');
|
|
3927
4018
|
list = null;
|
|
3928
4019
|
listConfig;
|
|
4020
|
+
// Template-side handle on the enum, so the name cell can tell a deleted row
|
|
4021
|
+
// (shown in Show Deleted mode) from a live one.
|
|
4022
|
+
deletedState = ReportState.Deleted;
|
|
3929
4023
|
_reportData = inject(ReportData);
|
|
3930
4024
|
_router = inject(Router);
|
|
3931
4025
|
_route = inject(ActivatedRoute);
|
|
@@ -4000,6 +4094,28 @@ class FsAiReportsComponent {
|
|
|
4000
4094
|
click: () => this.createReport(),
|
|
4001
4095
|
},
|
|
4002
4096
|
],
|
|
4097
|
+
rowActions: [
|
|
4098
|
+
{
|
|
4099
|
+
click: (report) => this._reportData.delete(report.id),
|
|
4100
|
+
remove: {
|
|
4101
|
+
title: 'Confirm',
|
|
4102
|
+
template: 'Are you sure you would like to delete this report?',
|
|
4103
|
+
},
|
|
4104
|
+
menu: true,
|
|
4105
|
+
label: 'Delete',
|
|
4106
|
+
},
|
|
4107
|
+
],
|
|
4108
|
+
// `filter: true` makes fs-list render the Show Deleted toggle and send
|
|
4109
|
+
// `query` with the fetch; in that mode it also swaps the row's Delete
|
|
4110
|
+
// action for Restore.
|
|
4111
|
+
restore: {
|
|
4112
|
+
query: { state: ReportState.Deleted },
|
|
4113
|
+
filter: true,
|
|
4114
|
+
filterLabel: 'Show Deleted',
|
|
4115
|
+
menuLabel: 'Restore',
|
|
4116
|
+
reload: true,
|
|
4117
|
+
click: (report) => this._reportData.undelete(report.id),
|
|
4118
|
+
},
|
|
4003
4119
|
fetch: (query) => {
|
|
4004
4120
|
return this._reportData.reports(query)
|
|
4005
4121
|
.pipe(map((reports) => ({ data: reports ?? [] })));
|
|
@@ -4007,7 +4123,7 @@ class FsAiReportsComponent {
|
|
|
4007
4123
|
};
|
|
4008
4124
|
}
|
|
4009
4125
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FsAiReportsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4010
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.
|
|
4126
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: FsAiReportsComponent, isStandalone: true, selector: "fs-ai-reports", inputs: { basePath: { classPropertyName: "basePath", publicName: "basePath", isSignal: true, isRequired: false, transformFunction: null }, openPattern: { classPropertyName: "openPattern", publicName: "openPattern", isSignal: true, isRequired: false, transformFunction: null }, createPattern: { classPropertyName: "createPattern", publicName: "createPattern", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "list", first: true, predicate: FsListComponent, descendants: true }], ngImport: i0, template: "<fs-list\n [config]=\"listConfig\"\n #list>\n\n <fs-list-column title=\"Name\">\n <ng-template\n fs-list-cell\n let-row=\"row\">\n <!-- A deleted report can't be opened (the backend only assembles active\n ones), so in Show Deleted mode the name is plain text \u2014 restore it\n from the row menu first. -->\n @if (row.state === deletedState) {\n {{ row.name }}\n } @else {\n <a [routerLink]=\"openLink(row)\">{{ row.name }}</a>\n }\n </ng-template>\n </fs-list-column>\n\n <fs-list-column title=\"Modified\">\n <ng-template\n fs-list-cell\n let-row=\"row\">\n <fs-date [date]=\"row.modifyDate\"></fs-date>\n </ng-template>\n </fs-list-column>\n\n</fs-list>\n", styles: ["a{cursor:pointer}\n"], dependencies: [{ kind: "ngmodule", type: FsDateModule }, { kind: "component", type: i2$4.FsDateComponent, selector: "fs-date", inputs: ["date", "format", "timezone"] }, { kind: "ngmodule", type: FsListModule }, { kind: "component", type: i1.FsListComponent, selector: "fs-list", inputs: ["config", "loaderLines", "cellRowType"], outputs: ["filtersReady"] }, { kind: "directive", type: i1.FsListColumnDirective, selector: "fs-list-column", inputs: ["show", "title", "name", "customizable", "sortable", "sortableDefault", "sortableDirection", "direction", "align", "width", "class"] }, { kind: "directive", type: i1.FsListCellDirective, selector: "[fs-list-cell],[fsListCell]", inputs: ["colspan", "align", "class", "fsListCell", "configTyping"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4011
4127
|
}
|
|
4012
4128
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FsAiReportsComponent, decorators: [{
|
|
4013
4129
|
type: Component,
|
|
@@ -4015,7 +4131,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
4015
4131
|
FsDateModule,
|
|
4016
4132
|
FsListModule,
|
|
4017
4133
|
RouterLink,
|
|
4018
|
-
], template: "<fs-list\n [config]=\"listConfig\"\n #list>\n\n <fs-list-column title=\"Name\">\n <ng-template\n fs-list-cell\n let-row=\"row\">\n <a [routerLink]=\"openLink(row)\">{{ row.name }}</a>\n </ng-template>\n </fs-list-column>\n\n <fs-list-column title=\"Modified\">\n <ng-template\n fs-list-cell\n let-row=\"row\">\n <fs-date [date]=\"row.modifyDate\"></fs-date>\n </ng-template>\n </fs-list-column>\n\n</fs-list>\n", styles: ["a{cursor:pointer}\n"] }]
|
|
4134
|
+
], template: "<fs-list\n [config]=\"listConfig\"\n #list>\n\n <fs-list-column title=\"Name\">\n <ng-template\n fs-list-cell\n let-row=\"row\">\n <!-- A deleted report can't be opened (the backend only assembles active\n ones), so in Show Deleted mode the name is plain text \u2014 restore it\n from the row menu first. -->\n @if (row.state === deletedState) {\n {{ row.name }}\n } @else {\n <a [routerLink]=\"openLink(row)\">{{ row.name }}</a>\n }\n </ng-template>\n </fs-list-column>\n\n <fs-list-column title=\"Modified\">\n <ng-template\n fs-list-cell\n let-row=\"row\">\n <fs-date [date]=\"row.modifyDate\"></fs-date>\n </ng-template>\n </fs-list-column>\n\n</fs-list>\n", styles: ["a{cursor:pointer}\n"] }]
|
|
4019
4135
|
}], ctorParameters: () => [], propDecorators: { list: [{
|
|
4020
4136
|
type: ViewChild,
|
|
4021
4137
|
args: [FsListComponent]
|
|
@@ -4273,7 +4389,7 @@ class FsAiReportComponent {
|
|
|
4273
4389
|
ReportPptxService,
|
|
4274
4390
|
ReportPdfService,
|
|
4275
4391
|
// Tree-shaken ECharts core + the 'report' house theme, loaded lazily.
|
|
4276
|
-
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-
|
|
4392
|
+
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-CMOBB-Er.mjs').then((module) => module.default) }),
|
|
4277
4393
|
], viewQueries: [{ propertyName: "_split", first: true, predicate: ["split"], descendants: true, static: true }, { propertyName: "_chatPanel", first: true, predicate: ["chatPanel"], descendants: true, read: ElementRef, static: true }], ngImport: i0, template: "<div class=\"fs-ai-report fs-column\">\n\n <div class=\"fs-ai-report__header fs-row.align-center\">\n <h2 class=\"fs-ai-report__title fs-flex\">{{ report?.name }}</h2>\n\n @if (report) {\n <!-- Report-level filters, inline with the toolbar actions. fs-filter\n reads its config once at init, so it's keyed on the filter\n signature: when the report-level filter set changes the block is\n recreated and re-reads the rebuilt config. -->\n @if (reportHasFilters) {\n @for (key of [reportFilterKey]; track key) {\n <fs-filter\n class=\"fs-ai-report__filters\"\n [config]=\"reportFilterConfig\">\n </fs-filter>\n }\n }\n\n <button\n mat-icon-button\n type=\"button\"\n [matTooltip]=\"editMode ? 'Done' : 'Edit layout'\"\n (click)=\"toggleEditMode()\">\n <mat-icon>{{ editMode ? 'lock' : 'open_with' }}</mat-icon>\n </button>\n\n <button\n mat-icon-button\n type=\"button\"\n matTooltip=\"Export\"\n [fsMenuTriggerFor]=\"exportMenu\">\n <mat-icon>download</mat-icon>\n </button>\n <fs-menu #exportMenu>\n <ng-template\n fs-menu-item\n (click)=\"exportPowerpoint()\">\n Export PowerPoint\n </ng-template>\n <ng-template\n fs-menu-item\n (click)=\"exportPdf()\">\n Export PDF\n </ng-template>\n </fs-menu>\n\n <button\n mat-icon-button\n type=\"button\"\n matTooltip=\"Report settings\"\n (click)=\"reportSettings()\">\n <mat-icon>settings</mat-icon>\n </button>\n }\n </div>\n\n <div\n #split\n class=\"fs-ai-report__split fs-row.align-start\">\n <fs-ai-chat\n #chatPanel\n class=\"chat\"\n [basePath]=\"basePath()\"\n [requestData]=\"{ reportId: reportId() }\"\n (response)=\"onChatResponse($event)\">\n </fs-ai-chat>\n\n <div\n class=\"resizer\"\n (pointerdown)=\"onResizeStart($event)\">\n </div>\n\n <div class=\"viewer fs-flex fs-column\">\n @if (report) {\n <app-report-canvas\n [report]=\"report\"\n [editMode]=\"editMode\"\n (componentSettings)=\"componentSettings($event)\"\n (reportChanged)=\"onCanvasReportChanged()\"\n (editDone)=\"toggleEditMode()\">\n </app-report-canvas>\n }\n </div>\n </div>\n</div>\n", styles: [".fs-ai-report{height:100%;min-height:500px;display:flex;flex-direction:column}.fs-ai-report__header{flex:0 0 auto;margin-bottom:10px}.fs-ai-report__title{margin:0;min-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fs-ai-report__filters{flex:0 1 auto;min-width:0;display:block;margin:0 5px 0 0}.fs-ai-report__filters ::ng-deep .filter-bar-container{align-items:center!important}.fs-ai-report__filters ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none}.fs-ai-report__split{flex:1 1 auto;min-height:0}.fs-ai-report .chat{display:block;flex:0 0 25%;width:100%;height:100%;min-height:400px;min-width:0;border:none}.fs-ai-report .viewer{display:flex;flex-direction:column;min-width:0;height:100%;overflow:hidden}.fs-ai-report .resizer{flex:0 0 11px;align-self:stretch;display:flex;justify-content:center;cursor:col-resize;touch-action:none;-webkit-user-select:none;user-select:none}.fs-ai-report .resizer:before{content:\"\";width:3px;border-radius:3px;background:transparent;transition:background-color .12s ease}.fs-ai-report .resizer:hover:before{background:var(--brand-primary-color)}.fs-ai-report__split.resizing{cursor:col-resize;-webkit-user-select:none;user-select:none}.fs-ai-report__split.resizing .resizer:before{background:var(--brand-primary-color)}.fs-ai-report__split.resizing .chat,.fs-ai-report__split.resizing .viewer{pointer-events:none}\n"], dependencies: [{ kind: "ngmodule", type: FsFilterModule }, { kind: "component", type: i2.FilterComponent, selector: "fs-filter", inputs: ["config"], outputs: ["closed", "opened", "ready"] }, { kind: "ngmodule", type: FsMenuModule }, { kind: "component", type: i2$3.FsMenuComponent, selector: "fs-menu", inputs: ["class", "buttonClass", "buttonType", "buttonColor"], outputs: ["opened", "closed"] }, { kind: "directive", type: i2$3.FsMenuItemDirective, selector: "fs-menu-group,[fs-menu-item]" }, { kind: "directive", type: i2$3.FsMenuTriggerDirective, selector: "[fsMenuTriggerFor]", inputs: ["fsMenuTriggerFor"] }, { kind: "component", type: FsAiChatComponent, selector: "fs-ai-chat", inputs: ["basePath", "requestData", "introMessage"], outputs: ["response"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3$1.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4$2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i2$5.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: ReportCanvasComponent, selector: "app-report-canvas", inputs: ["report", "editMode"], outputs: ["componentSettings", "reportChanged", "editDone"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4278
4394
|
}
|
|
4279
4395
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FsAiReportComponent, decorators: [{
|
|
@@ -4286,7 +4402,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
4286
4402
|
ReportPptxService,
|
|
4287
4403
|
ReportPdfService,
|
|
4288
4404
|
// Tree-shaken ECharts core + the 'report' house theme, loaded lazily.
|
|
4289
|
-
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-
|
|
4405
|
+
provideEchartsCore({ echarts: () => import('./firestitch-report-echarts-CMOBB-Er.mjs').then((module) => module.default) }),
|
|
4290
4406
|
], imports: [
|
|
4291
4407
|
FsFilterModule,
|
|
4292
4408
|
FsMenuModule,
|
|
@@ -4316,5 +4432,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
4316
4432
|
* Generated bundle index. Do not edit.
|
|
4317
4433
|
*/
|
|
4318
4434
|
|
|
4319
|
-
export { FsAiReportsComponent as F, REPORT_CHART_COLORS_CSS as R, ReportComponent as a, FsAiReportComponent as b, ReportData as c, ReportService as d, ReportFilterStateService as e, FREQUENCY_OPTIONS as f };
|
|
4320
|
-
//# sourceMappingURL=firestitch-report-firestitch-report-
|
|
4435
|
+
export { FsAiReportsComponent as F, REPORT_CHART_COLORS_CSS as R, ReportComponent as a, FsAiReportComponent as b, ReportData as c, ReportService as d, ReportFilterStateService as e, FREQUENCY_OPTIONS as f, ReportState as g };
|
|
4436
|
+
//# sourceMappingURL=firestitch-report-firestitch-report-DwgbMhwr.mjs.map
|