@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
package/README.md
CHANGED
|
@@ -1,72 +1,158 @@
|
|
|
1
1
|
# @chengxianglibra/dsh-data-analysis
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
在 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 中,用自然语言完成数据分析,
|
|
4
|
+
浏览和复用业务指标定义,并将结果整理为可交互、可离线阅读的图表、报告和看板。
|
|
5
|
+
语义分析能力由 [Marivo](https://github.com/chengxianglibra/marivo) 提供;本地文件也可直接使用 pandas 或 DuckDB。
|
|
4
6
|
|
|
5
|
-
DeepSeek
|
|
7
|
+
本项目是个人维护的社区插件,并非 DeepSeek 官方发行或维护的软件。
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
- `marivo_help` 实时公共 Help transport;
|
|
9
|
-
- `marivo_datasource_test` 的 DSH Credentials 收集与显式 connection test;
|
|
10
|
-
- `marivo_datasource_access` 的 30 分钟、最多 64 次 foreground Shell credential lease;
|
|
11
|
-
- `marivo_evidence_sources({ session_id, sources })` 的可移植 Artifact-owned Finding 来源交付;
|
|
12
|
-
- `emit_dataset(BaseFrame, ...)`、`emit_computed(DataFrame, ...)` 与
|
|
13
|
-
`emit_session_trace(SessionGraph, ...)` 的有界 JavaScript 投影;
|
|
14
|
-
- 随包分发并挂载的 `dsh-data-analysis-report` Skill,以及 `marivo-analysis` 激活后的按需路由。
|
|
9
|
+
## 安装
|
|
15
10
|
|
|
16
|
-
|
|
17
|
-
卡片与 replay 入口已删除,不提供 alias。
|
|
11
|
+
### 环境准备
|
|
18
12
|
|
|
19
|
-
|
|
13
|
+
- Node.js `^22.19.0 || >=24.0.0`,与 DeepSeek Harness 一致:22.x 需至少 22.19.0,或使用 24.0.0 及以上版本;不支持 23.x。
|
|
14
|
+
- 已安装并配置好 DeepSeek Harness `>=0.1.5-rc.1`(rc.1 验收状态见验证指南;范围内未来版本不视为已验收)。
|
|
15
|
+
- `pnpm` 可在命令行中使用,供 Harness 安装插件。
|
|
16
|
+
- 已安装 Python 3.10+,命令行可运行 `python3`(Windows 为 `python`),且支持 `venv`/`ensurepip`;部分 Linux 发行版需额外安装 `python3-venv`。
|
|
20
17
|
|
|
21
|
-
|
|
18
|
+
默认安装方式会在首次启动时使用本机 Python 创建独立的虚拟环境,并通过 pip 安装 Marivo,无需预装 Marivo 或 uv。
|
|
19
|
+
首次启动需要联网下载依赖,请等待环境准备完成。
|
|
22
20
|
|
|
23
|
-
|
|
24
|
-
- Marivo package spec 精确为 `marivo[duckdb,trino,clickhouse]==0.5.3`;
|
|
25
|
-
- 项目自有 Runtime marker 为 `dsh-data-analysis-runtime/v2`;
|
|
26
|
-
- 子进程策略为 `direct-argv-inherited-env-snapshot-overlay-v2`。
|
|
21
|
+
### 安装并启动
|
|
27
22
|
|
|
28
|
-
|
|
23
|
+
将插件安装到 Web Profile:
|
|
29
24
|
|
|
30
|
-
|
|
25
|
+
```bash
|
|
26
|
+
dsh plugin --profile web add @chengxianglibra/dsh-data-analysis
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
安装后启动 Web Profile;如果已在运行,请先退出再重新启动:
|
|
31
30
|
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
marivo_datasource_test({ name: string })
|
|
35
|
-
marivo_datasource_access({ name: string })
|
|
36
|
-
marivo_evidence_sources({
|
|
37
|
-
session_id: string,
|
|
38
|
-
sources: Array<{ artifact_ref: string, finding_id: string }>
|
|
39
|
-
})
|
|
31
|
+
```bash
|
|
32
|
+
dsh --profile web
|
|
40
33
|
```
|
|
41
34
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
Session recovery、Artifact revalidation、Quality、Evidence 读取、Session Graph 与 `to_pandas()` 都直接使用
|
|
45
|
-
Marivo 公共 API,不增加 convenience Tool。
|
|
35
|
+
在 Web 界面选择分析使用的工作区(Workspace),新建会话并发送一条消息。
|
|
36
|
+
会话标题旁按顺序显示“数据源”“语义层”“报告”三个入口,浏览页面在原生右侧标签页打开。本文以 Web 界面为例。
|
|
46
37
|
|
|
47
|
-
|
|
38
|
+
### 本地文件分析
|
|
48
39
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
快照;Artifact 与 Graph emitter 默认 `reader`,明确审计请求才使用 `audit`。多 Session 的独立 Graph 集中展示,
|
|
56
|
-
Frame preview 只按精确 Session 与 Artifact identity 关联。Agent 自由生成
|
|
57
|
-
`<workspace>/<new-report-directory>/index.html` 与可选相对资源。Native/both 的
|
|
58
|
-
最终入口使用顶层 DSH `write` / `edit`,并以精确 Markdown 行内路径交付,让 DSH Web 复用 Produced Files
|
|
59
|
-
提供点击;Code-only 在 `run_code` 内使用同一 Tool,但嵌套 mutation 只保证精确路径。插件不创建报告对象、
|
|
60
|
-
digest、不可变发布、历史字节 replay 或 share link。
|
|
40
|
+
上传文件后直接提问,或指定 Workspace 中已有文件。助手可在插件绑定的 Python 环境中使用 pandas
|
|
41
|
+
或原生 DuckDB 分析 CSV、JSON、Parquet,无需先配置数据源或建立语义模型;需要时可继续生成报告。
|
|
42
|
+
默认安装包含 Marivo 的 `duckdb` extra(同时保留 `trino`、`clickhouse`)。Excel `.xlsx` 通过 DuckDB 的
|
|
43
|
+
[excel 扩展](https://duckdb.org/docs/stable/core_extensions/excel.html)读取;首次使用需要联网下载扩展,
|
|
44
|
+
并允许写入 DuckDB 扩展缓存。它不是额外的 Python 包,无需为此安装 `openpyxl`。
|
|
45
|
+
旧版 `.xls` 不在该扩展支持范围内,需要先转换为 `.xlsx`、CSV 或 Parquet。
|
|
61
46
|
|
|
62
|
-
|
|
47
|
+
纯文件分析无需经过 Marivo `md.raw_sql`。下面的数据源配置流程适用于数据库连接和需要复用的 Marivo 数据源。
|
|
63
48
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
49
|
+
## 能力
|
|
50
|
+
|
|
51
|
+
| 你想做什么 | 插件提供的能力 |
|
|
52
|
+
| --- | --- |
|
|
53
|
+
| 回答业务问题 | 用自然语言提出问题,分析指标趋势、比较不同时间或业务分组、探索变化原因 |
|
|
54
|
+
| 分析本地文件 | 复用原生附件路径,以 pandas 或 DuckDB 直接计算,并按需交付图表或报告 |
|
|
55
|
+
| 连接数据 | 配置数据源、管理连接凭证并测试连接;默认包含 DuckDB、Trino、ClickHouse 支持 |
|
|
56
|
+
| 统一分析口径 | 与 Agent 一起整理和复用指标、维度等业务定义,浏览语义对象及其关系 |
|
|
57
|
+
| 明确分析对象 | 在输入框通过 `@` 搜索并引用已有语义对象 |
|
|
58
|
+
| 展示分析结果 | 生成包含文字、指标卡、图表、表格和来源说明的报告或看板 |
|
|
59
|
+
| 阅读与分享 | 交互筛选、调整呈现、保存版本,并下载 HTML 离线阅读 |
|
|
60
|
+
|
|
61
|
+
## 如何使用
|
|
62
|
+
|
|
63
|
+
### 1. 准备数据源
|
|
64
|
+
|
|
65
|
+
在会话标题旁打开“数据源”,查看所属工作区的连接属性与配置状态:
|
|
66
|
+
|
|
67
|
+
1. 点击“新增数据源”,选择引擎,按表单填写名称及连接信息。
|
|
68
|
+
2. 如果需要身份认证,在同一表单的“连接凭证”中直接填写用户名、密码或令牌。
|
|
69
|
+
3. 测试连接,根据结果检查地址、凭证或访问权限。
|
|
70
|
+
|
|
71
|
+
凭证引用名自动生成,可展开“凭证引用名 · 确认或修改”查看和调整;也可填写已有引用,并将值留空以复用凭证。
|
|
72
|
+
编辑时已保存的凭证值不回显,留空沿用;填写新值默认使用新的引用,不删除旧的共享凭证。
|
|
73
|
+
手动指定已存在的引用并填写新值时,不会自动覆盖,需到凭证页确认更新。
|
|
74
|
+
已有数据源可点击“编辑配置”修改连接信息,名称和引擎保持不变;保存后重新测试。
|
|
75
|
+
|
|
76
|
+
管理员可在 Harness 当前 profile 的本插件配置项下设置 `datasourceDefaults`,按引擎类型为新建表单填入默认值:
|
|
77
|
+
|
|
78
|
+
```yaml
|
|
79
|
+
datasourceDefaults:
|
|
80
|
+
trino:
|
|
81
|
+
host: trino.internal.example
|
|
82
|
+
port: 443
|
|
68
83
|
```
|
|
69
84
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
85
|
+
这是插件配置片段,随 Harness 现有配置加载机制生效;不是独立配置文件。未指定的字段保持原样,用户可修改或清空填充值;
|
|
86
|
+
切换引擎时清空表单并应用目标引擎的默认值。编辑或复用已有数据源不应用这些默认值。
|
|
87
|
+
只配置非敏感普通字段,不能设置 `*_env` 凭据引用或凭据值;内部地址只保存在部署环境的配置中,不必加入插件源码。
|
|
88
|
+
打开新建表单时按当前 Runtime 校验引擎、字段和类型,错误会明确显示且不回显配置值;配置错误不阻止已有数据源编辑。
|
|
89
|
+
|
|
90
|
+
分析需要新增或修复数据源时,助手可自动打开所属会话的右侧配置页。填写后点击“保存并测试,成功后继续”;
|
|
91
|
+
缺少凭证时继续在凭证表单补齐,测试成功后原调用返回,助手再核验目标表并继续分析。
|
|
92
|
+
请求页默认显示新增表单;切换“使用已有数据源”后,可选择已有连接并验证,无需重复创建。测试失败保留已保存的配置,允许修正后重试;取消请求不会作为成功继续。
|
|
93
|
+
|
|
94
|
+
更新插件后,如果旧浏览器页面中工具正在等待、却没有打开配置 Tab,也没有“待配置”入口,
|
|
95
|
+
先刷新浏览器页面以加载当前客户端。仍存活的原请求会重新显示;刷新页面不等于重启 Host。
|
|
96
|
+
若原调用已取消或 Host 已重启,需要重新发起配置请求。
|
|
97
|
+
请通过凭证表单提交密码或令牌,不要粘贴到聊天消息中。
|
|
98
|
+
|
|
99
|
+
### 2. 用自然语言提出分析问题
|
|
100
|
+
|
|
101
|
+
说明使用哪些数据、时间范围、指标口径和希望得到的结果。例如:
|
|
102
|
+
|
|
103
|
+
> 使用订单数据,分析 2026 年 8 月的销售额和订单量,与 7 月比较,按渠道拆分,并说明数据来源和口径。
|
|
104
|
+
|
|
105
|
+
> 检查最近七天的查询耗时,找出耗时最高的查询类型,给出趋势图和明细表。
|
|
106
|
+
|
|
107
|
+
如果还没有整理业务定义,可以先让 Agent 帮助梳理:
|
|
108
|
+
|
|
109
|
+
> 先查看当前数据源中有哪些订单相关表,和我确认销售额、支付订单数的统计口径,再保存为可复用的业务定义。
|
|
110
|
+
|
|
111
|
+
需要报告时,直接说明希望的内容:
|
|
112
|
+
|
|
113
|
+
> 将这次分析整理为一份报告,包含结论、关键指标、趋势图、渠道对比表和来源说明,并提供可下载的 HTML。
|
|
114
|
+
|
|
115
|
+
### 3. 浏览和引用业务定义
|
|
116
|
+
|
|
117
|
+
点击会话标题旁的“语义层”,按分类或关键词查找指标、维度等对象,查看业务定义和对象关系。
|
|
118
|
+
这里展示已有定义;浏览页面不会执行分析查询。浏览页固定属于入口所在会话的工作区,支持把语义对象独立打开。
|
|
119
|
+
|
|
120
|
+
在聊天输入框输入 `@` 可搜索并选择当前工作区的对象;多词搜索可输入 `@"monthly revenue"`。
|
|
121
|
+
选中后将显示完整的 `kind:path` 引用,便于 Agent 使用你指定的对象开展分析。
|
|
122
|
+
|
|
123
|
+
### 4. 阅读、调整和追问报告
|
|
124
|
+
|
|
125
|
+
当前会话交付新报告后,右侧自动打开该次交付的固定版本;后台完成、历史回放和重连不会抢占阅读页面。
|
|
126
|
+
同工作区的后台交付会更新已打开的报告目录,并在 current 页提示新版本,保留正在阅读的内容。
|
|
127
|
+
报告生成后自动在右侧 tab 打开固定版本,主对话不附加报告 block。默认只保存 JSON,点击下载或显式调用导出工具时才生成 HTML。“报告”目录按标题查找并打开 current;可以并排阅读不同报告和版本。
|
|
128
|
+
|
|
129
|
+
用户要求离线 HTML 文件时,DSH 可直接调用 `marivo_export_html({ report_id, build_id?, output_path })`,例如输出到 `reports/analysis.html`。省略 `build_id` 时固定调用开始时的 current;指定时导出该历史版本。输出是完整、自包含且可交互的报告,不含未保存编辑或临时筛选。路径必须位于当前 Workspace,已有文件不会覆盖;导出不需要 Python、数据源或发布配置。成功返回实际路径、Build ID、字节数和 SHA-256;DSH 随后通过原生 `present({ files: [{ path, description }] })` 交付该文件。
|
|
130
|
+
发现新版本时 current 先提示,点击刷新或重新打开才切换,旧版本筛选随切换重置。
|
|
131
|
+
没有活动会话时,侧栏的“报告”入口仍可阅读 Workspace 中的报告。
|
|
132
|
+
|
|
133
|
+
- **探索数据**:筛选图表和表格,调整支持的图形及展示字段。交互使用报告中已保存的数据;获取新数据或重新计算时,在会话中提出要求。
|
|
134
|
+
- **查看来源**:查看报告内容附带的来源信息,核对分析依据。
|
|
135
|
+
- **调整呈现**:从 current 页点击“编辑报告”,在原编辑窗口编辑标题、正文、指标标签和图表/表格配置,移动或删除内容,支持撤销、重做和保存。
|
|
136
|
+
- **继续追问**:在支持的报告内容菜单中选择“加入提问”,在输入框插入 `# <cell名称>` 引用标签,补充问题后发送;完整上下文会随引用一起提交。
|
|
137
|
+
- **查看历史**:保存编辑后可在历史侧栏查看和下载已保存的版本,历史版本只读。
|
|
138
|
+
|
|
139
|
+
页面筛选不会重新计算指标卡,也不会写入保存的报告。希望保留当前筛选结果时,使用“导出当前视图”。
|
|
140
|
+
|
|
141
|
+
### 5. 下载与分享
|
|
142
|
+
|
|
143
|
+
报告右上角的导出菜单提供两种方式:
|
|
144
|
+
|
|
145
|
+
| 选项 | 适合的用途 |
|
|
146
|
+
| --- | --- |
|
|
147
|
+
| 下载完整报告 | 下载所查看版本的完整 HTML 报告,供离线阅读 |
|
|
148
|
+
| 导出当前视图 | 将当前筛选、图形和表格排序固定为 HTML,附带来源版本及导出时间,分享当前看到的内容 |
|
|
149
|
+
|
|
150
|
+
编辑模式下,请先保存或取消编辑再导出。下载的文件可直接用浏览器打开。
|
|
151
|
+
|
|
152
|
+
## 许可证
|
|
153
|
+
|
|
154
|
+
本项目采用 [MIT License](LICENSE)。
|
|
155
|
+
|
|
156
|
+
## 报告发布到对象存储
|
|
157
|
+
|
|
158
|
+
通过 `reportPublishing.enabled` 开启 S3 兼容对象存储发布;在“数据源与凭证”管理独立 AK/SK 和可选 Token。开启后报告菜单的完整 HTML 与当前视图下载均替换为发布,成功返回链接。配置示例与边界见[报告 HTML 发布](../../docs/modules/report-publishing.md)。
|
package/cordis.patch.yml
CHANGED
package/lib/bin/environment.js
CHANGED
|
@@ -5,9 +5,9 @@ import { MarivoEnvironmentError } from "../environment/errors.js";
|
|
|
5
5
|
import { ensureSharedMarivoRuntime, MarivoWorkspaceEnvironmentManager, } from "../environment/index.js";
|
|
6
6
|
import { environmentPayload } from "../environment/summary.js";
|
|
7
7
|
function usage() {
|
|
8
|
-
return `Usage: dsh-data-analysis-env [--project-root PATH] [--python PATH] [--runtime-root PATH] [--
|
|
8
|
+
return `Usage: dsh-data-analysis-env [--project-root PATH] [--python PATH] [--runtime-root PATH] [--bootstrap-python PATH]
|
|
9
9
|
|
|
10
|
-
Ensures the shared pinned Marivo and
|
|
10
|
+
Ensures the shared pinned Marivo and presentation-kit Runtime, initializes the minimal
|
|
11
11
|
Workspace layout, and checks the exact interpreter, import identities, and doctor
|
|
12
12
|
admission used by the DSH plugin. The JSON output contains no credentials or raw
|
|
13
13
|
doctor details.
|
|
@@ -23,7 +23,7 @@ function parseArguments(argv) {
|
|
|
23
23
|
let projectRoot = process.env.DSH_DATA_ANALYSIS_PROJECT_ROOT ?? process.env.DSH_CWD ?? process.cwd();
|
|
24
24
|
let pythonExecutable = process.env.DSH_DATA_ANALYSIS_PYTHON;
|
|
25
25
|
let runtimeRoot = process.env.DSH_DATA_ANALYSIS_RUNTIME_ROOT;
|
|
26
|
-
let
|
|
26
|
+
let bootstrapPythonExecutable = process.env.DSH_DATA_ANALYSIS_BOOTSTRAP_PYTHON;
|
|
27
27
|
for (let index = 0; index < argv.length; index++) {
|
|
28
28
|
const option = argv[index];
|
|
29
29
|
if (option === '--help' || option === '-h')
|
|
@@ -43,8 +43,8 @@ function parseArguments(argv) {
|
|
|
43
43
|
index++;
|
|
44
44
|
continue;
|
|
45
45
|
}
|
|
46
|
-
if (option === '--
|
|
47
|
-
|
|
46
|
+
if (option === '--bootstrap-python') {
|
|
47
|
+
bootstrapPythonExecutable = requireValue(argv, index, option);
|
|
48
48
|
index++;
|
|
49
49
|
continue;
|
|
50
50
|
}
|
|
@@ -54,7 +54,7 @@ function parseArguments(argv) {
|
|
|
54
54
|
projectRoot,
|
|
55
55
|
...(pythonExecutable === undefined ? {} : { pythonExecutable }),
|
|
56
56
|
...(runtimeRoot === undefined ? {} : { runtimeRoot }),
|
|
57
|
-
...(
|
|
57
|
+
...(bootstrapPythonExecutable === undefined ? {} : { bootstrapPythonExecutable }),
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
60
|
async function main(argv) {
|
|
@@ -74,7 +74,9 @@ async function main(argv) {
|
|
|
74
74
|
const runtime = await ensureSharedMarivoRuntime({
|
|
75
75
|
...(args.pythonExecutable === undefined ? {} : { pythonExecutable: args.pythonExecutable }),
|
|
76
76
|
...(args.runtimeRoot === undefined ? {} : { runtimeRoot: args.runtimeRoot }),
|
|
77
|
-
...(args.
|
|
77
|
+
...(args.bootstrapPythonExecutable === undefined
|
|
78
|
+
? {}
|
|
79
|
+
: { bootstrapPythonExecutable: args.bootstrapPythonExecutable }),
|
|
78
80
|
});
|
|
79
81
|
const manager = new MarivoWorkspaceEnvironmentManager(runtime);
|
|
80
82
|
const environment = await manager.resolve(args.projectRoot);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
import { lintPresentationDraft } from "../presentation/lint.js";
|
|
5
|
+
const usage = 'Usage: dsh-data-analysis-presentation-lint DRAFT_PATH [--project-root PATH]\n';
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
if (args.length === 1 && (args[0] === '--help' || args[0] === '-h')) {
|
|
8
|
+
process.stdout.write(usage);
|
|
9
|
+
}
|
|
10
|
+
else if ((args.length !== 1 && args.length !== 3) ||
|
|
11
|
+
!args[0] ||
|
|
12
|
+
args[0].startsWith('-') ||
|
|
13
|
+
(args.length === 3 && (args[1] !== '--project-root' || !args[2]))) {
|
|
14
|
+
process.stderr.write(usage);
|
|
15
|
+
process.exitCode = 2;
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
const result = await lintPresentationDraft(path.resolve(args[2] ?? process.cwd()), args[0]);
|
|
19
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
20
|
+
process.exitCode = result.ok ? 0 : 1;
|
|
21
|
+
}
|
package/lib/bridges.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { MarivoDatasourceBridge } from "./datasource/bridge.js";
|
|
2
2
|
import { MarivoHelpBridge } from "./disclosure/bridge.js";
|
|
3
|
-
import {
|
|
3
|
+
import { MarivoPresentationProjection } from "./presentation/projection/index.js";
|
|
4
4
|
/** Compose domain adapters without adding domain methods back to the Environment. */
|
|
5
5
|
export function createMarivoBridgeSet(environment) {
|
|
6
6
|
return Object.freeze({
|
|
7
7
|
help: new MarivoHelpBridge(environment),
|
|
8
8
|
datasource: new MarivoDatasourceBridge(environment),
|
|
9
|
-
|
|
9
|
+
presentation: new MarivoPresentationProjection(environment),
|
|
10
10
|
});
|
|
11
11
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { credentialChangesRemote, } from "../../datasource/changes-contract.js";
|
|
2
|
+
/** Mount before opening a logical stream, using the existing Gateway WebSocket. */
|
|
3
|
+
export function credentialChanges(ctx) {
|
|
4
|
+
const mounted = ctx.remote.$mount(credentialChangesRemote);
|
|
5
|
+
// Initialization may be withdrawn before a Session starts consuming the mount.
|
|
6
|
+
void mounted.catch(() => { });
|
|
7
|
+
ctx.effect(() => async () => (await mounted)());
|
|
8
|
+
// Host and browser Context augmentations share this build; use the browser contract here.
|
|
9
|
+
const connection = ctx.connection;
|
|
10
|
+
return (sessionId) => {
|
|
11
|
+
let snapshot;
|
|
12
|
+
const stopConnection = connection.generation.subscribe(() => {
|
|
13
|
+
if (!connection.generation.getSnapshot())
|
|
14
|
+
snapshot?.abort();
|
|
15
|
+
});
|
|
16
|
+
const stream = ctx.remote.$stream({
|
|
17
|
+
name: 'dsh-data-analysis credential changes',
|
|
18
|
+
async *open(signal) {
|
|
19
|
+
const current = new AbortController();
|
|
20
|
+
snapshot = current;
|
|
21
|
+
try {
|
|
22
|
+
await mounted;
|
|
23
|
+
signal.throwIfAborted();
|
|
24
|
+
// A dynamically mounted namespace needs its own declared Cordis consumer.
|
|
25
|
+
let namespace;
|
|
26
|
+
const consumer = ctx.inject(['remote.dshDataAnalysisCredentials'], (scoped) => {
|
|
27
|
+
namespace = scoped.remote.dshDataAnalysisCredentials;
|
|
28
|
+
});
|
|
29
|
+
const stop = () => {
|
|
30
|
+
void consumer.dispose();
|
|
31
|
+
};
|
|
32
|
+
signal.addEventListener('abort', stop, { once: true });
|
|
33
|
+
if (signal.aborted)
|
|
34
|
+
stop();
|
|
35
|
+
try {
|
|
36
|
+
await consumer;
|
|
37
|
+
signal.throwIfAborted();
|
|
38
|
+
if (!namespace)
|
|
39
|
+
throw new Error('credential-notification-unavailable');
|
|
40
|
+
yield* namespace.changes(sessionId, signal);
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
// Abort snapshots before RemoteStream starts waiting for reconnection.
|
|
44
|
+
current.abort();
|
|
45
|
+
signal.removeEventListener('abort', stop);
|
|
46
|
+
await consumer.dispose();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
current.abort();
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
carrierFailed: () => snapshot?.abort(),
|
|
54
|
+
ended: () => new Error('credential-notification-ended'),
|
|
55
|
+
});
|
|
56
|
+
const dispose = async () => {
|
|
57
|
+
stopConnection();
|
|
58
|
+
snapshot?.abort();
|
|
59
|
+
await stream.dispose();
|
|
60
|
+
};
|
|
61
|
+
return {
|
|
62
|
+
async *[Symbol.asyncIterator]() {
|
|
63
|
+
try {
|
|
64
|
+
for await (const item of stream)
|
|
65
|
+
yield { ...item, signal: AbortSignal.any([item.signal, snapshot.signal]) };
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
await dispose();
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
dispose,
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// @ts-nocheck -- JSX is bundled by the plugin client build.
|
|
3
|
+
import { useEffect, useId, useRef, useState } from 'react';
|
|
4
|
+
import { useCopy } from "./../i18n/context.js";
|
|
5
|
+
import { errorMessage, message } from "./../i18n/copy.js";
|
|
6
|
+
import { credentialReference, prepareCredentials } from "./credential-draft.js";
|
|
7
|
+
import { creationFieldValues } from "./defaults.js";
|
|
8
|
+
import { datasourceDescription } from "./descriptions.js";
|
|
9
|
+
function credentialLabel(field) {
|
|
10
|
+
return ({
|
|
11
|
+
user_env: 'marivo.credentials.username',
|
|
12
|
+
password_env: 'marivo.credentials.password',
|
|
13
|
+
http_bearer_token_env: 'marivo.credentials.access-token',
|
|
14
|
+
http_headers_env: 'marivo.credentials.header-credential-value',
|
|
15
|
+
}[field] ?? field.replace(/_env$/, ''));
|
|
16
|
+
}
|
|
17
|
+
function credentialRows(schema, backend, values = {}) {
|
|
18
|
+
return (schema.backends.find((item) => item.name === backend)?.fields ?? [])
|
|
19
|
+
.filter((field) => field.name.endsWith('_env'))
|
|
20
|
+
.flatMap((field) => field.type === 'json'
|
|
21
|
+
? Object.entries(values[field.name] ?? {}).map(([key, existing]) => ({
|
|
22
|
+
id: crypto.randomUUID(),
|
|
23
|
+
field: field.name,
|
|
24
|
+
key,
|
|
25
|
+
existing,
|
|
26
|
+
value: '',
|
|
27
|
+
}))
|
|
28
|
+
: [{ id: crypto.randomUUID(), field: field.name, existing: values[field.name], value: '' }]);
|
|
29
|
+
}
|
|
30
|
+
export function CreateDatasource({ model, workspaceId, close, name, requestId }) {
|
|
31
|
+
const t = useCopy();
|
|
32
|
+
const formId = useId();
|
|
33
|
+
const active = useRef(false);
|
|
34
|
+
const [schema, setSchema] = useState(null);
|
|
35
|
+
const [original, setOriginal] = useState(null);
|
|
36
|
+
const [backend, setBackend] = useState('');
|
|
37
|
+
const [values, setValues] = useState({});
|
|
38
|
+
const [credentials, setCredentials] = useState([]);
|
|
39
|
+
const [busy, setBusy] = useState(false);
|
|
40
|
+
const [error, setError] = useState('');
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
const controller = new AbortController();
|
|
43
|
+
active.current = true;
|
|
44
|
+
Promise.all([
|
|
45
|
+
model.authoring(workspaceId, controller.signal, name ? 'edit' : 'create'),
|
|
46
|
+
name ? model.configuration(workspaceId, name, controller.signal) : Promise.resolve(null),
|
|
47
|
+
])
|
|
48
|
+
.then(([value, configuration]) => {
|
|
49
|
+
if (controller.signal.aborted)
|
|
50
|
+
return;
|
|
51
|
+
setSchema(value);
|
|
52
|
+
setOriginal(configuration);
|
|
53
|
+
setBackend(configuration?.backend ?? value.backends[0]?.name ?? '');
|
|
54
|
+
setCredentials(credentialRows(value, configuration?.backend ?? value.backends[0]?.name, configuration?.fields));
|
|
55
|
+
if (!configuration)
|
|
56
|
+
setValues(creationFieldValues(value, value.backends[0]?.name ?? ''));
|
|
57
|
+
if (configuration) {
|
|
58
|
+
const fields = value.backends.find((item) => item.name === configuration.backend)?.fields;
|
|
59
|
+
if (!fields)
|
|
60
|
+
throw new Error('marivo.credentials.this-runtime-cannot-fully-edit-this-datasource');
|
|
61
|
+
setValues(Object.fromEntries(fields
|
|
62
|
+
.filter((field) => !field.name.endsWith('_env') && configuration.fields[field.name] !== undefined)
|
|
63
|
+
.map((field) => [
|
|
64
|
+
field.name,
|
|
65
|
+
field.type === 'string'
|
|
66
|
+
? configuration.fields[field.name]
|
|
67
|
+
: JSON.stringify(configuration.fields[field.name]),
|
|
68
|
+
])));
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
.catch((error) => {
|
|
72
|
+
if (!controller.signal.aborted)
|
|
73
|
+
setError(errorMessage(error));
|
|
74
|
+
});
|
|
75
|
+
return () => {
|
|
76
|
+
active.current = false;
|
|
77
|
+
controller.abort();
|
|
78
|
+
};
|
|
79
|
+
}, [model, workspaceId, name]);
|
|
80
|
+
const fields = schema?.backends.find((item) => item.name === backend)?.fields ?? [];
|
|
81
|
+
const credentialFields = fields.filter((field) => field.name.endsWith('_env'));
|
|
82
|
+
const updateCredential = (id, change) => setCredentials((rows) => rows.map((row) => (row.id === id ? { ...row, ...change } : row)));
|
|
83
|
+
async function submit(event) {
|
|
84
|
+
event.preventDefault();
|
|
85
|
+
if (busy)
|
|
86
|
+
return;
|
|
87
|
+
setError('');
|
|
88
|
+
const input = {};
|
|
89
|
+
let changes = {};
|
|
90
|
+
try {
|
|
91
|
+
for (const field of fields.filter((field) => !field.name.endsWith('_env'))) {
|
|
92
|
+
const value = values[field.name];
|
|
93
|
+
if (value === undefined)
|
|
94
|
+
continue;
|
|
95
|
+
if (value === '' &&
|
|
96
|
+
(field.type !== 'string' ||
|
|
97
|
+
(name
|
|
98
|
+
? original?.fields[field.name] !== ''
|
|
99
|
+
: schema.creationDefaults?.[backend]?.[field.name] !== '')))
|
|
100
|
+
continue;
|
|
101
|
+
input[field.name] = field.type === 'string' ? value : JSON.parse(value);
|
|
102
|
+
}
|
|
103
|
+
const prepared = prepareCredentials(input.name ?? '', credentials);
|
|
104
|
+
Object.assign(input, prepared.fields);
|
|
105
|
+
changes = prepared.changes;
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
setError(error instanceof SyntaxError
|
|
109
|
+
? 'marivo.credentials.check-the-format-of-number-boolean-and-json-fields'
|
|
110
|
+
: errorMessage(error));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
setBusy(true);
|
|
114
|
+
// Remove secrets from mounted inputs as soon as the explicit submission begins.
|
|
115
|
+
setCredentials((rows) => rows.map((row) => ({ ...row, value: '' })));
|
|
116
|
+
try {
|
|
117
|
+
await model.saveConfiguration(workspaceId, schema, { backend, fields: input }, original, requestId, changes);
|
|
118
|
+
if (active.current)
|
|
119
|
+
close();
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
if (active.current)
|
|
123
|
+
setError(message('marivo.credentials.value-credential-inputs-have-been-cleared-enter-them-again', {
|
|
124
|
+
p0: errorMessage(error),
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
for (const ref of Object.keys(changes))
|
|
129
|
+
delete changes[ref];
|
|
130
|
+
if (active.current)
|
|
131
|
+
setBusy(false);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return (_jsxs("form", { className: "mc-form", "aria-label": name ? t('marivo.credentials.edit-datasource') : t('marivo.credentials.add-datasource'), onSubmit: submit, children: [_jsxs("div", { className: "mc-form-body", children: [_jsx("div", { className: "mc-detail-heading", children: _jsx("h3", { children: name
|
|
135
|
+
? t('marivo.credentials.edit-datasource')
|
|
136
|
+
: t('marivo.credentials.add-datasource') }) }), error && (_jsx("p", { role: "alert", className: "mc-result", "data-tone": "error", children: t(error) })), !schema && !error && (_jsx("p", { role: "status", children: t('marivo.credentials.loading-datasource-configuration-fields') })), schema && (_jsxs("fieldset", { className: "mc-field", disabled: busy, children: [_jsxs("label", { className: "mc-secret-input", children: [t('marivo.credentials.engine'), _jsx("select", { "aria-label": t('marivo.credentials.engine'), disabled: !!name, value: backend, onChange: (event) => {
|
|
137
|
+
setBackend(event.target.value);
|
|
138
|
+
setValues(creationFieldValues(schema, event.target.value));
|
|
139
|
+
setCredentials(credentialRows(schema, event.target.value));
|
|
140
|
+
setError('');
|
|
141
|
+
}, children: schema.backends.map((item) => (_jsx("option", { value: item.name, children: item.name }, item.name))) })] }), fields
|
|
142
|
+
.filter((field) => !field.name.endsWith('_env'))
|
|
143
|
+
.map((field) => (_jsxs("label", { className: "mc-secret-input", htmlFor: `${formId}-${field.name}`, children: [_jsxs("span", { className: "mc-input-heading", children: [_jsxs("span", { className: "mc-input-name", children: [field.name, field.required ? ' *' : ''] }), field.description && (_jsx("span", { className: "mc-input-description", children: t(datasourceDescription(field.description)) }))] }), field.type === 'boolean' ? (_jsxs("select", { id: `${formId}-${field.name}`, "aria-label": field.name, value: values[field.name] ?? '', onChange: (event) => setValues({ ...values, [field.name]: event.target.value }), children: [_jsxs("option", { value: "", children: [t('marivo.credentials.default'), field.default == null ? '' : ` (${field.default})`] }), _jsx("option", { value: "true", children: "true" }), _jsx("option", { value: "false", children: "false" })] })) : (_jsx("input", { id: `${formId}-${field.name}`, "aria-label": field.name, required: field.required, disabled: !!name && field.name === 'name', autoComplete: "off", type: field.type === 'number' ? 'number' : 'text', placeholder: field.default == null
|
|
144
|
+
? field.type === 'json'
|
|
145
|
+
? 'JSON'
|
|
146
|
+
: ''
|
|
147
|
+
: String(field.default), value: values[field.name] ?? '', onChange: (event) => setValues({ ...values, [field.name]: event.target.value }) }))] }, field.name))), credentialFields.length > 0 && (_jsxs("section", { className: "mc-inline-credentials", "aria-label": t('marivo.credentials.connection-credentials'), children: [_jsx("h4", { children: t('marivo.credentials.connection-credentials') }), _jsxs("p", { className: "mc-note", children: [t('marivo.credentials.enter-credential-values-directly-for-harness-to-save-references'), name ? t('marivo.credentials.leave-blank-to-keep-the-current-value-a-new') : ''] }), credentialFields.map((field) => (_jsxs("div", { className: "mc-credential-group", children: [credentials
|
|
148
|
+
.filter((row) => row.field === field.name)
|
|
149
|
+
.map((row) => {
|
|
150
|
+
const label = credentialLabel(field.name);
|
|
151
|
+
const reference = credentialReference(values.name ?? '', row);
|
|
152
|
+
return (_jsxs("div", { className: "mc-credential-entry", children: [row.key !== undefined && (_jsxs("label", { className: "mc-secret-input", children: [t('marivo.credentials.header-name'), _jsx("input", { "aria-label": t('marivo.credentials.header-name'), value: row.key, onChange: (event) => updateCredential(row.id, { key: event.target.value }) })] })), _jsxs("label", { className: "mc-secret-input", children: [_jsxs("span", { className: "mc-input-heading", children: [_jsxs("span", { className: "mc-input-name", children: [t(label), field.required ? ' *' : ''] }), _jsx("span", { className: "mc-input-description", children: row.existing
|
|
153
|
+
? t('marivo.credentials.a-reference-is-configured-leave-blank-to-keep-it')
|
|
154
|
+
: t('marivo.credentials.enter-the-value-directly-no-need-to-create-a') })] }), _jsx("input", { "aria-label": t(label), type: "password", autoComplete: "new-password", maxLength: 65536, required: field.required && !reference, value: row.value, placeholder: row.existing
|
|
155
|
+
? t('marivo.credentials.leave-blank-to-keep-enter-a-new-value-to')
|
|
156
|
+
: t('marivo.credentials.enter-credential-value'), onChange: (event) => updateCredential(row.id, { value: event.target.value }) })] }), _jsxs("details", { className: "mc-reference-options", children: [_jsx("summary", { children: t('marivo.credentials.credential-reference-review-or-change') }), _jsxs("label", { className: "mc-secret-input", children: [field.name, _jsx("input", { "aria-label": field.name, autoComplete: "off", value: reference, placeholder: t('marivo.credentials.generated-after-entering-a-credential-value'), onChange: (event) => updateCredential(row.id, { reference: event.target.value }) })] }), _jsx("p", { className: "mc-note", children: t('marivo.credentials.to-reuse-a-saved-credential-enter-its-reference-and') }), row.reference !== undefined && (_jsx("button", { type: "button", onClick: () => updateCredential(row.id, { reference: undefined }), children: t('marivo.credentials.restore-automatic-reference') }))] }), row.key !== undefined && (_jsx("button", { type: "button", className: "mc-quiet", onClick: () => setCredentials((rows) => rows.filter((item) => item.id !== row.id)), children: t('marivo.credentials.remove-header') }))] }, row.id));
|
|
157
|
+
}), field.type === 'json' && (_jsx("button", { type: "button", onClick: () => setCredentials((rows) => [
|
|
158
|
+
...rows,
|
|
159
|
+
{ id: crypto.randomUUID(), field: field.name, key: '', value: '' },
|
|
160
|
+
]), children: t('marivo.credentials.add-header-credential') }))] }, field.name)))] }))] }))] }), _jsx("p", { className: "mc-note", children: requestId
|
|
161
|
+
? t('marivo.credentials.save-configuration-and-credentials-then-test-the-connection-the')
|
|
162
|
+
: name
|
|
163
|
+
? t('marivo.credentials.test-the-connection-again-after-saving-name-and-engine')
|
|
164
|
+
: '' }), _jsxs("div", { className: "mc-form-footer", children: [_jsx("button", { className: "mc-primary", type: "submit", disabled: !backend || busy, children: busy
|
|
165
|
+
? t('marivo.credentials.saving-or-validating')
|
|
166
|
+
: requestId
|
|
167
|
+
? t('marivo.credentials.save-and-test-then-continue')
|
|
168
|
+
: name
|
|
169
|
+
? t('marivo.credentials.save-configuration')
|
|
170
|
+
: t('marivo.credentials.confirm-new-datasource') }), _jsx("button", { type: "button", onClick: close, disabled: busy, children: t('marivo.credentials.cancel') })] })] }));
|
|
171
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export function credentialReference(name, draft) {
|
|
2
|
+
if (draft.reference !== undefined)
|
|
3
|
+
return draft.reference.trim();
|
|
4
|
+
if (!draft.value)
|
|
5
|
+
return draft.existing ?? '';
|
|
6
|
+
const part = (value) => value
|
|
7
|
+
.toUpperCase()
|
|
8
|
+
.replace(/[^A-Z0-9_]/g, '_')
|
|
9
|
+
.slice(0, 40);
|
|
10
|
+
return `DS_${part(name) || 'CONNECTION'}_${part(draft.key ?? draft.field.replace(/_env$/, ''))}_${draft.id.replace(/-/g, '').toUpperCase()}`;
|
|
11
|
+
}
|
|
12
|
+
/** Values are kept separate from the public configuration, then handed to the credential operation. */
|
|
13
|
+
export function prepareCredentials(name, drafts) {
|
|
14
|
+
const fields = {};
|
|
15
|
+
const changes = Object.create(null);
|
|
16
|
+
for (const draft of drafts) {
|
|
17
|
+
const ref = credentialReference(name, draft);
|
|
18
|
+
if (!ref && !draft.value)
|
|
19
|
+
continue;
|
|
20
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(ref) ||
|
|
21
|
+
ref.length > 256 ||
|
|
22
|
+
/^(MARIVO_|DSH_DATA_ANALYSIS_|DSH_HOME$|DSH_SESSION_ID$|DSH_SESSION_JSONL$|DSH_SHELL$)/i.test(ref))
|
|
23
|
+
throw new Error('marivo.credentials.credential-references-may-contain-only-letters-numbers-and-underscores');
|
|
24
|
+
if (draft.key !== undefined) {
|
|
25
|
+
if (!draft.key.trim())
|
|
26
|
+
throw new Error('marivo.credentials.enter-the-header-name-for-this-credential');
|
|
27
|
+
fields[draft.field] ??= Object.create(null);
|
|
28
|
+
const mapping = fields[draft.field];
|
|
29
|
+
if (Object.hasOwn(mapping, draft.key))
|
|
30
|
+
throw new Error('marivo.credentials.header-names-must-be-unique');
|
|
31
|
+
mapping[draft.key] = ref;
|
|
32
|
+
}
|
|
33
|
+
else
|
|
34
|
+
fields[draft.field] = ref;
|
|
35
|
+
if (draft.value) {
|
|
36
|
+
if (changes[ref] !== undefined && changes[ref] !== draft.value)
|
|
37
|
+
throw new Error('marivo.credentials.the-same-credential-reference-cannot-have-different-values');
|
|
38
|
+
changes[ref] = draft.value;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return { fields, changes };
|
|
42
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function creationFieldValues(schema, backend) {
|
|
2
|
+
const defaults = schema.creationDefaults?.[backend] ?? {};
|
|
3
|
+
return Object.fromEntries((schema.backends.find((item) => item.name === backend)?.fields ?? [])
|
|
4
|
+
.filter((field) => !field.name.endsWith('_env') && Object.hasOwn(defaults, field.name))
|
|
5
|
+
.map((field) => [
|
|
6
|
+
field.name,
|
|
7
|
+
field.type === 'string'
|
|
8
|
+
? defaults[field.name]
|
|
9
|
+
: JSON.stringify(defaults[field.name]),
|
|
10
|
+
]));
|
|
11
|
+
}
|