@chengxianglibra/dsh-data-analysis 0.1.1 → 0.2.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/README.md +139 -53
- package/cordis.patch.yml +1 -1
- package/lib/bin/environment.js +9 -7
- package/lib/bin/presentation-lint.js +21 -0
- package/lib/bridges.js +2 -2
- package/lib/client/credentials/changes.js +74 -0
- package/lib/client/credentials/create-datasource.js +171 -0
- 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 +319 -0
- package/lib/client/credentials/model.js +766 -0
- package/lib/client/credentials/report-publishing.js +96 -0
- package/lib/client/credentials/styles.js +21 -0
- package/lib/client/i18n/context.js +22 -0
- package/lib/client/i18n/copy.js +57 -0
- package/lib/client/i18n/credentials.js +475 -0
- package/lib/client/i18n/host.js +27 -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/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/workspace-header-action.js +24 -0
- package/lib/client/workspace-shortcuts.js +26 -0
- package/lib/client.js +1250 -730
- package/lib/compatibility.js +2 -2
- package/lib/datasource/authoring.js +109 -0
- package/lib/datasource/bridge-programs.js +42 -27
- package/lib/datasource/bridge.js +144 -9
- 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 +4 -2
- package/lib/datasource/python-options.js +21 -0
- package/lib/datasource/python.js +206 -0
- package/lib/datasource/resolver-program.js +112 -0
- package/lib/datasource/rpc.js +150 -0
- package/lib/datasource/service.js +962 -0
- package/lib/datasource/shell-env.js +2 -333
- package/lib/datasource/test.js +25 -51
- package/lib/disclosure/activation.js +19 -7
- package/lib/disclosure/execution-guidance.js +52 -0
- package/lib/disclosure/help.js +4 -1
- package/lib/environment/binding.js +54 -9
- package/lib/environment/runtime.js +120 -108
- package/lib/environment/subprocess.js +6 -1
- package/lib/environment/summary.js +3 -3
- package/lib/environment/workspace.js +23 -1
- package/lib/index.js +1 -1
- package/lib/lifecycle.js +33 -0
- package/lib/plugin-agents.js +154 -0
- package/lib/plugin.js +172 -133
- package/lib/presentation/asset.js +34 -0
- package/lib/presentation/assets/portable.js +606 -0
- package/lib/presentation/assets/static.js +1216 -0
- package/lib/presentation/build/index.js +72 -0
- package/lib/presentation/commit.js +163 -0
- package/lib/presentation/contracts/asset-path.js +9 -0
- package/lib/presentation/contracts/catalog.js +62 -0
- package/lib/presentation/contracts/charts.js +453 -0
- package/lib/presentation/contracts/editing.js +58 -0
- package/lib/presentation/contracts/index.js +663 -0
- package/lib/presentation/contracts/interaction.js +62 -0
- package/lib/presentation/contracts/types.js +15 -0
- package/lib/presentation/contracts/values.js +28 -0
- package/lib/presentation/delivery.js +71 -0
- package/lib/presentation/export-html.js +214 -0
- package/lib/presentation/files.js +82 -0
- package/lib/presentation/index.js +6 -0
- package/lib/presentation/lint.js +46 -0
- package/lib/presentation/projection/files.js +85 -0
- package/lib/presentation/projection/index.js +196 -0
- package/lib/presentation/projection/program.js +137 -0
- package/lib/presentation/projection/sql-code.js +79 -0
- package/lib/presentation/receipt.js +34 -0
- package/lib/presentation/reports.js +219 -0
- package/lib/presentation/rpc.js +186 -0
- package/lib/presentation/tool.js +118 -0
- package/lib/python-execution-contracts.js +1 -0
- package/lib/python-execution.js +232 -0
- 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 +97 -0
- package/lib/semantic-browser/definition.js +64 -0
- package/lib/semantic-browser/program.js +129 -0
- package/lib/semantic-browser/service.js +106 -0
- package/lib/semantic-reference/bridge.js +127 -0
- package/lib/semantic-reference/contracts.js +127 -0
- package/lib/semantic-reference/environment.js +56 -0
- package/lib/semantic-reference/index.js +3 -0
- package/lib/semantic-reference/labels.js +16 -0
- package/lib/semantic-reference/rpc.js +143 -0
- package/lib/semantic-reference/search.js +95 -0
- package/lib/semantic-reference/usage.js +152 -0
- package/lib/tool-lifecycle.js +22 -0
- package/lib/types/bridges.d.ts +2 -2
- package/lib/types/client/credentials/changes.d.ts +9 -0
- package/lib/types/client/credentials/create-datasource.d.ts +7 -0
- 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 -0
- package/lib/types/client/credentials/model.d.ts +79 -0
- package/lib/types/client/credentials/report-publishing.d.ts +5 -0
- package/lib/types/client/credentials/styles.d.ts +1 -0
- package/lib/types/client/i18n/context.d.ts +17 -0
- package/lib/types/client/i18n/copy.d.ts +806 -0
- package/lib/types/client/i18n/credentials.d.ts +475 -0
- package/lib/types/client/i18n/host.d.ts +16 -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/input-context.d.ts +19 -0
- package/lib/types/client/presentation/ask-dsh.d.ts +5 -0
- 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-explorer.d.ts +12 -0
- package/lib/types/client/presentation/chart-geometry.d.ts +30 -0
- package/lib/types/client/presentation/chart-renderer.d.ts +10 -0
- package/lib/types/client/presentation/chart-specials.d.ts +10 -0
- package/lib/types/client/presentation/chart-styles.d.ts +1 -0
- package/lib/types/client/presentation/chart-tooltip.d.ts +13 -0
- package/lib/types/client/presentation/chart-view.d.ts +23 -0
- package/lib/types/client/presentation/context-reference.d.ts +12 -0
- package/lib/types/client/presentation/copy-context.d.ts +5 -0
- package/lib/types/client/presentation/delivery-model.d.ts +75 -0
- package/lib/types/client/presentation/delivery.d.ts +78 -0
- package/lib/types/client/presentation/download.d.ts +2 -0
- package/lib/types/client/presentation/editor-controls.d.ts +12 -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/global-filters.d.ts +6 -0
- package/lib/types/client/presentation/host-entry.d.ts +18 -0
- package/lib/types/client/presentation/icons.d.ts +4 -0
- package/lib/types/client/presentation/markdown.d.ts +5 -0
- package/lib/types/client/presentation/model.d.ts +61 -0
- package/lib/types/client/presentation/reader.d.ts +24 -0
- package/lib/types/client/presentation/reference-source.d.ts +9 -0
- package/lib/types/client/presentation/source-code-model.d.ts +17 -0
- package/lib/types/client/presentation/source-code.d.ts +6 -0
- package/lib/types/client/presentation/source-dialog.d.ts +12 -0
- package/lib/types/client/presentation/source-facts.d.ts +20 -0
- package/lib/types/client/presentation/source-format.d.ts +5 -0
- package/lib/types/client/presentation/source-highlight.d.ts +7 -0
- package/lib/types/client/presentation/sources.d.ts +13 -0
- package/lib/types/client/presentation/styles.d.ts +1 -0
- package/lib/types/client/presentation/table.d.ts +16 -0
- package/lib/types/client/presentation/use-element-width.d.ts +5 -0
- 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/workspace-header-action.d.ts +7 -0
- package/lib/types/client/workspace-shortcuts.d.ts +15 -0
- package/lib/types/client.d.ts +9 -124
- package/lib/types/compatibility.d.ts +3 -3
- package/lib/types/datasource/authoring.d.ts +35 -0
- package/lib/types/datasource/bridge.d.ts +43 -4
- 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 +8 -5
- package/lib/types/datasource/python-options.d.ts +9 -0
- package/lib/types/datasource/python.d.ts +19 -0
- package/lib/types/datasource/resolver-program.d.ts +6 -0
- package/lib/types/datasource/rpc.d.ts +5 -0
- package/lib/types/datasource/service.d.ts +155 -0
- package/lib/types/datasource/shell-env.d.ts +0 -29
- package/lib/types/datasource/test.d.ts +6 -29
- 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/binding.d.ts +5 -1
- package/lib/types/environment/errors.d.ts +1 -1
- package/lib/types/environment/runtime.d.ts +5 -5
- package/lib/types/environment/types.d.ts +15 -3
- package/lib/types/environment/workspace.d.ts +1 -0
- package/lib/types/index.d.ts +1 -1
- package/lib/types/lifecycle.d.ts +8 -0
- package/lib/types/plugin-agents.d.ts +27 -0
- package/lib/types/plugin.d.ts +14 -24
- package/lib/types/presentation/asset.d.ts +6 -0
- package/lib/types/presentation/build/index.d.ts +16 -0
- package/lib/types/presentation/commit.d.ts +4 -0
- 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 +11 -0
- package/lib/types/presentation/contracts/editing.d.ts +8 -0
- package/lib/types/presentation/contracts/index.d.ts +11 -0
- package/lib/types/presentation/contracts/interaction.d.ts +8 -0
- package/lib/types/presentation/contracts/types.d.ts +255 -0
- package/lib/types/presentation/contracts/values.d.ts +9 -0
- package/lib/types/presentation/delivery.d.ts +3 -0
- package/lib/types/presentation/export-html.d.ts +13 -0
- package/lib/types/presentation/files.d.ts +7 -0
- package/lib/types/presentation/index.d.ts +6 -0
- package/lib/types/presentation/lint.d.ts +13 -0
- package/lib/types/presentation/projection/files.d.ts +2 -0
- package/lib/types/presentation/projection/index.d.ts +20 -0
- package/lib/types/presentation/projection/program.d.ts +2 -0
- package/lib/types/presentation/projection/sql-code.d.ts +2 -0
- package/lib/types/presentation/receipt.d.ts +14 -0
- package/lib/types/presentation/reports.d.ts +10 -0
- package/lib/types/presentation/rpc.d.ts +25 -0
- package/lib/types/presentation/tool.d.ts +12 -0
- package/lib/types/python-execution-contracts.d.ts +10 -0
- package/lib/types/python-execution.d.ts +10 -0
- 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 +47 -0
- package/lib/types/semantic-browser/definition.d.ts +20 -0
- package/lib/types/semantic-browser/program.d.ts +2 -0
- package/lib/types/semantic-browser/service.d.ts +26 -0
- package/lib/types/semantic-reference/bridge.d.ts +12 -0
- package/lib/types/semantic-reference/contracts.d.ts +54 -0
- package/lib/types/semantic-reference/environment.d.ts +22 -0
- package/lib/types/semantic-reference/index.d.ts +3 -0
- package/lib/types/semantic-reference/labels.d.ts +16 -0
- package/lib/types/semantic-reference/rpc.d.ts +15 -0
- package/lib/types/semantic-reference/search.d.ts +10 -0
- package/lib/types/semantic-reference/usage.d.ts +43 -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 +99 -53
- 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 +32 -0
- package/skills/dsh-data-analysis-files/references/examples.md +93 -0
- package/skills/dsh-data-analysis-presentation/SKILL.md +28 -0
- package/skills/dsh-data-analysis-presentation/references/chart-examples.md +18 -0
- package/skills/dsh-data-analysis-presentation/references/charts.md +55 -0
- package/skills/dsh-data-analysis-presentation/references/examples/artifact.draft.json +36 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-bins.dataset.json +33 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-boxes.dataset.json +45 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-composition.dataset.json +33 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-funnel.dataset.json +27 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-precision.dataset.json +21 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-series.dataset.json +58 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-shares.dataset.json +33 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-waterfall.dataset.json +44 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts.draft.json +393 -0
- package/skills/dsh-data-analysis-presentation/references/examples/comparison-selected.dataset.json +24 -0
- package/skills/dsh-data-analysis-presentation/references/examples/comparison-summary.dataset.json +22 -0
- package/skills/dsh-data-analysis-presentation/references/examples/comparison.dataset.json +18 -0
- package/skills/dsh-data-analysis-presentation/references/examples/comparison.draft.json +80 -0
- package/skills/dsh-data-analysis-presentation/references/examples/computed-sources.draft.json +42 -0
- package/skills/dsh-data-analysis-presentation/references/examples/computed.dataset.json +12 -0
- package/skills/dsh-data-analysis-presentation/references/examples/computed.draft.json +58 -0
- package/skills/dsh-data-analysis-presentation/references/examples/interaction-detail.dataset.json +30 -0
- package/skills/dsh-data-analysis-presentation/references/examples/interaction-summary.dataset.json +22 -0
- package/skills/dsh-data-analysis-presentation/references/examples/interaction.draft.json +279 -0
- package/skills/dsh-data-analysis-presentation/references/examples/source-only.draft.json +27 -0
- package/skills/dsh-data-analysis-presentation/references/examples/write-charts.py +73 -0
- package/skills/dsh-data-analysis-presentation/references/examples/write-comparison.py +42 -0
- package/skills/dsh-data-analysis-presentation/references/examples/write-computed.py +21 -0
- package/skills/dsh-data-analysis-presentation/references/examples/write-interaction.py +33 -0
- package/skills/dsh-data-analysis-presentation/references/examples.md +44 -0
- package/skills/dsh-data-analysis-presentation/references/export-html.md +7 -0
- package/skills/dsh-data-analysis-presentation/references/interaction.md +56 -0
- package/skills/dsh-data-analysis-presentation/references/narrative.md +37 -0
- package/skills/dsh-data-analysis-presentation/references/publishing.md +3 -0
- package/skills/dsh-data-analysis-presentation/references/schema.md +162 -0
- package/lib/datasource/access.js +0 -60
- package/lib/datasource/credentials.js +0 -24
- package/lib/evidence/bridge-program.js +0 -153
- package/lib/evidence/bridge.js +0 -166
- package/lib/evidence/index.js +0 -2
- package/lib/evidence/sources.js +0 -263
- package/lib/types/datasource/access.d.ts +0 -27
- package/lib/types/evidence/bridge.d.ts +0 -40
- package/lib/types/evidence/index.d.ts +0 -4
- package/lib/types/evidence/sources.d.ts +0 -57
- package/python/report-kit/dist/dsh_data_analysis_report_kit-3.0.0-py3-none-any.whl +0 -0
- package/skills/dsh-data-analysis-report/SKILL.md +0 -57
- package/skills/dsh-data-analysis-report/assets/marivo-artifact.js +0 -113
- package/skills/dsh-data-analysis-report/assets/marivo-session-dag.js +0 -1201
- package/skills/dsh-data-analysis-report/assets/report-data.js +0 -449
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { isCredentialRefName } from '@deepseek-ai/dsh-credentials';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
const reference = z.string().refine(isCredentialRefName);
|
|
4
|
+
const url = z
|
|
5
|
+
.string()
|
|
6
|
+
.url()
|
|
7
|
+
.refine((value) => {
|
|
8
|
+
let parsed;
|
|
9
|
+
try {
|
|
10
|
+
parsed = new URL(value);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
return (['http:', 'https:'].includes(parsed.protocol) &&
|
|
16
|
+
!parsed.username &&
|
|
17
|
+
!parsed.password &&
|
|
18
|
+
!parsed.search &&
|
|
19
|
+
!parsed.hash);
|
|
20
|
+
});
|
|
21
|
+
export const publishingConfigSchema = z
|
|
22
|
+
.object({
|
|
23
|
+
enabled: z.boolean().default(false),
|
|
24
|
+
storage: z
|
|
25
|
+
.object({
|
|
26
|
+
name: z.string().min(1).max(100),
|
|
27
|
+
protocol: z.literal('s3').default('s3'),
|
|
28
|
+
endpoint: url,
|
|
29
|
+
region: z.string().min(1),
|
|
30
|
+
bucket: z
|
|
31
|
+
.string()
|
|
32
|
+
.min(1)
|
|
33
|
+
.max(255)
|
|
34
|
+
.regex(/^[a-zA-Z0-9][a-zA-Z0-9.-]*$/),
|
|
35
|
+
forcePathStyle: z.boolean().default(false),
|
|
36
|
+
accessKeyIdRef: reference,
|
|
37
|
+
secretAccessKeyRef: reference,
|
|
38
|
+
sessionTokenRef: reference.optional(),
|
|
39
|
+
})
|
|
40
|
+
.strict(),
|
|
41
|
+
pathPrefix: z
|
|
42
|
+
.string()
|
|
43
|
+
.default('reports')
|
|
44
|
+
.refine((value) => value === '' ||
|
|
45
|
+
value.split('/').every((part) => /^[a-zA-Z0-9_-][a-zA-Z0-9._-]*$/.test(part))),
|
|
46
|
+
publicBaseUrl: url,
|
|
47
|
+
})
|
|
48
|
+
.strict()
|
|
49
|
+
.superRefine((config, ctx) => {
|
|
50
|
+
const refs = [
|
|
51
|
+
config.storage.accessKeyIdRef,
|
|
52
|
+
config.storage.secretAccessKeyRef,
|
|
53
|
+
config.storage.sessionTokenRef,
|
|
54
|
+
].filter(Boolean);
|
|
55
|
+
if (new Set(refs).size !== refs.length)
|
|
56
|
+
ctx.addIssue({ code: 'custom', message: 'distinct-credential-references-required' });
|
|
57
|
+
});
|
|
58
|
+
export function resolvePublishingConfig(value) {
|
|
59
|
+
if (value === undefined)
|
|
60
|
+
return undefined;
|
|
61
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
62
|
+
throw new Error('report-publishing-config-invalid');
|
|
63
|
+
if (value.enabled === false || value.enabled === undefined)
|
|
64
|
+
return undefined;
|
|
65
|
+
const result = publishingConfigSchema.safeParse(value);
|
|
66
|
+
if (!result.success)
|
|
67
|
+
throw new Error('report-publishing-config-invalid');
|
|
68
|
+
return result.data;
|
|
69
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
|
|
3
|
+
import { credentialRef } from '@deepseek-ai/dsh-credentials';
|
|
4
|
+
import { PRESENTATION_BUDGETS } from "../presentation/contracts/index.js";
|
|
5
|
+
export function publishingPathName(value, fallback) {
|
|
6
|
+
const name = Array.from(value.normalize('NFKC').replace(/[^\p{L}\p{N}_-]+/gu, '-'))
|
|
7
|
+
.slice(0, 48)
|
|
8
|
+
.join('')
|
|
9
|
+
.replace(/^-+|-+$/g, '');
|
|
10
|
+
return name || fallback;
|
|
11
|
+
}
|
|
12
|
+
export async function uploadReport(input) {
|
|
13
|
+
const { config, credentials, key, bytes, signal } = input;
|
|
14
|
+
const client = new S3Client({
|
|
15
|
+
endpoint: config.storage.endpoint,
|
|
16
|
+
region: config.storage.region,
|
|
17
|
+
forcePathStyle: config.storage.forcePathStyle,
|
|
18
|
+
credentials,
|
|
19
|
+
maxAttempts: 3,
|
|
20
|
+
requestChecksumCalculation: 'WHEN_REQUIRED',
|
|
21
|
+
});
|
|
22
|
+
try {
|
|
23
|
+
await client.send(new PutObjectCommand({
|
|
24
|
+
Bucket: config.storage.bucket,
|
|
25
|
+
Key: key,
|
|
26
|
+
Body: bytes,
|
|
27
|
+
ContentType: 'text/html; charset=utf-8',
|
|
28
|
+
ContentDisposition: 'inline',
|
|
29
|
+
CacheControl: 'public, max-age=31536000, immutable',
|
|
30
|
+
}), { abortSignal: signal });
|
|
31
|
+
}
|
|
32
|
+
finally {
|
|
33
|
+
client.destroy();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export class ReportPublishingService {
|
|
37
|
+
configId = randomUUID();
|
|
38
|
+
config;
|
|
39
|
+
store;
|
|
40
|
+
files;
|
|
41
|
+
upload;
|
|
42
|
+
workspaceName;
|
|
43
|
+
constructor(config, store, files, upload = uploadReport, workspaceName = () => 'workspace') {
|
|
44
|
+
this.workspaceName = workspaceName;
|
|
45
|
+
this.config = config
|
|
46
|
+
? Object.freeze({ ...config, storage: Object.freeze({ ...config.storage }) })
|
|
47
|
+
: undefined;
|
|
48
|
+
this.store = store;
|
|
49
|
+
this.files = files;
|
|
50
|
+
this.upload = upload;
|
|
51
|
+
}
|
|
52
|
+
assertConfig(configId) {
|
|
53
|
+
if (configId !== this.configId)
|
|
54
|
+
throw new Error('report-publishing-config-changed');
|
|
55
|
+
}
|
|
56
|
+
references() {
|
|
57
|
+
const storage = this.config?.storage;
|
|
58
|
+
if (!storage)
|
|
59
|
+
return [];
|
|
60
|
+
return [
|
|
61
|
+
{ field: 'accessKeyId', reference: storage.accessKeyIdRef, required: true },
|
|
62
|
+
{ field: 'secretAccessKey', reference: storage.secretAccessKeyRef, required: true },
|
|
63
|
+
...(storage.sessionTokenRef
|
|
64
|
+
? [{ field: 'sessionToken', reference: storage.sessionTokenRef, required: true }]
|
|
65
|
+
: []),
|
|
66
|
+
];
|
|
67
|
+
}
|
|
68
|
+
async describe() {
|
|
69
|
+
if (!this.config)
|
|
70
|
+
return { enabled: false, fields: [] };
|
|
71
|
+
const fields = await Promise.all(this.references().map(async (field) => {
|
|
72
|
+
const info = await this.store.describe(credentialRef(field.reference));
|
|
73
|
+
return {
|
|
74
|
+
...field,
|
|
75
|
+
configured: info.configured,
|
|
76
|
+
writable: info.writable,
|
|
77
|
+
...(info.source ? { source: info.source } : {}),
|
|
78
|
+
};
|
|
79
|
+
}));
|
|
80
|
+
return {
|
|
81
|
+
enabled: true,
|
|
82
|
+
configId: this.configId,
|
|
83
|
+
name: this.config.storage.name,
|
|
84
|
+
bucket: this.config.storage.bucket,
|
|
85
|
+
fields,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
async change(field, value, signal) {
|
|
89
|
+
const item = this.references().find((item) => item.field === field);
|
|
90
|
+
if (!item)
|
|
91
|
+
throw new Error('report-publishing-disabled-or-field-unavailable');
|
|
92
|
+
signal.throwIfAborted();
|
|
93
|
+
if (value === undefined)
|
|
94
|
+
await this.store.unset(credentialRef(item.reference));
|
|
95
|
+
else
|
|
96
|
+
await this.store.set(credentialRef(item.reference), value);
|
|
97
|
+
}
|
|
98
|
+
async publish(scope, reportId, buildId, signal, viewHtml) {
|
|
99
|
+
const config = this.config;
|
|
100
|
+
if (!config)
|
|
101
|
+
throw new Error('report-publishing-disabled');
|
|
102
|
+
const history = await this.files.catalog('reports/history', { ...scope, reportId }, signal);
|
|
103
|
+
if (!('versions' in history))
|
|
104
|
+
throw new Error('report-publishing-build-unavailable');
|
|
105
|
+
const receipt = history.versions.find((entry) => entry.receipt.buildId === buildId)?.receipt;
|
|
106
|
+
if (!receipt)
|
|
107
|
+
throw new Error('report-publishing-build-unavailable');
|
|
108
|
+
const credentials = {};
|
|
109
|
+
for (const field of this.references()) {
|
|
110
|
+
signal.throwIfAborted();
|
|
111
|
+
const hit = await this.store.resolve(credentialRef(field.reference));
|
|
112
|
+
if (!hit?.value)
|
|
113
|
+
throw new Error('report-publishing-credentials-missing');
|
|
114
|
+
credentials[field.field] = hit.value;
|
|
115
|
+
}
|
|
116
|
+
const asset = await this.files.read({ ...scope, receipt, asset: viewHtml === undefined ? 'index.html' : 'presentation.json' }, signal);
|
|
117
|
+
let bytes;
|
|
118
|
+
if (viewHtml === undefined)
|
|
119
|
+
bytes = Buffer.from(asset.bodyBase64, 'base64');
|
|
120
|
+
else {
|
|
121
|
+
if (Buffer.byteLength(viewHtml) > PRESENTATION_BUDGETS.htmlBytes ||
|
|
122
|
+
!viewHtml.startsWith('<!doctype html><html lang="zh-CN"><head>'))
|
|
123
|
+
throw new Error('report-publishing-view-invalid');
|
|
124
|
+
// Browser-authored snapshot: force a restrictive policy before any client-supplied markup.
|
|
125
|
+
bytes = Buffer.from(viewHtml.replace('<head>', `<head><meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; base-uri 'none'; form-action 'none'">`));
|
|
126
|
+
if (bytes.length > PRESENTATION_BUDGETS.htmlBytes)
|
|
127
|
+
throw new Error('report-publishing-view-invalid');
|
|
128
|
+
}
|
|
129
|
+
const sha256 = createHash('sha256').update(bytes).digest('hex');
|
|
130
|
+
const identity = createHash('sha256')
|
|
131
|
+
.update(JSON.stringify([
|
|
132
|
+
receipt.workspaceId,
|
|
133
|
+
reportId,
|
|
134
|
+
buildId,
|
|
135
|
+
viewHtml === undefined ? 'report' : 'view',
|
|
136
|
+
sha256,
|
|
137
|
+
]))
|
|
138
|
+
.digest('hex')
|
|
139
|
+
.slice(0, 32);
|
|
140
|
+
const key = [
|
|
141
|
+
config.pathPrefix,
|
|
142
|
+
publishingPathName(this.workspaceName(receipt.workspaceId), 'workspace'),
|
|
143
|
+
publishingPathName(receipt.title, 'report'),
|
|
144
|
+
identity,
|
|
145
|
+
'index.html',
|
|
146
|
+
]
|
|
147
|
+
.filter(Boolean)
|
|
148
|
+
.join('/');
|
|
149
|
+
signal.throwIfAborted();
|
|
150
|
+
try {
|
|
151
|
+
await this.upload({
|
|
152
|
+
config,
|
|
153
|
+
credentials: credentials,
|
|
154
|
+
key,
|
|
155
|
+
bytes,
|
|
156
|
+
signal,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
throw new Error('report-publishing-upload-unconfirmed');
|
|
161
|
+
}
|
|
162
|
+
finally {
|
|
163
|
+
for (const key of Object.keys(credentials))
|
|
164
|
+
delete credentials[key];
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
storage: config.storage.name,
|
|
168
|
+
bucket: config.storage.bucket,
|
|
169
|
+
key,
|
|
170
|
+
url: `${config.publicBaseUrl.replace(/\/+$/, '')}/${key.split('/').map(encodeURIComponent).join('/')}`,
|
|
171
|
+
workspaceId: receipt.workspaceId,
|
|
172
|
+
reportId,
|
|
173
|
+
buildId,
|
|
174
|
+
sha256,
|
|
175
|
+
bytes: bytes.length,
|
|
176
|
+
kind: viewHtml === undefined ? 'report' : 'view',
|
|
177
|
+
publishedAt: new Date().toISOString(),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
package/lib/rpc.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/** Dedicated plugin RPC envelopes over Harness-owned authenticated exact Fetch routes. */
|
|
2
|
+
import { clientRequestSchema, } from '@deepseek-ai/dsh-client-connection';
|
|
3
|
+
import { finishCleanup, PendingTasks } from "./lifecycle.js";
|
|
4
|
+
export function registerPluginRpc(connection, channel, endpoints, handler) {
|
|
5
|
+
const lifetime = new AbortController();
|
|
6
|
+
const tasks = new PendingTasks();
|
|
7
|
+
let closing;
|
|
8
|
+
const disposers = [];
|
|
9
|
+
const close = () => {
|
|
10
|
+
if (closing)
|
|
11
|
+
return closing;
|
|
12
|
+
lifetime.abort();
|
|
13
|
+
closing = finishCleanup([...disposers.reverse(), () => tasks.drain()]);
|
|
14
|
+
return closing;
|
|
15
|
+
};
|
|
16
|
+
try {
|
|
17
|
+
for (const endpoint of endpoints) {
|
|
18
|
+
const method = `${channel.slice(1)}/${endpoint}`;
|
|
19
|
+
disposers.push(connection.fetch.register({
|
|
20
|
+
path: `/api/${method}`,
|
|
21
|
+
methods: ['POST'],
|
|
22
|
+
requestBody: 'buffered',
|
|
23
|
+
fetch(request) {
|
|
24
|
+
if (lifetime.signal.aborted)
|
|
25
|
+
return Promise.resolve(new Response('Plugin disposed', { status: 503 }));
|
|
26
|
+
return tasks.track((async () => {
|
|
27
|
+
if (request.headers.get('content-type')?.split(';')[0]?.trim().toLowerCase() !==
|
|
28
|
+
'application/json')
|
|
29
|
+
return new Response('Expected application/json', { status: 415 });
|
|
30
|
+
const body = await request.json().catch(() => undefined);
|
|
31
|
+
const parsed = clientRequestSchema.safeParse(body);
|
|
32
|
+
if (!parsed.success || parsed.data.method !== method)
|
|
33
|
+
return new Response('Invalid plugin request', { status: 400 });
|
|
34
|
+
// A request admitted before withdrawal may still be decoding its body.
|
|
35
|
+
if (lifetime.signal.aborted)
|
|
36
|
+
return new Response('Plugin disposed', { status: 503 });
|
|
37
|
+
const result = await handler(endpoint, parsed.data.payload, AbortSignal.any([request.signal, lifetime.signal]));
|
|
38
|
+
return Response.json({ type: 'server-response', rpcId: parsed.data.rpcId, result });
|
|
39
|
+
})());
|
|
40
|
+
},
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
void close();
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
return close;
|
|
49
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { boundedText, closed, parseRef, refKey, } from "../semantic-reference/contracts.js";
|
|
2
|
+
import { parseComputation } from "./definition.js";
|
|
3
|
+
export const BROWSER_ENDPOINT = 'semantic-browser/catalog';
|
|
4
|
+
export const CATALOG_MAX_BYTES = 32 * 1024 * 1024;
|
|
5
|
+
export const CATALOG_ERROR_SCHEMA = 'marivo.semantic_catalog_error/v1';
|
|
6
|
+
const CATALOG_ERROR_MAX_CHARS = 16 * 1024;
|
|
7
|
+
function array(value, max) {
|
|
8
|
+
if (!Array.isArray(value) || value.length > max)
|
|
9
|
+
throw new Error('invalid-catalog');
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
function text(value) {
|
|
13
|
+
return boundedText(value, CATALOG_MAX_BYTES, true);
|
|
14
|
+
}
|
|
15
|
+
function nullable(value) {
|
|
16
|
+
return value === null ? null : text(value);
|
|
17
|
+
}
|
|
18
|
+
export function parseCatalogProjection(value) {
|
|
19
|
+
const r = closed(value, ['fingerprint', 'kinds', 'objects']);
|
|
20
|
+
const kinds = array(r.kinds, 128).map((item) => boundedText(item, 128));
|
|
21
|
+
const objects = array(r.objects, 100_000).map((item) => {
|
|
22
|
+
const row = closed(item, [
|
|
23
|
+
'ref',
|
|
24
|
+
'computation',
|
|
25
|
+
'name',
|
|
26
|
+
'domain',
|
|
27
|
+
'definition',
|
|
28
|
+
'guardrails',
|
|
29
|
+
'source',
|
|
30
|
+
'fields',
|
|
31
|
+
'relations',
|
|
32
|
+
]);
|
|
33
|
+
const ref = parseRef(row.ref);
|
|
34
|
+
if (!kinds.includes(ref.kind))
|
|
35
|
+
throw new Error('invalid-catalog');
|
|
36
|
+
const source = closed(row.source, ['file', 'line', 'symbol']);
|
|
37
|
+
if (!Number.isSafeInteger(source.line) || source.line < 0)
|
|
38
|
+
throw new Error('invalid-catalog');
|
|
39
|
+
return {
|
|
40
|
+
ref,
|
|
41
|
+
computation: parseComputation(row.computation, ref, boundedText(r.fingerprint, 256)),
|
|
42
|
+
name: text(row.name),
|
|
43
|
+
domain: nullable(row.domain),
|
|
44
|
+
definition: nullable(row.definition),
|
|
45
|
+
guardrails: array(row.guardrails, 100_000).map(text),
|
|
46
|
+
source: { file: text(source.file), line: source.line, symbol: text(source.symbol) },
|
|
47
|
+
fields: array(row.fields, 128).map((value) => {
|
|
48
|
+
const field = closed(value, ['name', 'value']);
|
|
49
|
+
return { name: boundedText(field.name, 128), value: text(field.value) };
|
|
50
|
+
}),
|
|
51
|
+
relations: array(row.relations, 100_000).map((value) => {
|
|
52
|
+
const relation = closed(value, ['field', 'ref']);
|
|
53
|
+
return { field: boundedText(relation.field, 128), ref: parseRef(relation.ref) };
|
|
54
|
+
}),
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
if (new Set(kinds).size !== kinds.length ||
|
|
58
|
+
new Set(objects.map((x) => refKey(x.ref))).size !== objects.length)
|
|
59
|
+
throw new Error('invalid-catalog');
|
|
60
|
+
return { fingerprint: boundedText(r.fingerprint, 256), kinds, objects };
|
|
61
|
+
}
|
|
62
|
+
export function parseCatalogFailure(value) {
|
|
63
|
+
try {
|
|
64
|
+
const r = closed(value, ['schema', 'kind', 'message']);
|
|
65
|
+
if (r.schema !== CATALOG_ERROR_SCHEMA || r.kind !== 'semantic')
|
|
66
|
+
return undefined;
|
|
67
|
+
return {
|
|
68
|
+
schema: CATALOG_ERROR_SCHEMA,
|
|
69
|
+
kind: 'semantic',
|
|
70
|
+
message: boundedText(r.message, CATALOG_ERROR_MAX_CHARS),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
export function parseCatalogSnapshot(value) {
|
|
78
|
+
const r = closed(value, [
|
|
79
|
+
'workspaceId',
|
|
80
|
+
'projectRoot',
|
|
81
|
+
'environmentFingerprint',
|
|
82
|
+
'loadedAt',
|
|
83
|
+
'fingerprint',
|
|
84
|
+
'kinds',
|
|
85
|
+
'objects',
|
|
86
|
+
]);
|
|
87
|
+
const loadedAt = boundedText(r.loadedAt, 64);
|
|
88
|
+
if (!Number.isFinite(Date.parse(loadedAt)))
|
|
89
|
+
throw new Error('invalid-catalog');
|
|
90
|
+
return {
|
|
91
|
+
...parseCatalogProjection({ fingerprint: r.fingerprint, kinds: r.kinds, objects: r.objects }),
|
|
92
|
+
workspaceId: boundedText(r.workspaceId, 256),
|
|
93
|
+
projectRoot: boundedText(r.projectRoot, 8192),
|
|
94
|
+
environmentFingerprint: boundedText(r.environmentFingerprint, 256),
|
|
95
|
+
loadedAt,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { closed, parseRef, refKey } from "../semantic-reference/contracts.js";
|
|
2
|
+
// Transport allowlist for the public definition payload, not a semantic validator.
|
|
3
|
+
const keys = new Set('kind operation q target target_kind filter dimension operator values value weight numerator denominator terms sign metric base over selection ref resolution anchor count unit grain calendar level status expression reason entity name value_type operand data_type left right condition when_true when_false declared override effective source fold schema path display language form text bindings alias redacted_literals'.split(' '));
|
|
4
|
+
function value(input, depth = 0, budget = { left: 10000 }) {
|
|
5
|
+
if (--budget.left < 0 || depth > 64)
|
|
6
|
+
throw new Error('definition-too-large');
|
|
7
|
+
if (input === null || typeof input === 'boolean')
|
|
8
|
+
return input;
|
|
9
|
+
if (typeof input === 'string' && input.length <= 1024 * 1024)
|
|
10
|
+
return input;
|
|
11
|
+
if (typeof input === 'number' && Number.isFinite(input))
|
|
12
|
+
return input;
|
|
13
|
+
if (Array.isArray(input))
|
|
14
|
+
return input.map((x) => value(x, depth + 1, budget));
|
|
15
|
+
if (typeof input !== 'object' || input === null)
|
|
16
|
+
throw new Error('invalid-definition');
|
|
17
|
+
const result = {};
|
|
18
|
+
for (const [key, item] of Object.entries(input)) {
|
|
19
|
+
if (!keys.has(key))
|
|
20
|
+
throw new Error('invalid-definition-field');
|
|
21
|
+
result[key] = value(item, depth + 1, budget);
|
|
22
|
+
}
|
|
23
|
+
if (result.schema === 'marivo.semantic_ref/v1')
|
|
24
|
+
parseRef(result);
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
export function parseComputation(input, ref, fingerprint) {
|
|
28
|
+
if (input === null)
|
|
29
|
+
return null;
|
|
30
|
+
const r = closed(input, [
|
|
31
|
+
'schema',
|
|
32
|
+
'ref',
|
|
33
|
+
'catalog_definition_fingerprint',
|
|
34
|
+
'node',
|
|
35
|
+
'temporal',
|
|
36
|
+
'source_location',
|
|
37
|
+
]);
|
|
38
|
+
if (r.schema !== 'marivo.semantic_definition/v1' ||
|
|
39
|
+
refKey(parseRef(r.ref)) !== refKey(ref) ||
|
|
40
|
+
r.catalog_definition_fingerprint !== fingerprint)
|
|
41
|
+
throw new Error('definition-identity-mismatch');
|
|
42
|
+
const source = closed(r.source_location, ['file', 'line']);
|
|
43
|
+
if (typeof source.file !== 'string' ||
|
|
44
|
+
!Number.isSafeInteger(source.line) ||
|
|
45
|
+
source.line < 0)
|
|
46
|
+
throw new Error('invalid-definition-source');
|
|
47
|
+
const node = value(r.node), temporal = value(r.temporal);
|
|
48
|
+
if (!node ||
|
|
49
|
+
typeof node !== 'object' ||
|
|
50
|
+
Array.isArray(node) ||
|
|
51
|
+
typeof node.kind !== 'string' ||
|
|
52
|
+
!temporal ||
|
|
53
|
+
typeof temporal !== 'object' ||
|
|
54
|
+
Array.isArray(temporal))
|
|
55
|
+
throw new Error('invalid-definition');
|
|
56
|
+
return {
|
|
57
|
+
schema: r.schema,
|
|
58
|
+
ref,
|
|
59
|
+
catalog_definition_fingerprint: fingerprint,
|
|
60
|
+
node,
|
|
61
|
+
temporal,
|
|
62
|
+
source_location: { file: source.file, line: source.line },
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/** Explicit public Details projection. No object dump, source-code read or data operation. */
|
|
2
|
+
export const BROWSER_CATALOG_PROGRAM = String.raw `
|
|
3
|
+
import contextlib, json, sys
|
|
4
|
+
from datetime import date, datetime
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from collections.abc import Mapping
|
|
7
|
+
|
|
8
|
+
class DiscardOutput:
|
|
9
|
+
def write(self, text):
|
|
10
|
+
return len(text)
|
|
11
|
+
def flush(self):
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
with contextlib.redirect_stdout(DiscardOutput()), contextlib.redirect_stderr(DiscardOutput()):
|
|
15
|
+
import marivo.semantic as ms
|
|
16
|
+
from marivo.refs import Ref, RefPayloadV1, SemanticKind
|
|
17
|
+
from marivo.semantic.errors import SemanticError, SemanticLoadFailed
|
|
18
|
+
|
|
19
|
+
FIELDS = {
|
|
20
|
+
"domain": "owner default",
|
|
21
|
+
"datasource": "backend_type",
|
|
22
|
+
"entity": "datasource primary_key",
|
|
23
|
+
"dimension": "entity",
|
|
24
|
+
"measure": "entity unit additivity",
|
|
25
|
+
"time_dimension": "entity parse_kind data_type granularity format timezone is_default sample_interval",
|
|
26
|
+
"metric": "metric_type entities root_entity aggregation aggregation_target aggregation_target_kind measure unit additivity fold status_time_dimension fanout_policy filter effective_entities candidate_dimensions candidate_time_dimensions measure_lineage weighted_mean_value weighted_mean_weight composition components linear_terms required_relationships parity_status",
|
|
27
|
+
"relationship": "from_entity to_entity from_keys to_keys",
|
|
28
|
+
"event": "source_entity identity occurred_at participants predicate_kind definition_fingerprint",
|
|
29
|
+
"state_model": "subject states inceptions transitions definition_fingerprint",
|
|
30
|
+
"period_calendar": "date boundary_timezone coverage correspondences snapshot_status snapshot_digest",
|
|
31
|
+
"temporal_set": "boundary_timezone coverage occurrence_id start end category occurrence_count snapshot_status",
|
|
32
|
+
"work_schedule": "boundary_timezone coverage date is_working snapshot_status",
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
def ref_payload(value):
|
|
36
|
+
return RefPayloadV1.from_ref(value).to_dict()
|
|
37
|
+
|
|
38
|
+
# Traversal is restricted to values of the explicitly selected public fields.
|
|
39
|
+
def display(value, field, relations):
|
|
40
|
+
if isinstance(value, Ref):
|
|
41
|
+
relations.append({"field": field, "ref": ref_payload(value)})
|
|
42
|
+
return value.key
|
|
43
|
+
if isinstance(value, Enum):
|
|
44
|
+
return str(value.value)
|
|
45
|
+
if isinstance(value, (date, datetime)):
|
|
46
|
+
return value.isoformat()
|
|
47
|
+
if value is None:
|
|
48
|
+
return "—"
|
|
49
|
+
if isinstance(value, bool):
|
|
50
|
+
return "true" if value else "false"
|
|
51
|
+
if isinstance(value, (str, int, float)):
|
|
52
|
+
return str(value)
|
|
53
|
+
if isinstance(value, (tuple, list)):
|
|
54
|
+
return "\n".join(display(v, field, relations) for v in value)
|
|
55
|
+
if isinstance(value, Mapping):
|
|
56
|
+
return "\n".join(str(k) + ": " + display(v, field, relations) for k, v in value.items())
|
|
57
|
+
# Public temporal value, not a generic repr of a Python object.
|
|
58
|
+
if field == "sample_interval":
|
|
59
|
+
return value.to_token()
|
|
60
|
+
raise TypeError("unsupported display field: " + field)
|
|
61
|
+
|
|
62
|
+
def project(entry):
|
|
63
|
+
d = entry.details()
|
|
64
|
+
relations, fields = [], []
|
|
65
|
+
def add(name, value):
|
|
66
|
+
if value is not None:
|
|
67
|
+
fields.append({"name": name, "value": display(value, name, relations)})
|
|
68
|
+
for name in FIELDS.get(entry.kind.value, "").split():
|
|
69
|
+
if hasattr(d, name):
|
|
70
|
+
add(name, getattr(d, name))
|
|
71
|
+
for name in ("parents", "children", "dependents"):
|
|
72
|
+
for ref in getattr(d, name):
|
|
73
|
+
relations.append({"field": name, "ref": ref_payload(ref)})
|
|
74
|
+
if entry.kind.value == "entity":
|
|
75
|
+
source = d.source
|
|
76
|
+
add("source_kind", source.kind)
|
|
77
|
+
# Remote file URLs may contain credentials. Only table identity is projected.
|
|
78
|
+
if source.kind == "table":
|
|
79
|
+
add("source_table", source.table)
|
|
80
|
+
add("source_database", source.database)
|
|
81
|
+
version = d.versioning
|
|
82
|
+
if version is not None:
|
|
83
|
+
for name in ("kind", "partition_field", "grain", "timezone", "format", "valid_from", "valid_to", "interval", "open_end"):
|
|
84
|
+
if hasattr(version, name):
|
|
85
|
+
add("version_" + name, getattr(version, name))
|
|
86
|
+
if entry.kind.value == "period_calendar":
|
|
87
|
+
for level in d.levels:
|
|
88
|
+
add("level", level.name)
|
|
89
|
+
for name in ("key_ref", "period_count", "direct_finer_levels", "direct_coarser_levels", "rollup_targets"):
|
|
90
|
+
add("level_" + name, getattr(level, name))
|
|
91
|
+
computation = d.definition.to_dict() if entry.kind.value in ("metric", "measure", "dimension", "time_dimension") else None
|
|
92
|
+
def definition_refs(value, role="definition"):
|
|
93
|
+
if isinstance(value, dict):
|
|
94
|
+
if value.get("schema") == "marivo.semantic_ref/v1":
|
|
95
|
+
relations.append({"field": role, "ref": value})
|
|
96
|
+
else:
|
|
97
|
+
for key, item in value.items():
|
|
98
|
+
definition_refs(item, role + "." + key)
|
|
99
|
+
elif isinstance(value, list):
|
|
100
|
+
for item in value:
|
|
101
|
+
definition_refs(item, role)
|
|
102
|
+
if computation is not None:
|
|
103
|
+
definition_refs(computation["node"])
|
|
104
|
+
definition_refs(computation["temporal"], "temporal")
|
|
105
|
+
unique = {(r["field"], r["ref"]["kind"], r["ref"]["path"]): r for r in relations}
|
|
106
|
+
return {
|
|
107
|
+
"computation": computation, "ref": ref_payload(entry.ref), "name": entry.name, "domain": d.domain,
|
|
108
|
+
"definition": d.context.business_definition, "guardrails": list(d.context.guardrails),
|
|
109
|
+
"source": {"file": d.source_location.file, "line": d.source_location.line, "symbol": d.python_symbol},
|
|
110
|
+
"fields": fields, "relations": list(unique.values()),
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
catalog = ms.load(workspace_dir=sys.argv[1])
|
|
115
|
+
result = {
|
|
116
|
+
"fingerprint": catalog.definition_fingerprint,
|
|
117
|
+
"kinds": [kind.value for kind in SemanticKind],
|
|
118
|
+
"objects": [project(entry) for kind in SemanticKind for entry in catalog.items(kind).items],
|
|
119
|
+
}
|
|
120
|
+
except (SemanticLoadFailed, SemanticError) as error:
|
|
121
|
+
print(json.dumps({
|
|
122
|
+
"schema": "marivo.semantic_catalog_error/v1",
|
|
123
|
+
"kind": "semantic",
|
|
124
|
+
"message": str(error),
|
|
125
|
+
}, ensure_ascii=True))
|
|
126
|
+
raise SystemExit(1)
|
|
127
|
+
|
|
128
|
+
print(json.dumps(result, ensure_ascii=True))
|
|
129
|
+
`.trim();
|