@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
|
@@ -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
|
+
}
|
|
@@ -2,6 +2,8 @@ import { boundedText, closed, parseRef, refKey, } from "../semantic-reference/co
|
|
|
2
2
|
import { parseComputation } from "./definition.js";
|
|
3
3
|
export const BROWSER_ENDPOINT = 'semantic-browser/catalog';
|
|
4
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;
|
|
5
7
|
function array(value, max) {
|
|
6
8
|
if (!Array.isArray(value) || value.length > max)
|
|
7
9
|
throw new Error('invalid-catalog');
|
|
@@ -57,6 +59,21 @@ export function parseCatalogProjection(value) {
|
|
|
57
59
|
throw new Error('invalid-catalog');
|
|
58
60
|
return { fingerprint: boundedText(r.fingerprint, 256), kinds, objects };
|
|
59
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
|
+
}
|
|
60
77
|
export function parseCatalogSnapshot(value) {
|
|
61
78
|
const r = closed(value, [
|
|
62
79
|
'workspaceId',
|
|
@@ -14,6 +14,7 @@ class DiscardOutput:
|
|
|
14
14
|
with contextlib.redirect_stdout(DiscardOutput()), contextlib.redirect_stderr(DiscardOutput()):
|
|
15
15
|
import marivo.semantic as ms
|
|
16
16
|
from marivo.refs import Ref, RefPayloadV1, SemanticKind
|
|
17
|
+
from marivo.semantic.errors import SemanticError, SemanticLoadFailed
|
|
17
18
|
|
|
18
19
|
FIELDS = {
|
|
19
20
|
"domain": "owner default",
|
|
@@ -109,11 +110,20 @@ with contextlib.redirect_stdout(DiscardOutput()), contextlib.redirect_stderr(Dis
|
|
|
109
110
|
"fields": fields, "relations": list(unique.values()),
|
|
110
111
|
}
|
|
111
112
|
|
|
113
|
+
try:
|
|
112
114
|
catalog = ms.load(workspace_dir=sys.argv[1])
|
|
113
115
|
result = {
|
|
114
116
|
"fingerprint": catalog.definition_fingerprint,
|
|
115
117
|
"kinds": [kind.value for kind in SemanticKind],
|
|
116
118
|
"objects": [project(entry) for kind in SemanticKind for entry in catalog.items(kind).items],
|
|
117
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
|
+
|
|
118
128
|
print(json.dumps(result, ensure_ascii=True))
|
|
119
129
|
`.trim();
|
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
import { realpath } from 'node:fs/promises';
|
|
2
|
+
import { finishCleanup, PendingTasks } from "../lifecycle.js";
|
|
2
3
|
import { boundedText, closed } from "../semantic-reference/contracts.js";
|
|
4
|
+
import { semanticEnvironmentFingerprint } from "../semantic-reference/environment.js";
|
|
3
5
|
import { abortable } from "../semantic-reference/rpc.js";
|
|
4
|
-
import { CATALOG_MAX_BYTES, parseCatalogProjection } from "./contracts.js";
|
|
6
|
+
import { CATALOG_MAX_BYTES, parseCatalogFailure, parseCatalogProjection, } from "./contracts.js";
|
|
5
7
|
import { BROWSER_CATALOG_PROGRAM } from "./program.js";
|
|
8
|
+
export class SemanticCatalogLoadError extends Error {
|
|
9
|
+
code = 'catalog-load-failed';
|
|
10
|
+
diagnostic;
|
|
11
|
+
constructor(diagnostic) {
|
|
12
|
+
super(diagnostic);
|
|
13
|
+
this.diagnostic = diagnostic;
|
|
14
|
+
this.name = 'SemanticCatalogLoadError';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
6
17
|
/** Workspace-addressed read service; intentionally has no Agent or credential dependency. */
|
|
7
18
|
export class SemanticBrowserService {
|
|
8
19
|
#host;
|
|
9
20
|
#lifetime = new AbortController();
|
|
21
|
+
#tasks = new PendingTasks();
|
|
22
|
+
#closing;
|
|
10
23
|
constructor(host) {
|
|
11
24
|
this.#host = host;
|
|
12
25
|
}
|
|
@@ -15,7 +28,10 @@ export class SemanticBrowserService {
|
|
|
15
28
|
if (!current || current.path !== workspacePath || this.#host.projectRoot(current) !== root)
|
|
16
29
|
throw new Error('workspace-changed');
|
|
17
30
|
}
|
|
18
|
-
|
|
31
|
+
read(payload, caller) {
|
|
32
|
+
return this.#tasks.track(this.#read(payload, caller));
|
|
33
|
+
}
|
|
34
|
+
async #read(payload, caller) {
|
|
19
35
|
const signal = AbortSignal.any([caller, this.#lifetime.signal]);
|
|
20
36
|
signal.throwIfAborted();
|
|
21
37
|
const request = closed(payload, ['workspaceId']);
|
|
@@ -24,7 +40,7 @@ export class SemanticBrowserService {
|
|
|
24
40
|
if (!workspace)
|
|
25
41
|
throw new Error('workspace-unavailable');
|
|
26
42
|
const root = this.#host.projectRoot(workspace);
|
|
27
|
-
const runner = await abortable(this.#host.resolve(root), signal);
|
|
43
|
+
const runner = await abortable(this.#tasks.track(this.#host.resolve(root)), signal);
|
|
28
44
|
this.#assertOwner(id, workspace.path, root);
|
|
29
45
|
if (runner.status !== 'ready')
|
|
30
46
|
throw new Error('environment-failed');
|
|
@@ -40,8 +56,19 @@ export class SemanticBrowserService {
|
|
|
40
56
|
throw new Error('workspace-changed');
|
|
41
57
|
this.#assertOwner(id, workspace.path, root);
|
|
42
58
|
signal.throwIfAborted();
|
|
43
|
-
if (result.exitCode !== 0)
|
|
59
|
+
if (result.exitCode !== 0) {
|
|
60
|
+
const failure = (() => {
|
|
61
|
+
try {
|
|
62
|
+
return parseCatalogFailure(JSON.parse(result.stdout.toString('utf8')));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
})();
|
|
68
|
+
if (failure)
|
|
69
|
+
throw new SemanticCatalogLoadError(failure.message);
|
|
44
70
|
throw new Error('catalog-load-failed');
|
|
71
|
+
}
|
|
45
72
|
if (result.stdout.byteLength > CATALOG_MAX_BYTES)
|
|
46
73
|
throw new Error('catalog-too-large');
|
|
47
74
|
const projection = parseCatalogProjection(JSON.parse(result.stdout.toString('utf8')));
|
|
@@ -49,17 +76,24 @@ export class SemanticBrowserService {
|
|
|
49
76
|
...projection,
|
|
50
77
|
workspaceId: id,
|
|
51
78
|
projectRoot: runner.binding.projectRoot,
|
|
52
|
-
environmentFingerprint: runner.binding.fingerprint,
|
|
79
|
+
environmentFingerprint: semanticEnvironmentFingerprint(id, runner.binding.fingerprint),
|
|
53
80
|
loadedAt: new Date().toISOString(),
|
|
54
81
|
};
|
|
55
82
|
}
|
|
83
|
+
close() {
|
|
84
|
+
this.dispose();
|
|
85
|
+
this.#closing ??= finishCleanup([() => this.#tasks.drain()]);
|
|
86
|
+
return this.#closing;
|
|
87
|
+
}
|
|
56
88
|
dispose() {
|
|
57
89
|
this.#lifetime.abort();
|
|
58
90
|
}
|
|
59
91
|
}
|
|
60
|
-
/**
|
|
92
|
+
/** Preserve Marivo semantic diagnostics; keep unknown Python failures behind a fixed fallback. */
|
|
61
93
|
export function browserFailure(error) {
|
|
62
94
|
const code = error instanceof Error && 'code' in error ? error.code : undefined;
|
|
95
|
+
if (error instanceof SemanticCatalogLoadError)
|
|
96
|
+
return `语义层模型加载失败:\n${error.diagnostic}`;
|
|
63
97
|
if (code === 'subprocess-timeout')
|
|
64
98
|
return '语义层加载超时,请检查模型后重试。';
|
|
65
99
|
if (code === 'subprocess-output-limit' ||
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { finishCleanup, PendingTasks } from "../lifecycle.js";
|
|
1
2
|
import { parseProjection } from "./contracts.js";
|
|
2
3
|
export const CATALOG_PROGRAM = String.raw `
|
|
3
4
|
import contextlib, io, json, sys
|
|
@@ -19,6 +20,8 @@ export class SemanticReferenceBridge {
|
|
|
19
20
|
#slots = new Map();
|
|
20
21
|
#clock;
|
|
21
22
|
#disposed = false;
|
|
23
|
+
#tasks = new PendingTasks();
|
|
24
|
+
#closing;
|
|
22
25
|
constructor(clock = Date.now) {
|
|
23
26
|
this.#clock = clock;
|
|
24
27
|
}
|
|
@@ -47,7 +50,8 @@ export class SemanticReferenceBridge {
|
|
|
47
50
|
promise: Promise.resolve({ kinds: [], items: [] }),
|
|
48
51
|
};
|
|
49
52
|
current.flight = flight;
|
|
50
|
-
flight.promise = this.#
|
|
53
|
+
flight.promise = this.#tasks
|
|
54
|
+
.track(this.#load(runner, controller.signal))
|
|
51
55
|
.then((projection) => {
|
|
52
56
|
if (!controller.signal.aborted &&
|
|
53
57
|
!this.#disposed &&
|
|
@@ -105,6 +109,11 @@ export class SemanticReferenceBridge {
|
|
|
105
109
|
throw new Error('catalog-load-failed');
|
|
106
110
|
return parseProjection(JSON.parse(result.stdout.toString('utf8')));
|
|
107
111
|
}
|
|
112
|
+
close() {
|
|
113
|
+
this.dispose();
|
|
114
|
+
this.#closing ??= finishCleanup([() => this.#tasks.drain()]);
|
|
115
|
+
return this.#closing;
|
|
116
|
+
}
|
|
108
117
|
invalidate(key) {
|
|
109
118
|
const slot = this.#slots.get(key);
|
|
110
119
|
this.#slots.delete(key);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/** Browser-safe wire contracts. These bounds do not reimplement Marivo's domain grammar. */
|
|
2
2
|
export const CHANNEL = '/dsh-data-analysis';
|
|
3
3
|
export const SOURCE = 'marivo-semantic';
|
|
4
|
-
export const LIMIT = 40;
|
|
5
4
|
export const MAX_WIRE_BYTES = 1024 * 1024;
|
|
6
5
|
export function closed(value, keys) {
|
|
7
6
|
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
@@ -87,20 +86,19 @@ export function parseProjection(value) {
|
|
|
87
86
|
return Object.freeze({ kinds: Object.freeze(kinds), items: Object.freeze(items) });
|
|
88
87
|
}
|
|
89
88
|
export function parseCandidatesRequest(value) {
|
|
90
|
-
const r = closed(value, ['version', 'sessionId', 'query', 'quoted'
|
|
91
|
-
if (r.version !== 1 ||
|
|
89
|
+
const r = closed(value, ['version', 'sessionId', 'query', 'quoted']);
|
|
90
|
+
if (r.version !== 1 || typeof r.quoted !== 'boolean')
|
|
92
91
|
throw new Error('invalid-request');
|
|
93
92
|
return {
|
|
94
93
|
version: 1,
|
|
95
94
|
sessionId: boundedText(r.sessionId, 256),
|
|
96
95
|
query: boundedText(r.query, 128, true),
|
|
97
96
|
quoted: r.quoted,
|
|
98
|
-
limit: LIMIT,
|
|
99
97
|
};
|
|
100
98
|
}
|
|
101
99
|
export function parseCandidatesResponse(value) {
|
|
102
100
|
const r = closed(value, ['environmentFingerprint', 'items']);
|
|
103
|
-
if (!Array.isArray(r.items)
|
|
101
|
+
if (!Array.isArray(r.items))
|
|
104
102
|
throw new Error('invalid-response');
|
|
105
103
|
const items = r.items.map((raw) => {
|
|
106
104
|
const item = closed(raw, ['ref', 'refKey', 'name', 'businessDefinition', 'section']);
|
|
@@ -109,5 +107,21 @@ export function parseCandidatesResponse(value) {
|
|
|
109
107
|
throw new Error('invalid-response');
|
|
110
108
|
return { ...parseCandidate(candidate), section };
|
|
111
109
|
});
|
|
112
|
-
return {
|
|
110
|
+
return {
|
|
111
|
+
environmentFingerprint: boundedText(r.environmentFingerprint, 256),
|
|
112
|
+
items,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/** Browser selection identity only; preparation never reloads semantic content. */
|
|
116
|
+
export function parsePrepareRequest(value) {
|
|
117
|
+
const r = closed(value, ['sessionId', 'workspaceId', 'environmentFingerprint', 'ref']);
|
|
118
|
+
return {
|
|
119
|
+
workspaceId: boundedText(r.workspaceId, 256),
|
|
120
|
+
envelope: parseEnvelope({
|
|
121
|
+
schema: 'dsh-data-analysis-semantic-reference/v1',
|
|
122
|
+
sessionId: r.sessionId,
|
|
123
|
+
environmentFingerprint: r.environmentFingerprint,
|
|
124
|
+
ref: r.ref,
|
|
125
|
+
}),
|
|
126
|
+
};
|
|
113
127
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
/** Reference identity includes Harness ownership; the underlying Runtime stays unchanged. */
|
|
3
|
+
export function semanticEnvironmentFingerprint(workspaceId, runtimeFingerprint) {
|
|
4
|
+
return createHash('sha256')
|
|
5
|
+
.update(JSON.stringify(['marivo-semantic-workspace/v1', workspaceId, runtimeFingerprint]))
|
|
6
|
+
.digest('hex');
|
|
7
|
+
}
|
|
8
|
+
/** Explicit selection establishes ownership; serialization never repairs it. */
|
|
9
|
+
export function referenceEnvironmentResolver(host) {
|
|
10
|
+
const owners = new WeakMap();
|
|
11
|
+
return async (sessionId, purpose, workspaceId) => {
|
|
12
|
+
const agent = host.agent(sessionId);
|
|
13
|
+
if (!agent)
|
|
14
|
+
throw new Error('unknown-session');
|
|
15
|
+
const priorOwner = owners.get(agent);
|
|
16
|
+
const owner = purpose === 'reference' ? priorOwner?.workspace : host.workspace(sessionId);
|
|
17
|
+
if (purpose === 'prepare' && (!workspaceId || owner?.id !== workspaceId))
|
|
18
|
+
throw new Error('workspace-mismatch');
|
|
19
|
+
const checkOwner = () => {
|
|
20
|
+
if (!owner)
|
|
21
|
+
return;
|
|
22
|
+
const current = host.workspace(sessionId);
|
|
23
|
+
if (current.id !== owner.id || current.path !== owner.path)
|
|
24
|
+
throw new Error('workspace-changed');
|
|
25
|
+
};
|
|
26
|
+
checkOwner();
|
|
27
|
+
const bound = host.binding(agent);
|
|
28
|
+
if (purpose === 'reference' &&
|
|
29
|
+
(!owner || !bound || bound !== priorOwner?.binding || bound.root !== host.projectRoot(agent)))
|
|
30
|
+
throw new Error('environment-unbound');
|
|
31
|
+
const pending = purpose === 'reference' ? bound.environment : host.resolve(agent);
|
|
32
|
+
const expected = host.binding(agent);
|
|
33
|
+
const environment = await pending;
|
|
34
|
+
if (host.agent(sessionId) !== agent ||
|
|
35
|
+
host.binding(agent) !== expected ||
|
|
36
|
+
expected?.root !== host.projectRoot(agent))
|
|
37
|
+
throw new Error('environment-changed');
|
|
38
|
+
checkOwner();
|
|
39
|
+
if (!owner || !expected)
|
|
40
|
+
throw new Error('environment-unbound');
|
|
41
|
+
if (purpose !== 'reference')
|
|
42
|
+
owners.set(agent, { workspace: owner, binding: expected });
|
|
43
|
+
// Only the semantic surface uses the ownership fingerprint. Execution is delegated
|
|
44
|
+
// to the original runner, preserving its Runtime identity and dynamic health.
|
|
45
|
+
return {
|
|
46
|
+
binding: {
|
|
47
|
+
...environment.binding,
|
|
48
|
+
fingerprint: semanticEnvironmentFingerprint(owner.id, environment.binding.fingerprint),
|
|
49
|
+
},
|
|
50
|
+
get status() {
|
|
51
|
+
return environment.status;
|
|
52
|
+
},
|
|
53
|
+
runChecked: (request) => environment.runChecked(request),
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Shared display labels and search aliases; object kinds still come from the live Catalog. */
|
|
2
|
+
export const semanticKindLabels = {
|
|
3
|
+
domain: '业务域',
|
|
4
|
+
datasource: '数据源',
|
|
5
|
+
entity: '实体',
|
|
6
|
+
dimension: '维度',
|
|
7
|
+
measure: '度量',
|
|
8
|
+
time_dimension: '时间维度',
|
|
9
|
+
metric: '指标',
|
|
10
|
+
relationship: '关系',
|
|
11
|
+
event: '事件',
|
|
12
|
+
state_model: '状态模型',
|
|
13
|
+
period_calendar: '周期日历',
|
|
14
|
+
temporal_set: '时间集合',
|
|
15
|
+
work_schedule: '工作日程',
|
|
16
|
+
};
|