@chengxianglibra/dsh-data-analysis 0.1.2 → 0.2.1
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/README.en.md +135 -0
- package/README.md +105 -98
- package/cordis.patch.yml +1 -1
- package/lib/bin/environment.js +8 -6
- package/lib/client/credentials/changes.js +74 -0
- package/lib/client/credentials/create-datasource.js +126 -22
- package/lib/client/credentials/credential-draft.js +42 -0
- package/lib/client/credentials/defaults.js +11 -0
- package/lib/client/credentials/descriptions.js +46 -0
- package/lib/client/credentials/install.js +209 -81
- package/lib/client/credentials/model.js +323 -51
- package/lib/client/credentials/report-publishing.js +96 -0
- package/lib/client/credentials/styles.js +13 -9
- package/lib/client/i18n/context.js +22 -0
- package/lib/client/i18n/copy.js +60 -0
- package/lib/client/i18n/credentials.js +475 -0
- package/lib/client/i18n/host.js +28 -0
- package/lib/client/i18n/navigation.js +41 -0
- package/lib/client/i18n/presentation.js +577 -0
- package/lib/client/i18n/python.js +36 -0
- package/lib/client/i18n/semantic.js +441 -0
- package/lib/client/i18n/settings.js +30 -0
- package/lib/client/input-context.js +21 -0
- package/lib/client/python-tool/card.js +35 -0
- package/lib/client/python-tool/execution-error.js +35 -0
- package/lib/client/python-tool/install.js +9 -0
- package/lib/client/python-tool/model.js +101 -0
- package/lib/client/right-tabs/install.js +535 -0
- package/lib/client/right-tabs/live-delivery.js +58 -0
- package/lib/client/right-tabs/navigation.js +47 -0
- package/lib/client/right-tabs/page.js +178 -0
- package/lib/client/right-tabs/styles.js +11 -0
- package/lib/client/rpc.js +5 -0
- package/lib/client/semantic-reference.js +10 -0
- package/lib/client/settings/card.js +90 -0
- package/lib/client/settings/install.js +10 -0
- package/lib/client/workspace-header-action.js +14 -1
- package/lib/client/workspace-shortcuts.js +26 -0
- package/lib/client.js +388 -242
- package/lib/compatibility.js +1 -1
- package/lib/datasource/authoring.js +43 -4
- package/lib/datasource/bridge-programs.js +23 -20
- package/lib/datasource/bridge.js +46 -3
- package/lib/datasource/changes-contract.js +41 -0
- package/lib/datasource/changes.js +49 -0
- package/lib/datasource/configure.js +39 -0
- package/lib/datasource/defaults.js +25 -0
- package/lib/datasource/index.js +2 -1
- package/lib/datasource/python-options.js +15 -0
- package/lib/datasource/python.js +119 -25
- package/lib/datasource/rpc.js +75 -13
- package/lib/datasource/service.js +286 -11
- package/lib/datasource/test.js +3 -2
- package/lib/disclosure/activation.js +19 -7
- package/lib/disclosure/execution-guidance.js +49 -0
- package/lib/disclosure/help.js +4 -1
- package/lib/environment/runtime.js +35 -34
- package/lib/environment/workspace.js +18 -1
- package/lib/lifecycle.js +33 -0
- package/lib/plugin-agents.js +154 -0
- package/lib/plugin.js +98 -172
- package/lib/presentation/asset.js +34 -0
- package/lib/presentation/assets/portable.js +14 -14
- package/lib/presentation/assets/static.js +344 -73
- package/lib/presentation/build/index.js +3 -2
- package/lib/presentation/commit.js +19 -12
- package/lib/presentation/contracts/asset-path.js +9 -0
- package/lib/presentation/contracts/catalog.js +62 -0
- package/lib/presentation/contracts/charts.js +0 -23
- package/lib/presentation/contracts/index.js +17 -5
- package/lib/presentation/contracts/types.js +1 -1
- package/lib/presentation/delivery.js +45 -30
- package/lib/presentation/export-html.js +214 -0
- package/lib/presentation/files.js +4 -5
- package/lib/presentation/projection/index.js +2 -1
- package/lib/presentation/projection/program.js +2 -9
- package/lib/presentation/projection/sql-code.js +13 -13
- package/lib/presentation/reports.js +114 -17
- package/lib/presentation/rpc.js +96 -58
- package/lib/presentation/tool.js +6 -10
- package/lib/report-publishing/adapters.js +111 -0
- package/lib/report-publishing/config.js +69 -0
- package/lib/report-publishing/service.js +180 -0
- package/lib/rpc.js +49 -0
- package/lib/semantic-browser/contracts.js +17 -0
- package/lib/semantic-browser/program.js +10 -0
- package/lib/semantic-browser/service.js +40 -6
- package/lib/semantic-reference/bridge.js +10 -1
- package/lib/semantic-reference/contracts.js +20 -6
- package/lib/semantic-reference/environment.js +56 -0
- package/lib/semantic-reference/labels.js +16 -0
- package/lib/semantic-reference/rpc.js +39 -18
- package/lib/semantic-reference/search.js +5 -4
- package/lib/semantic-reference/usage.js +12 -3
- package/lib/settings.js +21 -0
- package/lib/tool-lifecycle.js +22 -0
- package/lib/types/client/credentials/changes.d.ts +9 -0
- package/lib/types/client/credentials/create-datasource.d.ts +3 -1
- package/lib/types/client/credentials/credential-draft.d.ts +14 -0
- package/lib/types/client/credentials/defaults.d.ts +2 -0
- package/lib/types/client/credentials/descriptions.d.ts +1 -0
- package/lib/types/client/credentials/install.d.ts +19 -1
- package/lib/types/client/credentials/model.d.ts +31 -11
- package/lib/types/client/credentials/report-publishing.d.ts +5 -0
- package/lib/types/client/credentials/styles.d.ts +1 -1
- package/lib/types/client/i18n/context.d.ts +17 -0
- package/lib/types/client/i18n/copy.d.ts +821 -0
- package/lib/types/client/i18n/credentials.d.ts +475 -0
- package/lib/types/client/i18n/host.d.ts +17 -0
- package/lib/types/client/i18n/navigation.d.ts +41 -0
- package/lib/types/client/i18n/presentation.d.ts +577 -0
- package/lib/types/client/i18n/python.d.ts +19 -0
- package/lib/types/client/i18n/semantic.d.ts +441 -0
- package/lib/types/client/i18n/settings.d.ts +16 -0
- package/lib/types/client/input-context.d.ts +19 -0
- package/lib/types/client/presentation/ask-dsh.d.ts +4 -8
- package/lib/types/client/presentation/catalog-model.d.ts +24 -0
- package/lib/types/client/presentation/catalog.d.ts +20 -0
- package/lib/types/client/presentation/chart-tooltip.d.ts +2 -2
- package/lib/types/client/presentation/context-reference.d.ts +12 -0
- package/lib/types/client/presentation/copy-context.d.ts +2 -2
- package/lib/types/client/presentation/delivery-model.d.ts +27 -6
- package/lib/types/client/presentation/delivery.d.ts +8 -7
- package/lib/types/client/presentation/download.d.ts +2 -0
- package/lib/types/client/presentation/export-menu.d.ts +26 -0
- package/lib/types/client/presentation/export-view.d.ts +63 -0
- package/lib/types/client/presentation/host-entry.d.ts +8 -2
- package/lib/types/client/presentation/icons.d.ts +1 -1
- package/lib/types/client/presentation/model.d.ts +14 -15
- package/lib/types/client/presentation/reader.d.ts +15 -2
- package/lib/types/client/presentation/reference-source.d.ts +9 -0
- package/lib/types/client/presentation/source-code-model.d.ts +2 -1
- package/lib/types/client/presentation/source-code.d.ts +0 -5
- package/lib/types/client/presentation/source-dialog.d.ts +1 -2
- package/lib/types/client/presentation/sources.d.ts +5 -5
- package/lib/types/client/presentation/styles.d.ts +1 -1
- package/lib/types/client/presentation/table.d.ts +5 -1
- package/lib/types/client/python-tool/card.d.ts +2 -0
- package/lib/types/client/python-tool/execution-error.d.ts +3 -0
- package/lib/types/client/python-tool/install.d.ts +2 -0
- package/lib/types/client/python-tool/model.d.ts +20 -0
- package/lib/types/client/right-tabs/install.d.ts +18 -0
- package/lib/types/client/right-tabs/live-delivery.d.ts +8 -0
- package/lib/types/client/right-tabs/navigation.d.ts +17 -0
- package/lib/types/client/right-tabs/page.d.ts +36 -0
- package/lib/types/client/right-tabs/styles.d.ts +2 -0
- package/lib/types/client/rpc.d.ts +3 -0
- package/lib/types/client/semantic-reference.d.ts +4 -0
- package/lib/types/client/settings/card.d.ts +9 -0
- package/lib/types/client/settings/install.d.ts +2 -0
- package/lib/types/client/workspace-header-action.d.ts +1 -1
- package/lib/types/client/workspace-shortcuts.d.ts +15 -0
- package/lib/types/client.d.ts +5 -2
- package/lib/types/compatibility.d.ts +1 -1
- package/lib/types/datasource/authoring.d.ts +10 -0
- package/lib/types/datasource/bridge.d.ts +23 -2
- package/lib/types/datasource/changes-contract.d.ts +18 -0
- package/lib/types/datasource/changes.d.ts +15 -0
- package/lib/types/datasource/configure.d.ts +7 -0
- package/lib/types/datasource/defaults.d.ts +7 -0
- package/lib/types/datasource/index.d.ts +6 -1
- package/lib/types/datasource/python-options.d.ts +8 -0
- package/lib/types/datasource/python.d.ts +17 -2
- package/lib/types/datasource/rpc.d.ts +2 -1
- package/lib/types/datasource/service.d.ts +48 -5
- package/lib/types/datasource/test.d.ts +1 -1
- package/lib/types/disclosure/activation.d.ts +2 -1
- package/lib/types/disclosure/execution-guidance.d.ts +6 -0
- package/lib/types/disclosure/help.d.ts +1 -1
- package/lib/types/environment/types.d.ts +2 -1
- package/lib/types/environment/workspace.d.ts +1 -0
- package/lib/types/lifecycle.d.ts +8 -0
- package/lib/types/plugin-agents.d.ts +29 -0
- package/lib/types/plugin.d.ts +12 -28
- package/lib/types/presentation/asset.d.ts +6 -0
- package/lib/types/presentation/build/index.d.ts +7 -2
- package/lib/types/presentation/commit.d.ts +1 -1
- package/lib/types/presentation/contracts/asset-path.d.ts +2 -0
- package/lib/types/presentation/contracts/catalog.d.ts +27 -0
- package/lib/types/presentation/contracts/charts.d.ts +0 -2
- package/lib/types/presentation/contracts/types.d.ts +7 -4
- package/lib/types/presentation/delivery.d.ts +1 -1
- package/lib/types/presentation/export-html.d.ts +13 -0
- package/lib/types/presentation/reports.d.ts +6 -1
- package/lib/types/presentation/rpc.d.ts +3 -2
- package/lib/types/presentation/tool.d.ts +2 -2
- package/lib/types/report-publishing/adapters.d.ts +6 -0
- package/lib/types/report-publishing/config.d.ts +22 -0
- package/lib/types/report-publishing/service.d.ts +68 -0
- package/lib/types/rpc.d.ts +3 -0
- package/lib/types/semantic-browser/contracts.d.ts +7 -0
- package/lib/types/semantic-browser/service.d.ts +7 -1
- package/lib/types/semantic-reference/bridge.d.ts +1 -0
- package/lib/types/semantic-reference/contracts.d.ts +5 -2
- package/lib/types/semantic-reference/environment.d.ts +22 -0
- package/lib/types/semantic-reference/labels.d.ts +16 -0
- package/lib/types/semantic-reference/rpc.d.ts +1 -1
- package/lib/types/semantic-reference/search.d.ts +1 -1
- package/lib/types/settings.d.ts +14 -0
- package/lib/types/tool-lifecycle.d.ts +4 -0
- package/lib/types/workspace-identity.d.ts +6 -0
- package/lib/workspace-identity.js +9 -0
- package/package.json +93 -61
- package/python/presentation-kit/dist/dsh_data_analysis_presentation_kit-1.1.0-py3-none-any.whl +0 -0
- package/skills/dsh-data-analysis-files/SKILL.md +44 -0
- package/skills/dsh-data-analysis-files/references/delivery.md +15 -0
- package/skills/dsh-data-analysis-files/references/examples.md +93 -0
- package/skills/dsh-data-analysis-presentation/SKILL.md +15 -15
- package/skills/dsh-data-analysis-presentation/references/examples/artifact.draft.json +13 -4
- package/skills/dsh-data-analysis-presentation/references/examples/charts.draft.json +3 -2
- package/skills/dsh-data-analysis-presentation/references/examples/comparison.draft.json +3 -2
- package/skills/dsh-data-analysis-presentation/references/examples/computed-sources.draft.json +12 -5
- package/skills/dsh-data-analysis-presentation/references/examples/computed.draft.json +3 -2
- package/skills/dsh-data-analysis-presentation/references/examples/interaction.draft.json +3 -2
- package/skills/dsh-data-analysis-presentation/references/examples/source-only.draft.json +12 -4
- package/skills/dsh-data-analysis-presentation/references/examples.md +3 -1
- package/skills/dsh-data-analysis-presentation/references/export-html.md +7 -0
- package/skills/dsh-data-analysis-presentation/references/interaction.md +1 -1
- package/skills/dsh-data-analysis-presentation/references/narrative.md +1 -1
- package/skills/dsh-data-analysis-presentation/references/publishing.md +3 -0
- package/skills/dsh-data-analysis-presentation/references/schema.md +29 -2
- package/lib/types/client/presentation/install.d.ts +0 -15
|
@@ -7,10 +7,11 @@ import {
|
|
|
7
7
|
} from "../contracts/index.js";
|
|
8
8
|
var escapeHtml = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
9
9
|
var inlineJson = (value) => JSON.stringify(value).replaceAll("<", "\\u003c").replaceAll("\u2028", "\\u2028").replaceAll("\u2029", "\\u2029");
|
|
10
|
-
async function buildPresentation(value) {
|
|
10
|
+
async function buildPresentation(value, options = { html: true }) {
|
|
11
11
|
parsePresentationDocument(value);
|
|
12
12
|
const documentBytes = Buffer.from(JSON.stringify(value), "utf8");
|
|
13
13
|
const document = parsePresentationDocument(JSON.parse(documentBytes.toString("utf8")));
|
|
14
|
+
if (!options.html) return { document, documentBytes };
|
|
14
15
|
const [portable, staticAssets] = await Promise.all([
|
|
15
16
|
import(new URL("../../../lib/presentation/assets/portable.js", import.meta.url).href),
|
|
16
17
|
import(new URL("../../../lib/presentation/assets/static.js", import.meta.url).href)
|
|
@@ -35,7 +36,7 @@ async function buildPresentation(value) {
|
|
|
35
36
|
].join("; ");
|
|
36
37
|
const htmlBytes = Buffer.from(
|
|
37
38
|
`<!doctype html>
|
|
38
|
-
<html lang="
|
|
39
|
+
<html lang="${escapeHtml(document.locale)}">
|
|
39
40
|
<head>
|
|
40
41
|
<meta charset="utf-8">
|
|
41
42
|
<meta http-equiv="Content-Security-Policy" content="${escapeHtml(csp)}">
|
|
@@ -23,18 +23,18 @@ async function sameDirectory(filename, expected) {
|
|
|
23
23
|
if (actual.dev !== expected.dev || actual.ino !== expected.ino)
|
|
24
24
|
throw new Error('presentation-directory-changed');
|
|
25
25
|
}
|
|
26
|
-
/** Owns only one newly-created build. A single directory rename publishes
|
|
26
|
+
/** Owns only one newly-created build. A single directory rename publishes all requested complete files. */
|
|
27
27
|
export async function commitPresentation(workspaceRoot, input, assertOwner, signal) {
|
|
28
28
|
signal?.throwIfAborted();
|
|
29
29
|
const document = parsePresentationDocument(structuredClone(input.document));
|
|
30
30
|
const built = {
|
|
31
31
|
document,
|
|
32
32
|
documentBytes: Buffer.from(input.documentBytes),
|
|
33
|
-
htmlBytes: Buffer.from(input.htmlBytes),
|
|
33
|
+
htmlBytes: input.htmlBytes && Buffer.from(input.htmlBytes),
|
|
34
34
|
};
|
|
35
35
|
if (built.documentBytes.length > PRESENTATION_BUDGETS.documentBytes ||
|
|
36
|
-
built.htmlBytes
|
|
37
|
-
|
|
36
|
+
(built.htmlBytes !== undefined &&
|
|
37
|
+
(built.htmlBytes.length === 0 || built.htmlBytes.length > PRESENTATION_BUDGETS.htmlBytes)))
|
|
38
38
|
throw new Error('presentation-build-budget');
|
|
39
39
|
if (!built.documentBytes.equals(Buffer.from(JSON.stringify(document))))
|
|
40
40
|
throw new Error('presentation-build-document-mismatch');
|
|
@@ -82,6 +82,8 @@ export async function commitPresentation(workspaceRoot, input, assertOwner, sign
|
|
|
82
82
|
['presentation.json', built.documentBytes],
|
|
83
83
|
['index.html', built.htmlBytes],
|
|
84
84
|
]) {
|
|
85
|
+
if (!bytes)
|
|
86
|
+
continue;
|
|
85
87
|
await sameDirectory(temporary, ownedIdentity);
|
|
86
88
|
signal?.throwIfAborted();
|
|
87
89
|
const file = await open(path.join(temporary, asset), constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
|
|
@@ -112,8 +114,9 @@ export async function commitPresentation(workspaceRoot, input, assertOwner, sign
|
|
|
112
114
|
await check();
|
|
113
115
|
await sameDirectory(final, ownedIdentity);
|
|
114
116
|
const document = await readPresentationAsset(root, built.document.reportId, built.document.buildId, 'presentation.json', signal);
|
|
115
|
-
const html =
|
|
116
|
-
|
|
117
|
+
const html = built.htmlBytes &&
|
|
118
|
+
(await readPresentationAsset(root, built.document.reportId, built.document.buildId, 'index.html', signal));
|
|
119
|
+
if (!document.equals(built.documentBytes) || (html && !html.equals(built.htmlBytes)))
|
|
117
120
|
throw new Error('presentation-build-changed');
|
|
118
121
|
await check();
|
|
119
122
|
return parsePresentationReceipt({
|
|
@@ -131,12 +134,16 @@ export async function commitPresentation(workspaceRoot, input, assertOwner, sign
|
|
|
131
134
|
sha256: presentationSha256(document),
|
|
132
135
|
bytes: document.length,
|
|
133
136
|
},
|
|
134
|
-
html
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
137
|
+
...(html
|
|
138
|
+
? {
|
|
139
|
+
html: {
|
|
140
|
+
asset: 'index.html',
|
|
141
|
+
path: path.join(final, 'index.html'),
|
|
142
|
+
sha256: presentationSha256(html),
|
|
143
|
+
bytes: html.length,
|
|
144
|
+
},
|
|
145
|
+
}
|
|
146
|
+
: {}),
|
|
140
147
|
},
|
|
141
148
|
});
|
|
142
149
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Fixed Build locator shared by the browser and Workspace storage. */
|
|
2
|
+
import { parsePresentationBuildId } from "./index.js";
|
|
3
|
+
export function presentationAssetRelativePath(reportId, buildId, asset) {
|
|
4
|
+
parsePresentationBuildId(reportId, '/reportId');
|
|
5
|
+
parsePresentationBuildId(buildId);
|
|
6
|
+
if (asset !== 'presentation.json' && asset !== 'index.html')
|
|
7
|
+
throw new Error('invalid-asset');
|
|
8
|
+
return `.dsh-data-analysis/presentations/${reportId}/builds/${buildId}/${asset}`;
|
|
9
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { parsePresentationReceipt } from "./index.js";
|
|
2
|
+
export const REPORT_HISTORY_BYTES = 16 * 1024 * 1024;
|
|
3
|
+
export const REPORT_HISTORY_LIMIT = 4096;
|
|
4
|
+
export function parseReportVersion(value) {
|
|
5
|
+
const entry = value;
|
|
6
|
+
if (!entry ||
|
|
7
|
+
Object.keys(entry).sort().join(',') !== 'publishedAt,receipt,source' ||
|
|
8
|
+
!(entry.publishedAt === null ||
|
|
9
|
+
(typeof entry.publishedAt === 'string' &&
|
|
10
|
+
Number.isFinite(Date.parse(entry.publishedAt)) &&
|
|
11
|
+
new Date(entry.publishedAt).toISOString() === entry.publishedAt)) ||
|
|
12
|
+
!(entry.source === null ||
|
|
13
|
+
(entry.source &&
|
|
14
|
+
Object.keys(entry.source).sort().join(',') === 'kind,sessionId' &&
|
|
15
|
+
['agent', 'reader'].includes(entry.source.kind) &&
|
|
16
|
+
(entry.source.sessionId === null ||
|
|
17
|
+
(typeof entry.source.sessionId === 'string' &&
|
|
18
|
+
!!entry.source.sessionId.trim() &&
|
|
19
|
+
entry.source.sessionId.length <= 512)))))
|
|
20
|
+
throw new Error('invalid-report-history');
|
|
21
|
+
parsePresentationReceipt(entry.receipt);
|
|
22
|
+
return entry;
|
|
23
|
+
}
|
|
24
|
+
export function parseReportHistory(value) {
|
|
25
|
+
const history = value;
|
|
26
|
+
if (!history ||
|
|
27
|
+
!Array.isArray(history.versions) ||
|
|
28
|
+
!history.versions.length ||
|
|
29
|
+
history.versions.length > REPORT_HISTORY_LIMIT ||
|
|
30
|
+
typeof history.legacyHistoryUnavailable !== 'boolean')
|
|
31
|
+
throw new Error('invalid-report-history');
|
|
32
|
+
const ids = new Set();
|
|
33
|
+
for (const entry of history.versions) {
|
|
34
|
+
const { receipt } = parseReportVersion(entry);
|
|
35
|
+
if (receipt.workspaceId !== history.workspaceId ||
|
|
36
|
+
receipt.reportId !== history.reportId ||
|
|
37
|
+
ids.has(receipt.buildId))
|
|
38
|
+
throw new Error('invalid-report-history');
|
|
39
|
+
ids.add(receipt.buildId);
|
|
40
|
+
}
|
|
41
|
+
if (history.currentBuildId !== history.versions[0].receipt.buildId)
|
|
42
|
+
throw new Error('invalid-report-history');
|
|
43
|
+
return history;
|
|
44
|
+
}
|
|
45
|
+
export function parseReportCatalog(value) {
|
|
46
|
+
const catalog = value;
|
|
47
|
+
if (!catalog ||
|
|
48
|
+
typeof catalog.workspaceId !== 'string' ||
|
|
49
|
+
!Array.isArray(catalog.reports) ||
|
|
50
|
+
catalog.reports.length > 4096 ||
|
|
51
|
+
!Number.isInteger(catalog.unavailable) ||
|
|
52
|
+
catalog.unavailable < 0)
|
|
53
|
+
throw new Error('invalid-report-catalog');
|
|
54
|
+
const ids = new Set();
|
|
55
|
+
for (const item of catalog.reports) {
|
|
56
|
+
const { receipt } = parseReportVersion(item);
|
|
57
|
+
if (receipt.workspaceId !== catalog.workspaceId || ids.has(receipt.reportId))
|
|
58
|
+
throw new Error('invalid-report-catalog');
|
|
59
|
+
ids.add(receipt.reportId);
|
|
60
|
+
}
|
|
61
|
+
return catalog;
|
|
62
|
+
}
|
|
@@ -20,26 +20,6 @@ export const CHART_TYPES = [
|
|
|
20
20
|
'funnel',
|
|
21
21
|
'waterfall',
|
|
22
22
|
];
|
|
23
|
-
export const CHART_LABELS = {
|
|
24
|
-
line: '折线图',
|
|
25
|
-
area: '面积图',
|
|
26
|
-
stackedArea: '堆叠面积图',
|
|
27
|
-
sparkline: '迷你趋势图',
|
|
28
|
-
bar: '柱状图',
|
|
29
|
-
horizontalBar: '横向条形图',
|
|
30
|
-
stackedBar: '堆叠柱状图',
|
|
31
|
-
stackedBar100: '百分比堆叠柱状图',
|
|
32
|
-
horizontalStackedBar: '横向堆叠条形图',
|
|
33
|
-
horizontalStackedBar100: '横向百分比堆叠条形图',
|
|
34
|
-
histogram: '直方图',
|
|
35
|
-
boxPlot: '箱线图',
|
|
36
|
-
scatter: '散点图',
|
|
37
|
-
heatmap: '热力图',
|
|
38
|
-
pie: '环形图',
|
|
39
|
-
leaderboard: '排行榜',
|
|
40
|
-
funnel: '漏斗图',
|
|
41
|
-
waterfall: '瀑布图',
|
|
42
|
-
};
|
|
43
23
|
const trendTypes = ['line', 'area', 'stackedArea', 'sparkline'];
|
|
44
24
|
const ratioTypes = ['stackedBar100', 'horizontalStackedBar100'];
|
|
45
25
|
const singleSeriesTypes = [
|
|
@@ -233,9 +213,6 @@ export function parseChartViewShape(value, path = '') {
|
|
|
233
213
|
export function chartColumns(view) {
|
|
234
214
|
return [...new Set([view.x, ...view.y, ...Object.values(view.bindings ?? {})])];
|
|
235
215
|
}
|
|
236
|
-
export function chartSeriesFields(view) {
|
|
237
|
-
return [...view.y];
|
|
238
|
-
}
|
|
239
216
|
/** Verifies supplied values only: never bins, ranks, accumulates, normalizes or sorts. */
|
|
240
217
|
export function validateChartView(view, dataset, path = '', dataPath = '/data', rowIndices) {
|
|
241
218
|
const rows = rowIndices ? rowIndices.map((index) => dataset.rows[index]) : dataset.rows;
|
|
@@ -263,7 +263,7 @@ export function formatCell(value, _column) {
|
|
|
263
263
|
return value === null ? '—' : String(value);
|
|
264
264
|
}
|
|
265
265
|
function common(value, generated) {
|
|
266
|
-
version(value, '', generated ?
|
|
266
|
+
version(value, '', generated ? 3 : 2);
|
|
267
267
|
string(value.title, '/title', 512);
|
|
268
268
|
const sources = array(value.sources, '/sources', budgets.sources);
|
|
269
269
|
const sourceIds = sources.map((value, i) => {
|
|
@@ -566,18 +566,27 @@ function interaction(value, datasets, generated) {
|
|
|
566
566
|
}
|
|
567
567
|
});
|
|
568
568
|
}
|
|
569
|
+
function presentationLanguage(value, expected) {
|
|
570
|
+
if (value.schemaVersion !== expected)
|
|
571
|
+
fail('/schemaVersion', `Only schemaVersion ${expected} is accepted. Regenerate this report with an explicit locale.`, 'report-version-unsupported');
|
|
572
|
+
if (value.locale !== 'zh-CN' && value.locale !== 'en-US')
|
|
573
|
+
fail('/locale', 'Report locale must be zh-CN or en-US. Ask DSH to regenerate the report with an explicit language.', 'report-locale-invalid');
|
|
574
|
+
}
|
|
569
575
|
export function parsePresentationDraft(value) {
|
|
570
576
|
bytes(value, budgets.draftBytes);
|
|
571
577
|
const entry = object(value, '');
|
|
572
|
-
|
|
578
|
+
presentationLanguage(entry, 2);
|
|
579
|
+
keys(entry, ['schemaVersion', 'locale', 'title', 'datasets', 'sources', 'blocks'], ['interaction'], '');
|
|
573
580
|
common(entry, false);
|
|
574
581
|
return entry;
|
|
575
582
|
}
|
|
576
583
|
export function parsePresentationDocument(value) {
|
|
577
584
|
bytes(value, budgets.documentBytes);
|
|
578
585
|
const entry = object(value, '');
|
|
586
|
+
presentationLanguage(entry, 3);
|
|
579
587
|
keys(entry, [
|
|
580
588
|
'schemaVersion',
|
|
589
|
+
'locale',
|
|
581
590
|
'workspaceId',
|
|
582
591
|
'reportId',
|
|
583
592
|
'buildId',
|
|
@@ -618,11 +627,13 @@ export function parsePresentationReceipt(value) {
|
|
|
618
627
|
string(entry.title, '/title', 512);
|
|
619
628
|
string(entry.summary, '/summary', 2048);
|
|
620
629
|
const files = object(entry.files, '/files');
|
|
621
|
-
keys(files, ['document', 'html'],
|
|
630
|
+
keys(files, ['document'], ['html'], '/files');
|
|
622
631
|
for (const [key, asset, max] of [
|
|
623
632
|
['document', 'presentation.json', budgets.documentBytes],
|
|
624
633
|
['html', 'index.html', budgets.htmlBytes],
|
|
625
634
|
]) {
|
|
635
|
+
if (key === 'html' && !Object.hasOwn(files, key))
|
|
636
|
+
continue;
|
|
626
637
|
const path = `/files/${key}`;
|
|
627
638
|
const file = object(files[key], path);
|
|
628
639
|
keys(file, ['asset', 'path', 'sha256', 'bytes'], [], path);
|
|
@@ -643,8 +654,9 @@ export function parsePresentationReceipt(value) {
|
|
|
643
654
|
integer(file.bytes, `${path}/bytes`, 1, max);
|
|
644
655
|
}
|
|
645
656
|
const documentPath = String(object(files.document, '/files/document').path);
|
|
646
|
-
const htmlPath = String(object(files.html, '/files/html').path);
|
|
647
|
-
if (
|
|
657
|
+
const htmlPath = files.html ? String(object(files.html, '/files/html').path) : undefined;
|
|
658
|
+
if (htmlPath !== undefined &&
|
|
659
|
+
documentPath.slice(0, -'presentation.json'.length) !== htmlPath.slice(0, -'index.html'.length)) {
|
|
648
660
|
fail('/files/html/path', 'Both files must belong to the same build directory.', 'file_boundary');
|
|
649
661
|
}
|
|
650
662
|
return entry;
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
import { finishCleanup, PendingTasks } from "../lifecycle.js";
|
|
1
2
|
import { MARIVO_PRESENT_TOOL_NAME, MARIVO_PRESENTATION_DELIVERY_KIND, parsePresentationDelivery, } from "./receipt.js";
|
|
2
3
|
/** Persist the same envelope on nested Code dispatch even when code discards the return value. */
|
|
3
4
|
export function installMarivoPresentationCodeDelivery(ctx) {
|
|
5
|
+
const tasks = new PendingTasks();
|
|
6
|
+
let active = true;
|
|
4
7
|
const pending = new Map();
|
|
5
8
|
const stopResult = ctx.on('tools/result', (exec, result) => {
|
|
9
|
+
if (!active)
|
|
10
|
+
return;
|
|
6
11
|
if (exec.name !== MARIVO_PRESENT_TOOL_NAME || exec.parent === undefined || result.isError)
|
|
7
12
|
return;
|
|
8
13
|
try {
|
|
@@ -19,38 +24,48 @@ export function installMarivoPresentationCodeDelivery(ctx) {
|
|
|
19
24
|
/* Invalid output cannot create a success card. */
|
|
20
25
|
}
|
|
21
26
|
});
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
item
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
27
|
+
let stopLog;
|
|
28
|
+
try {
|
|
29
|
+
stopLog = ctx.on('tools/ptc-dispatch-log', (dispatch, next) => tasks.track((async () => {
|
|
30
|
+
const content = await next();
|
|
31
|
+
if (!active)
|
|
32
|
+
return content;
|
|
33
|
+
if (dispatch.name !== MARIVO_PRESENT_TOOL_NAME)
|
|
34
|
+
return content;
|
|
35
|
+
const key = String(dispatch.subCallId);
|
|
36
|
+
const item = pending.get(key);
|
|
37
|
+
pending.delete(key);
|
|
38
|
+
if (dispatch.isError ||
|
|
39
|
+
!item ||
|
|
40
|
+
item.delivery.dshSessionId !== String(dispatch.agent?.session.id) ||
|
|
41
|
+
item.rootCallId !== String(dispatch.exec.rootCallId))
|
|
42
|
+
return content;
|
|
43
|
+
const root = [...(dispatch.agent?.session.snapshotEvents() ?? [])]
|
|
44
|
+
.reverse()
|
|
45
|
+
.find((event) => event.type === 'tool/call' && String(event.data.callId) === item.rootCallId);
|
|
46
|
+
if (root?.type !== 'tool/call' || root.data.turn !== item.delivery.turn)
|
|
47
|
+
return content;
|
|
48
|
+
return [
|
|
49
|
+
...content,
|
|
50
|
+
{
|
|
51
|
+
type: MARIVO_PRESENTATION_DELIVERY_KIND,
|
|
52
|
+
delivery: item.delivery,
|
|
53
|
+
},
|
|
54
|
+
];
|
|
55
|
+
})()), { prepend: true });
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
active = false;
|
|
59
|
+
void finishCleanup([stopResult, () => tasks.drain()]);
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
let closing;
|
|
48
63
|
return () => {
|
|
49
|
-
if (
|
|
50
|
-
return;
|
|
64
|
+
if (closing)
|
|
65
|
+
return closing;
|
|
51
66
|
active = false;
|
|
52
|
-
stopLog();
|
|
53
|
-
stopResult();
|
|
67
|
+
closing = finishCleanup([stopLog, stopResult, () => tasks.drain()]);
|
|
54
68
|
pending.clear();
|
|
69
|
+
return closing;
|
|
55
70
|
};
|
|
56
71
|
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { constants } from 'node:fs';
|
|
3
|
+
import { link, lstat, mkdir, open, realpath, unlink } from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
6
|
+
import { registerMarivoTool } from "../tool-lifecycle.js";
|
|
7
|
+
import { readReceiptAsset } from "./asset.js";
|
|
8
|
+
import { parsePresentationBuildId } from "./contracts/index.js";
|
|
9
|
+
import { readReportHistory } from "./reports.js";
|
|
10
|
+
export const MARIVO_EXPORT_HTML_TOOL_NAME = 'marivo_export_html';
|
|
11
|
+
export function parseHtmlOutputPath(value) {
|
|
12
|
+
if (typeof value !== 'string' ||
|
|
13
|
+
!value ||
|
|
14
|
+
value.length > 4096 ||
|
|
15
|
+
value.includes('\\') ||
|
|
16
|
+
value.includes('\0') ||
|
|
17
|
+
path.isAbsolute(value) ||
|
|
18
|
+
!value.endsWith('.html'))
|
|
19
|
+
throw new Error('invalid-html-output-path');
|
|
20
|
+
const segments = value.split('/');
|
|
21
|
+
if (segments.some((part) => !part || part === '.' || part === '..') ||
|
|
22
|
+
segments[0].toLowerCase() === '.dsh-data-analysis')
|
|
23
|
+
throw new Error('invalid-html-output-path');
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
/** Workspace-local hard-link publication is atomic and never replaces an existing target. */
|
|
27
|
+
async function writeHtml(root, relative, bytes, assertOwner) {
|
|
28
|
+
const parents = [root];
|
|
29
|
+
const identities = [];
|
|
30
|
+
const inspect = async (filename) => {
|
|
31
|
+
const stat = await lstat(filename);
|
|
32
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || (await realpath(filename)) !== filename)
|
|
33
|
+
throw new Error('html-output-directory-mismatch');
|
|
34
|
+
return stat;
|
|
35
|
+
};
|
|
36
|
+
const check = async () => {
|
|
37
|
+
await assertOwner();
|
|
38
|
+
for (const [index, directory] of parents.entries()) {
|
|
39
|
+
const stat = await inspect(directory);
|
|
40
|
+
if (stat.dev !== identities[index].dev || stat.ino !== identities[index].ino)
|
|
41
|
+
throw new Error('html-output-directory-changed');
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
identities.push(await inspect(root));
|
|
45
|
+
for (const part of relative.split('/').slice(0, -1)) {
|
|
46
|
+
await check();
|
|
47
|
+
const directory = path.join(parents.at(-1), part);
|
|
48
|
+
try {
|
|
49
|
+
await mkdir(directory, { mode: 0o700 });
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
if (error.code !== 'EEXIST')
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
identities.push(await inspect(directory));
|
|
56
|
+
parents.push(directory);
|
|
57
|
+
}
|
|
58
|
+
await check();
|
|
59
|
+
const target = path.join(root, relative);
|
|
60
|
+
// Stage at the highest ancestor on the destination filesystem, outside movable
|
|
61
|
+
// output subdirectories where possible, without introducing cross-device links.
|
|
62
|
+
const stagingParent = parents[identities.findIndex((identity) => identity.dev === identities.at(-1).dev)];
|
|
63
|
+
const temporary = path.join(stagingParent, `.html-export-${randomUUID()}.tmp`);
|
|
64
|
+
const handle = await open(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
|
|
65
|
+
const owned = await handle.stat();
|
|
66
|
+
let linked = false;
|
|
67
|
+
const removeOwned = async (filename) => {
|
|
68
|
+
try {
|
|
69
|
+
const stat = await lstat(filename);
|
|
70
|
+
if (stat.dev === owned.dev && stat.ino === owned.ino)
|
|
71
|
+
await unlink(filename);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
if (error.code !== 'ENOENT')
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
try {
|
|
79
|
+
await check();
|
|
80
|
+
await handle.writeFile(bytes);
|
|
81
|
+
await handle.sync();
|
|
82
|
+
await check();
|
|
83
|
+
await link(temporary, target);
|
|
84
|
+
linked = true;
|
|
85
|
+
await check();
|
|
86
|
+
const stat = await lstat(target);
|
|
87
|
+
if (stat.dev !== owned.dev || stat.ino !== owned.ino || stat.size !== bytes.length)
|
|
88
|
+
throw new Error('html-output-changed');
|
|
89
|
+
await removeOwned(temporary);
|
|
90
|
+
await check();
|
|
91
|
+
return target;
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
// Paths may have moved or been replaced. The open handle still owns the exact inode,
|
|
95
|
+
// including any published hard link: erase failed output without following new paths.
|
|
96
|
+
const failures = [error];
|
|
97
|
+
try {
|
|
98
|
+
await handle.truncate(0);
|
|
99
|
+
await handle.sync();
|
|
100
|
+
}
|
|
101
|
+
catch (cleanupError) {
|
|
102
|
+
failures.push(cleanupError);
|
|
103
|
+
}
|
|
104
|
+
if (linked) {
|
|
105
|
+
try {
|
|
106
|
+
await removeOwned(target);
|
|
107
|
+
}
|
|
108
|
+
catch (cleanupError) {
|
|
109
|
+
failures.push(cleanupError);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (failures.length > 1)
|
|
113
|
+
throw new AggregateError(failures, 'html-export-cleanup-failed');
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
try {
|
|
118
|
+
await removeOwned(temporary);
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
await handle.close();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
export function createMarivoExportHtmlTool(source, session) {
|
|
126
|
+
return defineTool({
|
|
127
|
+
name: MARIVO_EXPORT_HTML_TOOL_NAME,
|
|
128
|
+
description: 'Export a saved complete report as self-contained offline HTML only when requested. Uses the current Session Workspace, without analysis, Python, publication, new Builds or unsaved reader state. Specify build_id for an exact version; otherwise freezes current at call start. output_path must be a new Workspace-relative .html file; existing files are never overwritten. Returns file metadata, not HTML content. After success, call native present({files:[{path: result.path, description: "Offline HTML report"}]}) to deliver the file before your final response.',
|
|
129
|
+
parameters: {
|
|
130
|
+
report_id: {
|
|
131
|
+
type: 'string',
|
|
132
|
+
required: true,
|
|
133
|
+
description: 'Saved Report ID from its receipt.',
|
|
134
|
+
},
|
|
135
|
+
build_id: { type: 'string', description: 'Exact saved Build ID; omit to export current.' },
|
|
136
|
+
output_path: {
|
|
137
|
+
type: 'string',
|
|
138
|
+
required: true,
|
|
139
|
+
description: 'New Workspace-relative .html path, e.g. reports/analysis.html. Parent directories may be created.',
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
output: {
|
|
143
|
+
schema: {
|
|
144
|
+
type: 'object',
|
|
145
|
+
additionalProperties: false,
|
|
146
|
+
properties: {
|
|
147
|
+
workspace_id: { type: 'string', required: true },
|
|
148
|
+
report_id: { type: 'string', required: true },
|
|
149
|
+
build_id: { type: 'string', required: true },
|
|
150
|
+
path: { type: 'string', required: true },
|
|
151
|
+
mime_type: { type: 'string', required: true },
|
|
152
|
+
bytes: { type: 'number', required: true },
|
|
153
|
+
sha256: { type: 'string', required: true },
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
render: (_args, value) => [{ type: 'text', text: JSON.stringify(value) }],
|
|
157
|
+
presentationMeta: (_args, value) => value,
|
|
158
|
+
},
|
|
159
|
+
presentCall(args) {
|
|
160
|
+
return {
|
|
161
|
+
card: 'generic',
|
|
162
|
+
kind: 'edit',
|
|
163
|
+
title: 'Export report HTML',
|
|
164
|
+
locations: [{ path: args.output_path }],
|
|
165
|
+
};
|
|
166
|
+
},
|
|
167
|
+
timeoutMs: 120_000,
|
|
168
|
+
async execute(args, exec) {
|
|
169
|
+
const reportId = parsePresentationBuildId(args.report_id, '/report_id');
|
|
170
|
+
const buildId = args.build_id === undefined
|
|
171
|
+
? undefined
|
|
172
|
+
: parsePresentationBuildId(args.build_id, '/build_id');
|
|
173
|
+
const relative = parseHtmlOutputPath(args.output_path);
|
|
174
|
+
const owner = exec.agent;
|
|
175
|
+
if (!owner || owner.session !== session)
|
|
176
|
+
throw new Error('presentation-session-mismatch');
|
|
177
|
+
exec.signal.throwIfAborted();
|
|
178
|
+
const workspace = await source();
|
|
179
|
+
const root = path.resolve(workspace.path);
|
|
180
|
+
const check = async () => {
|
|
181
|
+
exec.signal.throwIfAborted();
|
|
182
|
+
if (owner.session !== session)
|
|
183
|
+
throw new Error('presentation-session-mismatch');
|
|
184
|
+
const current = await source();
|
|
185
|
+
if (current.id !== workspace.id || path.resolve(current.path) !== root)
|
|
186
|
+
throw new Error('presentation-workspace-changed');
|
|
187
|
+
exec.signal.throwIfAborted();
|
|
188
|
+
};
|
|
189
|
+
await check();
|
|
190
|
+
const history = await readReportHistory(root, workspace.id, reportId, exec.signal);
|
|
191
|
+
const receipt = buildId === undefined
|
|
192
|
+
? history.versions[0].receipt
|
|
193
|
+
: history.versions.find((entry) => entry.receipt.buildId === buildId)?.receipt;
|
|
194
|
+
if (!receipt)
|
|
195
|
+
throw new Error('presentation-build-unavailable');
|
|
196
|
+
await check();
|
|
197
|
+
const asset = await readReceiptAsset(root, workspace.id, receipt, 'index.html', exec.signal);
|
|
198
|
+
await check();
|
|
199
|
+
const filename = await writeHtml(root, relative, asset.bytes, check);
|
|
200
|
+
return {
|
|
201
|
+
workspace_id: workspace.id,
|
|
202
|
+
report_id: reportId,
|
|
203
|
+
build_id: receipt.buildId,
|
|
204
|
+
path: filename,
|
|
205
|
+
mime_type: 'text/html',
|
|
206
|
+
bytes: asset.bytes.length,
|
|
207
|
+
sha256: asset.sha256,
|
|
208
|
+
};
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
export function registerMarivoExportHtmlTool(ctx, source, session) {
|
|
213
|
+
return registerMarivoTool(ctx, createMarivoExportHtmlTool(source, session));
|
|
214
|
+
}
|
|
@@ -2,16 +2,14 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { constants } from 'node:fs';
|
|
3
3
|
import { lstat, open, realpath } from 'node:fs/promises';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
+
import { presentationAssetRelativePath } from "./contracts/asset-path.js";
|
|
5
6
|
import { PRESENTATION_BUDGETS, parsePresentationBuildId, } from "./contracts/index.js";
|
|
6
7
|
export function presentationReportPath(root, reportId) {
|
|
7
8
|
parsePresentationBuildId(reportId, '/reportId');
|
|
8
9
|
return path.join(root, '.dsh-data-analysis', 'presentations', reportId);
|
|
9
10
|
}
|
|
10
11
|
export function presentationAssetPath(root, reportId, buildId, asset) {
|
|
11
|
-
|
|
12
|
-
if (asset !== 'presentation.json' && asset !== 'index.html')
|
|
13
|
-
throw new Error('invalid-asset');
|
|
14
|
-
return path.join(presentationReportPath(root, reportId), 'builds', buildId, asset);
|
|
12
|
+
return path.join(root, presentationAssetRelativePath(reportId, buildId, asset));
|
|
15
13
|
}
|
|
16
14
|
export function presentationSha256(bytes) {
|
|
17
15
|
return createHash('sha256').update(bytes).digest('hex');
|
|
@@ -46,7 +44,8 @@ export async function readReportFile(workspaceRoot, filename, maximum, signal) {
|
|
|
46
44
|
throw new Error('asset-not-file');
|
|
47
45
|
if (before.size > maximum)
|
|
48
46
|
throw new Error('asset-too-large');
|
|
49
|
-
|
|
47
|
+
// Allocate for the observed file, retaining one extra byte to detect growth.
|
|
48
|
+
const bytes = Buffer.alloc(before.size + 1);
|
|
50
49
|
let length = 0;
|
|
51
50
|
while (length < bytes.length) {
|
|
52
51
|
signal?.throwIfAborted();
|
|
@@ -179,7 +179,8 @@ export class MarivoPresentationProjection {
|
|
|
179
179
|
}
|
|
180
180
|
this.#assertReady(options.signal);
|
|
181
181
|
return parsePresentationDocument({
|
|
182
|
-
schemaVersion:
|
|
182
|
+
schemaVersion: 3,
|
|
183
|
+
locale: draft.locale,
|
|
183
184
|
reportId: options.reportId,
|
|
184
185
|
workspaceId: options.workspaceId,
|
|
185
186
|
buildId: options.buildId,
|
|
@@ -33,7 +33,6 @@ ${MARIVO_PRESENTATION_SQL_CODE_PROGRAM}
|
|
|
33
33
|
def read(request):
|
|
34
34
|
sessions, artifacts, contracts = {}, {}, {}
|
|
35
35
|
sources, datasets, diagnostics = [], [], []
|
|
36
|
-
definition_sources = []
|
|
37
36
|
read_at = datetime.now(timezone.utc).isoformat()
|
|
38
37
|
try:
|
|
39
38
|
for index, declared in enumerate(request["sources"]):
|
|
@@ -58,7 +57,7 @@ def read(request):
|
|
|
58
57
|
except Exception:
|
|
59
58
|
# Only the requested source becomes unavailable. Exception messages may
|
|
60
59
|
# include credentials, project paths or raw SQL and are never returned.
|
|
61
|
-
sources.append({**declared, "status": "unavailable", "reason": "
|
|
60
|
+
sources.append({**declared, "status": "unavailable", "reason": "marivo.presentation.source-unavailable"})
|
|
62
61
|
continue
|
|
63
62
|
|
|
64
63
|
artifacts[declared["id"]], contracts[declared["id"]] = artifact, contract
|
|
@@ -85,17 +84,11 @@ def read(request):
|
|
|
85
84
|
except ProjectionFailure:
|
|
86
85
|
raise
|
|
87
86
|
except Exception:
|
|
88
|
-
fact("选择的 Finding unavailable", "
|
|
87
|
+
fact("选择的 Finding unavailable", "marivo.presentation.finding-unavailable")
|
|
89
88
|
diagnostics.append({"code": "finding_unavailable", "path": source_path + "/ref/findingId", "message": "Artifact is available; its requested Finding is unavailable."})
|
|
90
|
-
if meta.kind == "metric_frame" or any(item.semantic_kind.value == "metric" for item in contract.semantic_inputs):
|
|
91
|
-
definition_sources.append(declared["id"])
|
|
92
|
-
fact("指标定义说明", "信息:Artifact 公开契约未提供生成时的指标定义快照,来源概要无法展示当时的指标含义与口径。不影响已保存数据、图表及已有代码的展示,正常阅读无需处理;核验历史口径需补充生成时的定义快照,当前语义层定义不能替代历史定义。")
|
|
93
89
|
fact("revalidation", "not_requested")
|
|
94
90
|
sources.append({**declared, "status": "available", "label": text(str(meta.kind) + " " + artifact.ref, 512), "facts": facts, "code": sql_code_snapshot(session, artifact)})
|
|
95
91
|
|
|
96
|
-
if definition_sources:
|
|
97
|
-
diagnostics.append({"code": "definition_unavailable", "path": "/sources", "message": "信息:" + str(len(definition_sources)) + " 个指标来源的 Artifact 公开契约未提供生成时的指标定义快照,来源概要无法展示当时的指标含义与口径。不影响已保存数据、图表及已有代码的展示,正常阅读无需处理;核验历史口径需补充生成时的定义快照,当前语义层定义不能替代历史定义。"})
|
|
98
|
-
|
|
99
92
|
for selection in request["datasets"]:
|
|
100
93
|
dataset_path = "/datasets/" + str(selection["index"])
|
|
101
94
|
artifact = artifacts.get(selection["sourceId"])
|