@memberjunction/ng-dashboards 6.1.0-edge.3 → 6.1.0-edge.5
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/dist/AI/components/analytics/cost-budget/cost-budget.component.d.ts +7 -1
- package/dist/AI/components/analytics/cost-budget/cost-budget.component.d.ts.map +1 -1
- package/dist/AI/components/analytics/cost-budget/cost-budget.component.js +35 -12
- package/dist/AI/components/analytics/cost-budget/cost-budget.component.js.map +1 -1
- package/dist/AI/components/analytics/realtime/realtime-transcripts-data.d.ts.map +1 -1
- package/dist/AI/components/analytics/realtime/realtime-transcripts-data.js +4 -4
- package/dist/AI/components/analytics/realtime/realtime-transcripts-data.js.map +1 -1
- package/dist/Admin/base-admin-container.component.d.ts +8 -0
- package/dist/Admin/base-admin-container.component.d.ts.map +1 -1
- package/dist/Admin/base-admin-container.component.js +18 -0
- package/dist/Admin/base-admin-container.component.js.map +1 -1
- package/dist/DatabaseDesigner/services/database-designer.engine.d.ts.map +1 -1
- package/dist/DatabaseDesigner/services/database-designer.engine.js +5 -9
- package/dist/DatabaseDesigner/services/database-designer.engine.js.map +1 -1
- package/package.json +67 -63
|
@@ -79,7 +79,13 @@ export declare class AnalyticsCostBudgetComponent extends BaseAngularComponent i
|
|
|
79
79
|
private rateKey;
|
|
80
80
|
/** The cache rate for a run's model+vendor, or undefined when no active cost row is configured. */
|
|
81
81
|
private rateFor;
|
|
82
|
-
/**
|
|
82
|
+
/**
|
|
83
|
+
* Build the per-model+vendor rate lookup, normalizing each per-unit price to currency-per-token.
|
|
84
|
+
*
|
|
85
|
+
* The scale comes from the unit type's DriverClass, not its display name — the name is editable
|
|
86
|
+
* metadata (`Per 1M Tokens`) while the driver class is the contract the pricing drivers register
|
|
87
|
+
* under, so this cannot drift the way a hardcoded name table does.
|
|
88
|
+
*/
|
|
83
89
|
private buildCacheRateMap;
|
|
84
90
|
/** Sum net cache savings across a set of runs using each run's model+vendor rate. */
|
|
85
91
|
private sumCacheSavings;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cost-budget.component.d.ts","sourceRoot":"","sources":["../../../../../src/AI/components/analytics/cost-budget/cost-budget.component.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACuB,YAAY,EACtC,MAAM,EAAE,SAAS,EACpB,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"cost-budget.component.d.ts","sourceRoot":"","sources":["../../../../../src/AI/components/analytics/cost-budget/cost-budget.component.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,6 +8,7 @@ import { Component, Input, Output, EventEmitter, ChangeDetectorRef, inject } fro
|
|
|
8
8
|
import { Subject } from 'rxjs';
|
|
9
9
|
import { RunView } from '@memberjunction/core';
|
|
10
10
|
import { NormalizeUUID } from '@memberjunction/global';
|
|
11
|
+
import { TOKEN_PRICE_UNIT_TYPE_DIVISORS } from '@memberjunction/ai-engine-base';
|
|
11
12
|
import { cacheHitRate, hasCacheActivity, netCacheSavings } from '../../../services/cache-metrics';
|
|
12
13
|
import { BaseAngularComponent } from '@memberjunction/ng-base-types';
|
|
13
14
|
import * as i0 from "@angular/core";
|
|
@@ -294,12 +295,6 @@ const FIELDS = [
|
|
|
294
295
|
'ID', 'RunAt', 'Cost', 'TotalCost', 'TokensPrompt', 'TokensCompletion',
|
|
295
296
|
'TokensUsed', 'TokensCacheRead', 'TokensCacheWrite', 'ModelID', 'Model', 'VendorID', 'Vendor', 'Success'
|
|
296
297
|
];
|
|
297
|
-
// AIModelCost UnitType name -> token divisor, matching the BasePriceUnitType driver classes.
|
|
298
|
-
const UNIT_DIVISORS = {
|
|
299
|
-
'Per Million Tokens': 1_000_000,
|
|
300
|
-
'Per Hundred Thousand Tokens': 100_000,
|
|
301
|
-
'Per Thousand Tokens': 1_000
|
|
302
|
-
};
|
|
303
298
|
const TIME_RANGE_OPTIONS = ['Today', '7d', '30d', 'MTD'];
|
|
304
299
|
const TREEMAP_COLORS = [
|
|
305
300
|
'var(--mj-brand-primary)',
|
|
@@ -391,7 +386,7 @@ export class AnalyticsCostBudgetComponent extends BaseAngularComponent {
|
|
|
391
386
|
const modelFilter = this.buildModelFilter();
|
|
392
387
|
const currentFilter = this.combineDateAndModelFilter(currentStart, now, modelFilter);
|
|
393
388
|
const prevFilter = this.combineDateAndModelFilter(previousStart, currentStart, modelFilter);
|
|
394
|
-
const [currentResult, prevResult, rateResult] = await rv.RunViews([
|
|
389
|
+
const [currentResult, prevResult, rateResult, unitTypeResult] = await rv.RunViews([
|
|
395
390
|
{
|
|
396
391
|
EntityName: 'MJ: AI Prompt Runs',
|
|
397
392
|
ExtraFilter: currentFilter,
|
|
@@ -409,13 +404,26 @@ export class AnalyticsCostBudgetComponent extends BaseAngularComponent {
|
|
|
409
404
|
{
|
|
410
405
|
EntityName: 'MJ: AI Model Costs',
|
|
411
406
|
ExtraFilter: `Status='Active' AND ProcessingType='Realtime'`,
|
|
412
|
-
Fields: ['ModelID', 'VendorID', 'InputPricePerUnit', 'OutputPricePerUnit', 'CacheReadPricePerUnit', 'CacheWritePricePerUnit', '
|
|
407
|
+
Fields: ['ModelID', 'VendorID', 'InputPricePerUnit', 'OutputPricePerUnit', 'CacheReadPricePerUnit', 'CacheWritePricePerUnit', 'UnitTypeID'],
|
|
408
|
+
ResultType: 'simple'
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
EntityName: 'MJ: AI Model Price Unit Types',
|
|
412
|
+
Fields: ['ID', 'DriverClass'],
|
|
413
413
|
ResultType: 'simple'
|
|
414
414
|
}
|
|
415
415
|
]);
|
|
416
416
|
this.allRuns = (currentResult?.Results ?? []);
|
|
417
417
|
this.previousPeriodRuns = (prevResult?.Results ?? []);
|
|
418
|
-
|
|
418
|
+
// A failed unit-type view is NOT the same as "these rows are unpriceable". Without the
|
|
419
|
+
// driver classes every rate row falls into the `continue` below, and cache savings
|
|
420
|
+
// render as a confident 0 instead of an error — the figure most likely to be believed.
|
|
421
|
+
// Say so rather than let the empty map speak for it.
|
|
422
|
+
if (unitTypeResult && !unitTypeResult.Success) {
|
|
423
|
+
console.error('Cost & Budget: price unit types failed to load; cache-savings figures will read 0. ' +
|
|
424
|
+
unitTypeResult.ErrorMessage);
|
|
425
|
+
}
|
|
426
|
+
this.buildCacheRateMap(rateResult?.Results ?? [], unitTypeResult?.Results ?? []);
|
|
419
427
|
this.computeKpis();
|
|
420
428
|
this.computeDailyBars();
|
|
421
429
|
this.computeTreemap();
|
|
@@ -438,11 +446,26 @@ export class AnalyticsCostBudgetComponent extends BaseAngularComponent {
|
|
|
438
446
|
rateFor(run) {
|
|
439
447
|
return this.cacheRates.get(this.rateKey(run.ModelID, run.VendorID));
|
|
440
448
|
}
|
|
441
|
-
/**
|
|
442
|
-
|
|
449
|
+
/**
|
|
450
|
+
* Build the per-model+vendor rate lookup, normalizing each per-unit price to currency-per-token.
|
|
451
|
+
*
|
|
452
|
+
* The scale comes from the unit type's DriverClass, not its display name — the name is editable
|
|
453
|
+
* metadata (`Per 1M Tokens`) while the driver class is the contract the pricing drivers register
|
|
454
|
+
* under, so this cannot drift the way a hardcoded name table does.
|
|
455
|
+
*/
|
|
456
|
+
buildCacheRateMap(rows, unitTypes) {
|
|
443
457
|
this.cacheRates.clear();
|
|
458
|
+
const driverClassByUnitType = new Map(unitTypes.filter(u => u.DriverClass).map(u => [NormalizeUUID(u.ID), u.DriverClass]));
|
|
444
459
|
for (const row of rows) {
|
|
445
|
-
const
|
|
460
|
+
const driverClass = driverClassByUnitType.get(NormalizeUUID(row.UnitTypeID ?? ''));
|
|
461
|
+
const divisor = TOKEN_PRICE_UNIT_TYPE_DIVISORS[driverClass ?? ''];
|
|
462
|
+
if (divisor === undefined) {
|
|
463
|
+
// A non-token unit type (per minute/hour/image), or one this build has no driver
|
|
464
|
+
// for. Defaulting to the per-1M-token divisor would divide an hourly audio rate by
|
|
465
|
+
// a million and report a savings figure that is pure noise; no rate at all is the
|
|
466
|
+
// honest answer.
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
446
469
|
const inputRate = (row.InputPricePerUnit ?? 0) / divisor;
|
|
447
470
|
// Cache read/write fall back to the input rate when no distinct rate is recorded — exactly
|
|
448
471
|
// as the server-side cost calculator does — which makes the corresponding savings term 0.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cost-budget.component.js","sourceRoot":"","sources":["../../../../../src/AI/components/analytics/cost-budget/cost-budget.component.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Cost & Budget Analytics -- Option B.\n *\n * Displays cost KPIs with period deltas, a daily cost bar chart with anomaly detection,\n * a cost breakdown treemap, and a detailed cost-by-model table with CSV export.\n */\n\nimport {\n Component, Input, Output, EventEmitter,\n OnInit, OnDestroy, ChangeDetectorRef, inject\n} from '@angular/core';\nimport { Subject } from 'rxjs';\nimport { RunView } from '@memberjunction/core';\nimport { NormalizeUUID } from '@memberjunction/global';\nimport { CacheRate, CacheTokenTotals, cacheHitRate, hasCacheActivity, netCacheSavings } from '../../../services/cache-metrics';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport { GlobalFilterState } from '../../../interfaces/analytics-preferences.interface';\n\n// ── Interfaces ──\n\ninterface PromptRunRecord {\n ID: string;\n RunAt: string;\n Cost: number | null;\n TotalCost: number | null;\n TokensPrompt: number | null;\n TokensCompletion: number | null;\n TokensUsed: number | null;\n TokensCacheRead: number | null;\n TokensCacheWrite: number | null;\n ModelID: string | null;\n Model: string | null;\n VendorID: string | null;\n Vendor: string | null;\n Success: boolean;\n}\n\ninterface CostKpi {\n Label: string;\n Value: string;\n Delta: number | null;\n DeltaDirection: 'up' | 'down' | 'stable';\n Highlighted: boolean;\n Icon: string;\n}\n\ninterface DailyBar {\n Date: string;\n Label: string;\n Cost: number;\n HeightPercent: number;\n IsAnomaly: boolean;\n}\n\ninterface TreemapCell {\n Label: string;\n Cost: number;\n Percent: number;\n Color: string;\n GridArea: string;\n}\n\ninterface CostByModelRow {\n Model: string;\n Vendor: string;\n Runs: number;\n InputTokens: number;\n OutputTokens: number;\n CacheReadTokens: number;\n CacheWriteTokens: number;\n CacheHitRate: number;\n CacheSavings: number;\n InputCost: number;\n OutputCost: number;\n TotalCost: number;\n PercentOfTotal: number;\n}\n\nconst FIELDS = [\n 'ID', 'RunAt', 'Cost', 'TotalCost', 'TokensPrompt', 'TokensCompletion',\n 'TokensUsed', 'TokensCacheRead', 'TokensCacheWrite', 'ModelID', 'Model', 'VendorID', 'Vendor', 'Success'\n];\n\ninterface ModelCostRow {\n ModelID: string | null;\n VendorID: string | null;\n InputPricePerUnit: number | null;\n OutputPricePerUnit: number | null;\n CacheReadPricePerUnit: number | null;\n CacheWritePricePerUnit: number | null;\n UnitType: string | null;\n}\n\n// AIModelCost UnitType name -> token divisor, matching the BasePriceUnitType driver classes.\nconst UNIT_DIVISORS: Record<string, number> = {\n 'Per Million Tokens': 1_000_000,\n 'Per Hundred Thousand Tokens': 100_000,\n 'Per Thousand Tokens': 1_000\n};\n\nconst TIME_RANGE_OPTIONS = ['Today', '7d', '30d', 'MTD'];\n\nconst TREEMAP_COLORS = [\n 'var(--mj-brand-primary)',\n 'var(--mj-brand-accent, var(--mj-brand-primary-hover))',\n 'var(--mj-status-info)',\n 'var(--mj-status-success)',\n 'var(--mj-status-warning)',\n 'var(--mj-text-disabled)'\n];\n\n@Component({\n standalone: false,\n selector: 'app-analytics-cost-budget',\n template: `\n\n @if (IsLoading) {\n <div class=\"loading-container\">\n <mj-loading text=\"Loading cost data...\"></mj-loading>\n </div>\n } @else {\n <!-- Cost KPI Row -->\n <div class=\"kpi-row\">\n @for (kpi of CostKpis; track kpi.Label) {\n <div class=\"kpi-card\" [class.kpi-card--highlighted]=\"kpi.Highlighted\">\n <div class=\"kpi-icon\">\n <i [class]=\"kpi.Icon\"></i>\n </div>\n <div class=\"kpi-content\">\n <div class=\"kpi-label\">{{ kpi.Label }}</div>\n <div class=\"kpi-value\">{{ kpi.Value }}</div>\n @if (kpi.Delta != null) {\n <div class=\"kpi-delta\"\n [class.kpi-delta--up]=\"kpi.DeltaDirection === 'up'\"\n [class.kpi-delta--down]=\"kpi.DeltaDirection === 'down'\">\n @if (kpi.DeltaDirection === 'up') {\n <i class=\"fa-solid fa-arrow-up\"></i>\n } @else if (kpi.DeltaDirection === 'down') {\n <i class=\"fa-solid fa-arrow-down\"></i>\n }\n {{ kpi.Delta | number:'1.1-1' }}% vs prev\n </div>\n }\n </div>\n </div>\n }\n </div>\n\n <!-- Two-Column Layout -->\n <div class=\"two-col\">\n <!-- Daily Cost Trend -->\n <div class=\"panel panel-chart\">\n <div class=\"panel-header\">\n <div class=\"panel-header__title\">\n <i class=\"fa-solid fa-chart-column panel-header__icon\"></i>\n Daily Cost Trend\n </div>\n </div>\n <div class=\"chart-body\">\n @if (DailyBars.length === 0) {\n <mj-empty-state Size=\"compact\" Variant=\"empty\" Icon=\"fa-solid fa-chart-column\"\n Title=\"No cost data for selected period\" />\n } @else {\n <div class=\"bar-chart\">\n <div class=\"bar-chart-area\">\n @if (AvgLinePercent > 0) {\n <div class=\"avg-line\" [style.bottom.%]=\"AvgLinePercent\">\n <span class=\"avg-label\">avg</span>\n </div>\n }\n @for (bar of DailyBars; track bar.Date) {\n <div class=\"bar-col\" [title]=\"bar.Label + ': ' + FormatCurrency(bar.Cost)\">\n <div\n class=\"bar\"\n [class.bar--anomaly]=\"bar.IsAnomaly\"\n [style.height.%]=\"bar.HeightPercent\"\n ></div>\n <div class=\"bar-label\">{{ bar.Label }}</div>\n </div>\n }\n </div>\n </div>\n }\n </div>\n </div>\n\n <!-- Cost Breakdown Treemap -->\n <div class=\"panel panel-treemap\">\n <div class=\"panel-header\">\n <div class=\"panel-header__title\">\n <i class=\"fa-solid fa-chart-pie panel-header__icon\"></i>\n Cost by Vendor\n </div>\n </div>\n <div class=\"treemap-body\">\n @if (TreemapCells.length === 0) {\n <mj-empty-state Size=\"compact\" Variant=\"empty\" Icon=\"fa-solid fa-chart-pie\"\n Title=\"No vendor cost data\" />\n } @else {\n <div class=\"treemap-grid\">\n @for (cell of TreemapCells; track cell.Label) {\n <div\n class=\"treemap-cell\"\n [style.background]=\"cell.Color\"\n [style.flex-basis.%]=\"cell.Percent\"\n [title]=\"cell.Label + ': ' + FormatCurrency(cell.Cost) + ' (' + (cell.Percent | number:'1.0-0') + '%)'\">\n <span class=\"treemap-label\">{{ cell.Label }}</span>\n <span class=\"treemap-value\">{{ FormatCurrency(cell.Cost) }}</span>\n <span class=\"treemap-pct\">{{ cell.Percent | number:'1.0-0' }}%</span>\n </div>\n }\n </div>\n }\n </div>\n </div>\n </div>\n\n <!-- Cost by Model Table -->\n <div class=\"panel\">\n <div class=\"panel-header\">\n <div class=\"panel-header__title\">\n <i class=\"fa-solid fa-table panel-header__icon\"></i>\n Cost by Model\n </div>\n <button class=\"export-btn\" (click)=\"ExportCSV()\">\n <i class=\"fa-solid fa-download\"></i>\n Export CSV\n </button>\n </div>\n <div class=\"table-wrapper\">\n <table class=\"data-table\">\n <thead>\n <tr>\n <th>Model</th>\n <th>Vendor</th>\n <th class=\"col-numeric\">Runs</th>\n <th class=\"col-numeric\">Input Tokens</th>\n <th class=\"col-numeric\">Output Tokens</th>\n <th class=\"col-numeric\" title=\"Share of input tokens served from the provider's prompt cache\">Cache Hit</th>\n <th class=\"col-numeric\">Input Cost</th>\n <th class=\"col-numeric\">Output Cost</th>\n <th class=\"col-numeric\">Total Cost</th>\n <th class=\"col-numeric\" title=\"Net dollars saved by caching vs. full input pricing (requires cache rates on AI Model Costs)\">Cache Saved</th>\n <th class=\"col-numeric\">% of Total</th>\n </tr>\n </thead>\n <tbody>\n @if (CostByModelRows.length === 0) {\n <tr><td colspan=\"11\" class=\"empty-row\">No data available</td></tr>\n }\n @for (row of CostByModelRows; track row.Model) {\n <tr>\n <td class=\"cell-model\">{{ row.Model }}</td>\n <td class=\"cell-vendor\">{{ row.Vendor }}</td>\n <td class=\"cell-numeric\">{{ row.Runs | number }}</td>\n <td class=\"cell-numeric\">{{ row.InputTokens | number }}</td>\n <td class=\"cell-numeric\">{{ row.OutputTokens | number }}</td>\n <td class=\"cell-numeric\">{{ (row.CacheReadTokens + row.CacheWriteTokens) > 0 ? ((row.CacheHitRate * 100 | number:'1.0-0') + '%') : '—' }}</td>\n <td class=\"cell-numeric\">{{ FormatCurrency(row.InputCost, 4) }}</td>\n <td class=\"cell-numeric\">{{ FormatCurrency(row.OutputCost, 4) }}</td>\n <td class=\"cell-numeric cell-cost\">{{ FormatCurrency(row.TotalCost) }}</td>\n <td class=\"cell-numeric\">{{ row.CacheSavings > 0 ? FormatCurrency(row.CacheSavings, 4) : '—' }}</td>\n <td class=\"cell-numeric\">{{ row.PercentOfTotal | number:'1.1-1' }}%</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n }\n `,\n styles: [`\n :host { display: block; }\n\n .loading-container {\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 300px;\n }\n\n /* ── KPI Row ── */\n .kpi-row {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n gap: 12px;\n margin: 16px 0;\n }\n\n .kpi-card {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n padding: 16px;\n display: flex;\n align-items: flex-start;\n gap: 12px;\n transition: transform 0.2s ease, box-shadow 0.2s ease;\n }\n\n .kpi-card:hover {\n transform: translateY(-2px);\n box-shadow: 0 4px 12px color-mix(in srgb, var(--mj-text-primary) 8%, transparent);\n }\n\n .kpi-card--highlighted {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 5%, var(--mj-bg-surface));\n }\n\n .kpi-icon {\n width: 36px;\n height: 36px;\n border-radius: 10px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 14px;\n flex-shrink: 0;\n }\n\n .kpi-card--highlighted .kpi-icon {\n background: color-mix(in srgb, var(--mj-brand-primary) 20%, var(--mj-bg-surface));\n }\n\n .kpi-label {\n font-size: 11px;\n font-weight: 600;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n }\n\n .kpi-value {\n font-size: 22px;\n font-weight: 700;\n color: var(--mj-text-primary);\n margin: 2px 0;\n letter-spacing: -0.02em;\n }\n\n .kpi-delta {\n font-size: 11px;\n color: var(--mj-text-muted);\n display: flex;\n align-items: center;\n gap: 3px;\n }\n\n .kpi-delta--up {\n color: var(--mj-status-error);\n }\n\n .kpi-delta--down {\n color: var(--mj-status-success);\n }\n\n /* ── Two-Column Layout ── */\n .two-col {\n display: grid;\n grid-template-columns: 3fr 2fr;\n gap: 16px;\n margin-bottom: 16px;\n }\n\n /* ── Panel ── */\n .panel {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n overflow: hidden;\n margin-bottom: 16px;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 14px 18px;\n border-bottom: 1px solid var(--mj-border-subtle);\n }\n\n .panel-header__title {\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-primary);\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .panel-header__icon {\n font-size: 13px;\n color: var(--mj-brand-primary);\n }\n\n .export-btn {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 5px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n font-size: 13px;\n cursor: pointer;\n transition: background 0.15s, color 0.15s;\n }\n\n .export-btn:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n\n /* ── Bar Chart ── */\n .chart-body {\n padding: 16px 18px;\n }\n\n .bar-chart {\n width: 100%;\n }\n\n .bar-chart-area {\n display: flex;\n align-items: flex-end;\n gap: 4px;\n height: 180px;\n position: relative;\n padding-bottom: 24px;\n }\n\n .bar-col {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n height: 100%;\n justify-content: flex-end;\n }\n\n .bar {\n width: 100%;\n max-width: 32px;\n border-radius: 4px 4px 0 0;\n background: var(--mj-brand-primary);\n transition: height 0.4s cubic-bezier(0.4, 0, 0.2, 1);\n min-height: 2px;\n }\n\n .bar--anomaly {\n background: var(--mj-status-error);\n }\n\n .bar-label {\n font-size: 10px;\n color: var(--mj-text-muted);\n margin-top: 4px;\n white-space: nowrap;\n }\n\n .avg-line {\n position: absolute;\n left: 0;\n right: 0;\n border-top: 2px dashed var(--mj-text-disabled);\n z-index: 1;\n pointer-events: none;\n }\n\n .avg-label {\n position: absolute;\n right: 0;\n top: -14px;\n font-size: 10px;\n color: var(--mj-text-disabled);\n font-weight: 600;\n }\n\n /* ── Treemap ── */\n .treemap-body {\n padding: 16px 18px;\n }\n\n .treemap-grid {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n min-height: 160px;\n }\n\n .treemap-cell {\n border-radius: 8px;\n padding: 12px;\n display: flex;\n flex-direction: column;\n justify-content: center;\n min-width: 80px;\n min-height: 70px;\n flex-grow: 1;\n color: var(--mj-text-inverse, white);\n transition: opacity 0.2s;\n }\n\n .treemap-cell:hover {\n opacity: 0.85;\n }\n\n .treemap-label {\n font-size: 12px;\n font-weight: 600;\n opacity: 0.95;\n }\n\n .treemap-value {\n font-size: 16px;\n font-weight: 700;\n margin-top: 2px;\n }\n\n .treemap-pct {\n font-size: 11px;\n opacity: 0.8;\n }\n\n /* ── Table ── */\n .table-wrapper {\n overflow-x: auto;\n }\n\n .data-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n }\n\n .data-table th,\n .data-table td {\n padding: 10px 14px;\n text-align: left;\n border-bottom: 1px solid var(--mj-border-subtle);\n }\n\n .data-table th {\n font-size: 11px;\n font-weight: 600;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.4px;\n background: var(--mj-bg-surface-card);\n position: sticky;\n top: 0;\n white-space: nowrap;\n }\n\n .col-numeric { text-align: right; }\n\n .data-table tbody tr {\n transition: background 0.15s;\n }\n\n .data-table tbody tr:hover {\n background: var(--mj-bg-surface-hover);\n }\n\n .cell-model {\n font-weight: 500;\n color: var(--mj-text-primary);\n }\n\n .cell-vendor {\n color: var(--mj-text-secondary);\n }\n\n .cell-numeric {\n text-align: right;\n font-variant-numeric: tabular-nums;\n color: var(--mj-text-secondary);\n }\n\n .cell-cost {\n font-weight: 600;\n color: var(--mj-text-primary);\n }\n\n .empty-row {\n text-align: center;\n color: var(--mj-text-disabled);\n padding: 24px;\n }\n\n /* ── Responsive ── */\n @media (max-width: 1200px) {\n .two-col {\n grid-template-columns: 1fr;\n }\n }\n\n @media (max-width: 768px) {\n .kpi-row {\n grid-template-columns: repeat(2, 1fr);\n }\n\n .kpi-value {\n font-size: 18px;\n }\n }\n `]\n})\nexport class AnalyticsCostBudgetComponent extends BaseAngularComponent implements OnInit, OnDestroy {\n private _timeRange = '7d';\n @Input()\n set TimeRange(value: string) {\n const prev = this._timeRange;\n this._timeRange = value;\n if (prev !== value && this.initialized) this.LoadData();\n }\n get TimeRange(): string { return this._timeRange; }\n\n private _filters: GlobalFilterState = { Models: [], Agents: [], Prompts: [], Statuses: [] };\n @Input()\n set Filters(value: GlobalFilterState) {\n const next = value ?? { Models: [], Agents: [], Prompts: [], Statuses: [] };\n const changed = !this.shallowFiltersEqual(this._filters, next);\n this._filters = next;\n if (changed && this.initialized) this.LoadData();\n }\n get Filters(): GlobalFilterState { return this._filters; }\n\n private shallowFiltersEqual(a: GlobalFilterState, b: GlobalFilterState): boolean {\n const sameArr = (x: string[], y: string[]) => x.length === y.length && x.every((v, i) => v === y[i]);\n return sameArr(a.Models, b.Models) && sameArr(a.Agents, b.Agents) && sameArr(a.Prompts, b.Prompts) && sameArr(a.Statuses, b.Statuses);\n }\n\n @Output() TimeRangeChange = new EventEmitter<string>();\n @Output() FiltersChange = new EventEmitter<GlobalFilterState>();\n\n private initialized = false;\n\n private cdr = inject(ChangeDetectorRef);\n private destroy$ = new Subject<void>();\n\n public IsLoading = false;\n public TimeRangeOptionsList = TIME_RANGE_OPTIONS;\n\n public CostKpis: CostKpi[] = [];\n public DailyBars: DailyBar[] = [];\n public AvgLinePercent = 0;\n public TreemapCells: TreemapCell[] = [];\n public CostByModelRows: CostByModelRow[] = [];\n\n private allRuns: PromptRunRecord[] = [];\n private previousPeriodRuns: PromptRunRecord[] = [];\n\n // Per model+vendor cache pricing (rates already normalized to currency-per-token). Empty until\n // AIModelCost cache rates are configured — savings then stays 0 (surfaced as \"Set rates\").\n private cacheRates = new Map<string, CacheRate>();\n\n ngOnInit(): void {\n this.initialized = true;\n this.LoadData();\n }\n\n ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n }\n\n // ── Public Handlers ──\n\n public OnTimeRangeChange(range: string): void {\n this.TimeRange = range;\n this.TimeRangeChange.emit(range);\n this.LoadData();\n }\n\n public OnFiltersChange(filters: GlobalFilterState): void {\n this.Filters = filters;\n this.FiltersChange.emit(filters);\n this.LoadData();\n }\n\n public FormatCurrency(value: number, decimals = 2): string {\n if (value === 0) return '$0.00';\n if (value < 0.01 && decimals < 4) decimals = 4;\n return '$' + value.toFixed(decimals);\n }\n\n public ExportCSV(): void {\n const header = 'Model,Vendor,Runs,Input Tokens,Output Tokens,Cache Read Tokens,Cache Write Tokens,Cache Hit Rate %,Input Cost,Output Cost,Total Cost,Cache Saved,% of Total';\n const rows = this.CostByModelRows.map(r =>\n `\"${r.Model}\",\"${r.Vendor}\",${r.Runs},${r.InputTokens},${r.OutputTokens},${r.CacheReadTokens},${r.CacheWriteTokens},${(r.CacheHitRate * 100).toFixed(1)},${r.InputCost.toFixed(6)},${r.OutputCost.toFixed(6)},${r.TotalCost.toFixed(6)},${r.CacheSavings.toFixed(6)},${r.PercentOfTotal.toFixed(1)}`\n );\n const csv = [header, ...rows].join('\\n');\n this.downloadCSV(csv, 'cost-by-model.csv');\n }\n\n // ── Data Loading ──\n\n private async LoadData(): Promise<void> {\n this.IsLoading = true;\n this.cdr.detectChanges();\n\n try {\n const rv = RunView.FromMetadataProvider(this.ProviderToUse);\n const { currentStart, previousStart } = this.getDateBounds();\n const now = new Date();\n const modelFilter = this.buildModelFilter();\n const currentFilter = this.combineDateAndModelFilter(currentStart, now, modelFilter);\n const prevFilter = this.combineDateAndModelFilter(previousStart, currentStart, modelFilter);\n\n const [currentResult, prevResult, rateResult] = await rv.RunViews([\n {\n EntityName: 'MJ: AI Prompt Runs',\n ExtraFilter: currentFilter,\n Fields: FIELDS,\n OrderBy: 'RunAt ASC',\n ResultType: 'simple'\n },\n {\n EntityName: 'MJ: AI Prompt Runs',\n ExtraFilter: prevFilter,\n Fields: FIELDS,\n OrderBy: 'RunAt ASC',\n ResultType: 'simple'\n },\n {\n EntityName: 'MJ: AI Model Costs',\n ExtraFilter: `Status='Active' AND ProcessingType='Realtime'`,\n Fields: ['ModelID', 'VendorID', 'InputPricePerUnit', 'OutputPricePerUnit', 'CacheReadPricePerUnit', 'CacheWritePricePerUnit', 'UnitType'],\n ResultType: 'simple'\n }\n ]);\n\n this.allRuns = (currentResult?.Results ?? []) as PromptRunRecord[];\n this.previousPeriodRuns = (prevResult?.Results ?? []) as PromptRunRecord[];\n this.buildCacheRateMap(rateResult?.Results ?? []);\n\n this.computeKpis();\n this.computeDailyBars();\n this.computeTreemap();\n this.computeCostByModel();\n } catch (e) {\n console.error('Cost & Budget load error:', e);\n } finally {\n this.IsLoading = false;\n this.cdr.detectChanges();\n }\n }\n\n // ── Cache pricing ──\n\n /** Stable map key for a model+vendor pair (UUIDs normalized for case-insensitive matching). */\n private rateKey(modelID: string | null, vendorID: string | null): string {\n return `${NormalizeUUID(modelID ?? '')}|${NormalizeUUID(vendorID ?? '')}`;\n }\n\n /** The cache rate for a run's model+vendor, or undefined when no active cost row is configured. */\n private rateFor(run: PromptRunRecord): CacheRate | undefined {\n return this.cacheRates.get(this.rateKey(run.ModelID, run.VendorID));\n }\n\n /** Build the per-model+vendor rate lookup, normalizing each per-unit price to currency-per-token. */\n private buildCacheRateMap(rows: ModelCostRow[]): void {\n this.cacheRates.clear();\n for (const row of rows) {\n const divisor = UNIT_DIVISORS[row.UnitType ?? ''] ?? 1_000_000;\n const inputRate = (row.InputPricePerUnit ?? 0) / divisor;\n // Cache read/write fall back to the input rate when no distinct rate is recorded — exactly\n // as the server-side cost calculator does — which makes the corresponding savings term 0.\n const cacheReadRate = (row.CacheReadPricePerUnit ?? row.InputPricePerUnit ?? 0) / divisor;\n const cacheWriteRate = (row.CacheWritePricePerUnit ?? row.InputPricePerUnit ?? 0) / divisor;\n this.cacheRates.set(this.rateKey(row.ModelID, row.VendorID), { inputRate, cacheReadRate, cacheWriteRate });\n }\n }\n\n /** Sum net cache savings across a set of runs using each run's model+vendor rate. */\n private sumCacheSavings(runs: PromptRunRecord[]): number {\n return runs.reduce((total, run) => total + netCacheSavings({\n uncachedInputTokens: 0,\n cacheReadTokens: run.TokensCacheRead ?? 0,\n cacheWriteTokens: run.TokensCacheWrite ?? 0\n }, this.rateFor(run)), 0);\n }\n\n // ── Computations ──\n\n private computeKpis(): void {\n const now = new Date();\n const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());\n const weekStart = new Date(todayStart.getTime() - 6 * 86400000);\n const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);\n\n const todaySpend = this.sumCostInRange(this.allRuns, todayStart, now);\n const weekSpend = this.sumCostInRange(this.allRuns, weekStart, now);\n const monthSpend = this.sumCostInRange(this.allRuns, monthStart, now);\n\n const prevTotalCost = this.previousPeriodRuns.reduce((s, r) => s + (r.Cost ?? r.TotalCost ?? 0), 0);\n const currentTotalCost = this.allRuns.reduce((s, r) => s + (r.Cost ?? r.TotalCost ?? 0), 0);\n\n // Project monthly cost based on current daily average\n const daysIntoMonth = Math.max(1, now.getDate());\n const projectedMonthly = (monthSpend / daysIntoMonth) * new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate();\n\n const delta = prevTotalCost > 0 ? ((currentTotalCost - prevTotalCost) / prevTotalCost) * 100 : null;\n\n this.CostKpis = [\n {\n Label: \"Today's Spend\",\n Value: this.FormatCurrency(todaySpend),\n Delta: null,\n DeltaDirection: 'stable',\n Highlighted: false,\n Icon: 'fa-solid fa-calendar-day'\n },\n {\n Label: 'This Week',\n Value: this.FormatCurrency(weekSpend),\n Delta: null,\n DeltaDirection: 'stable',\n Highlighted: false,\n Icon: 'fa-solid fa-calendar-week'\n },\n {\n Label: 'This Month',\n Value: this.FormatCurrency(monthSpend),\n Delta: delta,\n DeltaDirection: delta != null ? (delta > 0 ? 'up' : delta < 0 ? 'down' : 'stable') : 'stable',\n Highlighted: false,\n Icon: 'fa-solid fa-calendar'\n },\n {\n Label: 'Projected Monthly',\n Value: this.FormatCurrency(projectedMonthly),\n Delta: null,\n DeltaDirection: 'stable',\n Highlighted: true,\n Icon: 'fa-solid fa-chart-line'\n }\n ];\n\n this.appendCacheKpis();\n }\n\n /** Append the cache hit-rate and cache-savings KPIs (computed from the current-period runs). */\n private appendCacheKpis(): void {\n const totals: CacheTokenTotals = { uncachedInputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 };\n for (const r of this.allRuns) {\n totals.uncachedInputTokens += r.TokensPrompt ?? 0;\n totals.cacheReadTokens += r.TokensCacheRead ?? 0;\n totals.cacheWriteTokens += r.TokensCacheWrite ?? 0;\n }\n const savings = this.sumCacheSavings(this.allRuns);\n const activity = hasCacheActivity(totals);\n\n this.CostKpis.push({\n Label: 'Cache Hit Rate',\n Value: (cacheHitRate(totals) * 100).toFixed(1) + '%',\n Delta: null,\n DeltaDirection: 'stable',\n Highlighted: false,\n Icon: 'fa-solid fa-bolt'\n });\n\n // Savings requires cache rates on AIModelCost. When cache engaged but no savings computed,\n // it means rates aren't configured yet — say so rather than implying $0 was saved.\n const savingsValue = savings > 0\n ? this.FormatCurrency(savings)\n : (activity ? 'Set rates' : '$0.00');\n this.CostKpis.push({\n Label: 'Saved via Cache',\n Value: savingsValue,\n Delta: null,\n DeltaDirection: 'stable',\n Highlighted: savings > 0,\n Icon: 'fa-solid fa-piggy-bank'\n });\n }\n\n private computeDailyBars(): void {\n const buckets = new Map<string, number>();\n for (const run of this.allRuns) {\n const date = new Date(run.RunAt);\n const key = date.toISOString().slice(0, 10);\n buckets.set(key, (buckets.get(key) ?? 0) + (run.Cost ?? run.TotalCost ?? 0));\n }\n\n const sortedKeys = Array.from(buckets.keys()).sort();\n const values = sortedKeys.map(k => buckets.get(k) ?? 0);\n const maxVal = Math.max(...values, 0.001);\n\n // Anomaly detection: > 2 standard deviations from mean\n const mean = values.length > 0 ? values.reduce((s, v) => s + v, 0) / values.length : 0;\n const variance = values.length > 1\n ? values.reduce((s, v) => s + Math.pow(v - mean, 2), 0) / values.length\n : 0;\n const stdDev = Math.sqrt(variance);\n const anomalyThreshold = mean + 2 * stdDev;\n\n this.DailyBars = sortedKeys.map((key, i) => ({\n Date: key,\n Label: this.formatBarLabel(key),\n Cost: values[i],\n HeightPercent: maxVal > 0 ? (values[i] / maxVal) * 100 : 0,\n IsAnomaly: stdDev > 0 && values[i] > anomalyThreshold\n }));\n\n this.AvgLinePercent = maxVal > 0 ? (mean / maxVal) * 100 : 0;\n }\n\n private computeTreemap(): void {\n const vendorCosts = new Map<string, number>();\n for (const run of this.allRuns) {\n const vendor = run.Vendor ?? 'Other';\n vendorCosts.set(vendor, (vendorCosts.get(vendor) ?? 0) + (run.Cost ?? run.TotalCost ?? 0));\n }\n\n const total = Array.from(vendorCosts.values()).reduce((s, v) => s + v, 0);\n const sorted = Array.from(vendorCosts.entries()).sort((a, b) => b[1] - a[1]);\n\n this.TreemapCells = sorted.map(([vendor, cost], i) => ({\n Label: vendor,\n Cost: cost,\n Percent: total > 0 ? (cost / total) * 100 : 0,\n Color: TREEMAP_COLORS[i % TREEMAP_COLORS.length],\n GridArea: ''\n }));\n }\n\n private computeCostByModel(): void {\n const groups = new Map<string, PromptRunRecord[]>();\n for (const run of this.allRuns) {\n const key = run.ModelID ?? 'unknown';\n if (!groups.has(key)) groups.set(key, []);\n groups.get(key)!.push(run);\n }\n\n const totalCost = this.allRuns.reduce((s, r) => s + (r.Cost ?? r.TotalCost ?? 0), 0);\n\n const rows: CostByModelRow[] = [];\n for (const [, modelRuns] of groups) {\n const inputTokens = modelRuns.reduce((s, r) => s + (r.TokensPrompt ?? 0), 0);\n const outputTokens = modelRuns.reduce((s, r) => s + (r.TokensCompletion ?? 0), 0);\n const cacheReadTokens = modelRuns.reduce((s, r) => s + (r.TokensCacheRead ?? 0), 0);\n const cacheWriteTokens = modelRuns.reduce((s, r) => s + (r.TokensCacheWrite ?? 0), 0);\n const cost = modelRuns.reduce((s, r) => s + (r.Cost ?? r.TotalCost ?? 0), 0);\n\n // Approximate input/output cost split based on token ratio\n const totalTk = inputTokens + outputTokens;\n const inputCost = totalTk > 0 ? cost * (inputTokens / totalTk) : 0;\n const outputCost = totalTk > 0 ? cost * (outputTokens / totalTk) : 0;\n\n rows.push({\n Model: modelRuns[0].Model ?? 'Unknown',\n Vendor: modelRuns[0].Vendor ?? 'Unknown',\n Runs: modelRuns.length,\n InputTokens: inputTokens,\n OutputTokens: outputTokens,\n CacheReadTokens: cacheReadTokens,\n CacheWriteTokens: cacheWriteTokens,\n CacheHitRate: cacheHitRate({ uncachedInputTokens: inputTokens, cacheReadTokens, cacheWriteTokens }),\n CacheSavings: this.sumCacheSavings(modelRuns),\n InputCost: inputCost,\n OutputCost: outputCost,\n TotalCost: cost,\n PercentOfTotal: totalCost > 0 ? (cost / totalCost) * 100 : 0\n });\n }\n\n this.CostByModelRows = rows.sort((a, b) => b.TotalCost - a.TotalCost);\n }\n\n // ── Helpers ──\n\n private sumCostInRange(runs: PromptRunRecord[], start: Date, end: Date): number {\n return runs\n .filter(r => {\n const d = new Date(r.RunAt);\n return d >= start && d <= end;\n })\n .reduce((s, r) => s + (r.Cost ?? r.TotalCost ?? 0), 0);\n }\n\n private getDateBounds(): { currentStart: Date; previousStart: Date } {\n const now = new Date();\n const msMap: Record<string, number> = {\n 'Today': 86400000,\n '7d': 604800000,\n '30d': 2592000000,\n 'MTD': now.getDate() * 86400000\n };\n const ms = msMap[this.TimeRange] ?? 604800000;\n const currentStart = new Date(now.getTime() - ms);\n const previousStart = new Date(currentStart.getTime() - ms);\n return { currentStart, previousStart };\n }\n\n private buildModelFilter(): string {\n if (this.Filters.Models.length === 0) return '';\n const ids = this.Filters.Models.map(id => `'${id}'`).join(',');\n return `ModelID IN (${ids})`;\n }\n\n private combineDateAndModelFilter(start: Date, end: Date, modelFilter: string): string {\n const parts = [\n `RunAt >= '${start.toISOString()}'`,\n `RunAt <= '${end.toISOString()}'`\n ];\n if (modelFilter) parts.push(modelFilter);\n return parts.join(' AND ');\n }\n\n private formatBarLabel(dateStr: string): string {\n const d = new Date(dateStr + 'T00:00:00');\n const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];\n return months[d.getMonth()] + ' ' + d.getDate();\n }\n\n private downloadCSV(csv: string, filename: string): void {\n const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = filename;\n link.click();\n URL.revokeObjectURL(url);\n }\n}\n\nexport function LoadAnalyticsCostBudget() { /* tree-shaking prevention */ }\n"]}
|
|
1
|
+
{"version":3,"file":"cost-budget.component.js","sourceRoot":"","sources":["../../../../../src/AI/components/analytics/cost-budget/cost-budget.component.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Cost & Budget Analytics -- Option B.\n *\n * Displays cost KPIs with period deltas, a daily cost bar chart with anomaly detection,\n * a cost breakdown treemap, and a detailed cost-by-model table with CSV export.\n */\n\nimport {\n Component, Input, Output, EventEmitter,\n OnInit, OnDestroy, ChangeDetectorRef, inject\n} from '@angular/core';\nimport { Subject } from 'rxjs';\nimport { RunView } from '@memberjunction/core';\nimport { NormalizeUUID } from '@memberjunction/global';\nimport { TOKEN_PRICE_UNIT_TYPE_DIVISORS } from '@memberjunction/ai-engine-base';\nimport { CacheRate, CacheTokenTotals, cacheHitRate, hasCacheActivity, netCacheSavings } from '../../../services/cache-metrics';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport { GlobalFilterState } from '../../../interfaces/analytics-preferences.interface';\n\n// ── Interfaces ──\n\ninterface PromptRunRecord {\n ID: string;\n RunAt: string;\n Cost: number | null;\n TotalCost: number | null;\n TokensPrompt: number | null;\n TokensCompletion: number | null;\n TokensUsed: number | null;\n TokensCacheRead: number | null;\n TokensCacheWrite: number | null;\n ModelID: string | null;\n Model: string | null;\n VendorID: string | null;\n Vendor: string | null;\n Success: boolean;\n}\n\ninterface CostKpi {\n Label: string;\n Value: string;\n Delta: number | null;\n DeltaDirection: 'up' | 'down' | 'stable';\n Highlighted: boolean;\n Icon: string;\n}\n\ninterface DailyBar {\n Date: string;\n Label: string;\n Cost: number;\n HeightPercent: number;\n IsAnomaly: boolean;\n}\n\ninterface TreemapCell {\n Label: string;\n Cost: number;\n Percent: number;\n Color: string;\n GridArea: string;\n}\n\ninterface CostByModelRow {\n Model: string;\n Vendor: string;\n Runs: number;\n InputTokens: number;\n OutputTokens: number;\n CacheReadTokens: number;\n CacheWriteTokens: number;\n CacheHitRate: number;\n CacheSavings: number;\n InputCost: number;\n OutputCost: number;\n TotalCost: number;\n PercentOfTotal: number;\n}\n\nconst FIELDS = [\n 'ID', 'RunAt', 'Cost', 'TotalCost', 'TokensPrompt', 'TokensCompletion',\n 'TokensUsed', 'TokensCacheRead', 'TokensCacheWrite', 'ModelID', 'Model', 'VendorID', 'Vendor', 'Success'\n];\n\ninterface ModelCostRow {\n ModelID: string | null;\n VendorID: string | null;\n InputPricePerUnit: number | null;\n OutputPricePerUnit: number | null;\n CacheReadPricePerUnit: number | null;\n CacheWritePricePerUnit: number | null;\n UnitTypeID: string | null;\n}\n\n/** A price unit type, reduced to what the scale lookup needs. */\ninterface PriceUnitTypeRow {\n ID: string;\n DriverClass: string | null;\n}\n\nconst TIME_RANGE_OPTIONS = ['Today', '7d', '30d', 'MTD'];\n\nconst TREEMAP_COLORS = [\n 'var(--mj-brand-primary)',\n 'var(--mj-brand-accent, var(--mj-brand-primary-hover))',\n 'var(--mj-status-info)',\n 'var(--mj-status-success)',\n 'var(--mj-status-warning)',\n 'var(--mj-text-disabled)'\n];\n\n@Component({\n standalone: false,\n selector: 'app-analytics-cost-budget',\n template: `\n\n @if (IsLoading) {\n <div class=\"loading-container\">\n <mj-loading text=\"Loading cost data...\"></mj-loading>\n </div>\n } @else {\n <!-- Cost KPI Row -->\n <div class=\"kpi-row\">\n @for (kpi of CostKpis; track kpi.Label) {\n <div class=\"kpi-card\" [class.kpi-card--highlighted]=\"kpi.Highlighted\">\n <div class=\"kpi-icon\">\n <i [class]=\"kpi.Icon\"></i>\n </div>\n <div class=\"kpi-content\">\n <div class=\"kpi-label\">{{ kpi.Label }}</div>\n <div class=\"kpi-value\">{{ kpi.Value }}</div>\n @if (kpi.Delta != null) {\n <div class=\"kpi-delta\"\n [class.kpi-delta--up]=\"kpi.DeltaDirection === 'up'\"\n [class.kpi-delta--down]=\"kpi.DeltaDirection === 'down'\">\n @if (kpi.DeltaDirection === 'up') {\n <i class=\"fa-solid fa-arrow-up\"></i>\n } @else if (kpi.DeltaDirection === 'down') {\n <i class=\"fa-solid fa-arrow-down\"></i>\n }\n {{ kpi.Delta | number:'1.1-1' }}% vs prev\n </div>\n }\n </div>\n </div>\n }\n </div>\n\n <!-- Two-Column Layout -->\n <div class=\"two-col\">\n <!-- Daily Cost Trend -->\n <div class=\"panel panel-chart\">\n <div class=\"panel-header\">\n <div class=\"panel-header__title\">\n <i class=\"fa-solid fa-chart-column panel-header__icon\"></i>\n Daily Cost Trend\n </div>\n </div>\n <div class=\"chart-body\">\n @if (DailyBars.length === 0) {\n <mj-empty-state Size=\"compact\" Variant=\"empty\" Icon=\"fa-solid fa-chart-column\"\n Title=\"No cost data for selected period\" />\n } @else {\n <div class=\"bar-chart\">\n <div class=\"bar-chart-area\">\n @if (AvgLinePercent > 0) {\n <div class=\"avg-line\" [style.bottom.%]=\"AvgLinePercent\">\n <span class=\"avg-label\">avg</span>\n </div>\n }\n @for (bar of DailyBars; track bar.Date) {\n <div class=\"bar-col\" [title]=\"bar.Label + ': ' + FormatCurrency(bar.Cost)\">\n <div\n class=\"bar\"\n [class.bar--anomaly]=\"bar.IsAnomaly\"\n [style.height.%]=\"bar.HeightPercent\"\n ></div>\n <div class=\"bar-label\">{{ bar.Label }}</div>\n </div>\n }\n </div>\n </div>\n }\n </div>\n </div>\n\n <!-- Cost Breakdown Treemap -->\n <div class=\"panel panel-treemap\">\n <div class=\"panel-header\">\n <div class=\"panel-header__title\">\n <i class=\"fa-solid fa-chart-pie panel-header__icon\"></i>\n Cost by Vendor\n </div>\n </div>\n <div class=\"treemap-body\">\n @if (TreemapCells.length === 0) {\n <mj-empty-state Size=\"compact\" Variant=\"empty\" Icon=\"fa-solid fa-chart-pie\"\n Title=\"No vendor cost data\" />\n } @else {\n <div class=\"treemap-grid\">\n @for (cell of TreemapCells; track cell.Label) {\n <div\n class=\"treemap-cell\"\n [style.background]=\"cell.Color\"\n [style.flex-basis.%]=\"cell.Percent\"\n [title]=\"cell.Label + ': ' + FormatCurrency(cell.Cost) + ' (' + (cell.Percent | number:'1.0-0') + '%)'\">\n <span class=\"treemap-label\">{{ cell.Label }}</span>\n <span class=\"treemap-value\">{{ FormatCurrency(cell.Cost) }}</span>\n <span class=\"treemap-pct\">{{ cell.Percent | number:'1.0-0' }}%</span>\n </div>\n }\n </div>\n }\n </div>\n </div>\n </div>\n\n <!-- Cost by Model Table -->\n <div class=\"panel\">\n <div class=\"panel-header\">\n <div class=\"panel-header__title\">\n <i class=\"fa-solid fa-table panel-header__icon\"></i>\n Cost by Model\n </div>\n <button class=\"export-btn\" (click)=\"ExportCSV()\">\n <i class=\"fa-solid fa-download\"></i>\n Export CSV\n </button>\n </div>\n <div class=\"table-wrapper\">\n <table class=\"data-table\">\n <thead>\n <tr>\n <th>Model</th>\n <th>Vendor</th>\n <th class=\"col-numeric\">Runs</th>\n <th class=\"col-numeric\">Input Tokens</th>\n <th class=\"col-numeric\">Output Tokens</th>\n <th class=\"col-numeric\" title=\"Share of input tokens served from the provider's prompt cache\">Cache Hit</th>\n <th class=\"col-numeric\">Input Cost</th>\n <th class=\"col-numeric\">Output Cost</th>\n <th class=\"col-numeric\">Total Cost</th>\n <th class=\"col-numeric\" title=\"Net dollars saved by caching vs. full input pricing (requires cache rates on AI Model Costs)\">Cache Saved</th>\n <th class=\"col-numeric\">% of Total</th>\n </tr>\n </thead>\n <tbody>\n @if (CostByModelRows.length === 0) {\n <tr><td colspan=\"11\" class=\"empty-row\">No data available</td></tr>\n }\n @for (row of CostByModelRows; track row.Model) {\n <tr>\n <td class=\"cell-model\">{{ row.Model }}</td>\n <td class=\"cell-vendor\">{{ row.Vendor }}</td>\n <td class=\"cell-numeric\">{{ row.Runs | number }}</td>\n <td class=\"cell-numeric\">{{ row.InputTokens | number }}</td>\n <td class=\"cell-numeric\">{{ row.OutputTokens | number }}</td>\n <td class=\"cell-numeric\">{{ (row.CacheReadTokens + row.CacheWriteTokens) > 0 ? ((row.CacheHitRate * 100 | number:'1.0-0') + '%') : '—' }}</td>\n <td class=\"cell-numeric\">{{ FormatCurrency(row.InputCost, 4) }}</td>\n <td class=\"cell-numeric\">{{ FormatCurrency(row.OutputCost, 4) }}</td>\n <td class=\"cell-numeric cell-cost\">{{ FormatCurrency(row.TotalCost) }}</td>\n <td class=\"cell-numeric\">{{ row.CacheSavings > 0 ? FormatCurrency(row.CacheSavings, 4) : '—' }}</td>\n <td class=\"cell-numeric\">{{ row.PercentOfTotal | number:'1.1-1' }}%</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n }\n `,\n styles: [`\n :host { display: block; }\n\n .loading-container {\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 300px;\n }\n\n /* ── KPI Row ── */\n .kpi-row {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n gap: 12px;\n margin: 16px 0;\n }\n\n .kpi-card {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n padding: 16px;\n display: flex;\n align-items: flex-start;\n gap: 12px;\n transition: transform 0.2s ease, box-shadow 0.2s ease;\n }\n\n .kpi-card:hover {\n transform: translateY(-2px);\n box-shadow: 0 4px 12px color-mix(in srgb, var(--mj-text-primary) 8%, transparent);\n }\n\n .kpi-card--highlighted {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 5%, var(--mj-bg-surface));\n }\n\n .kpi-icon {\n width: 36px;\n height: 36px;\n border-radius: 10px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 14px;\n flex-shrink: 0;\n }\n\n .kpi-card--highlighted .kpi-icon {\n background: color-mix(in srgb, var(--mj-brand-primary) 20%, var(--mj-bg-surface));\n }\n\n .kpi-label {\n font-size: 11px;\n font-weight: 600;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n }\n\n .kpi-value {\n font-size: 22px;\n font-weight: 700;\n color: var(--mj-text-primary);\n margin: 2px 0;\n letter-spacing: -0.02em;\n }\n\n .kpi-delta {\n font-size: 11px;\n color: var(--mj-text-muted);\n display: flex;\n align-items: center;\n gap: 3px;\n }\n\n .kpi-delta--up {\n color: var(--mj-status-error);\n }\n\n .kpi-delta--down {\n color: var(--mj-status-success);\n }\n\n /* ── Two-Column Layout ── */\n .two-col {\n display: grid;\n grid-template-columns: 3fr 2fr;\n gap: 16px;\n margin-bottom: 16px;\n }\n\n /* ── Panel ── */\n .panel {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n overflow: hidden;\n margin-bottom: 16px;\n }\n\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 14px 18px;\n border-bottom: 1px solid var(--mj-border-subtle);\n }\n\n .panel-header__title {\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-primary);\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .panel-header__icon {\n font-size: 13px;\n color: var(--mj-brand-primary);\n }\n\n .export-btn {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 5px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n font-size: 13px;\n cursor: pointer;\n transition: background 0.15s, color 0.15s;\n }\n\n .export-btn:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n }\n\n /* ── Bar Chart ── */\n .chart-body {\n padding: 16px 18px;\n }\n\n .bar-chart {\n width: 100%;\n }\n\n .bar-chart-area {\n display: flex;\n align-items: flex-end;\n gap: 4px;\n height: 180px;\n position: relative;\n padding-bottom: 24px;\n }\n\n .bar-col {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n height: 100%;\n justify-content: flex-end;\n }\n\n .bar {\n width: 100%;\n max-width: 32px;\n border-radius: 4px 4px 0 0;\n background: var(--mj-brand-primary);\n transition: height 0.4s cubic-bezier(0.4, 0, 0.2, 1);\n min-height: 2px;\n }\n\n .bar--anomaly {\n background: var(--mj-status-error);\n }\n\n .bar-label {\n font-size: 10px;\n color: var(--mj-text-muted);\n margin-top: 4px;\n white-space: nowrap;\n }\n\n .avg-line {\n position: absolute;\n left: 0;\n right: 0;\n border-top: 2px dashed var(--mj-text-disabled);\n z-index: 1;\n pointer-events: none;\n }\n\n .avg-label {\n position: absolute;\n right: 0;\n top: -14px;\n font-size: 10px;\n color: var(--mj-text-disabled);\n font-weight: 600;\n }\n\n /* ── Treemap ── */\n .treemap-body {\n padding: 16px 18px;\n }\n\n .treemap-grid {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n min-height: 160px;\n }\n\n .treemap-cell {\n border-radius: 8px;\n padding: 12px;\n display: flex;\n flex-direction: column;\n justify-content: center;\n min-width: 80px;\n min-height: 70px;\n flex-grow: 1;\n color: var(--mj-text-inverse, white);\n transition: opacity 0.2s;\n }\n\n .treemap-cell:hover {\n opacity: 0.85;\n }\n\n .treemap-label {\n font-size: 12px;\n font-weight: 600;\n opacity: 0.95;\n }\n\n .treemap-value {\n font-size: 16px;\n font-weight: 700;\n margin-top: 2px;\n }\n\n .treemap-pct {\n font-size: 11px;\n opacity: 0.8;\n }\n\n /* ── Table ── */\n .table-wrapper {\n overflow-x: auto;\n }\n\n .data-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n }\n\n .data-table th,\n .data-table td {\n padding: 10px 14px;\n text-align: left;\n border-bottom: 1px solid var(--mj-border-subtle);\n }\n\n .data-table th {\n font-size: 11px;\n font-weight: 600;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.4px;\n background: var(--mj-bg-surface-card);\n position: sticky;\n top: 0;\n white-space: nowrap;\n }\n\n .col-numeric { text-align: right; }\n\n .data-table tbody tr {\n transition: background 0.15s;\n }\n\n .data-table tbody tr:hover {\n background: var(--mj-bg-surface-hover);\n }\n\n .cell-model {\n font-weight: 500;\n color: var(--mj-text-primary);\n }\n\n .cell-vendor {\n color: var(--mj-text-secondary);\n }\n\n .cell-numeric {\n text-align: right;\n font-variant-numeric: tabular-nums;\n color: var(--mj-text-secondary);\n }\n\n .cell-cost {\n font-weight: 600;\n color: var(--mj-text-primary);\n }\n\n .empty-row {\n text-align: center;\n color: var(--mj-text-disabled);\n padding: 24px;\n }\n\n /* ── Responsive ── */\n @media (max-width: 1200px) {\n .two-col {\n grid-template-columns: 1fr;\n }\n }\n\n @media (max-width: 768px) {\n .kpi-row {\n grid-template-columns: repeat(2, 1fr);\n }\n\n .kpi-value {\n font-size: 18px;\n }\n }\n `]\n})\nexport class AnalyticsCostBudgetComponent extends BaseAngularComponent implements OnInit, OnDestroy {\n private _timeRange = '7d';\n @Input()\n set TimeRange(value: string) {\n const prev = this._timeRange;\n this._timeRange = value;\n if (prev !== value && this.initialized) this.LoadData();\n }\n get TimeRange(): string { return this._timeRange; }\n\n private _filters: GlobalFilterState = { Models: [], Agents: [], Prompts: [], Statuses: [] };\n @Input()\n set Filters(value: GlobalFilterState) {\n const next = value ?? { Models: [], Agents: [], Prompts: [], Statuses: [] };\n const changed = !this.shallowFiltersEqual(this._filters, next);\n this._filters = next;\n if (changed && this.initialized) this.LoadData();\n }\n get Filters(): GlobalFilterState { return this._filters; }\n\n private shallowFiltersEqual(a: GlobalFilterState, b: GlobalFilterState): boolean {\n const sameArr = (x: string[], y: string[]) => x.length === y.length && x.every((v, i) => v === y[i]);\n return sameArr(a.Models, b.Models) && sameArr(a.Agents, b.Agents) && sameArr(a.Prompts, b.Prompts) && sameArr(a.Statuses, b.Statuses);\n }\n\n @Output() TimeRangeChange = new EventEmitter<string>();\n @Output() FiltersChange = new EventEmitter<GlobalFilterState>();\n\n private initialized = false;\n\n private cdr = inject(ChangeDetectorRef);\n private destroy$ = new Subject<void>();\n\n public IsLoading = false;\n public TimeRangeOptionsList = TIME_RANGE_OPTIONS;\n\n public CostKpis: CostKpi[] = [];\n public DailyBars: DailyBar[] = [];\n public AvgLinePercent = 0;\n public TreemapCells: TreemapCell[] = [];\n public CostByModelRows: CostByModelRow[] = [];\n\n private allRuns: PromptRunRecord[] = [];\n private previousPeriodRuns: PromptRunRecord[] = [];\n\n // Per model+vendor cache pricing (rates already normalized to currency-per-token). Empty until\n // AIModelCost cache rates are configured — savings then stays 0 (surfaced as \"Set rates\").\n private cacheRates = new Map<string, CacheRate>();\n\n ngOnInit(): void {\n this.initialized = true;\n this.LoadData();\n }\n\n ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n }\n\n // ── Public Handlers ──\n\n public OnTimeRangeChange(range: string): void {\n this.TimeRange = range;\n this.TimeRangeChange.emit(range);\n this.LoadData();\n }\n\n public OnFiltersChange(filters: GlobalFilterState): void {\n this.Filters = filters;\n this.FiltersChange.emit(filters);\n this.LoadData();\n }\n\n public FormatCurrency(value: number, decimals = 2): string {\n if (value === 0) return '$0.00';\n if (value < 0.01 && decimals < 4) decimals = 4;\n return '$' + value.toFixed(decimals);\n }\n\n public ExportCSV(): void {\n const header = 'Model,Vendor,Runs,Input Tokens,Output Tokens,Cache Read Tokens,Cache Write Tokens,Cache Hit Rate %,Input Cost,Output Cost,Total Cost,Cache Saved,% of Total';\n const rows = this.CostByModelRows.map(r =>\n `\"${r.Model}\",\"${r.Vendor}\",${r.Runs},${r.InputTokens},${r.OutputTokens},${r.CacheReadTokens},${r.CacheWriteTokens},${(r.CacheHitRate * 100).toFixed(1)},${r.InputCost.toFixed(6)},${r.OutputCost.toFixed(6)},${r.TotalCost.toFixed(6)},${r.CacheSavings.toFixed(6)},${r.PercentOfTotal.toFixed(1)}`\n );\n const csv = [header, ...rows].join('\\n');\n this.downloadCSV(csv, 'cost-by-model.csv');\n }\n\n // ── Data Loading ──\n\n private async LoadData(): Promise<void> {\n this.IsLoading = true;\n this.cdr.detectChanges();\n\n try {\n const rv = RunView.FromMetadataProvider(this.ProviderToUse);\n const { currentStart, previousStart } = this.getDateBounds();\n const now = new Date();\n const modelFilter = this.buildModelFilter();\n const currentFilter = this.combineDateAndModelFilter(currentStart, now, modelFilter);\n const prevFilter = this.combineDateAndModelFilter(previousStart, currentStart, modelFilter);\n\n const [currentResult, prevResult, rateResult, unitTypeResult] = await rv.RunViews([\n {\n EntityName: 'MJ: AI Prompt Runs',\n ExtraFilter: currentFilter,\n Fields: FIELDS,\n OrderBy: 'RunAt ASC',\n ResultType: 'simple'\n },\n {\n EntityName: 'MJ: AI Prompt Runs',\n ExtraFilter: prevFilter,\n Fields: FIELDS,\n OrderBy: 'RunAt ASC',\n ResultType: 'simple'\n },\n {\n EntityName: 'MJ: AI Model Costs',\n ExtraFilter: `Status='Active' AND ProcessingType='Realtime'`,\n Fields: ['ModelID', 'VendorID', 'InputPricePerUnit', 'OutputPricePerUnit', 'CacheReadPricePerUnit', 'CacheWritePricePerUnit', 'UnitTypeID'],\n ResultType: 'simple'\n },\n {\n EntityName: 'MJ: AI Model Price Unit Types',\n Fields: ['ID', 'DriverClass'],\n ResultType: 'simple'\n }\n ]);\n\n this.allRuns = (currentResult?.Results ?? []) as PromptRunRecord[];\n this.previousPeriodRuns = (prevResult?.Results ?? []) as PromptRunRecord[];\n // A failed unit-type view is NOT the same as \"these rows are unpriceable\". Without the\n // driver classes every rate row falls into the `continue` below, and cache savings\n // render as a confident 0 instead of an error — the figure most likely to be believed.\n // Say so rather than let the empty map speak for it.\n if (unitTypeResult && !unitTypeResult.Success) {\n console.error('Cost & Budget: price unit types failed to load; cache-savings figures will read 0. ' +\n unitTypeResult.ErrorMessage);\n }\n this.buildCacheRateMap(rateResult?.Results ?? [], unitTypeResult?.Results ?? []);\n\n this.computeKpis();\n this.computeDailyBars();\n this.computeTreemap();\n this.computeCostByModel();\n } catch (e) {\n console.error('Cost & Budget load error:', e);\n } finally {\n this.IsLoading = false;\n this.cdr.detectChanges();\n }\n }\n\n // ── Cache pricing ──\n\n /** Stable map key for a model+vendor pair (UUIDs normalized for case-insensitive matching). */\n private rateKey(modelID: string | null, vendorID: string | null): string {\n return `${NormalizeUUID(modelID ?? '')}|${NormalizeUUID(vendorID ?? '')}`;\n }\n\n /** The cache rate for a run's model+vendor, or undefined when no active cost row is configured. */\n private rateFor(run: PromptRunRecord): CacheRate | undefined {\n return this.cacheRates.get(this.rateKey(run.ModelID, run.VendorID));\n }\n\n /**\n * Build the per-model+vendor rate lookup, normalizing each per-unit price to currency-per-token.\n *\n * The scale comes from the unit type's DriverClass, not its display name — the name is editable\n * metadata (`Per 1M Tokens`) while the driver class is the contract the pricing drivers register\n * under, so this cannot drift the way a hardcoded name table does.\n */\n private buildCacheRateMap(rows: ModelCostRow[], unitTypes: PriceUnitTypeRow[]): void {\n this.cacheRates.clear();\n const driverClassByUnitType = new Map<string, string>(\n unitTypes.filter(u => u.DriverClass).map(u => [NormalizeUUID(u.ID), u.DriverClass!])\n );\n for (const row of rows) {\n const driverClass = driverClassByUnitType.get(NormalizeUUID(row.UnitTypeID ?? ''));\n const divisor = TOKEN_PRICE_UNIT_TYPE_DIVISORS[driverClass ?? ''];\n if (divisor === undefined) {\n // A non-token unit type (per minute/hour/image), or one this build has no driver\n // for. Defaulting to the per-1M-token divisor would divide an hourly audio rate by\n // a million and report a savings figure that is pure noise; no rate at all is the\n // honest answer.\n continue;\n }\n const inputRate = (row.InputPricePerUnit ?? 0) / divisor;\n // Cache read/write fall back to the input rate when no distinct rate is recorded — exactly\n // as the server-side cost calculator does — which makes the corresponding savings term 0.\n const cacheReadRate = (row.CacheReadPricePerUnit ?? row.InputPricePerUnit ?? 0) / divisor;\n const cacheWriteRate = (row.CacheWritePricePerUnit ?? row.InputPricePerUnit ?? 0) / divisor;\n this.cacheRates.set(this.rateKey(row.ModelID, row.VendorID), { inputRate, cacheReadRate, cacheWriteRate });\n }\n }\n\n /** Sum net cache savings across a set of runs using each run's model+vendor rate. */\n private sumCacheSavings(runs: PromptRunRecord[]): number {\n return runs.reduce((total, run) => total + netCacheSavings({\n uncachedInputTokens: 0,\n cacheReadTokens: run.TokensCacheRead ?? 0,\n cacheWriteTokens: run.TokensCacheWrite ?? 0\n }, this.rateFor(run)), 0);\n }\n\n // ── Computations ──\n\n private computeKpis(): void {\n const now = new Date();\n const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());\n const weekStart = new Date(todayStart.getTime() - 6 * 86400000);\n const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);\n\n const todaySpend = this.sumCostInRange(this.allRuns, todayStart, now);\n const weekSpend = this.sumCostInRange(this.allRuns, weekStart, now);\n const monthSpend = this.sumCostInRange(this.allRuns, monthStart, now);\n\n const prevTotalCost = this.previousPeriodRuns.reduce((s, r) => s + (r.Cost ?? r.TotalCost ?? 0), 0);\n const currentTotalCost = this.allRuns.reduce((s, r) => s + (r.Cost ?? r.TotalCost ?? 0), 0);\n\n // Project monthly cost based on current daily average\n const daysIntoMonth = Math.max(1, now.getDate());\n const projectedMonthly = (monthSpend / daysIntoMonth) * new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate();\n\n const delta = prevTotalCost > 0 ? ((currentTotalCost - prevTotalCost) / prevTotalCost) * 100 : null;\n\n this.CostKpis = [\n {\n Label: \"Today's Spend\",\n Value: this.FormatCurrency(todaySpend),\n Delta: null,\n DeltaDirection: 'stable',\n Highlighted: false,\n Icon: 'fa-solid fa-calendar-day'\n },\n {\n Label: 'This Week',\n Value: this.FormatCurrency(weekSpend),\n Delta: null,\n DeltaDirection: 'stable',\n Highlighted: false,\n Icon: 'fa-solid fa-calendar-week'\n },\n {\n Label: 'This Month',\n Value: this.FormatCurrency(monthSpend),\n Delta: delta,\n DeltaDirection: delta != null ? (delta > 0 ? 'up' : delta < 0 ? 'down' : 'stable') : 'stable',\n Highlighted: false,\n Icon: 'fa-solid fa-calendar'\n },\n {\n Label: 'Projected Monthly',\n Value: this.FormatCurrency(projectedMonthly),\n Delta: null,\n DeltaDirection: 'stable',\n Highlighted: true,\n Icon: 'fa-solid fa-chart-line'\n }\n ];\n\n this.appendCacheKpis();\n }\n\n /** Append the cache hit-rate and cache-savings KPIs (computed from the current-period runs). */\n private appendCacheKpis(): void {\n const totals: CacheTokenTotals = { uncachedInputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 };\n for (const r of this.allRuns) {\n totals.uncachedInputTokens += r.TokensPrompt ?? 0;\n totals.cacheReadTokens += r.TokensCacheRead ?? 0;\n totals.cacheWriteTokens += r.TokensCacheWrite ?? 0;\n }\n const savings = this.sumCacheSavings(this.allRuns);\n const activity = hasCacheActivity(totals);\n\n this.CostKpis.push({\n Label: 'Cache Hit Rate',\n Value: (cacheHitRate(totals) * 100).toFixed(1) + '%',\n Delta: null,\n DeltaDirection: 'stable',\n Highlighted: false,\n Icon: 'fa-solid fa-bolt'\n });\n\n // Savings requires cache rates on AIModelCost. When cache engaged but no savings computed,\n // it means rates aren't configured yet — say so rather than implying $0 was saved.\n const savingsValue = savings > 0\n ? this.FormatCurrency(savings)\n : (activity ? 'Set rates' : '$0.00');\n this.CostKpis.push({\n Label: 'Saved via Cache',\n Value: savingsValue,\n Delta: null,\n DeltaDirection: 'stable',\n Highlighted: savings > 0,\n Icon: 'fa-solid fa-piggy-bank'\n });\n }\n\n private computeDailyBars(): void {\n const buckets = new Map<string, number>();\n for (const run of this.allRuns) {\n const date = new Date(run.RunAt);\n const key = date.toISOString().slice(0, 10);\n buckets.set(key, (buckets.get(key) ?? 0) + (run.Cost ?? run.TotalCost ?? 0));\n }\n\n const sortedKeys = Array.from(buckets.keys()).sort();\n const values = sortedKeys.map(k => buckets.get(k) ?? 0);\n const maxVal = Math.max(...values, 0.001);\n\n // Anomaly detection: > 2 standard deviations from mean\n const mean = values.length > 0 ? values.reduce((s, v) => s + v, 0) / values.length : 0;\n const variance = values.length > 1\n ? values.reduce((s, v) => s + Math.pow(v - mean, 2), 0) / values.length\n : 0;\n const stdDev = Math.sqrt(variance);\n const anomalyThreshold = mean + 2 * stdDev;\n\n this.DailyBars = sortedKeys.map((key, i) => ({\n Date: key,\n Label: this.formatBarLabel(key),\n Cost: values[i],\n HeightPercent: maxVal > 0 ? (values[i] / maxVal) * 100 : 0,\n IsAnomaly: stdDev > 0 && values[i] > anomalyThreshold\n }));\n\n this.AvgLinePercent = maxVal > 0 ? (mean / maxVal) * 100 : 0;\n }\n\n private computeTreemap(): void {\n const vendorCosts = new Map<string, number>();\n for (const run of this.allRuns) {\n const vendor = run.Vendor ?? 'Other';\n vendorCosts.set(vendor, (vendorCosts.get(vendor) ?? 0) + (run.Cost ?? run.TotalCost ?? 0));\n }\n\n const total = Array.from(vendorCosts.values()).reduce((s, v) => s + v, 0);\n const sorted = Array.from(vendorCosts.entries()).sort((a, b) => b[1] - a[1]);\n\n this.TreemapCells = sorted.map(([vendor, cost], i) => ({\n Label: vendor,\n Cost: cost,\n Percent: total > 0 ? (cost / total) * 100 : 0,\n Color: TREEMAP_COLORS[i % TREEMAP_COLORS.length],\n GridArea: ''\n }));\n }\n\n private computeCostByModel(): void {\n const groups = new Map<string, PromptRunRecord[]>();\n for (const run of this.allRuns) {\n const key = run.ModelID ?? 'unknown';\n if (!groups.has(key)) groups.set(key, []);\n groups.get(key)!.push(run);\n }\n\n const totalCost = this.allRuns.reduce((s, r) => s + (r.Cost ?? r.TotalCost ?? 0), 0);\n\n const rows: CostByModelRow[] = [];\n for (const [, modelRuns] of groups) {\n const inputTokens = modelRuns.reduce((s, r) => s + (r.TokensPrompt ?? 0), 0);\n const outputTokens = modelRuns.reduce((s, r) => s + (r.TokensCompletion ?? 0), 0);\n const cacheReadTokens = modelRuns.reduce((s, r) => s + (r.TokensCacheRead ?? 0), 0);\n const cacheWriteTokens = modelRuns.reduce((s, r) => s + (r.TokensCacheWrite ?? 0), 0);\n const cost = modelRuns.reduce((s, r) => s + (r.Cost ?? r.TotalCost ?? 0), 0);\n\n // Approximate input/output cost split based on token ratio\n const totalTk = inputTokens + outputTokens;\n const inputCost = totalTk > 0 ? cost * (inputTokens / totalTk) : 0;\n const outputCost = totalTk > 0 ? cost * (outputTokens / totalTk) : 0;\n\n rows.push({\n Model: modelRuns[0].Model ?? 'Unknown',\n Vendor: modelRuns[0].Vendor ?? 'Unknown',\n Runs: modelRuns.length,\n InputTokens: inputTokens,\n OutputTokens: outputTokens,\n CacheReadTokens: cacheReadTokens,\n CacheWriteTokens: cacheWriteTokens,\n CacheHitRate: cacheHitRate({ uncachedInputTokens: inputTokens, cacheReadTokens, cacheWriteTokens }),\n CacheSavings: this.sumCacheSavings(modelRuns),\n InputCost: inputCost,\n OutputCost: outputCost,\n TotalCost: cost,\n PercentOfTotal: totalCost > 0 ? (cost / totalCost) * 100 : 0\n });\n }\n\n this.CostByModelRows = rows.sort((a, b) => b.TotalCost - a.TotalCost);\n }\n\n // ── Helpers ──\n\n private sumCostInRange(runs: PromptRunRecord[], start: Date, end: Date): number {\n return runs\n .filter(r => {\n const d = new Date(r.RunAt);\n return d >= start && d <= end;\n })\n .reduce((s, r) => s + (r.Cost ?? r.TotalCost ?? 0), 0);\n }\n\n private getDateBounds(): { currentStart: Date; previousStart: Date } {\n const now = new Date();\n const msMap: Record<string, number> = {\n 'Today': 86400000,\n '7d': 604800000,\n '30d': 2592000000,\n 'MTD': now.getDate() * 86400000\n };\n const ms = msMap[this.TimeRange] ?? 604800000;\n const currentStart = new Date(now.getTime() - ms);\n const previousStart = new Date(currentStart.getTime() - ms);\n return { currentStart, previousStart };\n }\n\n private buildModelFilter(): string {\n if (this.Filters.Models.length === 0) return '';\n const ids = this.Filters.Models.map(id => `'${id}'`).join(',');\n return `ModelID IN (${ids})`;\n }\n\n private combineDateAndModelFilter(start: Date, end: Date, modelFilter: string): string {\n const parts = [\n `RunAt >= '${start.toISOString()}'`,\n `RunAt <= '${end.toISOString()}'`\n ];\n if (modelFilter) parts.push(modelFilter);\n return parts.join(' AND ');\n }\n\n private formatBarLabel(dateStr: string): string {\n const d = new Date(dateStr + 'T00:00:00');\n const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];\n return months[d.getMonth()] + ' ' + d.getDate();\n }\n\n private downloadCSV(csv: string, filename: string): void {\n const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = filename;\n link.click();\n URL.revokeObjectURL(url);\n }\n}\n\nexport function LoadAnalyticsCostBudget() { /* tree-shaking prevention */ }\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"realtime-transcripts-data.d.ts","sourceRoot":"","sources":["../../../../../src/AI/components/analytics/realtime/realtime-transcripts-data.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,iBAAiB,EAAW,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"realtime-transcripts-data.d.ts","sourceRoot":"","sources":["../../../../../src/AI/components/analytics/realtime/realtime-transcripts-data.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,iBAAiB,EAAW,MAAM,sBAAsB,CAAC;AASlE,oDAAoD;AACpD,MAAM,WAAW,kBAAkB;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,sGAAsG;IACtG,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;IAChB,YAAY,EAAE,IAAI,CAAC;CACtB;AAED,kEAAkE;AAClE,MAAM,WAAW,cAAc;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,mGAAmG;IACnG,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;IAClC,mGAAmG;IACnG,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,IAAI,CAAC;CACZ;AAID;;;;;GAKG;AACH,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,iBAAiB,EAAE,OAAO,SAAM,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAuBhH;AAED;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,CACpC,QAAQ,EAAE,iBAAiB,EAC3B,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,MAAM,GAChB,OAAO,CAAC,cAAc,EAAE,CAAC,CAyB3B;AAqDD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CACzB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1B,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,EACjE,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,cAAc,CAqBhB"}
|
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
* @module @memberjunction/ng-dashboards
|
|
9
9
|
*/
|
|
10
10
|
import { RunView } from '@memberjunction/core';
|
|
11
|
+
import { EscapeSQLString } from '@memberjunction/global';
|
|
11
12
|
import { AIEngineBase } from '@memberjunction/ai-engine-base';
|
|
12
13
|
const CONVERSATION_ENTITY = 'MJ: Conversations';
|
|
13
14
|
const CONVERSATION_DETAIL_ENTITY = 'MJ: Conversation Details';
|
|
14
15
|
const BRIDGE_ENTITY = 'MJ: AI Agent Session Bridges';
|
|
15
16
|
const PARTICIPANT_ENTITY = 'MJ: AI Agent Session Bridge Participants';
|
|
16
|
-
const escapeSql = (v) => v.replace(/'/g, "''");
|
|
17
17
|
const toDate = (v) => (v instanceof Date ? v : new Date(String(v ?? '')));
|
|
18
18
|
/**
|
|
19
19
|
* Loads the meeting-room transcript LIST (light — no per-room detail load). Newest activity first.
|
|
@@ -56,7 +56,7 @@ export async function LoadRoomTranscript(provider, conversationID, roomKey) {
|
|
|
56
56
|
const [detailResult, bridgeResult] = await rv.RunViews([
|
|
57
57
|
{
|
|
58
58
|
EntityName: CONVERSATION_DETAIL_ENTITY,
|
|
59
|
-
ExtraFilter: `ConversationID='${
|
|
59
|
+
ExtraFilter: `ConversationID='${EscapeSQLString(conversationID)}'`,
|
|
60
60
|
Fields: ['ID', 'Role', 'Message', 'AgentID', 'ExternalID', 'Error', '__mj_CreatedAt'],
|
|
61
61
|
OrderBy: '__mj_CreatedAt ASC',
|
|
62
62
|
MaxRows: 5000,
|
|
@@ -64,7 +64,7 @@ export async function LoadRoomTranscript(provider, conversationID, roomKey) {
|
|
|
64
64
|
},
|
|
65
65
|
{
|
|
66
66
|
EntityName: BRIDGE_ENTITY,
|
|
67
|
-
ExtraFilter: roomKey ? `ExternalConnectionID='${
|
|
67
|
+
ExtraFilter: roomKey ? `ExternalConnectionID='${EscapeSQLString(roomKey)}'` : `1=0`,
|
|
68
68
|
Fields: ['ID'],
|
|
69
69
|
MaxRows: 100,
|
|
70
70
|
ResultType: 'simple',
|
|
@@ -81,7 +81,7 @@ async function loadParticipantNames(provider, rv, bridgeIds) {
|
|
|
81
81
|
if (bridgeIds.length === 0) {
|
|
82
82
|
return map;
|
|
83
83
|
}
|
|
84
|
-
const inClause = bridgeIds.map((id) => `'${
|
|
84
|
+
const inClause = bridgeIds.map((id) => `'${EscapeSQLString(id)}'`).join(',');
|
|
85
85
|
const result = await rv.RunView({
|
|
86
86
|
EntityName: PARTICIPANT_ENTITY,
|
|
87
87
|
ExtraFilter: `SessionBridgeID IN (${inClause})`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"realtime-transcripts-data.js","sourceRoot":"","sources":["../../../../../src/AI/components/analytics/realtime/realtime-transcripts-data.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Pure data layer for the Realtime \"Voice Transcripts\" view. Loads the unified per-room\n * meeting transcripts the bridge writes (one `MJ: Conversations` of `Type='Meeting Room'` per room +\n * `MJ: Conversation Details` per utterance), and resolves each line's speaker — agent lines via `AgentID`,\n * heard ('User') lines via the diarized `ExternalID` → the room's `AIAgentSessionBridgeParticipant` roster\n * (DisplayName + IsAgent). No Angular imports — RunView(s) + client-side aggregation only.\n *\n * @module @memberjunction/ng-dashboards\n */\nimport { IMetadataProvider, RunView } from '@memberjunction/core';\nimport { AIEngineBase } from '@memberjunction/ai-engine-base';\n\nconst CONVERSATION_ENTITY = 'MJ: Conversations';\nconst CONVERSATION_DETAIL_ENTITY = 'MJ: Conversation Details';\nconst BRIDGE_ENTITY = 'MJ: AI Agent Session Bridges';\nconst PARTICIPANT_ENTITY = 'MJ: AI Agent Session Bridge Participants';\n\n/** One meeting-room transcript in the list view. */\nexport interface MeetingRoomSummary {\n ConversationID: string;\n Name: string;\n /** The room's external connection id (`ExternalID`) — the join key to the bridge/participant rows. */\n RoomKey: string;\n CreatedAt: Date;\n LastActivity: Date;\n}\n\n/** A speaker-attributed transcript line for the drill-in view. */\nexport interface TranscriptLine {\n ID: string;\n /** `'agent'` = an agent spoke; `'human'` = a human/participant spoke; `'error'` = an error row. */\n Kind: 'agent' | 'human' | 'error';\n /** Resolved display name of the speaker (agent name, participant name, or a sensible fallback). */\n Speaker: string;\n Message: string;\n At: Date;\n}\n\nconst escapeSql = (v: string): string => v.replace(/'/g, \"''\");\nconst toDate = (v: unknown): Date => (v instanceof Date ? v : new Date(String(v ?? '')));\n\n/**\n * Loads the meeting-room transcript LIST (light — no per-room detail load). Newest activity first.\n *\n * @param provider The request-scoped metadata provider.\n * @param maxRows Cap on rooms returned (default 500).\n */\nexport async function LoadMeetingRooms(provider: IMetadataProvider, maxRows = 500): Promise<MeetingRoomSummary[]> {\n const rv = RunView.FromMetadataProvider(provider);\n const result = await rv.RunView<Record<string, unknown>>(\n {\n EntityName: CONVERSATION_ENTITY,\n ExtraFilter: `Type='Meeting Room'`,\n Fields: ['ID', 'Name', 'ExternalID', '__mj_CreatedAt', '__mj_UpdatedAt'],\n OrderBy: '__mj_UpdatedAt DESC',\n MaxRows: maxRows,\n ResultType: 'simple',\n },\n provider.CurrentUser,\n );\n if (!result.Success) {\n return [];\n }\n return result.Results.map((r) => ({\n ConversationID: String(r['ID']),\n Name: String(r['Name'] ?? 'Meeting Room'),\n RoomKey: String(r['ExternalID'] ?? ''),\n CreatedAt: toDate(r['__mj_CreatedAt']),\n LastActivity: toDate(r['__mj_UpdatedAt']),\n }));\n}\n\n/**\n * Loads + speaker-attributes one room's transcript. Resolves agent lines via `AgentID` (AIEngine cache) and\n * heard lines via the diarized `ExternalID` against the room's participant roster (so a `User` line reads as\n * the actual person, and another agent heard in the room reads as that agent — not a generic \"User\").\n *\n * @param provider The request-scoped metadata provider.\n * @param conversationID The room conversation id.\n * @param roomKey The room's external id (to load its bridge participants for attribution).\n */\nexport async function LoadRoomTranscript(\n provider: IMetadataProvider,\n conversationID: string,\n roomKey: string,\n): Promise<TranscriptLine[]> {\n const rv = RunView.FromMetadataProvider(provider);\n const [detailResult, bridgeResult] = await rv.RunViews([\n {\n EntityName: CONVERSATION_DETAIL_ENTITY,\n ExtraFilter: `ConversationID='${escapeSql(conversationID)}'`,\n Fields: ['ID', 'Role', 'Message', 'AgentID', 'ExternalID', 'Error', '__mj_CreatedAt'],\n OrderBy: '__mj_CreatedAt ASC',\n MaxRows: 5000,\n ResultType: 'simple',\n },\n {\n EntityName: BRIDGE_ENTITY,\n ExtraFilter: roomKey ? `ExternalConnectionID='${escapeSql(roomKey)}'` : `1=0`,\n Fields: ['ID'],\n MaxRows: 100,\n ResultType: 'simple',\n },\n ], provider.CurrentUser);\n\n const participantNames = await loadParticipantNames(provider, rv, (bridgeResult?.Results ?? []).map((b) => String(b['ID'])));\n const agentName = buildAgentNameLookup();\n\n const details = detailResult?.Success ? detailResult.Results : [];\n return details.map((d) => attributeLine(d, participantNames, agentName));\n}\n\n/** roomBridges → a map of `ExternalParticipantID → { name, isAgent }` for diarized-line attribution. */\nasync function loadParticipantNames(\n provider: IMetadataProvider,\n rv: RunView,\n bridgeIds: string[],\n): Promise<Map<string, { name: string; isAgent: boolean }>> {\n const map = new Map<string, { name: string; isAgent: boolean }>();\n if (bridgeIds.length === 0) {\n return map;\n }\n const inClause = bridgeIds.map((id) => `'${escapeSql(id)}'`).join(',');\n const result = await rv.RunView<Record<string, unknown>>(\n {\n EntityName: PARTICIPANT_ENTITY,\n ExtraFilter: `SessionBridgeID IN (${inClause})`,\n Fields: ['ExternalParticipantID', 'DisplayName', 'IsAgent'],\n MaxRows: 1000,\n ResultType: 'simple',\n },\n provider.CurrentUser,\n );\n if (result.Success) {\n for (const p of result.Results) {\n const id = String(p['ExternalParticipantID'] ?? '').toLowerCase();\n if (!id) {\n continue;\n }\n // Each agent appears on multiple bridges' rosters — `IsAgent=1` only on its OWN bridge (where it's\n // the bot), `IsAgent=0` where another agent merely heard it. OR-reduce so it resolves to an agent,\n // and keep the most specific (non-fallback) DisplayName we've seen for the identity.\n const isAgent = p['IsAgent'] === true;\n const name = String(p['DisplayName'] ?? '');\n const prev = map.get(id);\n map.set(id, {\n name: name || prev?.name || 'Participant',\n isAgent: (prev?.isAgent ?? false) || isAgent,\n });\n }\n }\n return map;\n}\n\n/** AgentID → agent name, from the AIEngine cache (agents are always loaded for the analytics shell). */\nfunction buildAgentNameLookup(): Map<string, string> {\n const map = new Map<string, string>();\n for (const a of AIEngineBase.Instance.Agents ?? []) {\n map.set(a.ID.toLowerCase(), a.Name ?? 'Agent');\n }\n return map;\n}\n\n/**\n * Resolves one raw detail row into a display-ready, speaker-attributed line — the diarization-attribution\n * core. Exported for unit testing (the maps are easy to construct directly, no RunView mocking needed).\n *\n * @param d The raw `MJ: Conversation Details` row.\n * @param participantNames `ExternalParticipantID(lower) → { name, isAgent }` from the room roster.\n * @param agentName `AgentID(lower) → agent name`.\n */\nexport function attributeLine(\n d: Record<string, unknown>,\n participantNames: Map<string, { name: string; isAgent: boolean }>,\n agentName: Map<string, string>,\n): TranscriptLine {\n const role = String(d['Role'] ?? 'User');\n const externalId = String(d['ExternalID'] ?? '').toLowerCase();\n const base = {\n ID: String(d['ID']),\n Message: String(d['Message'] ?? d['Error'] ?? ''),\n At: toDate(d['__mj_CreatedAt']),\n };\n if (role === 'Error') {\n return { ...base, Kind: 'error', Speaker: 'Error' };\n }\n if (role === 'AI') {\n const agentId = String(d['AgentID'] ?? '').toLowerCase();\n return { ...base, Kind: 'agent', Speaker: agentName.get(agentId) ?? 'Agent' };\n }\n // 'User' — diarized to a participant when we have the speaker label; another agent heard reads as agent.\n const participant = externalId ? participantNames.get(externalId) : undefined;\n if (participant) {\n return { ...base, Kind: participant.isAgent ? 'agent' : 'human', Speaker: participant.name };\n }\n return { ...base, Kind: 'human', Speaker: 'Participant' };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"realtime-transcripts-data.js","sourceRoot":"","sources":["../../../../../src/AI/components/analytics/realtime/realtime-transcripts-data.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Pure data layer for the Realtime \"Voice Transcripts\" view. Loads the unified per-room\n * meeting transcripts the bridge writes (one `MJ: Conversations` of `Type='Meeting Room'` per room +\n * `MJ: Conversation Details` per utterance), and resolves each line's speaker — agent lines via `AgentID`,\n * heard ('User') lines via the diarized `ExternalID` → the room's `AIAgentSessionBridgeParticipant` roster\n * (DisplayName + IsAgent). No Angular imports — RunView(s) + client-side aggregation only.\n *\n * @module @memberjunction/ng-dashboards\n */\nimport { IMetadataProvider, RunView } from '@memberjunction/core';\nimport { EscapeSQLString } from '@memberjunction/global';\nimport { AIEngineBase } from '@memberjunction/ai-engine-base';\n\nconst CONVERSATION_ENTITY = 'MJ: Conversations';\nconst CONVERSATION_DETAIL_ENTITY = 'MJ: Conversation Details';\nconst BRIDGE_ENTITY = 'MJ: AI Agent Session Bridges';\nconst PARTICIPANT_ENTITY = 'MJ: AI Agent Session Bridge Participants';\n\n/** One meeting-room transcript in the list view. */\nexport interface MeetingRoomSummary {\n ConversationID: string;\n Name: string;\n /** The room's external connection id (`ExternalID`) — the join key to the bridge/participant rows. */\n RoomKey: string;\n CreatedAt: Date;\n LastActivity: Date;\n}\n\n/** A speaker-attributed transcript line for the drill-in view. */\nexport interface TranscriptLine {\n ID: string;\n /** `'agent'` = an agent spoke; `'human'` = a human/participant spoke; `'error'` = an error row. */\n Kind: 'agent' | 'human' | 'error';\n /** Resolved display name of the speaker (agent name, participant name, or a sensible fallback). */\n Speaker: string;\n Message: string;\n At: Date;\n}\n\nconst toDate = (v: unknown): Date => (v instanceof Date ? v : new Date(String(v ?? '')));\n\n/**\n * Loads the meeting-room transcript LIST (light — no per-room detail load). Newest activity first.\n *\n * @param provider The request-scoped metadata provider.\n * @param maxRows Cap on rooms returned (default 500).\n */\nexport async function LoadMeetingRooms(provider: IMetadataProvider, maxRows = 500): Promise<MeetingRoomSummary[]> {\n const rv = RunView.FromMetadataProvider(provider);\n const result = await rv.RunView<Record<string, unknown>>(\n {\n EntityName: CONVERSATION_ENTITY,\n ExtraFilter: `Type='Meeting Room'`,\n Fields: ['ID', 'Name', 'ExternalID', '__mj_CreatedAt', '__mj_UpdatedAt'],\n OrderBy: '__mj_UpdatedAt DESC',\n MaxRows: maxRows,\n ResultType: 'simple',\n },\n provider.CurrentUser,\n );\n if (!result.Success) {\n return [];\n }\n return result.Results.map((r) => ({\n ConversationID: String(r['ID']),\n Name: String(r['Name'] ?? 'Meeting Room'),\n RoomKey: String(r['ExternalID'] ?? ''),\n CreatedAt: toDate(r['__mj_CreatedAt']),\n LastActivity: toDate(r['__mj_UpdatedAt']),\n }));\n}\n\n/**\n * Loads + speaker-attributes one room's transcript. Resolves agent lines via `AgentID` (AIEngine cache) and\n * heard lines via the diarized `ExternalID` against the room's participant roster (so a `User` line reads as\n * the actual person, and another agent heard in the room reads as that agent — not a generic \"User\").\n *\n * @param provider The request-scoped metadata provider.\n * @param conversationID The room conversation id.\n * @param roomKey The room's external id (to load its bridge participants for attribution).\n */\nexport async function LoadRoomTranscript(\n provider: IMetadataProvider,\n conversationID: string,\n roomKey: string,\n): Promise<TranscriptLine[]> {\n const rv = RunView.FromMetadataProvider(provider);\n const [detailResult, bridgeResult] = await rv.RunViews([\n {\n EntityName: CONVERSATION_DETAIL_ENTITY,\n ExtraFilter: `ConversationID='${EscapeSQLString(conversationID)}'`,\n Fields: ['ID', 'Role', 'Message', 'AgentID', 'ExternalID', 'Error', '__mj_CreatedAt'],\n OrderBy: '__mj_CreatedAt ASC',\n MaxRows: 5000,\n ResultType: 'simple',\n },\n {\n EntityName: BRIDGE_ENTITY,\n ExtraFilter: roomKey ? `ExternalConnectionID='${EscapeSQLString(roomKey)}'` : `1=0`,\n Fields: ['ID'],\n MaxRows: 100,\n ResultType: 'simple',\n },\n ], provider.CurrentUser);\n\n const participantNames = await loadParticipantNames(provider, rv, (bridgeResult?.Results ?? []).map((b) => String(b['ID'])));\n const agentName = buildAgentNameLookup();\n\n const details = detailResult?.Success ? detailResult.Results : [];\n return details.map((d) => attributeLine(d, participantNames, agentName));\n}\n\n/** roomBridges → a map of `ExternalParticipantID → { name, isAgent }` for diarized-line attribution. */\nasync function loadParticipantNames(\n provider: IMetadataProvider,\n rv: RunView,\n bridgeIds: string[],\n): Promise<Map<string, { name: string; isAgent: boolean }>> {\n const map = new Map<string, { name: string; isAgent: boolean }>();\n if (bridgeIds.length === 0) {\n return map;\n }\n const inClause = bridgeIds.map((id) => `'${EscapeSQLString(id)}'`).join(',');\n const result = await rv.RunView<Record<string, unknown>>(\n {\n EntityName: PARTICIPANT_ENTITY,\n ExtraFilter: `SessionBridgeID IN (${inClause})`,\n Fields: ['ExternalParticipantID', 'DisplayName', 'IsAgent'],\n MaxRows: 1000,\n ResultType: 'simple',\n },\n provider.CurrentUser,\n );\n if (result.Success) {\n for (const p of result.Results) {\n const id = String(p['ExternalParticipantID'] ?? '').toLowerCase();\n if (!id) {\n continue;\n }\n // Each agent appears on multiple bridges' rosters — `IsAgent=1` only on its OWN bridge (where it's\n // the bot), `IsAgent=0` where another agent merely heard it. OR-reduce so it resolves to an agent,\n // and keep the most specific (non-fallback) DisplayName we've seen for the identity.\n const isAgent = p['IsAgent'] === true;\n const name = String(p['DisplayName'] ?? '');\n const prev = map.get(id);\n map.set(id, {\n name: name || prev?.name || 'Participant',\n isAgent: (prev?.isAgent ?? false) || isAgent,\n });\n }\n }\n return map;\n}\n\n/** AgentID → agent name, from the AIEngine cache (agents are always loaded for the analytics shell). */\nfunction buildAgentNameLookup(): Map<string, string> {\n const map = new Map<string, string>();\n for (const a of AIEngineBase.Instance.Agents ?? []) {\n map.set(a.ID.toLowerCase(), a.Name ?? 'Agent');\n }\n return map;\n}\n\n/**\n * Resolves one raw detail row into a display-ready, speaker-attributed line — the diarization-attribution\n * core. Exported for unit testing (the maps are easy to construct directly, no RunView mocking needed).\n *\n * @param d The raw `MJ: Conversation Details` row.\n * @param participantNames `ExternalParticipantID(lower) → { name, isAgent }` from the room roster.\n * @param agentName `AgentID(lower) → agent name`.\n */\nexport function attributeLine(\n d: Record<string, unknown>,\n participantNames: Map<string, { name: string; isAgent: boolean }>,\n agentName: Map<string, string>,\n): TranscriptLine {\n const role = String(d['Role'] ?? 'User');\n const externalId = String(d['ExternalID'] ?? '').toLowerCase();\n const base = {\n ID: String(d['ID']),\n Message: String(d['Message'] ?? d['Error'] ?? ''),\n At: toDate(d['__mj_CreatedAt']),\n };\n if (role === 'Error') {\n return { ...base, Kind: 'error', Speaker: 'Error' };\n }\n if (role === 'AI') {\n const agentId = String(d['AgentID'] ?? '').toLowerCase();\n return { ...base, Kind: 'agent', Speaker: agentName.get(agentId) ?? 'Agent' };\n }\n // 'User' — diarized to a participant when we have the speaker label; another agent heard reads as agent.\n const participant = externalId ? participantNames.get(externalId) : undefined;\n if (participant) {\n return { ...base, Kind: participant.isAgent ? 'agent' : 'human', Speaker: participant.name };\n }\n return { ...base, Kind: 'human', Speaker: 'Participant' };\n}\n"]}
|
|
@@ -53,6 +53,14 @@ export declare abstract class BaseAdminContainerComponent extends BaseResourceCo
|
|
|
53
53
|
* scroll position, etc. — and avoids expensive re-init.
|
|
54
54
|
*/
|
|
55
55
|
protected cache: Map<string, ComponentRef<unknown>>;
|
|
56
|
+
/**
|
|
57
|
+
* A cache reattach moves this container to another tab without recreating the section components
|
|
58
|
+
* inside it, so every cached child's tab stamp has to move with it — not just the visible one.
|
|
59
|
+
* A detached section is exactly the case this matters for: it is invisible, it still holds a
|
|
60
|
+
* subscription, and left on its birth tab it would read and write that tab's params from inside
|
|
61
|
+
* a tab it no longer belongs to.
|
|
62
|
+
*/
|
|
63
|
+
protected onTabIdRebound(tabId: string): void;
|
|
56
64
|
protected currentSectionId: string | null;
|
|
57
65
|
protected readonly cdr: ChangeDetectorRef;
|
|
58
66
|
ngOnInit(): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-admin-container.component.d.ts","sourceRoot":"","sources":["../../src/Admin/base-admin-container.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,MAAM,EACN,SAAS,EACT,iBAAiB,EAEjB,gBAAgB,EAChB,YAAY,EAGf,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,qBAAqB,EAAiB,MAAM,2BAA2B,CAAC;AAGjF,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;;AAEnF,iEAAiE;AACjE,MAAM,WAAW,YAAY;IACzB,iEAAiE;IACjE,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,kBAAkB,CAAC;CAC9B;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,kBAAkB,GACxB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GACzC;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnD;;;;;;;;;GASG;AACH,8BACsB,2BAA4B,SAAQ,qBAAsB,YAAW,MAAM,EAAE,SAAS;IACxG,kBAAyB,cAAc,EAAE,MAAM,CAAC;IAChD,kBAAyB,aAAa,EAAE,MAAM,CAAC;IAC/C,kBAAyB,iBAAiB,EAAE,MAAM,CAAC;IACnD,kBAAyB,QAAQ,EAAE,YAAY,EAAE,CAAC;IAGlD,SAAS,CAAC,WAAW,EAAG,gBAAgB,CAAC;IAElC,aAAa,SAAM;IACnB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAQ;IAChC,SAAS,UAAS;IAEzB;;;;;;OAMG;IACH,SAAS,CAAC,KAAK,qCAA4C;
|
|
1
|
+
{"version":3,"file":"base-admin-container.component.d.ts","sourceRoot":"","sources":["../../src/Admin/base-admin-container.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,MAAM,EACN,SAAS,EACT,iBAAiB,EAEjB,gBAAgB,EAChB,YAAY,EAGf,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,qBAAqB,EAAiB,MAAM,2BAA2B,CAAC;AAGjF,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;;AAEnF,iEAAiE;AACjE,MAAM,WAAW,YAAY;IACzB,iEAAiE;IACjE,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,kBAAkB,CAAC;CAC9B;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,kBAAkB,GACxB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GACzC;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnD;;;;;;;;;GASG;AACH,8BACsB,2BAA4B,SAAQ,qBAAsB,YAAW,MAAM,EAAE,SAAS;IACxG,kBAAyB,cAAc,EAAE,MAAM,CAAC;IAChD,kBAAyB,aAAa,EAAE,MAAM,CAAC;IAC/C,kBAAyB,iBAAiB,EAAE,MAAM,CAAC;IACnD,kBAAyB,QAAQ,EAAE,YAAY,EAAE,CAAC;IAGlD,SAAS,CAAC,WAAW,EAAG,gBAAgB,CAAC;IAElC,aAAa,SAAM;IACnB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAQ;IAChC,SAAS,UAAS;IAEzB;;;;;;OAMG;IACH,SAAS,CAAC,KAAK,qCAA4C;IAE3D;;;;;;OAMG;cACgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAKtD,SAAS,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAQ;IAEjD,SAAS,CAAC,QAAQ,CAAC,GAAG,oBAA6B;IAE7B,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB/B,WAAW,IAAI,IAAI;IASb,sBAAsB,IAAI,OAAO,CAAC,MAAM,CAAC;IACzC,oBAAoB,IAAI,OAAO,CAAC,MAAM,CAAC;IAE7D;;;;OAIG;IACH,IAAW,WAAW,IAAI,gBAAgB,EAAE,CAE3C;IAED,gFAAgF;IACzE,gBAAgB,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI;IAOlD,yEAAyE;cACtD,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,UAAU,GAAG,UAAU,GAAG,IAAI;IASlG,cAAc,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;YAOnD,aAAa;YA+Bb,aAAa;IAwB3B,OAAO,CAAC,aAAa;YAQP,iBAAiB;YAYjB,kBAAkB;IA+ChC,OAAO,CAAC,WAAW;yCAhOD,2BAA2B;2CAA3B,2BAA2B;CAoOhD"}
|
|
@@ -27,6 +27,18 @@ export class BaseAdminContainerComponent extends BaseResourceComponent {
|
|
|
27
27
|
* scroll position, etc. — and avoids expensive re-init.
|
|
28
28
|
*/
|
|
29
29
|
cache = new Map();
|
|
30
|
+
/**
|
|
31
|
+
* A cache reattach moves this container to another tab without recreating the section components
|
|
32
|
+
* inside it, so every cached child's tab stamp has to move with it — not just the visible one.
|
|
33
|
+
* A detached section is exactly the case this matters for: it is invisible, it still holds a
|
|
34
|
+
* subscription, and left on its birth tab it would read and write that tab's params from inside
|
|
35
|
+
* a tab it no longer belongs to.
|
|
36
|
+
*/
|
|
37
|
+
onTabIdRebound(tabId) {
|
|
38
|
+
for (const ref of this.cache.values()) {
|
|
39
|
+
this.rehomeChildToTab(ref.instance, tabId);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
30
42
|
currentSectionId = null;
|
|
31
43
|
cdr = inject(ChangeDetectorRef);
|
|
32
44
|
async ngOnInit() {
|
|
@@ -181,6 +193,12 @@ export class BaseAdminContainerComponent extends BaseResourceComponent {
|
|
|
181
193
|
}
|
|
182
194
|
const ref = this.contentHost.createComponent(reg.SubClass);
|
|
183
195
|
const instance = ref.instance;
|
|
196
|
+
// Scope the embedded dashboard's query-param reads/writes to THIS container's tab. The
|
|
197
|
+
// sibling `createResourceRef` path inherits the tab id through `instance.Data`; a dashboard
|
|
198
|
+
// gets no ResourceData, so hand it the tab id explicitly. Without it the dashboard has no
|
|
199
|
+
// tab identity and its URL updates are dropped (previously: applied to whichever tab the
|
|
200
|
+
// user was viewing, which need not be this one).
|
|
201
|
+
instance.ParentTabId = this.getTabId();
|
|
184
202
|
// BaseDashboard catches a failing initDashboard()/loadData(), emits Error and still signals
|
|
185
203
|
// load-complete — so without this subscription an embedded dashboard's load failure would
|
|
186
204
|
// render as a blank content pane with a console-only error. Surface it in LoadError, which
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-admin-container.component.js","sourceRoot":"","sources":["../../src/Admin/base-admin-container.component.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Directive,\n OnInit,\n OnDestroy,\n ChangeDetectorRef,\n ViewChild,\n ViewContainerRef,\n ComponentRef,\n Type,\n inject\n} from '@angular/core';\nimport { BaseResourceComponent, BaseDashboard } from '@memberjunction/ng-shared';\nimport { MJGlobal } from '@memberjunction/global';\nimport { DashboardEngine } from '@memberjunction/core-entities';\nimport { MJLeftNavItem, MJLeftNavSection } from '@memberjunction/ng-ui-components';\n\n/** A single sub-section inside an admin container's left-nav. */\nexport interface AdminSection {\n /** Stable identifier — used in the URL `?section=` deep-link. */\n id: string;\n label: string;\n icon: string;\n description: string;\n source: AdminSectionSource;\n}\n\n/**\n * How to resolve and instantiate the component for this sub-section.\n *\n * - `resource`: looks up `@RegisterClass(BaseResourceComponent, driverClass)` and renders it directly.\n * - `dashboard`: looks up the MJ Dashboard record by name, then renders its `DriverClass` via the\n * BaseDashboard registry with a synthetic `{ dashboard }` config (mirrors the shell's flow for\n * `ResourceType: \"Dashboards\"` nav items).\n */\nexport type AdminSectionSource =\n | { kind: 'resource'; driverClass: string }\n | { kind: 'dashboard'; dashboardName: string };\n\n/**\n * Shared base for all Admin app container resources. Subclasses declare:\n * - ContainerTitle / ContainerIcon / ContainerSubtitle\n * - Sections[]\n * - @RegisterClass(BaseResourceComponent, '...')\n *\n * The base handles: left-nav state, URL deep-linking via NavigationService,\n * dynamic component instantiation for both resource and dashboard sections,\n * teardown on switch.\n */\n@Directive()\nexport abstract class BaseAdminContainerComponent extends BaseResourceComponent implements OnInit, OnDestroy {\n public abstract readonly ContainerTitle: string;\n public abstract readonly ContainerIcon: string;\n public abstract readonly ContainerSubtitle: string;\n public abstract readonly Sections: AdminSection[];\n\n @ViewChild('contentHost', { read: ViewContainerRef, static: true })\n protected contentHost!: ViewContainerRef;\n\n public ActiveSection = '';\n public LoadError: string | null = null;\n public IsLoading = false;\n\n /**\n * Cache of section.id → ComponentRef. Once a sub-section is rendered we\n * keep its component alive across switches by detaching + reattaching the\n * view (instead of destroying + recreating). This preserves state — Event\n * Monitor's captured events, GraphQL Console's history, query inputs,\n * scroll position, etc. — and avoids expensive re-init.\n */\n protected cache = new Map<string, ComponentRef<unknown>>();\n protected currentSectionId: string | null = null;\n\n protected readonly cdr = inject(ChangeDetectorRef);\n\n public override async ngOnInit(): Promise<void> {\n super.ngOnInit();\n\n const params = this.GetQueryParams();\n const requested = params['section'];\n const initial = this.findSection(requested)?.id ?? this.Sections[0]?.id ?? '';\n const target = this.findSection(initial);\n if (target) {\n await this.selectSection(target, /*syncUrl*/ false);\n }\n\n if (!requested && this.ActiveSection) {\n this.UpdateQueryParams({ section: this.ActiveSection });\n }\n\n this.NotifyLoadComplete();\n }\n\n public override ngOnDestroy(): void {\n for (const ref of this.cache.values()) {\n try { ref.destroy(); } catch { /* ignore */ }\n }\n this.cache.clear();\n this.currentSectionId = null;\n super.ngOnDestroy();\n }\n\n public override async GetResourceDisplayName(): Promise<string> { return this.ContainerTitle; }\n public override async GetResourceIconClass(): Promise<string> { return this.ContainerIcon; }\n\n /**\n * Single-section view of `Sections` shaped for `<mj-left-nav>`. AdminSection\n * already has the required id / label / icon / description fields, so it's\n * structurally compatible with MJLeftNavItem.\n */\n public get NavSections(): MJLeftNavSection[] {\n return [{ items: this.Sections }];\n }\n\n /** Adapter from `<mj-left-nav>`'s ItemClicked event back to `selectSection`. */\n public OnNavItemClicked(item: MJLeftNavItem): void {\n const section = this.Sections.find(s => s.id === item.id);\n if (section) {\n void this.OnSectionClick(section);\n }\n }\n\n /** Called by the framework on browser back/forward + deep-link entry. */\n protected override OnQueryParamsChanged(params: Record<string, string>, _source: 'popstate' | 'deeplink'): void {\n const section = params['section'];\n if (!section || section === this.ActiveSection) return;\n const target = this.findSection(section);\n if (target) {\n void this.selectSection(target, /*syncUrl*/ false);\n }\n }\n\n public async OnSectionClick(section: AdminSection): Promise<void> {\n if (section.id === this.ActiveSection) return;\n await this.selectSection(section, /*syncUrl*/ true);\n }\n\n // ---------- protected ----------\n\n private async selectSection(section: AdminSection, syncUrl: boolean): Promise<void> {\n // Guard against duplicate calls for the same section. Without this,\n // ngOnInit's own GetQueryParams read races with the workspace stream's\n // synchronous replay through OnQueryParamsChanged on first mount —\n // both call selectSection with the same target, both reach\n // createComponent, and two instances of the sub-page end up mounted\n // in the contentHost. OnSectionClick + OnQueryParamsChanged each\n // guard their own call site, but defense-in-depth here covers any\n // future caller (and the ngOnInit ↔ stream-replay race specifically).\n if (section.id === this.ActiveSection) {\n return;\n }\n this.ActiveSection = section.id;\n this.IsLoading = true;\n this.LoadError = null;\n this.cdr.markForCheck();\n\n if (syncUrl) {\n this.UpdateQueryParams({ section: section.id });\n }\n\n try {\n await this.renderSection(section);\n } catch (err) {\n this.LoadError = err instanceof Error ? err.message : String(err);\n } finally {\n this.IsLoading = false;\n this.cdr.markForCheck();\n }\n }\n\n private async renderSection(section: AdminSection): Promise<void> {\n // Detach (don't destroy) the currently visible sub-component so it\n // stays alive in memory with its state intact.\n this.detachCurrent();\n\n // Reattach if cached\n const cached = this.cache.get(section.id);\n if (cached) {\n this.contentHost.insert(cached.hostView);\n this.currentSectionId = section.id;\n return;\n }\n\n // Otherwise, create fresh\n const ref = section.source.kind === 'resource'\n ? await this.createResourceRef(section.source.driverClass)\n : await this.createDashboardRef(section.source.dashboardName);\n\n if (ref) {\n this.cache.set(section.id, ref);\n this.currentSectionId = section.id;\n }\n }\n\n private detachCurrent(): void {\n if (!this.currentSectionId) return;\n const cur = this.cache.get(this.currentSectionId);\n if (!cur) return;\n const idx = this.contentHost.indexOf(cur.hostView);\n if (idx >= 0) this.contentHost.detach(idx);\n }\n\n private async createResourceRef(driverClass: string): Promise<ComponentRef<unknown> | null> {\n const reg = await MJGlobal.Instance.ClassFactory.GetRegistrationAsync(BaseResourceComponent, driverClass);\n if (!reg) {\n this.LoadError = `Component \"${driverClass}\" is not registered. Make sure its module is imported.`;\n return null;\n }\n const ref = this.contentHost.createComponent(reg.SubClass as Type<BaseResourceComponent>);\n const instance = ref.instance as BaseResourceComponent;\n if (this.Data) instance.Data = this.Data;\n return ref;\n }\n\n private async createDashboardRef(dashboardName: string): Promise<ComponentRef<unknown> | null> {\n await DashboardEngine.Instance.Config(false);\n const dashboard = DashboardEngine.Instance.Dashboards.find(d => d.Name === dashboardName);\n if (!dashboard) {\n this.LoadError = `Dashboard \"${dashboardName}\" was not found in metadata.`;\n return null;\n }\n if (dashboard.Type !== 'Code') {\n this.LoadError = `Dashboard \"${dashboardName}\" has type \"${dashboard.Type}\" — only Code dashboards can be embedded here.`;\n return null;\n }\n if (!dashboard.DriverClass) {\n this.LoadError = `Dashboard \"${dashboardName}\" has no DriverClass set.`;\n return null;\n }\n const reg = await MJGlobal.Instance.ClassFactory.GetRegistrationAsync(BaseDashboard, dashboard.DriverClass);\n if (!reg) {\n this.LoadError = `Driver class \"${dashboard.DriverClass}\" is not registered against BaseDashboard.`;\n return null;\n }\n const ref = this.contentHost.createComponent(reg.SubClass as Type<BaseDashboard>);\n const instance = ref.instance as BaseDashboard;\n // BaseDashboard catches a failing initDashboard()/loadData(), emits Error and still signals\n // load-complete — so without this subscription an embedded dashboard's load failure would\n // render as a blank content pane with a console-only error. Surface it in LoadError, which\n // the container already renders. Scoped to the INITIAL load (same reasoning as\n // DashboardResource): sections are cached and kept alive across switches, and LoadError only\n // resets on the next selectSection, so a post-mount Refresh() failure would otherwise pin a\n // stale error banner — potentially while a different section is on screen.\n let initialLoadSettled = false;\n const onComplete = instance.LoadCompleteEvent;\n instance.LoadCompleteEvent = () => { initialLoadSettled = true; onComplete?.(); };\n instance.Error.subscribe((err: Error) => {\n if (initialLoadSettled) return; // post-mount refresh failure — already logged by BaseDashboard.runGuardedLoad\n this.LoadError = err.message;\n });\n instance.Config = { dashboard, userState: undefined };\n instance.Refresh();\n return ref;\n }\n\n private findSection(id: string | undefined | null): AdminSection | undefined {\n if (!id) return undefined;\n return this.Sections.find(s => s.id === id);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"base-admin-container.component.js","sourceRoot":"","sources":["../../src/Admin/base-admin-container.component.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Directive,\n OnInit,\n OnDestroy,\n ChangeDetectorRef,\n ViewChild,\n ViewContainerRef,\n ComponentRef,\n Type,\n inject\n} from '@angular/core';\nimport { BaseResourceComponent, BaseDashboard } from '@memberjunction/ng-shared';\nimport { MJGlobal } from '@memberjunction/global';\nimport { DashboardEngine } from '@memberjunction/core-entities';\nimport { MJLeftNavItem, MJLeftNavSection } from '@memberjunction/ng-ui-components';\n\n/** A single sub-section inside an admin container's left-nav. */\nexport interface AdminSection {\n /** Stable identifier — used in the URL `?section=` deep-link. */\n id: string;\n label: string;\n icon: string;\n description: string;\n source: AdminSectionSource;\n}\n\n/**\n * How to resolve and instantiate the component for this sub-section.\n *\n * - `resource`: looks up `@RegisterClass(BaseResourceComponent, driverClass)` and renders it directly.\n * - `dashboard`: looks up the MJ Dashboard record by name, then renders its `DriverClass` via the\n * BaseDashboard registry with a synthetic `{ dashboard }` config (mirrors the shell's flow for\n * `ResourceType: \"Dashboards\"` nav items).\n */\nexport type AdminSectionSource =\n | { kind: 'resource'; driverClass: string }\n | { kind: 'dashboard'; dashboardName: string };\n\n/**\n * Shared base for all Admin app container resources. Subclasses declare:\n * - ContainerTitle / ContainerIcon / ContainerSubtitle\n * - Sections[]\n * - @RegisterClass(BaseResourceComponent, '...')\n *\n * The base handles: left-nav state, URL deep-linking via NavigationService,\n * dynamic component instantiation for both resource and dashboard sections,\n * teardown on switch.\n */\n@Directive()\nexport abstract class BaseAdminContainerComponent extends BaseResourceComponent implements OnInit, OnDestroy {\n public abstract readonly ContainerTitle: string;\n public abstract readonly ContainerIcon: string;\n public abstract readonly ContainerSubtitle: string;\n public abstract readonly Sections: AdminSection[];\n\n @ViewChild('contentHost', { read: ViewContainerRef, static: true })\n protected contentHost!: ViewContainerRef;\n\n public ActiveSection = '';\n public LoadError: string | null = null;\n public IsLoading = false;\n\n /**\n * Cache of section.id → ComponentRef. Once a sub-section is rendered we\n * keep its component alive across switches by detaching + reattaching the\n * view (instead of destroying + recreating). This preserves state — Event\n * Monitor's captured events, GraphQL Console's history, query inputs,\n * scroll position, etc. — and avoids expensive re-init.\n */\n protected cache = new Map<string, ComponentRef<unknown>>();\n\n /**\n * A cache reattach moves this container to another tab without recreating the section components\n * inside it, so every cached child's tab stamp has to move with it — not just the visible one.\n * A detached section is exactly the case this matters for: it is invisible, it still holds a\n * subscription, and left on its birth tab it would read and write that tab's params from inside\n * a tab it no longer belongs to.\n */\n protected override onTabIdRebound(tabId: string): void {\n for (const ref of this.cache.values()) {\n this.rehomeChildToTab(ref.instance as BaseResourceComponent | undefined, tabId);\n }\n }\n protected currentSectionId: string | null = null;\n\n protected readonly cdr = inject(ChangeDetectorRef);\n\n public override async ngOnInit(): Promise<void> {\n super.ngOnInit();\n\n const params = this.GetQueryParams();\n const requested = params['section'];\n const initial = this.findSection(requested)?.id ?? this.Sections[0]?.id ?? '';\n const target = this.findSection(initial);\n if (target) {\n await this.selectSection(target, /*syncUrl*/ false);\n }\n\n if (!requested && this.ActiveSection) {\n this.UpdateQueryParams({ section: this.ActiveSection });\n }\n\n this.NotifyLoadComplete();\n }\n\n public override ngOnDestroy(): void {\n for (const ref of this.cache.values()) {\n try { ref.destroy(); } catch { /* ignore */ }\n }\n this.cache.clear();\n this.currentSectionId = null;\n super.ngOnDestroy();\n }\n\n public override async GetResourceDisplayName(): Promise<string> { return this.ContainerTitle; }\n public override async GetResourceIconClass(): Promise<string> { return this.ContainerIcon; }\n\n /**\n * Single-section view of `Sections` shaped for `<mj-left-nav>`. AdminSection\n * already has the required id / label / icon / description fields, so it's\n * structurally compatible with MJLeftNavItem.\n */\n public get NavSections(): MJLeftNavSection[] {\n return [{ items: this.Sections }];\n }\n\n /** Adapter from `<mj-left-nav>`'s ItemClicked event back to `selectSection`. */\n public OnNavItemClicked(item: MJLeftNavItem): void {\n const section = this.Sections.find(s => s.id === item.id);\n if (section) {\n void this.OnSectionClick(section);\n }\n }\n\n /** Called by the framework on browser back/forward + deep-link entry. */\n protected override OnQueryParamsChanged(params: Record<string, string>, _source: 'popstate' | 'deeplink'): void {\n const section = params['section'];\n if (!section || section === this.ActiveSection) return;\n const target = this.findSection(section);\n if (target) {\n void this.selectSection(target, /*syncUrl*/ false);\n }\n }\n\n public async OnSectionClick(section: AdminSection): Promise<void> {\n if (section.id === this.ActiveSection) return;\n await this.selectSection(section, /*syncUrl*/ true);\n }\n\n // ---------- protected ----------\n\n private async selectSection(section: AdminSection, syncUrl: boolean): Promise<void> {\n // Guard against duplicate calls for the same section. Without this,\n // ngOnInit's own GetQueryParams read races with the workspace stream's\n // synchronous replay through OnQueryParamsChanged on first mount —\n // both call selectSection with the same target, both reach\n // createComponent, and two instances of the sub-page end up mounted\n // in the contentHost. OnSectionClick + OnQueryParamsChanged each\n // guard their own call site, but defense-in-depth here covers any\n // future caller (and the ngOnInit ↔ stream-replay race specifically).\n if (section.id === this.ActiveSection) {\n return;\n }\n this.ActiveSection = section.id;\n this.IsLoading = true;\n this.LoadError = null;\n this.cdr.markForCheck();\n\n if (syncUrl) {\n this.UpdateQueryParams({ section: section.id });\n }\n\n try {\n await this.renderSection(section);\n } catch (err) {\n this.LoadError = err instanceof Error ? err.message : String(err);\n } finally {\n this.IsLoading = false;\n this.cdr.markForCheck();\n }\n }\n\n private async renderSection(section: AdminSection): Promise<void> {\n // Detach (don't destroy) the currently visible sub-component so it\n // stays alive in memory with its state intact.\n this.detachCurrent();\n\n // Reattach if cached\n const cached = this.cache.get(section.id);\n if (cached) {\n this.contentHost.insert(cached.hostView);\n this.currentSectionId = section.id;\n return;\n }\n\n // Otherwise, create fresh\n const ref = section.source.kind === 'resource'\n ? await this.createResourceRef(section.source.driverClass)\n : await this.createDashboardRef(section.source.dashboardName);\n\n if (ref) {\n this.cache.set(section.id, ref);\n this.currentSectionId = section.id;\n }\n }\n\n private detachCurrent(): void {\n if (!this.currentSectionId) return;\n const cur = this.cache.get(this.currentSectionId);\n if (!cur) return;\n const idx = this.contentHost.indexOf(cur.hostView);\n if (idx >= 0) this.contentHost.detach(idx);\n }\n\n private async createResourceRef(driverClass: string): Promise<ComponentRef<unknown> | null> {\n const reg = await MJGlobal.Instance.ClassFactory.GetRegistrationAsync(BaseResourceComponent, driverClass);\n if (!reg) {\n this.LoadError = `Component \"${driverClass}\" is not registered. Make sure its module is imported.`;\n return null;\n }\n const ref = this.contentHost.createComponent(reg.SubClass as Type<BaseResourceComponent>);\n const instance = ref.instance as BaseResourceComponent;\n if (this.Data) instance.Data = this.Data;\n return ref;\n }\n\n private async createDashboardRef(dashboardName: string): Promise<ComponentRef<unknown> | null> {\n await DashboardEngine.Instance.Config(false);\n const dashboard = DashboardEngine.Instance.Dashboards.find(d => d.Name === dashboardName);\n if (!dashboard) {\n this.LoadError = `Dashboard \"${dashboardName}\" was not found in metadata.`;\n return null;\n }\n if (dashboard.Type !== 'Code') {\n this.LoadError = `Dashboard \"${dashboardName}\" has type \"${dashboard.Type}\" — only Code dashboards can be embedded here.`;\n return null;\n }\n if (!dashboard.DriverClass) {\n this.LoadError = `Dashboard \"${dashboardName}\" has no DriverClass set.`;\n return null;\n }\n const reg = await MJGlobal.Instance.ClassFactory.GetRegistrationAsync(BaseDashboard, dashboard.DriverClass);\n if (!reg) {\n this.LoadError = `Driver class \"${dashboard.DriverClass}\" is not registered against BaseDashboard.`;\n return null;\n }\n const ref = this.contentHost.createComponent(reg.SubClass as Type<BaseDashboard>);\n const instance = ref.instance as BaseDashboard;\n // Scope the embedded dashboard's query-param reads/writes to THIS container's tab. The\n // sibling `createResourceRef` path inherits the tab id through `instance.Data`; a dashboard\n // gets no ResourceData, so hand it the tab id explicitly. Without it the dashboard has no\n // tab identity and its URL updates are dropped (previously: applied to whichever tab the\n // user was viewing, which need not be this one).\n instance.ParentTabId = this.getTabId();\n // BaseDashboard catches a failing initDashboard()/loadData(), emits Error and still signals\n // load-complete — so without this subscription an embedded dashboard's load failure would\n // render as a blank content pane with a console-only error. Surface it in LoadError, which\n // the container already renders. Scoped to the INITIAL load (same reasoning as\n // DashboardResource): sections are cached and kept alive across switches, and LoadError only\n // resets on the next selectSection, so a post-mount Refresh() failure would otherwise pin a\n // stale error banner — potentially while a different section is on screen.\n let initialLoadSettled = false;\n const onComplete = instance.LoadCompleteEvent;\n instance.LoadCompleteEvent = () => { initialLoadSettled = true; onComplete?.(); };\n instance.Error.subscribe((err: Error) => {\n if (initialLoadSettled) return; // post-mount refresh failure — already logged by BaseDashboard.runGuardedLoad\n this.LoadError = err.message;\n });\n instance.Config = { dashboard, userState: undefined };\n instance.Refresh();\n return ref;\n }\n\n private findSection(id: string | undefined | null): AdminSection | undefined {\n if (!id) return undefined;\n return this.Sections.find(s => s.id === id);\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database-designer.engine.d.ts","sourceRoot":"","sources":["../../../src/DatabaseDesigner/services/database-designer.engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAGH,OAAO,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"database-designer.engine.d.ts","sourceRoot":"","sources":["../../../src/DatabaseDesigner/services/database-designer.engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAGH,OAAO,EAAE,aAAa,EAA+B,MAAM,wBAAwB,CAAC;AAEpF,OAAO,KAAK,EACR,gBAAgB,EAChB,sBAAsB,EAGtB,YAAY,EACf,MAAM,+BAA+B,CAAC;AAwEvC;;;GAGG;AACH,qBAAa,sBAAuB,SAAQ,aAAa,CAAC,sBAAsB,CAAC;IAE7E,WAAkB,QAAQ,IAAI,sBAAsB,CAEnD;IAED,SAAS;IAIT,gDAAgD;IAChD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA2C;IAElE,oFAAoF;IACpF,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAuC;IAEpE,sFAAsF;IAC/E,eAAe,IAAI,IAAI;IAU9B;;;;;;;;OAQG;IACU,sBAAsB,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAmBlE;;;OAGG;IACU,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC;IAqDvF;;;;;;;;;;;;;;OAcG;IACU,oBAAoB,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IAc5D,iFAAiF;YACnE,qBAAqB;IA+CnC,0FAA0F;YAC5E,iBAAiB;IAe/B;;;;;OAKG;IACU,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAgBzG,uFAAuF;YACzE,eAAe;IAsD7B,oEAAoE;YACtD,iBAAiB;IAkC/B,kFAAkF;YACpE,kBAAkB;IAmBhC,oFAAoF;IACpF,OAAO,CAAC,uBAAuB;IAQ/B,2EAA2E;IAC3E,OAAO,CAAC,oBAAoB;CAe/B"}
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* @see DatabaseDesignerService — for action invocation (create, modify, validate)
|
|
25
25
|
*/
|
|
26
26
|
import { Metadata, RunView, AuthorizationEvaluator } from '@memberjunction/core';
|
|
27
|
-
import { BaseSingleton, UUIDsEqual } from '@memberjunction/global';
|
|
27
|
+
import { BaseSingleton, UUIDsEqual, EscapeSQLString } from '@memberjunction/global';
|
|
28
28
|
// ─── Constants (mirror database-designer-core/src/interfaces.ts) ───────────────
|
|
29
29
|
// These string values MUST stay in sync with the server-side constants.
|
|
30
30
|
// If the server-side values change, update here in parallel.
|
|
@@ -247,8 +247,8 @@ export class DatabaseDesignerEngine extends BaseSingleton {
|
|
|
247
247
|
const rv = new RunView();
|
|
248
248
|
const result = await rv.RunView({
|
|
249
249
|
EntityName: 'MJ: Entities',
|
|
250
|
-
ExtraFilter: `(Name = '${
|
|
251
|
-
`AND SchemaName = '${
|
|
250
|
+
ExtraFilter: `(Name = '${EscapeSQLString(entityName)}' OR BaseTable = '${EscapeSQLString(tableName)}') ` +
|
|
251
|
+
`AND SchemaName = '${EscapeSQLString(schemaName)}'`,
|
|
252
252
|
Fields: ['ID'],
|
|
253
253
|
ResultType: 'simple',
|
|
254
254
|
});
|
|
@@ -308,7 +308,7 @@ export class DatabaseDesignerEngine extends BaseSingleton {
|
|
|
308
308
|
// Find entities owned by this user
|
|
309
309
|
const ownerResult = await rv.RunView({
|
|
310
310
|
EntityName: 'MJ: Entity Settings',
|
|
311
|
-
ExtraFilter: `Name = '${UDT_SETTINGS.OWNER_KEY}' AND Value = '${
|
|
311
|
+
ExtraFilter: `Name = '${UDT_SETTINGS.OWNER_KEY}' AND Value = '${EscapeSQLString(userId)}'`,
|
|
312
312
|
Fields: ['EntityID', 'Value'],
|
|
313
313
|
ResultType: 'simple',
|
|
314
314
|
});
|
|
@@ -343,7 +343,7 @@ export class DatabaseDesignerEngine extends BaseSingleton {
|
|
|
343
343
|
EntityName: 'MJ: Entity Settings',
|
|
344
344
|
ExtraFilter: (`EntityID = '${escapeSqlId(entityId)}' ` +
|
|
345
345
|
`AND Name = '${UDT_SETTINGS.OWNER_KEY}' ` +
|
|
346
|
-
`AND Value = '${
|
|
346
|
+
`AND Value = '${EscapeSQLString(userId)}'`),
|
|
347
347
|
Fields: ['EntityID', 'Value'],
|
|
348
348
|
ResultType: 'simple',
|
|
349
349
|
});
|
|
@@ -385,8 +385,4 @@ function buildInFilter(column, ids) {
|
|
|
385
385
|
function escapeSqlId(id) {
|
|
386
386
|
return id.replace(/[^a-zA-Z0-9-]/g, '');
|
|
387
387
|
}
|
|
388
|
-
/** Escape a string value for safe embedding in a SQL single-quoted literal. */
|
|
389
|
-
function escapeSqlLiteral(value) {
|
|
390
|
-
return value.replace(/'/g, "''");
|
|
391
|
-
}
|
|
392
388
|
//# sourceMappingURL=database-designer.engine.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database-designer.engine.js","sourceRoot":"","sources":["../../../src/DatabaseDesigner/services/database-designer.engine.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @module database-designer.engine\n * @description Read-only data access engine for the Database Designer Angular UI.\n *\n * `DatabaseDesignerEngine` is a `BaseSingleton` (NOT an Angular service) that:\n * - Loads the list of entities accessible to the current user\n * - Loads full entity detail for the modify wizard\n * - Loads available schemas from `MJ: Schema Info` (async, cached per-user)\n * - Checks whether a proposed entity name / table name is available\n *\n * ### Why BaseSingleton, not @Injectable?\n * Engine classes hold a cross-request cache that must survive Angular's DI\n * lifecycle boundaries (the same instance is reused across multiple wizard\n * openings in the same session). `BaseSingleton` uses the MJ Global Object\n * Store which guarantees exactly one instance per process, even across ESBuild\n * code-split chunks.\n *\n * ### Usage\n * ```typescript\n * const engine = DatabaseDesignerEngine.Instance;\n * const entities = await engine.loadAccessibleEntities();\n * ```\n *\n * @see DatabaseDesignerService — for action invocation (create, modify, validate)\n */\n\nimport { Metadata, RunView, AuthorizationEvaluator } from '@memberjunction/core';\nimport { BaseSingleton, UUIDsEqual } from '@memberjunction/global';\n\nimport type {\n AccessibleEntity,\n AccessibleEntityDetail,\n ColumnSpec,\n ForeignKeySpec,\n SchemaOption,\n} from '../database-designer.types.js';\n\n// ─── Constants (mirror database-designer-core/src/interfaces.ts) ───────────────\n// These string values MUST stay in sync with the server-side constants.\n// If the server-side values change, update here in parallel.\n\nconst UDT_SCHEMA_NAME = '__mj_UDT';\n\nconst UDT_SETTINGS = {\n OWNER_KEY: 'MJ:UDT:Owner',\n SOURCE_KEY: 'MJ:UDT:Source',\n SOURCE_DATABASE_DESIGNER: 'DatabaseDesigner',\n SOURCE_AGENT_MANAGER: 'AgentManager',\n} as const;\n\nconst ENTITY_DESIGNER_AUTH = {\n CREATE_IN_UDT_SCHEMA: 'Create in UDT Schema',\n CREATE_IN_CUSTOM_SCHEMA: 'Create in Custom Schema',\n MODIFY_OWN_ENTITIES: 'Modify Own Entities',\n MODIFY_ANY_UDT_ENTITIES: 'Modify Any UDT Entities',\n} as const;\n\n/** Cache TTL in milliseconds (5 minutes). */\nconst CACHE_TTL_MS = 5 * 60 * 1_000;\n\n// ─── Columns managed automatically by CodeGen (never shown as user-editable) ─\n\n/**\n * Normalised lower-case set of column names that CodeGen injects on every entity.\n * These are filtered out of the modify wizard so users cannot inadvertently remove or\n * rename them. The set is compared after calling `.toLowerCase()` on each column name\n * so it is case-insensitive regardless of DB platform.\n */\nconst AUTO_MANAGED_COLUMNS = new Set(['id', '__mj_createdat', '__mj_updatedat']);\n\n// ─── Internal row types for RunView results ───────────────────────────────────\n\ninterface SettingsRow { EntityID: string; Value: string }\ninterface EntityRow { ID: string; Name: string; BaseTable: string; SchemaName: string; __mj_CreatedAt: string }\ninterface FieldRow {\n ID: string;\n EntityID: string;\n Name: string;\n Type: string;\n AllowsNull: boolean;\n Description: string;\n DefaultValue: string | null;\n MaxLength: number | null;\n Precision: number | null;\n Scale: number | null;\n}\n\n// ─── Cache entry ─────────────────────────────────────────────────────────────\n\ninterface EntityListCacheEntry {\n entities: AccessibleEntity[];\n timestamp: number;\n}\n\ninterface SchemaCacheEntry {\n schemas: SchemaOption[];\n timestamp: number;\n}\n\n// ─── Frontend-only schema blocklist ──────────────────────────────────────────\n// `MJ: Schema Info` only surfaces MJ-registered schemas, so system schemas\n// (sys, dbo, information_schema) never appear. Only __mj must be blocked here.\n// Server-side RSM.GetAllProtectedSchemas() remains the authoritative gate.\nconst FRONTEND_BLOCKED_SCHEMAS = new Set(['__mj']);\n\n// ─── Engine ──────────────────────────────────────────────────────────────────\n\n/**\n * Singleton data access engine for Database Designer.\n * All methods read data; none modifies state (mutations go through `DatabaseDesignerService`).\n */\nexport class DatabaseDesignerEngine extends BaseSingleton<DatabaseDesignerEngine> {\n\n public static get Instance(): DatabaseDesignerEngine {\n return super.getInstance<DatabaseDesignerEngine>();\n }\n\n protected constructor() { super(); }\n\n // ─── Cache ────────────────────────────────────────────────────────────\n\n /** Per-user entity list cache. Key = userID. */\n private readonly _cache = new Map<string, EntityListCacheEntry>();\n\n /** Per-user schema list cache. Key = userID. Invalidated alongside entity cache. */\n private readonly _schemaCache = new Map<string, SchemaCacheEntry>();\n\n /** Invalidate all per-user caches for the current user (call after create/modify). */\n public invalidateCache(): void {\n const userId = new Metadata().CurrentUser?.ID; // global-provider-ok: client-side Angular engine, single provider\n if (userId) {\n this._cache.delete(userId);\n this._schemaCache.delete(userId);\n }\n }\n\n // ─── Entity list ──────────────────────────────────────────────────────\n\n /**\n * Load all entities accessible to the current user.\n *\n * - **Admin** (holds `Modify Any UDT Entities`): sees all entities created by\n * Database Designer or Agent Manager, with ownership annotations.\n * - **Regular user**: sees only entities they own (`MJ:UDT:Owner = userId`).\n *\n * Results are cached per-user with a 5-minute TTL.\n */\n public async loadAccessibleEntities(): Promise<AccessibleEntity[]> {\n const md = new Metadata(); // global-provider-ok: client-side Angular engine, single provider\n const userId = md.CurrentUser?.ID;\n if (!userId) return [];\n\n const cached = this._cache.get(userId);\n if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {\n return cached.entities;\n }\n\n const isAdmin = this.currentUserCanModifyAny();\n const entities = isAdmin\n ? await this.loadAllEntities(userId)\n : await this.loadOwnedEntities(userId);\n\n this._cache.set(userId, { entities, timestamp: Date.now() });\n return entities;\n }\n\n /**\n * Load full entity detail (columns, foreign keys, description) for the modify wizard.\n * Not cached — always fetches fresh data so the wizard reflects current DB state.\n */\n public async loadEntityDetail(entityId: string): Promise<AccessibleEntityDetail | null> {\n const rv = new RunView();\n // BypassCache: true on both queries because CodeGen writes Entity Fields via direct\n // SQL (outside BaseEntity.Save()), so the server-side RunView cache never receives\n // an invalidation event after a pipeline run. Without this, the detail view would\n // show stale field data (e.g. old AllowsNull value) until MJAPI restarts.\n const [entityResult, fieldsResult] = await rv.RunViews([\n {\n EntityName: 'MJ: Entities',\n ExtraFilter: `ID = '${escapeSqlId(entityId)}'`,\n Fields: ['ID', 'Name', 'BaseTable', 'SchemaName', 'Description', '__mj_CreatedAt'],\n ResultType: 'simple',\n BypassCache: true,\n },\n {\n EntityName: 'MJ: Entity Fields',\n ExtraFilter: `EntityID = '${escapeSqlId(entityId)}' AND IsVirtual = 0`,\n Fields: ['ID', 'EntityID', 'Name', 'Type', 'AllowsNull', 'Description', 'DefaultValue', 'MaxLength', 'Precision', 'Scale'],\n OrderBy: 'Sequence ASC, Name ASC',\n ResultType: 'simple',\n BypassCache: true,\n },\n ]);\n\n if (!entityResult.Success || entityResult.Results.length === 0) return null;\n\n const entity = entityResult.Results[0] as EntityRow & { Description: string };\n const fieldRows = (fieldsResult.Results ?? []) as FieldRow[];\n\n const isOwner = await this.isCurrentUserOwner(entityId);\n\n // Exclude auto-managed columns (ID, __mj_CreatedAt, __mj_UpdatedAt) from the editable\n // column list. CodeGen injects these automatically — they must not be user-modified.\n const editableFields = fieldRows.filter(\n f => !AUTO_MANAGED_COLUMNS.has(f.Name.toLowerCase())\n );\n\n return {\n entityId: entity.ID,\n entityName: entity.Name,\n tableName: entity.BaseTable,\n schemaName: entity.SchemaName,\n description: entity.Description ?? '',\n fieldCount: editableFields.length,\n createdAt: new Date(entity.__mj_CreatedAt),\n isOwner,\n columns: editableFields.map(f => this.mapFieldToColumnSpec(f)),\n foreignKeys: [], // FK detail loaded on demand — Phase 5e\n };\n }\n\n // ─── Schema options ───────────────────────────────────────────────────\n\n /**\n * Return the list of schemas the current user is authorized to create tables in.\n *\n * Async — queries `MJ: Schema Info` to surface real DB schemas for users\n * with `Create in Custom Schema` authorization. Results are cached per-user\n * with the same 5-minute TTL as the entity list.\n *\n * Order:\n * 1. `__mj_UDT` (if user holds `Create in UDT Schema`)\n * 2. Real DB schemas from `MJ: Schema Info`, excluding blocked schemas\n * and `__mj_UDT` (already listed first)\n * 3. \"Other (enter schema name)\" free-text option, at the end\n *\n * All items beyond #1 require `Create in Custom Schema` authorization.\n */\n public async loadAvailableSchemas(): Promise<SchemaOption[]> {\n const userId = new Metadata().CurrentUser?.ID; // global-provider-ok: client-side Angular engine, single provider\n if (!userId) return [];\n\n const cached = this._schemaCache.get(userId);\n if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {\n return cached.schemas;\n }\n\n const schemas = await this.fetchAvailableSchemas();\n this._schemaCache.set(userId, { schemas, timestamp: Date.now() });\n return schemas;\n }\n\n /** Build the schema option list: UDT first, then real DB schemas, then Other. */\n private async fetchAvailableSchemas(): Promise<SchemaOption[]> {\n const md = new Metadata(); // global-provider-ok: client-side Angular engine, single provider\n const evaluator = new AuthorizationEvaluator();\n\n const udtAuth = md.Authorizations.find(a => a.Name === ENTITY_DESIGNER_AUTH.CREATE_IN_UDT_SCHEMA);\n const customAuth = md.Authorizations.find(a => a.Name === ENTITY_DESIGNER_AUTH.CREATE_IN_CUSTOM_SCHEMA);\n\n const canUseUdt = !!(udtAuth && evaluator.CurrentUserCanExecuteWithAncestors(udtAuth));\n const canUseCustom = !!(customAuth && evaluator.CurrentUserCanExecuteWithAncestors(customAuth));\n\n const schemas: SchemaOption[] = [];\n\n if (canUseUdt) {\n schemas.push({\n value: UDT_SCHEMA_NAME,\n label: `${UDT_SCHEMA_NAME} — User-Defined Tables (default)`,\n isDefault: true,\n requiresElevatedAuth: false,\n });\n }\n\n if (canUseCustom) {\n const dbSchemas = await this.loadDbSchemaNames();\n for (const schemaName of dbSchemas) {\n // Skip the UDT schema (already first) and any server-blocked schemas\n if (schemaName === UDT_SCHEMA_NAME) continue;\n if (FRONTEND_BLOCKED_SCHEMAS.has(schemaName)) continue;\n schemas.push({\n value: schemaName,\n label: schemaName,\n isDefault: false,\n requiresElevatedAuth: true,\n });\n }\n\n // Free-text escape hatch for schemas not yet registered with MJ\n schemas.push({\n value: '',\n label: 'Other (enter schema name)',\n isDefault: false,\n requiresElevatedAuth: true,\n });\n }\n\n return schemas;\n }\n\n /** Query `MJ: Schema Info` for distinct schema names registered with this MJ instance. */\n private async loadDbSchemaNames(): Promise<string[]> {\n const rv = new RunView();\n const result = await rv.RunView<{ SchemaName: string }>({\n EntityName: 'MJ: Schema Info',\n Fields: ['SchemaName'],\n OrderBy: 'SchemaName ASC',\n ResultType: 'simple',\n });\n\n if (!result.Success) return [];\n return result.Results.map(r => r.SchemaName).filter(Boolean);\n }\n\n // ─── Name availability ────────────────────────────────────────────────\n\n /**\n * Check whether the proposed entity name and table name are available\n * (no existing MJ entity in the same schema shares either).\n *\n * Returns true when the proposed names are safe to use.\n */\n public async isNameAvailable(entityName: string, tableName: string, schemaName: string): Promise<boolean> {\n const rv = new RunView();\n const result = await rv.RunView<{ ID: string }>({\n EntityName: 'MJ: Entities',\n ExtraFilter:\n `(Name = '${escapeSqlLiteral(entityName)}' OR BaseTable = '${escapeSqlLiteral(tableName)}') ` +\n `AND SchemaName = '${escapeSqlLiteral(schemaName)}'`,\n Fields: ['ID'],\n ResultType: 'simple',\n });\n\n return result.Success && result.Results.length === 0;\n }\n\n // ─── Private helpers ──────────────────────────────────────────────────\n\n /** Admin path: load all database-Designer-managed entities + ownership annotations. */\n private async loadAllEntities(currentUserId: string): Promise<AccessibleEntity[]> {\n const rv = new RunView();\n\n // Round 1: get entity IDs from provenance settings and owner map\n const [sourceResult, ownerResult] = await rv.RunViews([\n {\n EntityName: 'MJ: Entity Settings',\n ExtraFilter: (\n `Name = '${UDT_SETTINGS.SOURCE_KEY}' ` +\n `AND Value IN ('${UDT_SETTINGS.SOURCE_DATABASE_DESIGNER}', '${UDT_SETTINGS.SOURCE_AGENT_MANAGER}')`\n ),\n Fields: ['EntityID', 'Value'],\n ResultType: 'simple',\n },\n {\n EntityName: 'MJ: Entity Settings',\n ExtraFilter: `Name = '${UDT_SETTINGS.OWNER_KEY}'`,\n Fields: ['EntityID', 'Value'],\n ResultType: 'simple',\n },\n ]);\n\n const sourceRows = (sourceResult.Results ?? []) as SettingsRow[];\n const ownerRows = (ownerResult.Results ?? []) as SettingsRow[];\n\n const entityIds = Array.from(new Set(sourceRows.map(r => r.EntityID)));\n if (entityIds.length === 0) return [];\n\n const ownerMap = new Map(ownerRows.map(r => [r.EntityID, r.Value]));\n\n // Round 2: entity details\n const entityResult = await rv.RunView<EntityRow>({\n EntityName: 'MJ: Entities',\n ExtraFilter: buildInFilter('ID', entityIds),\n Fields: ['ID', 'Name', 'BaseTable', 'SchemaName', '__mj_CreatedAt'],\n OrderBy: 'Name ASC',\n ResultType: 'simple',\n });\n\n return (entityResult.Results ?? []).map(row => {\n const ownerUserId = ownerMap.get(row.ID);\n return {\n entityId: row.ID,\n entityName: row.Name,\n tableName: row.BaseTable,\n schemaName: row.SchemaName,\n fieldCount: 0, // loaded on demand in detail view\n createdAt: new Date(row.__mj_CreatedAt),\n // UUIDsEqual handles SQL Server uppercase vs PostgreSQL lowercase UUIDs\n isOwner: ownerUserId != null && UUIDsEqual(ownerUserId, currentUserId),\n } satisfies AccessibleEntity;\n });\n }\n\n /** Non-admin path: load only entities owned by the current user. */\n private async loadOwnedEntities(userId: string): Promise<AccessibleEntity[]> {\n const rv = new RunView();\n\n // Find entities owned by this user\n const ownerResult = await rv.RunView<SettingsRow>({\n EntityName: 'MJ: Entity Settings',\n ExtraFilter: `Name = '${UDT_SETTINGS.OWNER_KEY}' AND Value = '${escapeSqlLiteral(userId)}'`,\n Fields: ['EntityID', 'Value'],\n ResultType: 'simple',\n });\n\n const entityIds = (ownerResult.Results ?? []).map(r => r.EntityID);\n if (entityIds.length === 0) return [];\n\n // Fetch entity details\n const entityResult = await rv.RunView<EntityRow>({\n EntityName: 'MJ: Entities',\n ExtraFilter: buildInFilter('ID', entityIds),\n Fields: ['ID', 'Name', 'BaseTable', 'SchemaName', '__mj_CreatedAt'],\n OrderBy: 'Name ASC',\n ResultType: 'simple',\n });\n\n return (entityResult.Results ?? []).map(row => ({\n entityId: row.ID,\n entityName: row.Name,\n tableName: row.BaseTable,\n schemaName: row.SchemaName,\n fieldCount: 0,\n createdAt: new Date(row.__mj_CreatedAt),\n isOwner: true, // user can only see their own entities in this path\n } satisfies AccessibleEntity));\n }\n\n /** Check if the current user owns the given entity (via MJ:UDT:Owner setting). */\n private async isCurrentUserOwner(entityId: string): Promise<boolean> {\n const userId = new Metadata().CurrentUser?.ID; // global-provider-ok: client-side Angular engine, single provider\n if (!userId) return false;\n\n const rv = new RunView();\n const result = await rv.RunView<SettingsRow>({\n EntityName: 'MJ: Entity Settings',\n ExtraFilter: (\n `EntityID = '${escapeSqlId(entityId)}' ` +\n `AND Name = '${UDT_SETTINGS.OWNER_KEY}' ` +\n `AND Value = '${escapeSqlLiteral(userId)}'`\n ),\n Fields: ['EntityID', 'Value'],\n ResultType: 'simple',\n });\n\n return result.Success && result.Results.length > 0;\n }\n\n /** True when the current user holds the `Modify Any UDT Entities` authorization. */\n private currentUserCanModifyAny(): boolean {\n const md = new Metadata(); // global-provider-ok: client-side Angular engine, single provider\n const auth = md.Authorizations.find(a => a.Name === ENTITY_DESIGNER_AUTH.MODIFY_ANY_UDT_ENTITIES);\n if (!auth) return false;\n const evaluator = new AuthorizationEvaluator();\n return evaluator.CurrentUserCanExecuteWithAncestors(auth);\n }\n\n /** Map a `MJ: Entity Fields` row to a `ColumnSpec` for the detail view. */\n private mapFieldToColumnSpec(field: FieldRow): ColumnSpec {\n return {\n Name: field.Name,\n Type: 'string', // detail view uses RawSqlType for display; Type is semantic\n RawSqlType: field.Type,\n IsNullable: field.AllowsNull,\n // Preserve precision/scale metadata so the modify wizard renders the correct\n // field type controls (e.g. DECIMAL(18,2) vs DECIMAL with no constraints)\n MaxLength: field.MaxLength ?? undefined,\n Precision: field.Precision ?? undefined,\n Scale: field.Scale ?? undefined,\n DefaultValue: field.DefaultValue ?? undefined,\n Description: field.Description ?? undefined,\n };\n }\n}\n\n// ─── Private module-level utilities ──────────────────────────────────────────\n\n/** Build a SQL `column IN ('id1', 'id2', ...)` filter for a UUID list. */\nfunction buildInFilter(column: string, ids: string[]): string {\n const quoted = ids.map(id => `'${escapeSqlId(id)}'`).join(', ');\n return `${column} IN (${quoted})`;\n}\n\n/** Escape a UUID for safe embedding in a SQL filter (UUIDs are alphanumeric + dashes, but be explicit). */\nfunction escapeSqlId(id: string): string {\n return id.replace(/[^a-zA-Z0-9-]/g, '');\n}\n\n/** Escape a string value for safe embedding in a SQL single-quoted literal. */\nfunction escapeSqlLiteral(value: string): string {\n return value.replace(/'/g, \"''\");\n}\n"]}
|
|
1
|
+
{"version":3,"file":"database-designer.engine.js","sourceRoot":"","sources":["../../../src/DatabaseDesigner/services/database-designer.engine.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @module database-designer.engine\n * @description Read-only data access engine for the Database Designer Angular UI.\n *\n * `DatabaseDesignerEngine` is a `BaseSingleton` (NOT an Angular service) that:\n * - Loads the list of entities accessible to the current user\n * - Loads full entity detail for the modify wizard\n * - Loads available schemas from `MJ: Schema Info` (async, cached per-user)\n * - Checks whether a proposed entity name / table name is available\n *\n * ### Why BaseSingleton, not @Injectable?\n * Engine classes hold a cross-request cache that must survive Angular's DI\n * lifecycle boundaries (the same instance is reused across multiple wizard\n * openings in the same session). `BaseSingleton` uses the MJ Global Object\n * Store which guarantees exactly one instance per process, even across ESBuild\n * code-split chunks.\n *\n * ### Usage\n * ```typescript\n * const engine = DatabaseDesignerEngine.Instance;\n * const entities = await engine.loadAccessibleEntities();\n * ```\n *\n * @see DatabaseDesignerService — for action invocation (create, modify, validate)\n */\n\nimport { Metadata, RunView, AuthorizationEvaluator } from '@memberjunction/core';\nimport { BaseSingleton, UUIDsEqual, EscapeSQLString } from '@memberjunction/global';\n\nimport type {\n AccessibleEntity,\n AccessibleEntityDetail,\n ColumnSpec,\n ForeignKeySpec,\n SchemaOption,\n} from '../database-designer.types.js';\n\n// ─── Constants (mirror database-designer-core/src/interfaces.ts) ───────────────\n// These string values MUST stay in sync with the server-side constants.\n// If the server-side values change, update here in parallel.\n\nconst UDT_SCHEMA_NAME = '__mj_UDT';\n\nconst UDT_SETTINGS = {\n OWNER_KEY: 'MJ:UDT:Owner',\n SOURCE_KEY: 'MJ:UDT:Source',\n SOURCE_DATABASE_DESIGNER: 'DatabaseDesigner',\n SOURCE_AGENT_MANAGER: 'AgentManager',\n} as const;\n\nconst ENTITY_DESIGNER_AUTH = {\n CREATE_IN_UDT_SCHEMA: 'Create in UDT Schema',\n CREATE_IN_CUSTOM_SCHEMA: 'Create in Custom Schema',\n MODIFY_OWN_ENTITIES: 'Modify Own Entities',\n MODIFY_ANY_UDT_ENTITIES: 'Modify Any UDT Entities',\n} as const;\n\n/** Cache TTL in milliseconds (5 minutes). */\nconst CACHE_TTL_MS = 5 * 60 * 1_000;\n\n// ─── Columns managed automatically by CodeGen (never shown as user-editable) ─\n\n/**\n * Normalised lower-case set of column names that CodeGen injects on every entity.\n * These are filtered out of the modify wizard so users cannot inadvertently remove or\n * rename them. The set is compared after calling `.toLowerCase()` on each column name\n * so it is case-insensitive regardless of DB platform.\n */\nconst AUTO_MANAGED_COLUMNS = new Set(['id', '__mj_createdat', '__mj_updatedat']);\n\n// ─── Internal row types for RunView results ───────────────────────────────────\n\ninterface SettingsRow { EntityID: string; Value: string }\ninterface EntityRow { ID: string; Name: string; BaseTable: string; SchemaName: string; __mj_CreatedAt: string }\ninterface FieldRow {\n ID: string;\n EntityID: string;\n Name: string;\n Type: string;\n AllowsNull: boolean;\n Description: string;\n DefaultValue: string | null;\n MaxLength: number | null;\n Precision: number | null;\n Scale: number | null;\n}\n\n// ─── Cache entry ─────────────────────────────────────────────────────────────\n\ninterface EntityListCacheEntry {\n entities: AccessibleEntity[];\n timestamp: number;\n}\n\ninterface SchemaCacheEntry {\n schemas: SchemaOption[];\n timestamp: number;\n}\n\n// ─── Frontend-only schema blocklist ──────────────────────────────────────────\n// `MJ: Schema Info` only surfaces MJ-registered schemas, so system schemas\n// (sys, dbo, information_schema) never appear. Only __mj must be blocked here.\n// Server-side RSM.GetAllProtectedSchemas() remains the authoritative gate.\nconst FRONTEND_BLOCKED_SCHEMAS = new Set(['__mj']);\n\n// ─── Engine ──────────────────────────────────────────────────────────────────\n\n/**\n * Singleton data access engine for Database Designer.\n * All methods read data; none modifies state (mutations go through `DatabaseDesignerService`).\n */\nexport class DatabaseDesignerEngine extends BaseSingleton<DatabaseDesignerEngine> {\n\n public static get Instance(): DatabaseDesignerEngine {\n return super.getInstance<DatabaseDesignerEngine>();\n }\n\n protected constructor() { super(); }\n\n // ─── Cache ────────────────────────────────────────────────────────────\n\n /** Per-user entity list cache. Key = userID. */\n private readonly _cache = new Map<string, EntityListCacheEntry>();\n\n /** Per-user schema list cache. Key = userID. Invalidated alongside entity cache. */\n private readonly _schemaCache = new Map<string, SchemaCacheEntry>();\n\n /** Invalidate all per-user caches for the current user (call after create/modify). */\n public invalidateCache(): void {\n const userId = new Metadata().CurrentUser?.ID; // global-provider-ok: client-side Angular engine, single provider\n if (userId) {\n this._cache.delete(userId);\n this._schemaCache.delete(userId);\n }\n }\n\n // ─── Entity list ──────────────────────────────────────────────────────\n\n /**\n * Load all entities accessible to the current user.\n *\n * - **Admin** (holds `Modify Any UDT Entities`): sees all entities created by\n * Database Designer or Agent Manager, with ownership annotations.\n * - **Regular user**: sees only entities they own (`MJ:UDT:Owner = userId`).\n *\n * Results are cached per-user with a 5-minute TTL.\n */\n public async loadAccessibleEntities(): Promise<AccessibleEntity[]> {\n const md = new Metadata(); // global-provider-ok: client-side Angular engine, single provider\n const userId = md.CurrentUser?.ID;\n if (!userId) return [];\n\n const cached = this._cache.get(userId);\n if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {\n return cached.entities;\n }\n\n const isAdmin = this.currentUserCanModifyAny();\n const entities = isAdmin\n ? await this.loadAllEntities(userId)\n : await this.loadOwnedEntities(userId);\n\n this._cache.set(userId, { entities, timestamp: Date.now() });\n return entities;\n }\n\n /**\n * Load full entity detail (columns, foreign keys, description) for the modify wizard.\n * Not cached — always fetches fresh data so the wizard reflects current DB state.\n */\n public async loadEntityDetail(entityId: string): Promise<AccessibleEntityDetail | null> {\n const rv = new RunView();\n // BypassCache: true on both queries because CodeGen writes Entity Fields via direct\n // SQL (outside BaseEntity.Save()), so the server-side RunView cache never receives\n // an invalidation event after a pipeline run. Without this, the detail view would\n // show stale field data (e.g. old AllowsNull value) until MJAPI restarts.\n const [entityResult, fieldsResult] = await rv.RunViews([\n {\n EntityName: 'MJ: Entities',\n ExtraFilter: `ID = '${escapeSqlId(entityId)}'`,\n Fields: ['ID', 'Name', 'BaseTable', 'SchemaName', 'Description', '__mj_CreatedAt'],\n ResultType: 'simple',\n BypassCache: true,\n },\n {\n EntityName: 'MJ: Entity Fields',\n ExtraFilter: `EntityID = '${escapeSqlId(entityId)}' AND IsVirtual = 0`,\n Fields: ['ID', 'EntityID', 'Name', 'Type', 'AllowsNull', 'Description', 'DefaultValue', 'MaxLength', 'Precision', 'Scale'],\n OrderBy: 'Sequence ASC, Name ASC',\n ResultType: 'simple',\n BypassCache: true,\n },\n ]);\n\n if (!entityResult.Success || entityResult.Results.length === 0) return null;\n\n const entity = entityResult.Results[0] as EntityRow & { Description: string };\n const fieldRows = (fieldsResult.Results ?? []) as FieldRow[];\n\n const isOwner = await this.isCurrentUserOwner(entityId);\n\n // Exclude auto-managed columns (ID, __mj_CreatedAt, __mj_UpdatedAt) from the editable\n // column list. CodeGen injects these automatically — they must not be user-modified.\n const editableFields = fieldRows.filter(\n f => !AUTO_MANAGED_COLUMNS.has(f.Name.toLowerCase())\n );\n\n return {\n entityId: entity.ID,\n entityName: entity.Name,\n tableName: entity.BaseTable,\n schemaName: entity.SchemaName,\n description: entity.Description ?? '',\n fieldCount: editableFields.length,\n createdAt: new Date(entity.__mj_CreatedAt),\n isOwner,\n columns: editableFields.map(f => this.mapFieldToColumnSpec(f)),\n foreignKeys: [], // FK detail loaded on demand — Phase 5e\n };\n }\n\n // ─── Schema options ───────────────────────────────────────────────────\n\n /**\n * Return the list of schemas the current user is authorized to create tables in.\n *\n * Async — queries `MJ: Schema Info` to surface real DB schemas for users\n * with `Create in Custom Schema` authorization. Results are cached per-user\n * with the same 5-minute TTL as the entity list.\n *\n * Order:\n * 1. `__mj_UDT` (if user holds `Create in UDT Schema`)\n * 2. Real DB schemas from `MJ: Schema Info`, excluding blocked schemas\n * and `__mj_UDT` (already listed first)\n * 3. \"Other (enter schema name)\" free-text option, at the end\n *\n * All items beyond #1 require `Create in Custom Schema` authorization.\n */\n public async loadAvailableSchemas(): Promise<SchemaOption[]> {\n const userId = new Metadata().CurrentUser?.ID; // global-provider-ok: client-side Angular engine, single provider\n if (!userId) return [];\n\n const cached = this._schemaCache.get(userId);\n if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {\n return cached.schemas;\n }\n\n const schemas = await this.fetchAvailableSchemas();\n this._schemaCache.set(userId, { schemas, timestamp: Date.now() });\n return schemas;\n }\n\n /** Build the schema option list: UDT first, then real DB schemas, then Other. */\n private async fetchAvailableSchemas(): Promise<SchemaOption[]> {\n const md = new Metadata(); // global-provider-ok: client-side Angular engine, single provider\n const evaluator = new AuthorizationEvaluator();\n\n const udtAuth = md.Authorizations.find(a => a.Name === ENTITY_DESIGNER_AUTH.CREATE_IN_UDT_SCHEMA);\n const customAuth = md.Authorizations.find(a => a.Name === ENTITY_DESIGNER_AUTH.CREATE_IN_CUSTOM_SCHEMA);\n\n const canUseUdt = !!(udtAuth && evaluator.CurrentUserCanExecuteWithAncestors(udtAuth));\n const canUseCustom = !!(customAuth && evaluator.CurrentUserCanExecuteWithAncestors(customAuth));\n\n const schemas: SchemaOption[] = [];\n\n if (canUseUdt) {\n schemas.push({\n value: UDT_SCHEMA_NAME,\n label: `${UDT_SCHEMA_NAME} — User-Defined Tables (default)`,\n isDefault: true,\n requiresElevatedAuth: false,\n });\n }\n\n if (canUseCustom) {\n const dbSchemas = await this.loadDbSchemaNames();\n for (const schemaName of dbSchemas) {\n // Skip the UDT schema (already first) and any server-blocked schemas\n if (schemaName === UDT_SCHEMA_NAME) continue;\n if (FRONTEND_BLOCKED_SCHEMAS.has(schemaName)) continue;\n schemas.push({\n value: schemaName,\n label: schemaName,\n isDefault: false,\n requiresElevatedAuth: true,\n });\n }\n\n // Free-text escape hatch for schemas not yet registered with MJ\n schemas.push({\n value: '',\n label: 'Other (enter schema name)',\n isDefault: false,\n requiresElevatedAuth: true,\n });\n }\n\n return schemas;\n }\n\n /** Query `MJ: Schema Info` for distinct schema names registered with this MJ instance. */\n private async loadDbSchemaNames(): Promise<string[]> {\n const rv = new RunView();\n const result = await rv.RunView<{ SchemaName: string }>({\n EntityName: 'MJ: Schema Info',\n Fields: ['SchemaName'],\n OrderBy: 'SchemaName ASC',\n ResultType: 'simple',\n });\n\n if (!result.Success) return [];\n return result.Results.map(r => r.SchemaName).filter(Boolean);\n }\n\n // ─── Name availability ────────────────────────────────────────────────\n\n /**\n * Check whether the proposed entity name and table name are available\n * (no existing MJ entity in the same schema shares either).\n *\n * Returns true when the proposed names are safe to use.\n */\n public async isNameAvailable(entityName: string, tableName: string, schemaName: string): Promise<boolean> {\n const rv = new RunView();\n const result = await rv.RunView<{ ID: string }>({\n EntityName: 'MJ: Entities',\n ExtraFilter:\n `(Name = '${EscapeSQLString(entityName)}' OR BaseTable = '${EscapeSQLString(tableName)}') ` +\n `AND SchemaName = '${EscapeSQLString(schemaName)}'`,\n Fields: ['ID'],\n ResultType: 'simple',\n });\n\n return result.Success && result.Results.length === 0;\n }\n\n // ─── Private helpers ──────────────────────────────────────────────────\n\n /** Admin path: load all database-Designer-managed entities + ownership annotations. */\n private async loadAllEntities(currentUserId: string): Promise<AccessibleEntity[]> {\n const rv = new RunView();\n\n // Round 1: get entity IDs from provenance settings and owner map\n const [sourceResult, ownerResult] = await rv.RunViews([\n {\n EntityName: 'MJ: Entity Settings',\n ExtraFilter: (\n `Name = '${UDT_SETTINGS.SOURCE_KEY}' ` +\n `AND Value IN ('${UDT_SETTINGS.SOURCE_DATABASE_DESIGNER}', '${UDT_SETTINGS.SOURCE_AGENT_MANAGER}')`\n ),\n Fields: ['EntityID', 'Value'],\n ResultType: 'simple',\n },\n {\n EntityName: 'MJ: Entity Settings',\n ExtraFilter: `Name = '${UDT_SETTINGS.OWNER_KEY}'`,\n Fields: ['EntityID', 'Value'],\n ResultType: 'simple',\n },\n ]);\n\n const sourceRows = (sourceResult.Results ?? []) as SettingsRow[];\n const ownerRows = (ownerResult.Results ?? []) as SettingsRow[];\n\n const entityIds = Array.from(new Set(sourceRows.map(r => r.EntityID)));\n if (entityIds.length === 0) return [];\n\n const ownerMap = new Map(ownerRows.map(r => [r.EntityID, r.Value]));\n\n // Round 2: entity details\n const entityResult = await rv.RunView<EntityRow>({\n EntityName: 'MJ: Entities',\n ExtraFilter: buildInFilter('ID', entityIds),\n Fields: ['ID', 'Name', 'BaseTable', 'SchemaName', '__mj_CreatedAt'],\n OrderBy: 'Name ASC',\n ResultType: 'simple',\n });\n\n return (entityResult.Results ?? []).map(row => {\n const ownerUserId = ownerMap.get(row.ID);\n return {\n entityId: row.ID,\n entityName: row.Name,\n tableName: row.BaseTable,\n schemaName: row.SchemaName,\n fieldCount: 0, // loaded on demand in detail view\n createdAt: new Date(row.__mj_CreatedAt),\n // UUIDsEqual handles SQL Server uppercase vs PostgreSQL lowercase UUIDs\n isOwner: ownerUserId != null && UUIDsEqual(ownerUserId, currentUserId),\n } satisfies AccessibleEntity;\n });\n }\n\n /** Non-admin path: load only entities owned by the current user. */\n private async loadOwnedEntities(userId: string): Promise<AccessibleEntity[]> {\n const rv = new RunView();\n\n // Find entities owned by this user\n const ownerResult = await rv.RunView<SettingsRow>({\n EntityName: 'MJ: Entity Settings',\n ExtraFilter: `Name = '${UDT_SETTINGS.OWNER_KEY}' AND Value = '${EscapeSQLString(userId)}'`,\n Fields: ['EntityID', 'Value'],\n ResultType: 'simple',\n });\n\n const entityIds = (ownerResult.Results ?? []).map(r => r.EntityID);\n if (entityIds.length === 0) return [];\n\n // Fetch entity details\n const entityResult = await rv.RunView<EntityRow>({\n EntityName: 'MJ: Entities',\n ExtraFilter: buildInFilter('ID', entityIds),\n Fields: ['ID', 'Name', 'BaseTable', 'SchemaName', '__mj_CreatedAt'],\n OrderBy: 'Name ASC',\n ResultType: 'simple',\n });\n\n return (entityResult.Results ?? []).map(row => ({\n entityId: row.ID,\n entityName: row.Name,\n tableName: row.BaseTable,\n schemaName: row.SchemaName,\n fieldCount: 0,\n createdAt: new Date(row.__mj_CreatedAt),\n isOwner: true, // user can only see their own entities in this path\n } satisfies AccessibleEntity));\n }\n\n /** Check if the current user owns the given entity (via MJ:UDT:Owner setting). */\n private async isCurrentUserOwner(entityId: string): Promise<boolean> {\n const userId = new Metadata().CurrentUser?.ID; // global-provider-ok: client-side Angular engine, single provider\n if (!userId) return false;\n\n const rv = new RunView();\n const result = await rv.RunView<SettingsRow>({\n EntityName: 'MJ: Entity Settings',\n ExtraFilter: (\n `EntityID = '${escapeSqlId(entityId)}' ` +\n `AND Name = '${UDT_SETTINGS.OWNER_KEY}' ` +\n `AND Value = '${EscapeSQLString(userId)}'`\n ),\n Fields: ['EntityID', 'Value'],\n ResultType: 'simple',\n });\n\n return result.Success && result.Results.length > 0;\n }\n\n /** True when the current user holds the `Modify Any UDT Entities` authorization. */\n private currentUserCanModifyAny(): boolean {\n const md = new Metadata(); // global-provider-ok: client-side Angular engine, single provider\n const auth = md.Authorizations.find(a => a.Name === ENTITY_DESIGNER_AUTH.MODIFY_ANY_UDT_ENTITIES);\n if (!auth) return false;\n const evaluator = new AuthorizationEvaluator();\n return evaluator.CurrentUserCanExecuteWithAncestors(auth);\n }\n\n /** Map a `MJ: Entity Fields` row to a `ColumnSpec` for the detail view. */\n private mapFieldToColumnSpec(field: FieldRow): ColumnSpec {\n return {\n Name: field.Name,\n Type: 'string', // detail view uses RawSqlType for display; Type is semantic\n RawSqlType: field.Type,\n IsNullable: field.AllowsNull,\n // Preserve precision/scale metadata so the modify wizard renders the correct\n // field type controls (e.g. DECIMAL(18,2) vs DECIMAL with no constraints)\n MaxLength: field.MaxLength ?? undefined,\n Precision: field.Precision ?? undefined,\n Scale: field.Scale ?? undefined,\n DefaultValue: field.DefaultValue ?? undefined,\n Description: field.Description ?? undefined,\n };\n }\n}\n\n// ─── Private module-level utilities ──────────────────────────────────────────\n\n/** Build a SQL `column IN ('id1', 'id2', ...)` filter for a UUID list. */\nfunction buildInFilter(column: string, ids: string[]): string {\n const quoted = ids.map(id => `'${escapeSqlId(id)}'`).join(', ');\n return `${column} IN (${quoted})`;\n}\n\n/** Escape a UUID for safe embedding in a SQL filter (UUIDs are alphanumeric + dashes, but be explicit). */\nfunction escapeSqlId(id: string): string {\n return id.replace(/[^a-zA-Z0-9-]/g, '');\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/ng-dashboards",
|
|
3
|
-
"version": "6.1.0-edge.
|
|
3
|
+
"version": "6.1.0-edge.5",
|
|
4
4
|
"description": "MemberJunction Dashboards",
|
|
5
5
|
"main": "./dist/public-api.js",
|
|
6
6
|
"typings": "./dist/public-api.d.ts",
|
|
@@ -29,6 +29,10 @@
|
|
|
29
29
|
"types": "./dist/scheduling-dashboards.module.d.ts",
|
|
30
30
|
"default": "./dist/scheduling-dashboards.module.js"
|
|
31
31
|
},
|
|
32
|
+
"./archiving-dashboards.module": {
|
|
33
|
+
"types": "./dist/archiving-dashboards.module.d.ts",
|
|
34
|
+
"default": "./dist/archiving-dashboards.module.js"
|
|
35
|
+
},
|
|
32
36
|
"./communication-dashboards.module": {
|
|
33
37
|
"types": "./dist/communication-dashboards.module.d.ts",
|
|
34
38
|
"default": "./dist/communication-dashboards.module.js"
|
|
@@ -85,7 +89,7 @@
|
|
|
85
89
|
"@angular/compiler-cli": "21.1.3",
|
|
86
90
|
"@types/d3": "^7.4.0",
|
|
87
91
|
"sass": "^1.97.3",
|
|
88
|
-
"@memberjunction/ng-test-utils": "6.1.0-edge.
|
|
92
|
+
"@memberjunction/ng-test-utils": "6.1.0-edge.5"
|
|
89
93
|
},
|
|
90
94
|
"peerDependencies": {
|
|
91
95
|
"@angular/common": "^21.1.3",
|
|
@@ -102,67 +106,67 @@
|
|
|
102
106
|
"@codemirror/merge": "^6.11.2",
|
|
103
107
|
"@codemirror/state": "^6.5.4",
|
|
104
108
|
"@codemirror/view": "^6.39.12",
|
|
105
|
-
"@memberjunction/actions-base": "6.1.0-edge.
|
|
106
|
-
"@memberjunction/ai-core-plus": "6.1.0-edge.
|
|
107
|
-
"@memberjunction/ai-engine-base": "6.1.0-edge.
|
|
108
|
-
"@memberjunction/api-keys-base": "6.1.0-edge.
|
|
109
|
-
"@memberjunction/core": "6.1.0-edge.
|
|
110
|
-
"@memberjunction/core-entities": "6.1.0-edge.
|
|
111
|
-
"@memberjunction/credentials": "6.1.0-edge.
|
|
112
|
-
"@memberjunction/export-engine": "6.1.0-edge.
|
|
113
|
-
"@memberjunction/global": "6.1.0-edge.
|
|
114
|
-
"@memberjunction/graphql-dataprovider": "6.1.0-edge.
|
|
115
|
-
"@memberjunction/integration-engine-base": "6.1.0-edge.
|
|
116
|
-
"@memberjunction/interactive-component-types": "6.1.0-edge.
|
|
117
|
-
"@memberjunction/lists-base": "6.1.0-edge.
|
|
118
|
-
"@memberjunction/ng-action-gallery": "6.1.0-edge.
|
|
119
|
-
"@memberjunction/ng-actions": "6.1.0-edge.
|
|
120
|
-
"@memberjunction/ng-agent-requests": "6.1.0-edge.
|
|
121
|
-
"@memberjunction/ng-agents": "6.1.0-edge.
|
|
122
|
-
"@memberjunction/ng-ai-test-harness": "6.1.0-edge.
|
|
123
|
-
"@memberjunction/ng-archive-manager": "6.1.0-edge.
|
|
124
|
-
"@memberjunction/ng-base-application": "6.1.0-edge.
|
|
125
|
-
"@memberjunction/ng-base-forms": "6.1.0-edge.
|
|
126
|
-
"@memberjunction/ng-base-types": "6.1.0-edge.
|
|
127
|
-
"@memberjunction/ng-clustering": "6.1.0-edge.
|
|
128
|
-
"@memberjunction/ng-code-editor": "6.1.0-edge.
|
|
129
|
-
"@memberjunction/ng-composer": "6.1.0-edge.
|
|
130
|
-
"@memberjunction/ng-container-directives": "6.1.0-edge.
|
|
131
|
-
"@memberjunction/ng-conversations": "6.1.0-edge.
|
|
132
|
-
"@memberjunction/ng-core-entity-forms": "6.1.0-edge.
|
|
133
|
-
"@memberjunction/ng-credentials": "6.1.0-edge.
|
|
134
|
-
"@memberjunction/ng-dashboard-viewer": "6.1.0-edge.
|
|
135
|
-
"@memberjunction/ng-entity-relationship-diagram": "6.1.0-edge.
|
|
136
|
-
"@memberjunction/ng-entity-viewer": "6.1.0-edge.
|
|
137
|
-
"@memberjunction/ng-explorer-settings": "6.1.0-edge.
|
|
138
|
-
"@memberjunction/ng-export-service": "6.1.0-edge.
|
|
139
|
-
"@memberjunction/ng-filter-builder": "6.1.0-edge.
|
|
140
|
-
"@memberjunction/ng-list-management": "6.1.0-edge.
|
|
141
|
-
"@memberjunction/ng-map-view": "6.1.0-edge.
|
|
142
|
-
"@memberjunction/ng-markdown": "6.1.0-edge.
|
|
143
|
-
"@memberjunction/ng-media-player": "6.1.0-edge.
|
|
144
|
-
"@memberjunction/ng-notifications": "6.1.0-edge.
|
|
145
|
-
"@memberjunction/ng-query-viewer": "6.1.0-edge.
|
|
146
|
-
"@memberjunction/ng-react": "6.1.0-edge.
|
|
147
|
-
"@memberjunction/ng-record-process-studio": "6.1.0-edge.
|
|
148
|
-
"@memberjunction/ng-resource-permissions": "6.1.0-edge.
|
|
149
|
-
"@memberjunction/ng-scheduling": "6.1.0-edge.
|
|
150
|
-
"@memberjunction/ng-search": "6.1.0-edge.
|
|
151
|
-
"@memberjunction/ng-shared": "6.1.0-edge.
|
|
152
|
-
"@memberjunction/ng-shared-generic": "6.1.0-edge.
|
|
153
|
-
"@memberjunction/ng-task-graph-editor": "6.1.0-edge.
|
|
154
|
-
"@memberjunction/ng-tabstrip": "6.1.0-edge.
|
|
155
|
-
"@memberjunction/ng-testing": "6.1.0-edge.
|
|
156
|
-
"@memberjunction/ng-trees": "6.1.0-edge.
|
|
157
|
-
"@memberjunction/ng-ui-components": "6.1.0-edge.
|
|
158
|
-
"@memberjunction/ng-user-routines": "6.1.0-edge.
|
|
159
|
-
"@memberjunction/ng-versions": "6.1.0-edge.
|
|
160
|
-
"@memberjunction/ng-word-cloud": "6.1.0-edge.
|
|
161
|
-
"@memberjunction/predictive-studio-core": "6.1.0-edge.
|
|
162
|
-
"@memberjunction/tag-engine-base": "6.1.0-edge.
|
|
163
|
-
"@memberjunction/templates-base-types": "6.1.0-edge.
|
|
164
|
-
"@memberjunction/testing-engine-base": "6.1.0-edge.
|
|
165
|
-
"@memberjunction/theme-engine": "6.1.0-edge.
|
|
109
|
+
"@memberjunction/actions-base": "6.1.0-edge.5",
|
|
110
|
+
"@memberjunction/ai-core-plus": "6.1.0-edge.5",
|
|
111
|
+
"@memberjunction/ai-engine-base": "6.1.0-edge.5",
|
|
112
|
+
"@memberjunction/api-keys-base": "6.1.0-edge.5",
|
|
113
|
+
"@memberjunction/core": "6.1.0-edge.5",
|
|
114
|
+
"@memberjunction/core-entities": "6.1.0-edge.5",
|
|
115
|
+
"@memberjunction/credentials": "6.1.0-edge.5",
|
|
116
|
+
"@memberjunction/export-engine": "6.1.0-edge.5",
|
|
117
|
+
"@memberjunction/global": "6.1.0-edge.5",
|
|
118
|
+
"@memberjunction/graphql-dataprovider": "6.1.0-edge.5",
|
|
119
|
+
"@memberjunction/integration-engine-base": "6.1.0-edge.5",
|
|
120
|
+
"@memberjunction/interactive-component-types": "6.1.0-edge.5",
|
|
121
|
+
"@memberjunction/lists-base": "6.1.0-edge.5",
|
|
122
|
+
"@memberjunction/ng-action-gallery": "6.1.0-edge.5",
|
|
123
|
+
"@memberjunction/ng-actions": "6.1.0-edge.5",
|
|
124
|
+
"@memberjunction/ng-agent-requests": "6.1.0-edge.5",
|
|
125
|
+
"@memberjunction/ng-agents": "6.1.0-edge.5",
|
|
126
|
+
"@memberjunction/ng-ai-test-harness": "6.1.0-edge.5",
|
|
127
|
+
"@memberjunction/ng-archive-manager": "6.1.0-edge.5",
|
|
128
|
+
"@memberjunction/ng-base-application": "6.1.0-edge.5",
|
|
129
|
+
"@memberjunction/ng-base-forms": "6.1.0-edge.5",
|
|
130
|
+
"@memberjunction/ng-base-types": "6.1.0-edge.5",
|
|
131
|
+
"@memberjunction/ng-clustering": "6.1.0-edge.5",
|
|
132
|
+
"@memberjunction/ng-code-editor": "6.1.0-edge.5",
|
|
133
|
+
"@memberjunction/ng-composer": "6.1.0-edge.5",
|
|
134
|
+
"@memberjunction/ng-container-directives": "6.1.0-edge.5",
|
|
135
|
+
"@memberjunction/ng-conversations": "6.1.0-edge.5",
|
|
136
|
+
"@memberjunction/ng-core-entity-forms": "6.1.0-edge.5",
|
|
137
|
+
"@memberjunction/ng-credentials": "6.1.0-edge.5",
|
|
138
|
+
"@memberjunction/ng-dashboard-viewer": "6.1.0-edge.5",
|
|
139
|
+
"@memberjunction/ng-entity-relationship-diagram": "6.1.0-edge.5",
|
|
140
|
+
"@memberjunction/ng-entity-viewer": "6.1.0-edge.5",
|
|
141
|
+
"@memberjunction/ng-explorer-settings": "6.1.0-edge.5",
|
|
142
|
+
"@memberjunction/ng-export-service": "6.1.0-edge.5",
|
|
143
|
+
"@memberjunction/ng-filter-builder": "6.1.0-edge.5",
|
|
144
|
+
"@memberjunction/ng-list-management": "6.1.0-edge.5",
|
|
145
|
+
"@memberjunction/ng-map-view": "6.1.0-edge.5",
|
|
146
|
+
"@memberjunction/ng-markdown": "6.1.0-edge.5",
|
|
147
|
+
"@memberjunction/ng-media-player": "6.1.0-edge.5",
|
|
148
|
+
"@memberjunction/ng-notifications": "6.1.0-edge.5",
|
|
149
|
+
"@memberjunction/ng-query-viewer": "6.1.0-edge.5",
|
|
150
|
+
"@memberjunction/ng-react": "6.1.0-edge.5",
|
|
151
|
+
"@memberjunction/ng-record-process-studio": "6.1.0-edge.5",
|
|
152
|
+
"@memberjunction/ng-resource-permissions": "6.1.0-edge.5",
|
|
153
|
+
"@memberjunction/ng-scheduling": "6.1.0-edge.5",
|
|
154
|
+
"@memberjunction/ng-search": "6.1.0-edge.5",
|
|
155
|
+
"@memberjunction/ng-shared": "6.1.0-edge.5",
|
|
156
|
+
"@memberjunction/ng-shared-generic": "6.1.0-edge.5",
|
|
157
|
+
"@memberjunction/ng-task-graph-editor": "6.1.0-edge.5",
|
|
158
|
+
"@memberjunction/ng-tabstrip": "6.1.0-edge.5",
|
|
159
|
+
"@memberjunction/ng-testing": "6.1.0-edge.5",
|
|
160
|
+
"@memberjunction/ng-trees": "6.1.0-edge.5",
|
|
161
|
+
"@memberjunction/ng-ui-components": "6.1.0-edge.5",
|
|
162
|
+
"@memberjunction/ng-user-routines": "6.1.0-edge.5",
|
|
163
|
+
"@memberjunction/ng-versions": "6.1.0-edge.5",
|
|
164
|
+
"@memberjunction/ng-word-cloud": "6.1.0-edge.5",
|
|
165
|
+
"@memberjunction/predictive-studio-core": "6.1.0-edge.5",
|
|
166
|
+
"@memberjunction/tag-engine-base": "6.1.0-edge.5",
|
|
167
|
+
"@memberjunction/templates-base-types": "6.1.0-edge.5",
|
|
168
|
+
"@memberjunction/testing-engine-base": "6.1.0-edge.5",
|
|
169
|
+
"@memberjunction/theme-engine": "6.1.0-edge.5",
|
|
166
170
|
"ag-grid-angular": "^35.0.1",
|
|
167
171
|
"ag-grid-community": "^35.0.1",
|
|
168
172
|
"angular-split": "^20.0.0",
|