@xpert-ai/plugin-live-dashboard 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.xpertai-plugin/plugin.json +1 -1
- package/README.md +4 -2
- package/dist/index.js +3 -1
- package/dist/lib/agent.schema.d.ts +9 -9
- package/dist/lib/agent.schema.js +2 -1
- package/dist/lib/archive.service.d.ts +26 -2
- package/dist/lib/archive.service.js +17 -3
- package/dist/lib/bounded-map.d.ts +2 -0
- package/dist/lib/bounded-map.js +25 -0
- package/dist/lib/capacity.d.ts +14 -0
- package/dist/lib/capacity.js +16 -0
- package/dist/lib/dashboard-archive.d.ts +1 -1
- package/dist/lib/dashboard-queries.d.ts +1 -1
- package/dist/lib/dashboard-queries.js +11 -6
- package/dist/lib/dashboard-receipt.js +1 -1
- package/dist/lib/dashboard-sources.js +3 -2
- package/dist/lib/dashboard-view.schema.d.ts +20 -20
- package/dist/lib/dashboard.schema.d.ts +18 -18
- package/dist/lib/dashboard.schema.js +2 -1
- package/dist/lib/dashboard.service.d.ts +24 -5
- package/dist/lib/dashboard.service.js +58 -8
- package/dist/lib/definition-tools.d.ts +4 -4
- package/dist/lib/document-host.js +1 -1
- package/dist/lib/document-package.d.ts +1 -1
- package/dist/lib/editing-patch.js +2 -1
- package/dist/lib/editing.d.ts +11 -10
- package/dist/lib/editing.js +5 -1
- package/dist/lib/filter-evidence.d.ts +28 -0
- package/dist/lib/filter-evidence.js +3 -2
- package/dist/lib/html-guidance.d.ts +1 -0
- package/dist/lib/html-guidance.js +2 -1
- package/dist/lib/html-validation.js +1 -1
- package/dist/lib/middleware.d.ts +3 -1
- package/dist/lib/middleware.js +89 -11
- package/dist/lib/plugin.js +2 -1
- package/dist/lib/presentation.provider.d.ts +1 -1
- package/dist/lib/preview-retry.d.ts +4 -3
- package/dist/lib/preview-retry.js +13 -7
- package/dist/lib/prompt-workflows.js +1 -1
- package/dist/lib/query-session.d.ts +9 -0
- package/dist/lib/query-session.js +64 -0
- package/dist/lib/save-requests.d.ts +118 -0
- package/dist/lib/save-requests.js +199 -0
- package/dist/lib/source-executions.d.ts +243 -2
- package/dist/lib/source-executions.js +29 -5
- package/dist/lib/view.provider.d.ts +38 -3
- package/dist/lib/view.provider.js +20 -4
- package/dist/remote/app.js +114 -65
- package/dist/remote/build-manifest.json +37 -32
- package/dist/sandbox-actions/action.json +1 -1
- package/dist/sandbox-actions/bundle/host.js +63 -14
- package/dist/sandbox-actions/bundle/validate.mjs +2 -2
- package/dist/validation/host-entry.d.ts +5 -1
- package/dist/validation/host-entry.js +21 -4
- package/docs/docs.json +2 -0
- package/docs/large-dashboards-acceptance.mdx +74 -0
- package/docs/large-dashboards.mdx +80 -0
- package/package.json +5 -5
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.DashboardSaveRequests = exports.waitSaveRequestSchema = exports.saveRequestSchema = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const node_crypto_1 = require("node:crypto");
|
|
18
|
+
const promises_1 = require("node:timers/promises");
|
|
19
|
+
const v3_1 = require("zod/v3");
|
|
20
|
+
const plugin_sdk_1 = require("@xpert-ai/plugin-sdk");
|
|
21
|
+
const artifact_document_1 = require("@xpert-ai/plugin-sdk/artifact-document");
|
|
22
|
+
const dashboard_service_1 = require("./dashboard.service");
|
|
23
|
+
const dashboard_schema_1 = require("./dashboard.schema");
|
|
24
|
+
const TTL = 86400, MAX_RUNTIME = 10 * 60_000, WAIT_MS = 20_000;
|
|
25
|
+
const hash = (value) => (0, node_crypto_1.createHash)('sha256').update(value).digest('hex');
|
|
26
|
+
const key = (scope, id) => {
|
|
27
|
+
if (!scope.tenantId || !scope.organizationId)
|
|
28
|
+
throw new Error('DASHBOARD_SAVE_REQUEST_DENIED');
|
|
29
|
+
return `live_dashboard:save:${scope.tenantId}:${scope.organizationId}:${id}`;
|
|
30
|
+
};
|
|
31
|
+
const contextSchema = v3_1.z.object({ tenantId: v3_1.z.string().uuid(), organizationId: v3_1.z.string().uuid(), userId: v3_1.z.string().uuid(),
|
|
32
|
+
hostType: v3_1.z.enum(['agent', 'project']), hostId: v3_1.z.string().uuid(), projectId: v3_1.z.string().uuid().optional() }).strict();
|
|
33
|
+
const fileSchema = v3_1.z.object({ filePath: v3_1.z.string(), workspacePath: v3_1.z.string(), sha256: v3_1.z.string(), size: v3_1.z.number() }).strict();
|
|
34
|
+
const receiptSchema = v3_1.z.object({ artifactId: v3_1.z.string().uuid(), draftId: v3_1.z.string(), draftRevision: v3_1.z.number(), baseVersionId: v3_1.z.string().nullable(), status: v3_1.z.literal('draft'), title: v3_1.z.string() });
|
|
35
|
+
const sourceFileSchema = v3_1.z.object({ path: v3_1.z.string().max(300), sha256: v3_1.z.string().regex(/^[a-f0-9]{64}$/) }).strict();
|
|
36
|
+
const recordSchema = v3_1.z.object({ requestId: v3_1.z.string().uuid(), jobId: v3_1.z.string(), context: contextSchema, file: fileSchema,
|
|
37
|
+
manifestId: v3_1.z.string().uuid(), expectedRevision: v3_1.z.number(), state: v3_1.z.enum(['queued', 'running', 'succeeded', 'failed']), stage: v3_1.z.string(), completed: v3_1.z.number(), total: v3_1.z.number(),
|
|
38
|
+
fingerprint: v3_1.z.string(), createdAt: v3_1.z.number(), deadline: v3_1.z.number(), error: v3_1.z.string().optional(), dashboard: receiptSchema.optional(), sourceFile: sourceFileSchema.optional() }).strict();
|
|
39
|
+
const inputSchema = v3_1.z.object({ manifest: dashboard_schema_1.dashboardSchema, expectedVersionId: v3_1.z.string().uuid().nullable(), expectedRevision: v3_1.z.number().int().nonnegative(),
|
|
40
|
+
dependencies: artifact_document_1.artifactDocumentDependencyAssets.optional() }).strict();
|
|
41
|
+
exports.saveRequestSchema = v3_1.z.object({ requestId: v3_1.z.string().uuid() }).strict();
|
|
42
|
+
exports.waitSaveRequestSchema = exports.saveRequestSchema.extend({ cursor: v3_1.z.string().min(1).max(100) }).strict();
|
|
43
|
+
function trustedScope(context) {
|
|
44
|
+
return contextSchema.parse({ tenantId: context.tenantId, organizationId: context.organizationId, userId: context.userId,
|
|
45
|
+
hostType: context.hostType, hostId: context.hostId,
|
|
46
|
+
...(context.hostType === 'project' ? { projectId: context.hostId } : context.runtimeScope?.projectId ? { projectId: context.runtimeScope.projectId } : {}) });
|
|
47
|
+
}
|
|
48
|
+
function restoredScope(scope) {
|
|
49
|
+
return { ...scope, slots: [], ...(scope.projectId ? { runtimeScope: { projectId: scope.projectId, conversationId: null,
|
|
50
|
+
dataScopeKey: `project:${scope.projectId}`, workspaceFiles: { catalog: 'projects', scopeId: scope.projectId, projectId: scope.projectId } } } : {}) };
|
|
51
|
+
}
|
|
52
|
+
/** Durable save orchestration; domain validation and CAS remain in DashboardService. */
|
|
53
|
+
let DashboardSaveRequests = class DashboardSaveRequests {
|
|
54
|
+
constructor(service, runtime, queue) {
|
|
55
|
+
this.service = service;
|
|
56
|
+
this.runtime = runtime;
|
|
57
|
+
this.queue = queue;
|
|
58
|
+
}
|
|
59
|
+
async submit(context, manifest, expectedVersionId, expectedRevision, idempotencyKey, sourceFile, dependencies) {
|
|
60
|
+
await this.service.authorizeProject(context, 'write');
|
|
61
|
+
const scope = trustedScope(context), parsed = inputSchema.parse({ manifest, expectedVersionId, expectedRevision, dependencies });
|
|
62
|
+
const fingerprint = hash(JSON.stringify({ ...parsed, manifest: { ...parsed.manifest, ...(expectedRevision === 0 ? { id: 'new' } : {}) }, sourceFile }));
|
|
63
|
+
const previousCount = await this.service.previousBindingCount(context, parsed.manifest);
|
|
64
|
+
this.service.assertCapacity(parsed.manifest.bindings.length, previousCount);
|
|
65
|
+
const redis = await this.queue.getRedis(), files = this.runtime.require(plugin_sdk_1.WorkspaceFilesRuntimeCapability);
|
|
66
|
+
const dedupe = `${key(scope, 'submission')}:${hash(JSON.stringify([scope, idempotencyKey]))}`;
|
|
67
|
+
const previous = await redis.get(dedupe);
|
|
68
|
+
if (previous) {
|
|
69
|
+
if ((await this.read(context, previous)).fingerprint !== fingerprint)
|
|
70
|
+
throw new Error('DASHBOARD_SAVE_IDEMPOTENCY_CONFLICT');
|
|
71
|
+
return this.get(context, previous);
|
|
72
|
+
}
|
|
73
|
+
const requestId = (0, node_crypto_1.randomUUID)(), buffer = Buffer.from(JSON.stringify(parsed));
|
|
74
|
+
if (buffer.length > 12_000_000)
|
|
75
|
+
throw new Error('DASHBOARD_MANIFEST_TOO_LARGE');
|
|
76
|
+
const stored = await files.uploadBuffer({ tenantId: scope.tenantId, userId: scope.userId, catalog: 'users', buffer,
|
|
77
|
+
originalName: 'save.json', fileName: 'save.json', folder: `live-dashboard-save/${requestId}`, mimeType: 'application/json' });
|
|
78
|
+
const record = { requestId, jobId: `dashboard-save-${requestId}`, context: scope,
|
|
79
|
+
file: { filePath: stored.filePath, workspacePath: stored.workspacePath, sha256: hash(buffer), size: buffer.length },
|
|
80
|
+
fingerprint, manifestId: manifest.id, expectedRevision, state: 'queued', stage: 'queued', completed: 0, total: manifest.bindings.length, createdAt: Date.now(), deadline: Date.now() + MAX_RUNTIME,
|
|
81
|
+
...(sourceFile ? { sourceFile: sourceFileSchema.parse(sourceFile) } : {}) };
|
|
82
|
+
const winner = await redis.eval("local id=redis.call('GET',KEYS[1]); if id then return id end; redis.call('SET',KEYS[2],ARGV[2],'EX',ARGV[3]); redis.call('SET',KEYS[1],ARGV[1],'EX',ARGV[3]); return ARGV[1]", 2, dedupe, key(scope, requestId), requestId, JSON.stringify(record), TTL);
|
|
83
|
+
if (winner !== requestId) {
|
|
84
|
+
await files.deleteFile(this.reference(record));
|
|
85
|
+
if ((await this.read(context, String(winner))).fingerprint !== fingerprint)
|
|
86
|
+
throw new Error('DASHBOARD_SAVE_IDEMPOTENCY_CONFLICT');
|
|
87
|
+
return this.get(context, String(winner));
|
|
88
|
+
}
|
|
89
|
+
// Recovery can enqueue the same deterministic job if the process stops at this boundary.
|
|
90
|
+
await this.enqueue(record);
|
|
91
|
+
return this.snapshot(record);
|
|
92
|
+
}
|
|
93
|
+
reference(record) {
|
|
94
|
+
return { source: 'platform.workspace.files', tenantId: record.context.tenantId, userId: record.context.userId, catalog: 'users',
|
|
95
|
+
filePath: record.file.filePath, workspacePath: record.file.workspacePath };
|
|
96
|
+
}
|
|
97
|
+
async enqueue(record) {
|
|
98
|
+
await this.queue.enqueue({ pluginName: dashboard_service_1.PLUGIN_NAME, queueName: 'save-validation', jobName: 'save', payload: { requestId: record.requestId },
|
|
99
|
+
tenantId: record.context.tenantId, organizationId: record.context.organizationId, userId: record.context.userId,
|
|
100
|
+
scopeKey: 'system:global', jobId: record.jobId, attempts: 1, removeOnComplete: { age: TTL, count: 1000 }, removeOnFail: { age: TTL, count: 1000 } });
|
|
101
|
+
}
|
|
102
|
+
async read(context, requestId) {
|
|
103
|
+
v3_1.z.string().uuid().parse(requestId);
|
|
104
|
+
const redis = await this.queue.getRedis(), value = await redis.get(key(context, requestId));
|
|
105
|
+
if (!value)
|
|
106
|
+
throw new Error('DASHBOARD_SAVE_REQUEST_UNAVAILABLE');
|
|
107
|
+
const record = recordSchema.parse(JSON.parse(value));
|
|
108
|
+
if (JSON.stringify(trustedScope(context)) !== JSON.stringify(record.context))
|
|
109
|
+
throw new Error('DASHBOARD_SAVE_REQUEST_DENIED');
|
|
110
|
+
return record;
|
|
111
|
+
}
|
|
112
|
+
snapshot(record) {
|
|
113
|
+
const terminal = record.state === 'succeeded' || record.state === 'failed';
|
|
114
|
+
const result = { requestId: record.requestId, state: record.state, stage: record.stage, completed: record.completed, total: record.total,
|
|
115
|
+
terminal, ...(record.dashboard ? { dashboard: record.dashboard, ...(record.sourceFile ? { sourceFile: record.sourceFile } : {}) } : {}),
|
|
116
|
+
...(record.error ? { error: record.error } : {}), nextAction: terminal ? 'report_result' : 'wait' };
|
|
117
|
+
return { ...result, cursor: hash(JSON.stringify(result)) };
|
|
118
|
+
}
|
|
119
|
+
async get(context, requestId) {
|
|
120
|
+
await this.service.authorizeProject(context);
|
|
121
|
+
let record = await this.read(context, requestId);
|
|
122
|
+
if (record.state === 'queued' || record.state === 'running') {
|
|
123
|
+
const job = await this.queue.getJob({ jobId: record.jobId });
|
|
124
|
+
if (!job && record.deadline > Date.now())
|
|
125
|
+
await this.enqueue(record);
|
|
126
|
+
else if (job?.state === 'failed' || !job && record.deadline <= Date.now()) {
|
|
127
|
+
// A completed domain receipt always wins a racing observation.
|
|
128
|
+
record = await this.read(context, requestId);
|
|
129
|
+
if (record.state !== 'succeeded') {
|
|
130
|
+
const receipt = await this.service.savedRequestReceipt(context, record.manifestId, record.requestId, record.expectedRevision);
|
|
131
|
+
record = receipt ? { ...record, state: 'succeeded', stage: 'saved', completed: record.total, dashboard: receipt }
|
|
132
|
+
: { ...record, state: 'failed', error: 'DASHBOARD_SAVE_JOB_FAILED: retrieve the current draft before retrying; no new save receipt was confirmed.' };
|
|
133
|
+
await (await this.queue.getRedis()).set(key(record.context, requestId), JSON.stringify(record), 'EX', TTL);
|
|
134
|
+
await this.runtime.require(plugin_sdk_1.WorkspaceFilesRuntimeCapability).deleteFile(this.reference(record)).catch(() => undefined);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return this.snapshot(record);
|
|
139
|
+
}
|
|
140
|
+
async wait(context, requestId, cursor, signal) {
|
|
141
|
+
const until = Date.now() + WAIT_MS;
|
|
142
|
+
let current = await this.get(context, requestId);
|
|
143
|
+
while (!current.terminal && current.cursor === cursor && Date.now() < until) {
|
|
144
|
+
await (0, promises_1.setTimeout)(Math.min(2000, until - Date.now()), undefined, { signal });
|
|
145
|
+
current = this.snapshot(await this.read(context, requestId));
|
|
146
|
+
}
|
|
147
|
+
return current.terminal || current.cursor !== cursor ? current : this.get(context, requestId);
|
|
148
|
+
}
|
|
149
|
+
async handle(job, identity) {
|
|
150
|
+
const requestId = v3_1.z.string().uuid().parse(job.data.requestId), redis = await this.queue.getRedis();
|
|
151
|
+
let record = recordSchema.parse(JSON.parse(await redis.get(key(identity, requestId)) ?? 'null'));
|
|
152
|
+
if (record.context.tenantId !== identity.tenantId || record.context.organizationId !== identity.organizationId || record.context.userId !== identity.userId)
|
|
153
|
+
throw new Error('DASHBOARD_SAVE_REQUEST_DENIED');
|
|
154
|
+
if (record.state === 'succeeded')
|
|
155
|
+
return;
|
|
156
|
+
let committed = false, terminalPersisted = false;
|
|
157
|
+
const persist = async () => { await redis.set(key(record.context, requestId), JSON.stringify(record), 'EX', TTL); };
|
|
158
|
+
const files = this.runtime.require(plugin_sdk_1.WorkspaceFilesRuntimeCapability);
|
|
159
|
+
try {
|
|
160
|
+
const file = await files.readBuffer(this.reference(record));
|
|
161
|
+
if (file.buffer.length !== record.file.size || hash(file.buffer) !== record.file.sha256)
|
|
162
|
+
throw new Error('DASHBOARD_SAVE_INPUT_CHANGED');
|
|
163
|
+
const input = inputSchema.parse(JSON.parse(file.buffer.toString('utf8')));
|
|
164
|
+
const dashboard = await this.service.save(restoredScope(record.context), input.manifest, input.expectedVersionId, input.expectedRevision, input.dependencies, { requestId, progress: async (stage, completed, total) => {
|
|
165
|
+
if (Date.now() >= record.deadline)
|
|
166
|
+
throw new Error('DASHBOARD_SAVE_DEADLINE');
|
|
167
|
+
record = { ...record, state: 'running', stage, completed, total };
|
|
168
|
+
await persist();
|
|
169
|
+
} });
|
|
170
|
+
committed = true;
|
|
171
|
+
record = { ...record, state: 'succeeded', stage: 'saved', completed: record.total, dashboard };
|
|
172
|
+
await persist();
|
|
173
|
+
terminalPersisted = true;
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
// Keep frozen input for recovery when the Artifact CAS succeeded but status persistence did not.
|
|
177
|
+
if (committed)
|
|
178
|
+
throw new Error('DASHBOARD_SAVE_COMMIT_UNCONFIRMED');
|
|
179
|
+
// Do not persist native provider messages or private query configuration.
|
|
180
|
+
const code = error instanceof Error ? error.message.match(/^DASHBOARD_[A-Z_]+/)?.[0] : undefined;
|
|
181
|
+
record = { ...record, state: 'failed', error: code ?? 'DASHBOARD_SAVE_VALIDATION_FAILED' };
|
|
182
|
+
await persist();
|
|
183
|
+
terminalPersisted = true;
|
|
184
|
+
throw new Error(record.error);
|
|
185
|
+
}
|
|
186
|
+
finally {
|
|
187
|
+
if (terminalPersisted)
|
|
188
|
+
await files.deleteFile(this.reference(record)).catch(() => undefined);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
exports.DashboardSaveRequests = DashboardSaveRequests;
|
|
193
|
+
exports.DashboardSaveRequests = DashboardSaveRequests = __decorate([
|
|
194
|
+
(0, common_1.Injectable)(),
|
|
195
|
+
(0, plugin_sdk_1.PluginJobProcessor)({ pluginName: dashboard_service_1.PLUGIN_NAME, queueName: 'save-validation', jobName: 'save', concurrency: 2 }),
|
|
196
|
+
__param(1, (0, common_1.Inject)(plugin_sdk_1.XPERT_RUNTIME_CAPABILITIES_TOKEN)),
|
|
197
|
+
__param(2, (0, common_1.Inject)(plugin_sdk_1.MANAGED_QUEUE_SERVICE_TOKEN)),
|
|
198
|
+
__metadata("design:paramtypes", [dashboard_service_1.DashboardService, Object, Object])
|
|
199
|
+
], DashboardSaveRequests);
|
|
@@ -169,20 +169,261 @@ export declare const sourceDatasetSchema: z.ZodObject<{
|
|
|
169
169
|
}, z.ZodTypeAny, "passthrough">>;
|
|
170
170
|
}, z.ZodTypeAny, "passthrough">>;
|
|
171
171
|
export type SourceDataset = z.infer<typeof sourceDatasetSchema>;
|
|
172
|
+
/** Rendering needs the complete typed contract; inspection deliberately accepts a smaller projection. */
|
|
173
|
+
export declare const renderDatasetSchema: z.ZodObject<{
|
|
174
|
+
schema: z.ZodLiteral<"xpert.analytics.dataset">;
|
|
175
|
+
datasetId: z.ZodString;
|
|
176
|
+
rows: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>>, "many">;
|
|
177
|
+
provenance: z.ZodObject<{
|
|
178
|
+
executionId: z.ZodString;
|
|
179
|
+
bindingId: z.ZodString;
|
|
180
|
+
providerId: z.ZodString;
|
|
181
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
182
|
+
executionId: z.ZodString;
|
|
183
|
+
bindingId: z.ZodString;
|
|
184
|
+
providerId: z.ZodString;
|
|
185
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
186
|
+
executionId: z.ZodString;
|
|
187
|
+
bindingId: z.ZodString;
|
|
188
|
+
providerId: z.ZodString;
|
|
189
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
190
|
+
} & {
|
|
191
|
+
fields: z.ZodArray<z.ZodObject<{
|
|
192
|
+
id: z.ZodString;
|
|
193
|
+
label: z.ZodString;
|
|
194
|
+
sourceField: z.ZodString;
|
|
195
|
+
logicalType: z.ZodEnum<["string", "boolean", "int64", "float64", "decimal", "date", "timestamp"]>;
|
|
196
|
+
nullable: z.ZodBoolean;
|
|
197
|
+
role: z.ZodEnum<["dimension", "measure", "time", "identifier"]>;
|
|
198
|
+
semanticRef: z.ZodOptional<z.ZodString>;
|
|
199
|
+
unit: z.ZodOptional<z.ZodString>;
|
|
200
|
+
}, "strict", z.ZodTypeAny, {
|
|
201
|
+
id: string;
|
|
202
|
+
label: string;
|
|
203
|
+
sourceField: string;
|
|
204
|
+
logicalType: "string" | "boolean" | "date" | "float64" | "int64" | "decimal" | "timestamp";
|
|
205
|
+
nullable: boolean;
|
|
206
|
+
role: "time" | "dimension" | "measure" | "identifier";
|
|
207
|
+
semanticRef?: string | undefined;
|
|
208
|
+
unit?: string | undefined;
|
|
209
|
+
}, {
|
|
210
|
+
id: string;
|
|
211
|
+
label: string;
|
|
212
|
+
sourceField: string;
|
|
213
|
+
logicalType: "string" | "boolean" | "date" | "float64" | "int64" | "decimal" | "timestamp";
|
|
214
|
+
nullable: boolean;
|
|
215
|
+
role: "time" | "dimension" | "measure" | "identifier";
|
|
216
|
+
semanticRef?: string | undefined;
|
|
217
|
+
unit?: string | undefined;
|
|
218
|
+
}>, "many">;
|
|
219
|
+
grain: z.ZodArray<z.ZodString, "many">;
|
|
220
|
+
completeness: z.ZodObject<{
|
|
221
|
+
returnedRows: z.ZodNumber;
|
|
222
|
+
totalRows: z.ZodOptional<z.ZodNumber>;
|
|
223
|
+
totalAccuracy: z.ZodEnum<["exact", "estimated", "unknown"]>;
|
|
224
|
+
scope: z.ZodEnum<["full_query_result", "page", "sample", "top_n"]>;
|
|
225
|
+
truncated: z.ZodBoolean;
|
|
226
|
+
}, "strip", z.ZodTypeAny, {
|
|
227
|
+
scope: "page" | "full_query_result" | "sample" | "top_n";
|
|
228
|
+
truncated: boolean;
|
|
229
|
+
returnedRows: number;
|
|
230
|
+
totalAccuracy: "exact" | "unknown" | "estimated";
|
|
231
|
+
totalRows?: number | undefined;
|
|
232
|
+
}, {
|
|
233
|
+
scope: "page" | "full_query_result" | "sample" | "top_n";
|
|
234
|
+
truncated: boolean;
|
|
235
|
+
returnedRows: number;
|
|
236
|
+
totalAccuracy: "exact" | "unknown" | "estimated";
|
|
237
|
+
totalRows?: number | undefined;
|
|
238
|
+
}>;
|
|
239
|
+
freshness: z.ZodObject<{
|
|
240
|
+
executedAt: z.ZodString;
|
|
241
|
+
sourceFetchedAt: z.ZodString;
|
|
242
|
+
dataAsOf: z.ZodOptional<z.ZodString>;
|
|
243
|
+
}, "strip", z.ZodTypeAny, {
|
|
244
|
+
sourceFetchedAt: string;
|
|
245
|
+
executedAt: string;
|
|
246
|
+
dataAsOf?: string | undefined;
|
|
247
|
+
}, {
|
|
248
|
+
sourceFetchedAt: string;
|
|
249
|
+
executedAt: string;
|
|
250
|
+
dataAsOf?: string | undefined;
|
|
251
|
+
}>;
|
|
252
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
253
|
+
schema: z.ZodLiteral<"xpert.analytics.dataset">;
|
|
254
|
+
datasetId: z.ZodString;
|
|
255
|
+
rows: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>>, "many">;
|
|
256
|
+
provenance: z.ZodObject<{
|
|
257
|
+
executionId: z.ZodString;
|
|
258
|
+
bindingId: z.ZodString;
|
|
259
|
+
providerId: z.ZodString;
|
|
260
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
261
|
+
executionId: z.ZodString;
|
|
262
|
+
bindingId: z.ZodString;
|
|
263
|
+
providerId: z.ZodString;
|
|
264
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
265
|
+
executionId: z.ZodString;
|
|
266
|
+
bindingId: z.ZodString;
|
|
267
|
+
providerId: z.ZodString;
|
|
268
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
269
|
+
} & {
|
|
270
|
+
fields: z.ZodArray<z.ZodObject<{
|
|
271
|
+
id: z.ZodString;
|
|
272
|
+
label: z.ZodString;
|
|
273
|
+
sourceField: z.ZodString;
|
|
274
|
+
logicalType: z.ZodEnum<["string", "boolean", "int64", "float64", "decimal", "date", "timestamp"]>;
|
|
275
|
+
nullable: z.ZodBoolean;
|
|
276
|
+
role: z.ZodEnum<["dimension", "measure", "time", "identifier"]>;
|
|
277
|
+
semanticRef: z.ZodOptional<z.ZodString>;
|
|
278
|
+
unit: z.ZodOptional<z.ZodString>;
|
|
279
|
+
}, "strict", z.ZodTypeAny, {
|
|
280
|
+
id: string;
|
|
281
|
+
label: string;
|
|
282
|
+
sourceField: string;
|
|
283
|
+
logicalType: "string" | "boolean" | "date" | "float64" | "int64" | "decimal" | "timestamp";
|
|
284
|
+
nullable: boolean;
|
|
285
|
+
role: "time" | "dimension" | "measure" | "identifier";
|
|
286
|
+
semanticRef?: string | undefined;
|
|
287
|
+
unit?: string | undefined;
|
|
288
|
+
}, {
|
|
289
|
+
id: string;
|
|
290
|
+
label: string;
|
|
291
|
+
sourceField: string;
|
|
292
|
+
logicalType: "string" | "boolean" | "date" | "float64" | "int64" | "decimal" | "timestamp";
|
|
293
|
+
nullable: boolean;
|
|
294
|
+
role: "time" | "dimension" | "measure" | "identifier";
|
|
295
|
+
semanticRef?: string | undefined;
|
|
296
|
+
unit?: string | undefined;
|
|
297
|
+
}>, "many">;
|
|
298
|
+
grain: z.ZodArray<z.ZodString, "many">;
|
|
299
|
+
completeness: z.ZodObject<{
|
|
300
|
+
returnedRows: z.ZodNumber;
|
|
301
|
+
totalRows: z.ZodOptional<z.ZodNumber>;
|
|
302
|
+
totalAccuracy: z.ZodEnum<["exact", "estimated", "unknown"]>;
|
|
303
|
+
scope: z.ZodEnum<["full_query_result", "page", "sample", "top_n"]>;
|
|
304
|
+
truncated: z.ZodBoolean;
|
|
305
|
+
}, "strip", z.ZodTypeAny, {
|
|
306
|
+
scope: "page" | "full_query_result" | "sample" | "top_n";
|
|
307
|
+
truncated: boolean;
|
|
308
|
+
returnedRows: number;
|
|
309
|
+
totalAccuracy: "exact" | "unknown" | "estimated";
|
|
310
|
+
totalRows?: number | undefined;
|
|
311
|
+
}, {
|
|
312
|
+
scope: "page" | "full_query_result" | "sample" | "top_n";
|
|
313
|
+
truncated: boolean;
|
|
314
|
+
returnedRows: number;
|
|
315
|
+
totalAccuracy: "exact" | "unknown" | "estimated";
|
|
316
|
+
totalRows?: number | undefined;
|
|
317
|
+
}>;
|
|
318
|
+
freshness: z.ZodObject<{
|
|
319
|
+
executedAt: z.ZodString;
|
|
320
|
+
sourceFetchedAt: z.ZodString;
|
|
321
|
+
dataAsOf: z.ZodOptional<z.ZodString>;
|
|
322
|
+
}, "strip", z.ZodTypeAny, {
|
|
323
|
+
sourceFetchedAt: string;
|
|
324
|
+
executedAt: string;
|
|
325
|
+
dataAsOf?: string | undefined;
|
|
326
|
+
}, {
|
|
327
|
+
sourceFetchedAt: string;
|
|
328
|
+
executedAt: string;
|
|
329
|
+
dataAsOf?: string | undefined;
|
|
330
|
+
}>;
|
|
331
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
332
|
+
schema: z.ZodLiteral<"xpert.analytics.dataset">;
|
|
333
|
+
datasetId: z.ZodString;
|
|
334
|
+
rows: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>>, "many">;
|
|
335
|
+
provenance: z.ZodObject<{
|
|
336
|
+
executionId: z.ZodString;
|
|
337
|
+
bindingId: z.ZodString;
|
|
338
|
+
providerId: z.ZodString;
|
|
339
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
340
|
+
executionId: z.ZodString;
|
|
341
|
+
bindingId: z.ZodString;
|
|
342
|
+
providerId: z.ZodString;
|
|
343
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
344
|
+
executionId: z.ZodString;
|
|
345
|
+
bindingId: z.ZodString;
|
|
346
|
+
providerId: z.ZodString;
|
|
347
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
348
|
+
} & {
|
|
349
|
+
fields: z.ZodArray<z.ZodObject<{
|
|
350
|
+
id: z.ZodString;
|
|
351
|
+
label: z.ZodString;
|
|
352
|
+
sourceField: z.ZodString;
|
|
353
|
+
logicalType: z.ZodEnum<["string", "boolean", "int64", "float64", "decimal", "date", "timestamp"]>;
|
|
354
|
+
nullable: z.ZodBoolean;
|
|
355
|
+
role: z.ZodEnum<["dimension", "measure", "time", "identifier"]>;
|
|
356
|
+
semanticRef: z.ZodOptional<z.ZodString>;
|
|
357
|
+
unit: z.ZodOptional<z.ZodString>;
|
|
358
|
+
}, "strict", z.ZodTypeAny, {
|
|
359
|
+
id: string;
|
|
360
|
+
label: string;
|
|
361
|
+
sourceField: string;
|
|
362
|
+
logicalType: "string" | "boolean" | "date" | "float64" | "int64" | "decimal" | "timestamp";
|
|
363
|
+
nullable: boolean;
|
|
364
|
+
role: "time" | "dimension" | "measure" | "identifier";
|
|
365
|
+
semanticRef?: string | undefined;
|
|
366
|
+
unit?: string | undefined;
|
|
367
|
+
}, {
|
|
368
|
+
id: string;
|
|
369
|
+
label: string;
|
|
370
|
+
sourceField: string;
|
|
371
|
+
logicalType: "string" | "boolean" | "date" | "float64" | "int64" | "decimal" | "timestamp";
|
|
372
|
+
nullable: boolean;
|
|
373
|
+
role: "time" | "dimension" | "measure" | "identifier";
|
|
374
|
+
semanticRef?: string | undefined;
|
|
375
|
+
unit?: string | undefined;
|
|
376
|
+
}>, "many">;
|
|
377
|
+
grain: z.ZodArray<z.ZodString, "many">;
|
|
378
|
+
completeness: z.ZodObject<{
|
|
379
|
+
returnedRows: z.ZodNumber;
|
|
380
|
+
totalRows: z.ZodOptional<z.ZodNumber>;
|
|
381
|
+
totalAccuracy: z.ZodEnum<["exact", "estimated", "unknown"]>;
|
|
382
|
+
scope: z.ZodEnum<["full_query_result", "page", "sample", "top_n"]>;
|
|
383
|
+
truncated: z.ZodBoolean;
|
|
384
|
+
}, "strip", z.ZodTypeAny, {
|
|
385
|
+
scope: "page" | "full_query_result" | "sample" | "top_n";
|
|
386
|
+
truncated: boolean;
|
|
387
|
+
returnedRows: number;
|
|
388
|
+
totalAccuracy: "exact" | "unknown" | "estimated";
|
|
389
|
+
totalRows?: number | undefined;
|
|
390
|
+
}, {
|
|
391
|
+
scope: "page" | "full_query_result" | "sample" | "top_n";
|
|
392
|
+
truncated: boolean;
|
|
393
|
+
returnedRows: number;
|
|
394
|
+
totalAccuracy: "exact" | "unknown" | "estimated";
|
|
395
|
+
totalRows?: number | undefined;
|
|
396
|
+
}>;
|
|
397
|
+
freshness: z.ZodObject<{
|
|
398
|
+
executedAt: z.ZodString;
|
|
399
|
+
sourceFetchedAt: z.ZodString;
|
|
400
|
+
dataAsOf: z.ZodOptional<z.ZodString>;
|
|
401
|
+
}, "strip", z.ZodTypeAny, {
|
|
402
|
+
sourceFetchedAt: string;
|
|
403
|
+
executedAt: string;
|
|
404
|
+
dataAsOf?: string | undefined;
|
|
405
|
+
}, {
|
|
406
|
+
sourceFetchedAt: string;
|
|
407
|
+
executedAt: string;
|
|
408
|
+
dataAsOf?: string | undefined;
|
|
409
|
+
}>;
|
|
410
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
172
411
|
export interface SourceExecution {
|
|
173
412
|
requestId: string;
|
|
174
|
-
state: 'running' | 'succeeded' | 'failed';
|
|
413
|
+
state: 'queued' | 'running' | 'succeeded' | 'failed';
|
|
175
414
|
controls: Record<string, string | number | boolean | null>;
|
|
176
415
|
requestedAt: string;
|
|
416
|
+
startedAt?: string;
|
|
177
417
|
finishedAt?: string;
|
|
178
418
|
result?: SourceDataset;
|
|
179
419
|
resultControls?: SourceExecution['controls'];
|
|
420
|
+
previewEvicted?: boolean;
|
|
180
421
|
}
|
|
181
422
|
export type SourceEvent = {
|
|
182
423
|
bindingId: string;
|
|
183
424
|
requestId: string;
|
|
184
425
|
} & ({
|
|
185
|
-
type: 'start';
|
|
426
|
+
type: 'queue' | 'start';
|
|
186
427
|
controls: SourceExecution['controls'];
|
|
187
428
|
at: string;
|
|
188
429
|
} | {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.sourceDatasetSchema = void 0;
|
|
3
|
+
exports.renderDatasetSchema = exports.sourceDatasetSchema = void 0;
|
|
4
4
|
exports.reduceSourceExecutions = reduceSourceExecutions;
|
|
5
5
|
const v3_1 = require("zod/v3");
|
|
6
|
+
const analytics_1 = require("@xpert-ai/plugin-sdk/analytics");
|
|
6
7
|
const scalar = v3_1.z.union([v3_1.z.string(), v3_1.z.number(), v3_1.z.boolean(), v3_1.z.null()]);
|
|
7
8
|
exports.sourceDatasetSchema = v3_1.z.object({
|
|
8
9
|
schema: v3_1.z.literal('xpert.analytics.dataset'), datasetId: v3_1.z.string(),
|
|
@@ -11,15 +12,38 @@ exports.sourceDatasetSchema = v3_1.z.object({
|
|
|
11
12
|
freshness: v3_1.z.object({ sourceFetchedAt: v3_1.z.string(), executedAt: v3_1.z.string().optional(), dataAsOf: v3_1.z.string().optional() }),
|
|
12
13
|
provenance: v3_1.z.object({ executionId: v3_1.z.string(), bindingId: v3_1.z.string(), providerId: v3_1.z.string() }).passthrough()
|
|
13
14
|
}).passthrough();
|
|
15
|
+
/** Rendering needs the complete typed contract; inspection deliberately accepts a smaller projection. */
|
|
16
|
+
exports.renderDatasetSchema = exports.sourceDatasetSchema.extend({
|
|
17
|
+
fields: v3_1.z.array(analytics_1.analyticsFieldSchema.required({ id: true, sourceField: true, label: true, logicalType: true, nullable: true, role: true })), grain: v3_1.z.array(v3_1.z.string()),
|
|
18
|
+
completeness: v3_1.z.object({ returnedRows: v3_1.z.number(), totalRows: v3_1.z.number().optional(), totalAccuracy: v3_1.z.enum(['exact', 'estimated', 'unknown']),
|
|
19
|
+
scope: v3_1.z.enum(['full_query_result', 'page', 'sample', 'top_n']), truncated: v3_1.z.boolean() }),
|
|
20
|
+
freshness: v3_1.z.object({ executedAt: v3_1.z.string(), sourceFetchedAt: v3_1.z.string(), dataAsOf: v3_1.z.string().optional() })
|
|
21
|
+
});
|
|
14
22
|
function reduceSourceExecutions(state, event) {
|
|
15
23
|
const previous = state[event.bindingId];
|
|
16
|
-
if (event.type === 'start')
|
|
24
|
+
if (event.type === 'start' || event.type === 'queue')
|
|
17
25
|
return { ...state, [event.bindingId]: {
|
|
18
|
-
requestId: event.requestId, state: 'running', controls: event.controls,
|
|
26
|
+
requestId: event.requestId, state: event.type === 'queue' ? 'queued' : 'running', controls: event.controls,
|
|
27
|
+
requestedAt: previous?.requestId === event.requestId ? previous.requestedAt : event.at,
|
|
28
|
+
...(event.type === 'start' ? { startedAt: event.at } : {}),
|
|
19
29
|
result: previous?.result, resultControls: previous?.resultControls
|
|
20
30
|
} };
|
|
21
31
|
if (!previous || previous.requestId !== event.requestId)
|
|
22
32
|
return state;
|
|
23
|
-
|
|
24
|
-
...(event.type === 'success' ? { result:
|
|
33
|
+
const next = { ...state, [event.bindingId]: { ...previous, state: event.type === 'success' ? 'succeeded' : 'failed', finishedAt: event.at,
|
|
34
|
+
...(event.type === 'success' ? { result: preview(event.result), resultControls: previous.controls, previewEvicted: false } : {}) } };
|
|
35
|
+
// Limit inspection snapshots independently of the rendering result budget.
|
|
36
|
+
const byAge = Object.values(next).filter(item => item.result).sort((a, b) => (a.finishedAt ?? '').localeCompare(b.finishedAt ?? ''));
|
|
37
|
+
while (new TextEncoder().encode(JSON.stringify(Object.values(next).map(item => item.result ?? null))).byteLength > 2_000_000 && byAge.length) {
|
|
38
|
+
const oldest = byAge.shift();
|
|
39
|
+
const id = Object.keys(next).find(id => next[id] === oldest);
|
|
40
|
+
next[id] = { ...oldest, result: undefined, resultControls: undefined, previewEvicted: true };
|
|
41
|
+
}
|
|
42
|
+
return next;
|
|
43
|
+
}
|
|
44
|
+
function preview(result) {
|
|
45
|
+
const rows = result.rows.slice(0, 50);
|
|
46
|
+
while (new TextEncoder().encode(JSON.stringify(rows)).byteLength > 64_000)
|
|
47
|
+
rows.pop();
|
|
48
|
+
return { ...result, rows };
|
|
25
49
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DashboardSaveRequests } from './save-requests';
|
|
1
2
|
import { DashboardProjectsService } from './projects.service';
|
|
2
3
|
import type { XpertExtensionViewManifest, XpertResolvedViewHostContext, XpertViewActionRequest, XpertViewQuery, XpertViewFileAccessRequest } from '@xpert-ai/contracts';
|
|
3
4
|
import { type IXpertViewExtensionProvider, type XpertViewFileActionFile } from '@xpert-ai/plugin-sdk';
|
|
@@ -12,7 +13,8 @@ export declare class DashboardViewProvider implements IXpertViewExtensionProvide
|
|
|
12
13
|
private readonly sharing;
|
|
13
14
|
private readonly projects;
|
|
14
15
|
private readonly archives;
|
|
15
|
-
|
|
16
|
+
private readonly saves?;
|
|
17
|
+
constructor(service: DashboardService, sharing: DashboardSharingService, projects: DashboardProjectsService, archives: DashboardArchiveService, saves?: DashboardSaveRequests | undefined);
|
|
16
18
|
supports(context: XpertResolvedViewHostContext): boolean;
|
|
17
19
|
getViewManifests(context: XpertResolvedViewHostContext, slot: string): XpertExtensionViewManifest[];
|
|
18
20
|
getRemoteComponentEntry(): Promise<{
|
|
@@ -71,8 +73,8 @@ export declare class DashboardViewProvider implements IXpertViewExtensionProvide
|
|
|
71
73
|
allowedValues?: (string | number | boolean | null)[] | undefined;
|
|
72
74
|
}[];
|
|
73
75
|
charts: {
|
|
74
|
-
title: string;
|
|
75
76
|
type: "bar" | "line" | "area" | "pie" | "scatter";
|
|
77
|
+
title: string;
|
|
76
78
|
id: string;
|
|
77
79
|
datasetId: string;
|
|
78
80
|
x: string;
|
|
@@ -198,6 +200,39 @@ export declare class DashboardViewProvider implements IXpertViewExtensionProvide
|
|
|
198
200
|
};
|
|
199
201
|
}>;
|
|
200
202
|
executeViewAction(context: XpertResolvedViewHostContext, viewKey: string, action: string, request: XpertViewActionRequest): Promise<{
|
|
203
|
+
success: boolean;
|
|
204
|
+
data: {
|
|
205
|
+
artifactId: string;
|
|
206
|
+
draftId: string;
|
|
207
|
+
draftRevision: number;
|
|
208
|
+
baseVersionId: string | null;
|
|
209
|
+
status: "draft";
|
|
210
|
+
title: string;
|
|
211
|
+
} | {
|
|
212
|
+
cursor: string;
|
|
213
|
+
nextAction: string;
|
|
214
|
+
error?: string | undefined;
|
|
215
|
+
sourceFile?: {
|
|
216
|
+
path: string;
|
|
217
|
+
sha256: string;
|
|
218
|
+
} | undefined;
|
|
219
|
+
dashboard?: {
|
|
220
|
+
status: "draft";
|
|
221
|
+
title: string;
|
|
222
|
+
artifactId: string;
|
|
223
|
+
draftRevision: number;
|
|
224
|
+
draftId: string;
|
|
225
|
+
baseVersionId: string | null;
|
|
226
|
+
} | undefined;
|
|
227
|
+
requestId: string;
|
|
228
|
+
state: "failed" | "running" | "succeeded" | "queued";
|
|
229
|
+
stage: string;
|
|
230
|
+
completed: number;
|
|
231
|
+
total: number;
|
|
232
|
+
terminal: boolean;
|
|
233
|
+
};
|
|
234
|
+
refresh: boolean;
|
|
235
|
+
} | {
|
|
201
236
|
success: boolean;
|
|
202
237
|
data: import("@xpert-ai/contracts").AnalyticsDiscoveryResult;
|
|
203
238
|
refresh: boolean;
|
|
@@ -354,8 +389,8 @@ export declare class DashboardViewProvider implements IXpertViewExtensionProvide
|
|
|
354
389
|
allowedValues?: (string | number | boolean | null)[] | undefined;
|
|
355
390
|
}[];
|
|
356
391
|
charts: {
|
|
357
|
-
title: string;
|
|
358
392
|
type: "bar" | "line" | "area" | "pie" | "scatter";
|
|
393
|
+
title: string;
|
|
359
394
|
id: string;
|
|
360
395
|
datasetId: string;
|
|
361
396
|
x: string;
|