@chengxianglibra/dsh-data-analysis 0.3.0 → 0.3.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.
Files changed (53) hide show
  1. package/README.en.md +7 -2
  2. package/README.md +7 -2
  3. package/assets/logo-horizontal.svg +42 -0
  4. package/assets/logo-mark.svg +20 -0
  5. package/lib/bin/environment.js +99 -76
  6. package/lib/client/analysis-ui/offline-runtime.generated.js +1 -1
  7. package/lib/client/credentials/install.js +42 -38
  8. package/lib/client/credentials/model.js +9 -0
  9. package/lib/client/credentials/report-publishing.js +84 -0
  10. package/lib/client/credentials/styles.js +2 -0
  11. package/lib/client/i18n/credentials.js +18 -0
  12. package/lib/client/i18n/presentation.js +24 -0
  13. package/lib/client/right-tabs/install.js +9 -1
  14. package/lib/client/right-tabs/report-actions.js +30 -4
  15. package/lib/client/right-tabs/report-publishing.js +107 -0
  16. package/lib/client/right-tabs/styles.js +3 -0
  17. package/lib/client/ui/styles.generated.js +3 -0
  18. package/lib/client-composition.js +202 -191
  19. package/lib/client.js +205 -194
  20. package/lib/datasource/resolver-program.js +4 -4
  21. package/lib/environment/binding.js +16 -23
  22. package/lib/environment/doctor.js +8 -1
  23. package/lib/environment/identity.js +32 -0
  24. package/lib/environment/project-preparation.js +5 -5
  25. package/lib/environment/runtime.js +15 -16
  26. package/lib/plugin-agents.js +6 -1
  27. package/lib/plugin.js +18 -0
  28. package/lib/presentation/rpc.js +19 -0
  29. package/lib/report-publishing/adapters.js +136 -0
  30. package/lib/report-publishing/config.js +58 -0
  31. package/lib/report-publishing/credential-contract.js +25 -0
  32. package/lib/report-publishing/service.js +133 -0
  33. package/lib/skill-build.generated.js +2 -2
  34. package/lib/types/client/credentials/install.d.ts +2 -1
  35. package/lib/types/client/credentials/model.d.ts +30 -0
  36. package/lib/types/client/credentials/report-publishing.d.ts +5 -0
  37. package/lib/types/client/credentials/styles.d.ts +1 -1
  38. package/lib/types/client/i18n/copy.d.ts +21 -0
  39. package/lib/types/client/i18n/credentials.d.ts +18 -0
  40. package/lib/types/client/i18n/presentation.d.ts +24 -0
  41. package/lib/types/client/right-tabs/report-actions.d.ts +5 -1
  42. package/lib/types/client/right-tabs/report-publishing.d.ts +21 -0
  43. package/lib/types/client/right-tabs/styles.d.ts +1 -1
  44. package/lib/types/environment/identity.d.ts +6 -0
  45. package/lib/types/plugin-agents.d.ts +2 -0
  46. package/lib/types/plugin.d.ts +4 -0
  47. package/lib/types/presentation/rpc.d.ts +1358 -0
  48. package/lib/types/report-publishing/adapters.d.ts +6 -0
  49. package/lib/types/report-publishing/config.d.ts +23 -0
  50. package/lib/types/report-publishing/credential-contract.d.ts +61 -0
  51. package/lib/types/report-publishing/service.d.ts +31 -0
  52. package/package.json +4 -1
  53. package/skills/dsh-data-analysis-files/references/delivery.md +3 -1
@@ -13,6 +13,7 @@ import { Input } from "../ui/primitives/input.js";
13
13
  import { TabsContent, TabsList, Tabs as TabsRoot, TabsTrigger } from "../ui/primitives/tabs.js";
14
14
  import { UIStyleSheet } from "../ui/theme.js";
15
15
  import { CredentialClientModel, credentialMessage } from "./model.js";
16
+ import { ReportPublishingCredentials } from "./report-publishing.js";
16
17
  import { credentialStyles } from "./styles.js";
17
18
  const statusLabels = {
18
19
  'awaiting-input': 'marivo.credentials.awaiting-input',
@@ -107,14 +108,16 @@ function CredentialForm({ context, request, state, model, workspaceId }) {
107
108
  }, children: t('marivo.credentials.confirm-saved-value-deletion') }), _jsx(Button, { variant: "outline", type: "button", onClick: () => setDeleting(''), children: t('marivo.credentials.keep') })] }) }))] }) }, ref));
108
109
  }) }) }))] }), _jsx(OperationOutcome, { entry: outcome })] }), !ended && (request || busy) && (_jsxs(CardFooter, { className: "mc-form-footer", children: [request && (_jsx(Button, { variant: "default", type: "button", disabled: busy, onClick: submit, children: t('marivo.credentials.submit-credentials-and-continue') })), request ? (_jsx(Button, { variant: "ghost", className: "mc-cancel", type: "button", onClick: () => void model.cancelRequest(request.id), children: t('marivo.credentials.cancel-this-round') })) : (busy && (_jsx(Button, { variant: "ghost", className: "mc-cancel", type: "button", onClick: () => void model.cancelOperation(context.token), children: t('marivo.credentials.cancel-operation') })))] })), ended && (_jsx(CardFooter, { className: "mc-form-footer", children: _jsx(Button, { variant: "outline", type: "button", onClick: () => model.show(workspaceId), children: t('marivo.credentials.back-to-datasource-management') }) }))] }));
109
110
  }
110
- export function CredentialPanel({ model, workspaces, onRefresh }) {
111
+ export function CredentialPanel({ model, workspaces, onRefresh, initialSection = 'datasources' }) {
111
112
  const t = useCopy();
112
113
  const formId = useId();
113
114
  const state = useSyncExternalStore(model.subscribe, model.getSnapshot);
115
+ const [section, setSection] = useState(initialSection);
114
116
  const columns = useRef(null);
115
117
  const navigation = useRef(null);
116
118
  const main = useRef(null);
117
119
  const request = state.requests.find((item) => item.id === state.requestId);
120
+ const activeSection = request || !state.workspaceId ? 'datasources' : section;
118
121
  const pending = state.requests.filter((item) => item.endedAt === undefined && item.sessionId === state.sessionId);
119
122
  const context = request
120
123
  ? request.context
@@ -139,7 +142,7 @@ export function CredentialPanel({ model, workspaces, onRefresh }) {
139
142
  else if (bounds.right > viewport.right)
140
143
  list.scrollLeft += bounds.right - viewport.right;
141
144
  }
142
- }, [context?.token]);
145
+ }, [context?.token, activeSection]);
143
146
  const workspaceId = request
144
147
  ? (workspaces.find((item) => item.sessionIds.includes(request.sessionId))?.workspaceId ?? '')
145
148
  : state.workspaceId;
@@ -149,42 +152,43 @@ export function CredentialPanel({ model, workspaces, onRefresh }) {
149
152
  if (!state.open)
150
153
  return null;
151
154
  const credentialForm = context && (_jsx(CredentialForm, { context: context, request: request, state: state, model: model, workspaceId: workspaceId }, context.token));
152
- return (_jsxs("section", { className: "mv-ui mc-panel", "aria-label": t('marivo.credentials.datasources-and-credentials'), children: [_jsx(UIStyleSheet, {}), _jsx("style", { children: credentialStyles }), _jsxs("div", { className: "rt-heading-row", children: [_jsx("h2", { className: "rt-heading", children: t('marivo.credentials.datasources-and-credentials') }), _jsx("div", { className: "mc-heading-actions", children: _jsx(IconButton, { variant: "ghost", className: "mc-icon-button", type: "button", title: t('marivo.credentials.refresh-datasources'), label: t('marivo.credentials.refresh-datasources'), disabled: state.loading, onClick: onRefresh ?? (() => void model.refreshDatasources()), children: _jsx(CredentialIcon, { name: "refresh" }) }) })] }), _jsx("p", { className: "rt-caption", children: workspaces.find((item) => item.workspaceId === workspaceId)?.title ?? workspaceId }), _jsxs("div", { className: "mc-shell", children: [(pending.length > 0 || request) && (_jsxs("div", { className: "mc-requests", children: [_jsx("span", { children: pending.length
153
- ? t('marivo.credentials.requests-in-this-session')
154
- : t('marivo.credentials.configuration-result') }), pending.map((item) => (_jsxs(Button, { variant: "outline", type: "button", onClick: () => model.openRequest(item.id), children: [item.context?.name, " \u00B7 ", t(statusLabels[item.status])] }, item.id))), request && (_jsx(Button, { variant: "ghost", type: "button", onClick: () => model.show(workspaceId), children: t('marivo.credentials.datasource-management') }))] })), _jsxs(TabsRoot, { className: "mc-columns", ref: columns, value: !request ? (context?.token ?? '') : '', activationMode: "manual", onValueChange: (token) => {
155
- model.select(token);
156
- }, children: [!request && (_jsx("aside", { className: "mc-nav", "aria-label": t('marivo.credentials.datasource-navigation'), children: _jsxs("div", { children: [_jsx("div", { className: "mc-nav-heading", children: _jsx("h3", { children: request
157
- ? t('marivo.credentials.requested-datasource')
158
- : t('marivo.credentials.datasource-value', { p0: datasources.length }) }) }), _jsx(TabsList, { variant: "line", className: "mc-datasources", ref: navigation, "aria-label": t('marivo.credentials.datasource-navigation'), children: datasources.map((item, index) => {
159
- const count = item.refs.filter((ref) => item.credentials[ref]?.configured).length;
160
- const ready = count === item.refs.length;
161
- const running = state.operations.some((entry) => entry.handle.scope === item.token);
162
- const status = item.refs.length === 0
163
- ? t('marivo.credentials.no-credentials-required')
164
- : ready
165
- ? t('marivo.credentials.all-credentials-configured')
166
- : t('marivo.credentials.value-value-configured', {
167
- p0: count,
168
- p1: item.refs.length,
169
- });
170
- const description = running
171
- ? `${status} · ${t('marivo.credentials.processing')}`
172
- : status;
173
- const statusId = `${formId}-datasource-status-${index}`;
174
- return (_jsxs(TabsTrigger, { value: item.token, className: "mc-datasource", type: "button", title: item.name, "aria-label": t('marivo.credentials.select-datasource-value', {
175
- p0: item.name,
176
- }), "aria-describedby": statusId, children: [_jsx(CredentialIcon, { name: "database" }), _jsxs("span", { className: "mc-datasource-copy", children: [_jsx("span", { className: "mc-datasource-name", children: item.name }), _jsx("span", { className: "mc-dot", "data-ready": ready, title: description, "aria-hidden": "true" })] }), _jsx("span", { id: statusId, className: "mc-visually-hidden", children: description })] }, item.token));
177
- }) })] }) })), _jsxs("main", { className: "mc-main", ref: main, children: [state.error && (_jsx(Alert, { role: "alert", className: "mc-alert", children: _jsx(AlertDescription, { children: t(state.error) }) })), state.loading && (_jsx("p", { className: "mc-loading", role: "status", children: t('marivo.credentials.loading-datasources-and-credential-status') })), !request && !state.loading && !context && !state.error && (_jsx(Empty, { className: "mc-empty", children: _jsxs(EmptyDescription, { children: [_jsx(CredentialIcon, { name: "database", size: 32 }), _jsx("h3", { children: !state.workspaceId
178
- ? t('marivo.credentials.this-session-is-not-bound-to-a-workspace')
179
- : state.datasources.length === 0
180
- ? t('marivo.credentials.no-datasources')
181
- : t('marivo.credentials.select-a-datasource') }), _jsx("p", { children: !state.workspaceId
182
- ? t('marivo.credentials.return-to-the-session-and-retry')
183
- : state.datasources.length === 0
184
- ? t('marivo.credentials.this-workspace-has-no-defined-datasources')
185
- : t('marivo.credentials.view-credential-configuration-and-the-latest-connection-test') })] }) })), request
186
- ? credentialForm
187
- : context && _jsx(TabsContent, { value: context.token, children: credentialForm }), operations.length > 0 && (_jsxs("section", { className: "mc-activity", "aria-label": t('marivo.credentials.credential-operations-in-progress'), children: [_jsxs("p", { className: "mc-activity-heading", children: [t('marivo.credentials.in-progress'), operations.length] }), _jsx("div", { className: "mc-activity-list", children: operations.map((entry) => (_jsxs(Button, { variant: "outline", type: "button", "aria-pressed": state.handle?.id === entry.handle.id, onClick: () => model.selectOperation(entry.handle.id), children: [entry.name, " ", t('marivo.credentials.processing-177')] }, entry.handle.id))) }), activeOperation?.status === 'running' && (_jsxs("div", { className: "mc-progress", children: [_jsxs("p", { children: [state.operations.find((entry) => entry.handle.id === state.handle?.id)?.name, ' ', "\u00B7 ", t('marivo.credentials.saving')] }), _jsx(Button, { variant: "outline", type: "button", onClick: () => void model.cancelOperation(), children: t('marivo.credentials.cancel-this-operation') }), activeOperation.saved.length > 0 && (_jsxs("p", { children: [t('marivo.credentials.saved'), activeOperation.saved.join('、'), "\u3002"] }))] }))] }))] })] })] })] }));
155
+ return (_jsxs("section", { className: "mv-ui mc-panel", "aria-label": t('marivo.credentials.datasources-and-credentials'), children: [_jsx(UIStyleSheet, {}), _jsx("style", { children: credentialStyles }), _jsxs("div", { className: "rt-heading-row", children: [_jsx("h2", { className: "rt-heading", children: t('marivo.credentials.datasources-and-credentials') }), activeSection === 'datasources' && (_jsx("div", { className: "mc-heading-actions", children: _jsx(IconButton, { variant: "ghost", className: "mc-icon-button", type: "button", title: t('marivo.credentials.refresh-datasources'), label: t('marivo.credentials.refresh-datasources'), disabled: state.loading, onClick: onRefresh ?? (() => void model.refreshDatasources()), children: _jsx(CredentialIcon, { name: "refresh" }) }) }))] }), _jsx("p", { className: "rt-caption", children: workspaces.find((item) => item.workspaceId === workspaceId)?.title ?? workspaceId }), _jsxs(TabsRoot, { className: "mc-shell", value: activeSection, onValueChange: setSection, children: [!request && (_jsxs(TabsList, { "aria-label": t('marivo.credentials.datasources-and-credentials'), children: [_jsx(TabsTrigger, { value: "datasources", children: t('marivo.credentials.datasources-tab') }), _jsx(TabsTrigger, { value: "credentials", disabled: !workspaceId, children: t('marivo.credentials.credential-management-tab') })] })), _jsxs(TabsContent, { value: "datasources", className: "mc-directory-content", children: [(pending.length > 0 || request) && (_jsxs("div", { className: "mc-requests", children: [_jsx("span", { children: pending.length
156
+ ? t('marivo.credentials.requests-in-this-session')
157
+ : t('marivo.credentials.configuration-result') }), pending.map((item) => (_jsxs(Button, { variant: "outline", type: "button", onClick: () => model.openRequest(item.id), children: [item.context?.name, " \u00B7 ", t(statusLabels[item.status])] }, item.id))), request && (_jsx(Button, { variant: "ghost", type: "button", onClick: () => model.show(workspaceId), children: t('marivo.credentials.datasource-management') }))] })), _jsxs(TabsRoot, { className: "mc-columns", ref: columns, value: !request ? (context?.token ?? '') : '', activationMode: "manual", onValueChange: (token) => {
158
+ model.select(token);
159
+ }, children: [!request && (_jsx("aside", { className: "mc-nav", "aria-label": t('marivo.credentials.datasource-navigation'), children: _jsxs("div", { children: [_jsx("div", { className: "mc-nav-heading", children: _jsx("h3", { children: request
160
+ ? t('marivo.credentials.requested-datasource')
161
+ : t('marivo.credentials.datasource-value', { p0: datasources.length }) }) }), _jsx(TabsList, { variant: "line", className: "mc-datasources", ref: navigation, "aria-label": t('marivo.credentials.datasource-navigation'), children: datasources.map((item, index) => {
162
+ const count = item.refs.filter((ref) => item.credentials[ref]?.configured).length;
163
+ const ready = count === item.refs.length;
164
+ const running = state.operations.some((entry) => entry.handle.scope === item.token);
165
+ const status = item.refs.length === 0
166
+ ? t('marivo.credentials.no-credentials-required')
167
+ : ready
168
+ ? t('marivo.credentials.all-credentials-configured')
169
+ : t('marivo.credentials.value-value-configured', {
170
+ p0: count,
171
+ p1: item.refs.length,
172
+ });
173
+ const description = running
174
+ ? `${status} · ${t('marivo.credentials.processing')}`
175
+ : status;
176
+ const statusId = `${formId}-datasource-status-${index}`;
177
+ return (_jsxs(TabsTrigger, { value: item.token, className: "mc-datasource", type: "button", title: item.name, "aria-label": t('marivo.credentials.select-datasource-value', {
178
+ p0: item.name,
179
+ }), "aria-describedby": statusId, children: [_jsx(CredentialIcon, { name: "database" }), _jsxs("span", { className: "mc-datasource-copy", children: [_jsx("span", { className: "mc-datasource-name", children: item.name }), _jsx("span", { className: "mc-dot", "data-ready": ready, title: description, "aria-hidden": "true" })] }), _jsx("span", { id: statusId, className: "mc-visually-hidden", children: description })] }, item.token));
180
+ }) })] }) })), _jsxs("main", { className: "mc-main", ref: main, children: [state.error && (_jsx(Alert, { role: "alert", className: "mc-alert", children: _jsx(AlertDescription, { children: t(state.error) }) })), state.loading && (_jsx("p", { className: "mc-loading", role: "status", children: t('marivo.credentials.loading-datasources-and-credential-status') })), !request && !state.loading && !context && !state.error && (_jsx(Empty, { className: "mc-empty", children: _jsxs(EmptyDescription, { children: [_jsx(CredentialIcon, { name: "database", size: 32 }), _jsx("h3", { children: !state.workspaceId
181
+ ? t('marivo.credentials.this-session-is-not-bound-to-a-workspace')
182
+ : state.datasources.length === 0
183
+ ? t('marivo.credentials.no-datasources')
184
+ : t('marivo.credentials.select-a-datasource') }), _jsx("p", { children: !state.workspaceId
185
+ ? t('marivo.credentials.return-to-the-session-and-retry')
186
+ : state.datasources.length === 0
187
+ ? t('marivo.credentials.this-workspace-has-no-defined-datasources')
188
+ : t('marivo.credentials.view-credential-configuration-and-the-latest-connection-test') })] }) })), request
189
+ ? credentialForm
190
+ : context && _jsx(TabsContent, { value: context.token, children: credentialForm }), operations.length > 0 && (_jsxs("section", { className: "mc-activity", "aria-label": t('marivo.credentials.credential-operations-in-progress'), children: [_jsxs("p", { className: "mc-activity-heading", children: [t('marivo.credentials.in-progress'), operations.length] }), _jsx("div", { className: "mc-activity-list", children: operations.map((entry) => (_jsxs(Button, { variant: "outline", type: "button", "aria-pressed": state.handle?.id === entry.handle.id, onClick: () => model.selectOperation(entry.handle.id), children: [entry.name, " ", t('marivo.credentials.processing-177')] }, entry.handle.id))) }), activeOperation?.status === 'running' && (_jsxs("div", { className: "mc-progress", children: [_jsxs("p", { children: [state.operations.find((entry) => entry.handle.id === state.handle?.id)
191
+ ?.name, ' ', "\u00B7 ", t('marivo.credentials.saving')] }), _jsx(Button, { variant: "outline", type: "button", onClick: () => void model.cancelOperation(), children: t('marivo.credentials.cancel-this-operation') }), activeOperation.saved.length > 0 && (_jsxs("p", { children: [t('marivo.credentials.saved'), activeOperation.saved.join('、'), "\u3002"] }))] }))] }))] })] })] }), _jsx(TabsContent, { value: "credentials", className: "mc-directory-content", children: activeSection === 'credentials' && (_jsx(ReportPublishingCredentials, { model: model, workspaceId: workspaceId }, workspaceId)) })] })] }));
188
192
  }
189
193
  export function installCredentials(ctx, rpc, changes) {
190
194
  let storage;
@@ -1,5 +1,6 @@
1
1
  import { credentialRevisionSchema } from "../../datasource/changes-contract.js";
2
2
  import { PendingTasks } from "../../lifecycle.js";
3
+ import { publishingCredentialResultSchema, REPORT_PUBLISHING_CHANNEL, } from "../../report-publishing/credential-contract.js";
3
4
  import { errorMessage } from "./../i18n/copy.js";
4
5
  const CHANNEL = '/dsh-data-analysis-credentials';
5
6
  const STORAGE_KEY = 'marivo-credential-operation';
@@ -118,6 +119,14 @@ export class CredentialClientModel {
118
119
  throw new CredentialResponseError(result.error?.message ?? '');
119
120
  return result.value;
120
121
  }
122
+ async publishingCredentials(workspaceId, { endpoint, ...input }, signal) {
123
+ const response = await this.#rpc.call(REPORT_PUBLISHING_CHANNEL, endpoint, { workspaceId, ...input }, signal);
124
+ signal.throwIfAborted();
125
+ const result = publishingCredentialResultSchema.parse(response);
126
+ if (!result.ok)
127
+ throw new CredentialResponseError(result.error.message);
128
+ return result.value;
129
+ }
121
130
  async show(workspaceId, selectedToken) {
122
131
  this.#suspended = false;
123
132
  this.#patch({ open: true, requestId: '' });
@@ -0,0 +1,84 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useId, useRef, useState } from 'react';
3
+ import { useCopy } from "../i18n/context.js";
4
+ import { Button } from "../ui/controls.js";
5
+ import { Alert, AlertDescription } from "../ui/primitives/alert.js";
6
+ import { Badge } from "../ui/primitives/badge.js";
7
+ import { Card, CardAction, CardContent, CardDescription, CardHeader, CardTitle, } from "../ui/primitives/card.js";
8
+ import { Field, FieldGroup, FieldLabel, FieldSet } from "../ui/primitives/field.js";
9
+ import { Input } from "../ui/primitives/input.js";
10
+ const labels = {
11
+ accessKeyId: 'marivo.credentials.scoped-access-key',
12
+ secretAccessKey: 'marivo.credentials.scoped-secret-key',
13
+ sessionToken: 'marivo.credentials.scoped-session-token',
14
+ };
15
+ export function ReportPublishingCredentials({ model, workspaceId, }) {
16
+ const t = useCopy();
17
+ const id = useId();
18
+ const [view, setView] = useState();
19
+ const [editor, setEditor] = useState();
20
+ const [busy, setBusy] = useState(true);
21
+ const [error, setError] = useState('');
22
+ const current = useRef();
23
+ const run = useCallback(async (request) => {
24
+ if (current.current)
25
+ return;
26
+ const controller = new AbortController();
27
+ current.current = controller;
28
+ setEditor(undefined);
29
+ setBusy(true);
30
+ setError('');
31
+ try {
32
+ const next = await model.publishingCredentials(workspaceId, request, controller.signal);
33
+ if (!controller.signal.aborted)
34
+ setView(next);
35
+ }
36
+ catch (failure) {
37
+ if (!controller.signal.aborted) {
38
+ setView(undefined);
39
+ setError(failure instanceof Error &&
40
+ 'code' in failure &&
41
+ failure.code === 'report-publishing-config-changed'
42
+ ? 'marivo.credentials.publishing-config-changed'
43
+ : 'marivo.credentials.publishing-credential-unconfirmed');
44
+ }
45
+ }
46
+ finally {
47
+ if (current.current === controller) {
48
+ current.current = undefined;
49
+ setBusy(false);
50
+ }
51
+ }
52
+ }, [model, workspaceId]);
53
+ useEffect(() => {
54
+ void run({ endpoint: 'describe' });
55
+ return () => {
56
+ current.current?.abort();
57
+ current.current = undefined;
58
+ };
59
+ }, [run]);
60
+ function save() {
61
+ if (!view?.enabled || !editor)
62
+ return;
63
+ const { configId } = view;
64
+ const { field, action } = editor;
65
+ if (action === 'unset')
66
+ void run({ endpoint: 'unset', configId, field });
67
+ else if (editor.value)
68
+ void run({ endpoint: 'set', configId, field, value: editor.value });
69
+ }
70
+ return (_jsxs(Card, { className: "mc-fields", "aria-labelledby": id, children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { id: id, role: "heading", "aria-level": 3, children: t('marivo.credentials.report-publishing-credentials') }), view?.enabled && (_jsxs(CardDescription, { className: "break-words", children: [view.name, " \u00B7 ", view.bucket] })), _jsx(CardAction, { children: _jsx(Button, { variant: "outline", type: "button", disabled: busy, onClick: () => void run({ endpoint: 'describe' }), children: t('marivo.credentials.refresh-credential-status') }) })] }), _jsxs(CardContent, { "aria-busy": busy, children: [error && (_jsx(Alert, { role: "alert", className: "mc-alert", children: _jsx(AlertDescription, { children: t(error) }) })), busy && !view && (_jsx("p", { role: "status", children: t('marivo.credentials.loading-publishing-credentials') })), view?.enabled === false && (_jsx("p", { className: "mc-note", children: t('marivo.credentials.publishing-disabled') })), view?.enabled &&
71
+ view.fields.map((item) => {
72
+ const active = editor?.field === item.field ? editor : undefined;
73
+ const inputId = `${id}-${item.field}`;
74
+ return (_jsx(FieldSet, { className: "mc-field", "aria-label": item.reference, disabled: busy, children: _jsxs(FieldGroup, { children: [_jsxs("div", { className: "mc-field-header", children: [_jsxs("div", { className: "mc-field-title", children: [_jsx("h4", { children: item.reference }), _jsxs("p", { children: [t(labels[item.field]), " ", t('marivo.credentials.source'), item.source ?? t('marivo.credentials.none'), !item.writable && t('marivo.credentials.read-only-source')] })] }), _jsxs("div", { className: "mc-field-actions", children: [_jsx(Badge, { className: "mc-badge", "data-ready": item.configured, children: t(item.configured
75
+ ? 'marivo.credentials.configured'
76
+ : 'marivo.credentials.not-configured') }), item.writable && !active && (_jsx(Button, { variant: "outline", type: "button", onClick: () => setEditor({ action: 'set', field: item.field, value: '' }), children: t(item.configured
77
+ ? 'marivo.credentials.replace'
78
+ : 'marivo.credentials.add-credential') })), item.writable && item.configured && !active && (_jsx(Button, { variant: "destructive", type: "button", onClick: () => setEditor({ action: 'unset', field: item.field }), children: t('marivo.credentials.delete-saved-value') }))] })] }), active?.action === 'set' && (_jsxs(Field, { children: [_jsx(FieldLabel, { htmlFor: inputId, children: t('marivo.credentials.new-value') }), _jsx(Input, { id: inputId, type: "password", autoComplete: "new-password", maxLength: 65_536, value: active.value, onChange: (event) => setEditor({ ...active, value: event.target.value }) }), _jsxs("div", { className: "mc-field-actions", children: [_jsx(Button, { type: "button", disabled: !active.value, onClick: save, children: t(item.configured
79
+ ? 'marivo.credentials.confirm-replacement'
80
+ : 'marivo.credentials.add-credential') }), _jsx(Button, { variant: "outline", type: "button", onClick: () => setEditor(undefined), children: t('marivo.credentials.cancel') })] })] })), active?.action === 'unset' && (_jsxs("div", { className: "mc-confirm", children: [_jsx("p", { children: t('marivo.credentials.delete-shared-publishing-credential', {
81
+ p0: item.reference,
82
+ }) }), _jsxs("div", { className: "mc-field-actions", children: [_jsx(Button, { variant: "destructive", type: "button", onClick: save, children: t('marivo.credentials.confirm-saved-value-deletion') }), _jsx(Button, { variant: "outline", type: "button", onClick: () => setEditor(undefined), children: t('marivo.credentials.keep') })] })] }))] }) }, item.field));
83
+ })] })] }));
84
+ }
@@ -2,6 +2,8 @@
2
2
  export const credentialStyles = `
3
3
  .mc-panel{min-width:0;width:100%;container-type:inline-size;--mc-accent:var(--dsw-alias-state-business-primary,#075bea)}
4
4
  .mc-shell,.mc-columns,.mc-main,.mc-form{display:flex;flex-direction:column;min-width:0}
5
+ .mc-directory-content{min-width:0}
6
+ .mc-directory-content>.mc-requests{margin-bottom:1rem}
5
7
  .mc-shell,.mc-columns,.mc-form-body{gap:1.5rem}
6
8
  .mc-form{gap:1rem;flex:1}
7
9
  .mc-form-body{display:flex;flex-direction:column;flex:1}
@@ -1,5 +1,14 @@
1
1
  /** Plugin-owned copy; keys are stable identifiers, never business labels. */
2
2
  export const zh = {
3
+ 'marivo.credentials.datasources-tab': '数据源',
4
+ 'marivo.credentials.credential-management-tab': '凭证管理',
5
+ 'marivo.credentials.report-publishing-credentials': '报告发布凭证',
6
+ 'marivo.credentials.refresh-credential-status': '刷新凭证状态',
7
+ 'marivo.credentials.loading-publishing-credentials': '正在读取发布凭证状态…',
8
+ 'marivo.credentials.publishing-disabled': '当前 Host 尚未启用对象存储发布。',
9
+ 'marivo.credentials.publishing-config-changed': '发布配置已更新,请刷新状态后重新填写。',
10
+ 'marivo.credentials.publishing-credential-unconfirmed': '凭证操作结果未确认,请刷新状态后重试。',
11
+ 'marivo.credentials.delete-shared-publishing-credential': '删除 {p0} 的已保存值?当前 Host 中使用此引用的 Workspace 都会受到影响。',
3
12
  'marivo.credentials.basic-settings': '基本配置',
4
13
  'marivo.credentials.advanced-settings': '高级设置',
5
14
  'marivo.credentials.autocommit-required': 'MySQL 和 PostgreSQL 仅支持默认或启用自动提交;请选择默认或 true。',
@@ -271,6 +280,15 @@ export const zh = {
271
280
  'marivo.credentials.save-failed': '保存配置失败。',
272
281
  };
273
282
  export const en = {
283
+ 'marivo.credentials.datasources-tab': 'Datasources',
284
+ 'marivo.credentials.credential-management-tab': 'Credential management',
285
+ 'marivo.credentials.report-publishing-credentials': 'Report publishing credentials',
286
+ 'marivo.credentials.refresh-credential-status': 'Refresh credential status',
287
+ 'marivo.credentials.loading-publishing-credentials': 'Loading publishing credential status…',
288
+ 'marivo.credentials.publishing-disabled': 'Object storage publishing is disabled on this Host.',
289
+ 'marivo.credentials.publishing-config-changed': 'Publishing configuration changed. Refresh the status and enter the credential again.',
290
+ 'marivo.credentials.publishing-credential-unconfirmed': 'Credential operation unconfirmed. Refresh the status before retrying.',
291
+ 'marivo.credentials.delete-shared-publishing-credential': 'Delete the saved value for {p0}? This affects every Workspace using this reference on the current Host.',
274
292
  'marivo.credentials.basic-settings': 'Basic configuration',
275
293
  'marivo.credentials.advanced-settings': 'Advanced settings',
276
294
  'marivo.credentials.autocommit-required': 'MySQL and PostgreSQL only support the default or enabled autocommit; choose default or true.',
@@ -162,6 +162,18 @@ export const zh = {
162
162
  'marivo.presentation.export-html-started': 'HTML 下载已开始。',
163
163
  'marivo.presentation.export-html-failed': '导出失败,请重试。',
164
164
  'marivo.presentation.export-html-dismiss': '关闭导出提示',
165
+ 'marivo.presentation.report-delivery-checking': '正在读取导出设置…',
166
+ 'marivo.presentation.report-delivery-unavailable': '导出设置不可用,重新打开菜单重试',
167
+ 'marivo.presentation.publish-report': '发布到对象存储',
168
+ 'marivo.presentation.publishing-report': '正在发布…',
169
+ 'marivo.presentation.publish-report-success': '报告已上传到对象存储。',
170
+ 'marivo.presentation.publish-report-credentials-missing': '缺少发布凭据。补齐后可重新发布。',
171
+ 'marivo.presentation.publish-report-config-changed': '发布配置已变更,请重新打开菜单。',
172
+ 'marivo.presentation.publish-report-unconfirmed': '上传结果未确认,请检查对象存储或重试。',
173
+ 'marivo.presentation.publish-report-failed': '无法发布报告,请重试。',
174
+ 'marivo.presentation.open-published-report': '查看已发布报告',
175
+ 'marivo.presentation.open-publishing-credentials': '前往凭证管理',
176
+ 'marivo.presentation.publish-report-dismiss': '关闭发布提示',
165
177
  'marivo.presentation.ui-open-tab': '在右侧打开分析视图',
166
178
  'marivo.presentation.ui-loading': '正在读取已保存快照…',
167
179
  'marivo.presentation.ui-preparing': '正在准备分析视图…',
@@ -398,6 +410,18 @@ export const en = {
398
410
  'marivo.presentation.export-html-started': 'HTML download started.',
399
411
  'marivo.presentation.export-html-failed': 'Export failed. Try again.',
400
412
  'marivo.presentation.export-html-dismiss': 'Dismiss export notification',
413
+ 'marivo.presentation.report-delivery-checking': 'Checking export settings…',
414
+ 'marivo.presentation.report-delivery-unavailable': 'Export settings unavailable. Reopen the menu to retry.',
415
+ 'marivo.presentation.publish-report': 'Publish to object storage',
416
+ 'marivo.presentation.publishing-report': 'Publishing…',
417
+ 'marivo.presentation.publish-report-success': 'Report uploaded to object storage.',
418
+ 'marivo.presentation.publish-report-credentials-missing': 'Publishing credentials are missing. Add them and retry.',
419
+ 'marivo.presentation.publish-report-config-changed': 'Publishing configuration changed. Reopen the menu.',
420
+ 'marivo.presentation.publish-report-unconfirmed': 'Upload unconfirmed. Check object storage or retry.',
421
+ 'marivo.presentation.publish-report-failed': 'Could not publish the report. Try again.',
422
+ 'marivo.presentation.open-published-report': 'Open published report',
423
+ 'marivo.presentation.open-publishing-credentials': 'Go to credential management',
424
+ 'marivo.presentation.publish-report-dismiss': 'Dismiss publishing notification',
401
425
  'marivo.presentation.ui-open-tab': 'Open analysis view in sidebar',
402
426
  'marivo.presentation.ui-loading': 'Reading saved snapshot…',
403
427
  'marivo.presentation.ui-preparing': 'Preparing analysis view…',
@@ -389,7 +389,15 @@ export function installRightTabs(ctx, { diagnostics = false } = {}) {
389
389
  setCleanupRetrying(false);
390
390
  }
391
391
  }, children: t('marivo.presentation.delete-report-retry-cleanup') })), cleanupError && _jsx("p", { children: t('marivo.presentation.delete-report-cleanup-failed') })] })) : (_jsxs(_Fragment, { children: [page.target.kind === 'reports' && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "rt-heading-row", children: [_jsx("h2", { className: "rt-heading", children: t(labels[page.target.kind]) }), page.target.kind === 'reports' && (_jsx(Button, { variant: "outline", type: "button", disabled: catalog.loading, onClick: () => void page.refresh(), children: t('marivo.presentation.refresh') }))] }), _jsx("p", { className: "rt-caption", children: workspaces.find((w) => w.workspaceId === page.target.workspaceId)?.title ??
392
- page.target.workspaceId })] })), state.newer && (_jsx(Alert, { role: "status", className: "rt-notice", children: _jsxs(AlertDescription, { children: [t('marivo.navigation.a-newer-version-exists-the-current-view-is-unchanged'), _jsx(Button, { variant: "outline", type: "button", onClick: () => void page.refresh(), children: t('marivo.navigation.view-new-version') })] }) })), state.notice && _jsx("p", { role: "status", children: t(state.notice) }), page.target.kind === 'reports' && (_jsx(ReportCatalogView, { model: page.catalog, reader: catalogReader, sessions: sessions, onOpenSession: (id) => ctx.uiWorkspace.openSession(id) })), page.target.kind === 'semantic' && (_jsx(SemanticBrowserPanel, { model: page.semantic, onAsk: canAsk ? () => page.semantic.addToQuestion(ctx, page.sessionId) : undefined, workspaces: workspaces })), page.target.kind === 'datasources' && (_jsx(CredentialPanel, { model: page.datasources, workspaces: workspaces, onRefresh: () => void page.refresh() })), page.target.kind === 'report' && (_jsxs(_Fragment, { children: [report.loading && _jsx("p", { role: "status", children: t('marivo.navigation.loading-report') }), report.error && (_jsxs("div", { role: "alert", children: [_jsx("p", { children: t(report.error) }), _jsx(Button, { variant: "outline", type: "button", onClick: () => void page.refresh(), children: t('marivo.navigation.reload-report') })] })), report.historyOpen && (_jsx(ReportHistoryPanel, { state: report, model: historyModel, sessions: sessions, onOpenSession: (id) => ctx.uiWorkspace.openSession(id), onClose: () => void page.reader.toggleHistory() })), report.analysisDocument && (_jsxs("div", { className: "rt-report-surface", children: [!revisionInProgress && (_jsx("div", { className: "rt-toolbar", children: _jsx(ReportActions, { document: report.analysisDocument, onHistory: () => void page.reader.toggleHistory(), onDelete: (isCurrent) => deleteReport(ctx, rpc, page, tab, report.analysisDocument, isCurrent, deleted), refreshLocked: refreshLocks.has(page.sessionId), onRefreshData: (isCurrent) => refreshReport(page, tab, report.analysisDocument, isCurrent), isCurrent: () => !tab.signal.aborted &&
392
+ page.target.workspaceId })] })), state.newer && (_jsx(Alert, { role: "status", className: "rt-notice", children: _jsxs(AlertDescription, { children: [t('marivo.navigation.a-newer-version-exists-the-current-view-is-unchanged'), _jsx(Button, { variant: "outline", type: "button", onClick: () => void page.refresh(), children: t('marivo.navigation.view-new-version') })] }) })), state.notice && _jsx("p", { role: "status", children: t(state.notice) }), page.target.kind === 'reports' && (_jsx(ReportCatalogView, { model: page.catalog, reader: catalogReader, sessions: sessions, onOpenSession: (id) => ctx.uiWorkspace.openSession(id) })), page.target.kind === 'semantic' && (_jsx(SemanticBrowserPanel, { model: page.semantic, onAsk: canAsk ? () => page.semantic.addToQuestion(ctx, page.sessionId) : undefined, workspaces: workspaces })), page.target.kind === 'datasources' && (_jsx(CredentialPanel, { model: page.datasources, workspaces: workspaces, initialSection: tab.navigation.params?.credentialSection === 'credentials'
393
+ ? 'credentials'
394
+ : 'datasources', onRefresh: () => void page.refresh() }, tab.navigation.revision)), page.target.kind === 'report' && (_jsxs(_Fragment, { children: [report.loading && _jsx("p", { role: "status", children: t('marivo.navigation.loading-report') }), report.error && (_jsxs("div", { role: "alert", children: [_jsx("p", { children: t(report.error) }), _jsx(Button, { variant: "outline", type: "button", onClick: () => void page.refresh(), children: t('marivo.navigation.reload-report') })] })), report.historyOpen && (_jsx(ReportHistoryPanel, { state: report, model: historyModel, sessions: sessions, onOpenSession: (id) => ctx.uiWorkspace.openSession(id), onClose: () => void page.reader.toggleHistory() })), report.analysisDocument && (_jsxs("div", { className: "rt-report-surface", children: [!revisionInProgress && (_jsx("div", { className: "rt-toolbar", children: _jsx(ReportActions, { document: report.analysisDocument, rpc: rpc, signal: tab.signal, onOpenPublishingCredentials: () => act(page, () => {
395
+ const { workspaceId } = page.target;
396
+ check(page.sessionId, workspaceId);
397
+ tab.actions.openTab(directoryKind('datasources'), {
398
+ params: { workspaceId, credentialSection: 'credentials' },
399
+ });
400
+ }), onHistory: () => void page.reader.toggleHistory(), onDelete: (isCurrent) => deleteReport(ctx, rpc, page, tab, report.analysisDocument, isCurrent, deleted), refreshLocked: refreshLocks.has(page.sessionId), onRefreshData: (isCurrent) => refreshReport(page, tab, report.analysisDocument, isCurrent), isCurrent: () => !tab.signal.aborted &&
393
401
  !page.getSnapshot().error &&
394
402
  page.reader.getSnapshot().analysisDocument ===
395
403
  report.analysisDocument &&
@@ -6,11 +6,26 @@ import { useCopy } from "../i18n/context.js";
6
6
  import { Button, IconButton } from "../ui/controls.js";
7
7
  import { useThemedPortal } from "../ui/portal.js";
8
8
  import { useExportHtml } from "./export-html.js";
9
- export function ReportActions({ document, isCurrent, onHistory, refreshLocked, onRefreshData, onDelete, }) {
9
+ import { useReportPublishing } from "./report-publishing.js";
10
+ const deliveryLabels = {
11
+ checking: 'marivo.presentation.report-delivery-checking',
12
+ unavailable: 'marivo.presentation.report-delivery-unavailable',
13
+ download: 'marivo.presentation.export-html',
14
+ publish: 'marivo.presentation.publish-report',
15
+ };
16
+ export function ReportActions({ document, isCurrent, onHistory, refreshLocked, onRefreshData, onDelete, rpc, signal, onOpenPublishingCredentials, }) {
10
17
  const t = useCopy();
11
18
  const source = useRef(null);
12
19
  const container = useThemedPortal(source);
13
20
  const { status, download, dismiss } = useExportHtml({ document, isCurrent });
21
+ const publishing = useReportPublishing({ rpc, document, signal, isCurrent });
22
+ const publication = publishing.status;
23
+ const deliveryBusy = status === 'working' || publication?.kind === 'working';
24
+ const deliveryLabel = status === 'working'
25
+ ? 'marivo.presentation.exporting-html'
26
+ : publication?.kind === 'working'
27
+ ? 'marivo.presentation.publishing-report'
28
+ : deliveryLabels[publishing.mode];
14
29
  const [refreshStatus, setRefreshStatus] = useState();
15
30
  const pending = useRef(false);
16
31
  const [confirmDelete, setConfirmDelete] = useState(false);
@@ -48,7 +63,18 @@ export function ReportActions({ document, isCurrent, onHistory, refreshLocked, o
48
63
  pending.current = false;
49
64
  }
50
65
  };
51
- return (_jsxs(_Fragment, { children: [_jsxs(DropdownMenu.Root, { children: [_jsx("span", { ref: source, children: _jsx(DropdownMenu.Trigger, { asChild: true, children: _jsx(IconButton, { label: t('marivo.presentation.report-actions'), variant: "ghost", children: _jsx(Ellipsis, { "aria-hidden": "true" }) }) }) }), _jsx(DropdownMenu.Portal, { container: container, children: _jsxs(DropdownMenu.Content, { align: "end", sideOffset: 4, className: "rt-actions-menu", children: [_jsx(DropdownMenu.Item, { className: "rt-actions-item", disabled: refreshLocked, onSelect: () => void refresh(), children: t('marivo.presentation.refresh-data') }), _jsx(DropdownMenu.Item, { className: "rt-actions-item", onSelect: () => setConfirmDelete(true), children: t('marivo.presentation.delete-report') }), _jsx(DropdownMenu.Item, { className: "rt-actions-item", onSelect: onHistory, children: t('marivo.presentation.history') }), _jsx(DropdownMenu.Item, { className: "rt-actions-item", disabled: status === 'working', onSelect: () => void download(), children: t(status === 'working'
52
- ? 'marivo.presentation.exporting-html'
53
- : 'marivo.presentation.export-html') })] }) })] }), _jsx(Dialog.Root, { open: confirmDelete, onOpenChange: (open) => !deleting && setConfirmDelete(open), children: _jsxs(Dialog.Portal, { container: container, children: [_jsx(Dialog.Overlay, { className: "rt-dialog-overlay" }), _jsxs(Dialog.Content, { className: "rt-dialog", children: [_jsx(Dialog.Title, { children: t('marivo.presentation.delete-report-confirm') }), _jsxs(Dialog.Description, { children: [document.title, " \u2014 ", t('marivo.presentation.delete-report-description')] }), _jsxs("div", { className: "rt-dialog-actions", children: [_jsx(Button, { type: "button", variant: "outline", disabled: deleting, onClick: () => setConfirmDelete(false), children: t('marivo.presentation.delete-report-cancel') }), _jsx(Button, { type: "button", variant: "destructive", disabled: deleting, onClick: () => void remove(), children: t('marivo.presentation.delete-report') })] })] })] }) }), deleteStatus && (_jsxs("div", { className: "rt-export-notice rt-export-notice-error", role: "alert", children: [_jsx(CircleAlert, { "aria-hidden": "true" }), _jsx("span", { children: t(`marivo.presentation.delete-report-${deleteStatus}`) }), _jsx(IconButton, { label: t('marivo.presentation.delete-report-dismiss'), variant: "ghost", onClick: () => setDeleteStatus(undefined), children: _jsx(X, { "aria-hidden": "true" }) })] })), refreshStatus && (_jsxs("div", { className: `rt-export-notice${refreshStatus === 'accepted' ? '' : ' rt-export-notice-error'}`, children: [refreshStatus === 'accepted' ? (_jsx(CheckCircle2, { "aria-hidden": "true" })) : (_jsx(CircleAlert, { "aria-hidden": "true" })), _jsx("span", { role: refreshStatus === 'accepted' ? 'status' : 'alert', children: t(`marivo.presentation.refresh-data-${refreshStatus}`) }), _jsx(IconButton, { label: t('marivo.presentation.refresh-data-dismiss'), variant: "ghost", onClick: () => setRefreshStatus(undefined), children: _jsx(X, { "aria-hidden": "true" }) })] })), status === 'success' && (_jsxs("div", { className: "rt-export-notice", role: "status", children: [_jsx(CheckCircle2, { "aria-hidden": "true" }), t('marivo.presentation.export-html-started')] })), status === 'failed' && (_jsxs("div", { className: "rt-export-notice rt-export-notice-error", children: [_jsx(CircleAlert, { "aria-hidden": "true" }), _jsx("span", { role: "alert", children: t('marivo.presentation.export-html-failed') }), _jsx(IconButton, { label: t('marivo.presentation.export-html-dismiss'), variant: "ghost", onClick: dismiss, children: _jsx(X, { "aria-hidden": "true" }) })] }))] }));
66
+ return (_jsxs(_Fragment, { children: [_jsxs(DropdownMenu.Root, { onOpenChange: (open) => {
67
+ if (open)
68
+ void publishing.check();
69
+ }, children: [_jsx("span", { ref: source, children: _jsx(DropdownMenu.Trigger, { asChild: true, children: _jsx(IconButton, { label: t('marivo.presentation.report-actions'), variant: "ghost", children: _jsx(Ellipsis, { "aria-hidden": "true" }) }) }) }), _jsx(DropdownMenu.Portal, { container: container, children: _jsxs(DropdownMenu.Content, { align: "end", sideOffset: 4, className: "rt-actions-menu", children: [_jsx(DropdownMenu.Item, { className: "rt-actions-item", disabled: refreshLocked, onSelect: () => void refresh(), children: t('marivo.presentation.refresh-data') }), _jsx(DropdownMenu.Item, { className: "rt-actions-item", onSelect: () => setConfirmDelete(true), children: t('marivo.presentation.delete-report') }), _jsx(DropdownMenu.Item, { className: "rt-actions-item", onSelect: onHistory, children: t('marivo.presentation.history') }), _jsx(DropdownMenu.Item, { className: "rt-actions-item", disabled: deliveryBusy || publishing.mode === 'checking' || publishing.mode === 'unavailable', onSelect: () => {
70
+ if (publishing.mode === 'publish')
71
+ void publishing.publish();
72
+ else if (publishing.mode === 'download')
73
+ void download();
74
+ }, children: t(deliveryLabel) })] }) })] }), _jsx(Dialog.Root, { open: confirmDelete, onOpenChange: (open) => !deleting && setConfirmDelete(open), children: _jsxs(Dialog.Portal, { container: container, children: [_jsx(Dialog.Overlay, { className: "rt-dialog-overlay" }), _jsxs(Dialog.Content, { className: "rt-dialog", children: [_jsx(Dialog.Title, { children: t('marivo.presentation.delete-report-confirm') }), _jsxs(Dialog.Description, { children: [document.title, " \u2014 ", t('marivo.presentation.delete-report-description')] }), _jsxs("div", { className: "rt-dialog-actions", children: [_jsx(Button, { type: "button", variant: "outline", disabled: deleting, onClick: () => setConfirmDelete(false), children: t('marivo.presentation.delete-report-cancel') }), _jsx(Button, { type: "button", variant: "destructive", disabled: deleting, onClick: () => void remove(), children: t('marivo.presentation.delete-report') })] })] })] }) }), deleteStatus && (_jsxs("div", { className: "rt-export-notice rt-export-notice-error", role: "alert", children: [_jsx(CircleAlert, { "aria-hidden": "true" }), _jsx("span", { children: t(`marivo.presentation.delete-report-${deleteStatus}`) }), _jsx(IconButton, { label: t('marivo.presentation.delete-report-dismiss'), variant: "ghost", onClick: () => setDeleteStatus(undefined), children: _jsx(X, { "aria-hidden": "true" }) })] })), refreshStatus && (_jsxs("div", { className: `rt-export-notice${refreshStatus === 'accepted' ? '' : ' rt-export-notice-error'}`, children: [refreshStatus === 'accepted' ? (_jsx(CheckCircle2, { "aria-hidden": "true" })) : (_jsx(CircleAlert, { "aria-hidden": "true" })), _jsx("span", { role: refreshStatus === 'accepted' ? 'status' : 'alert', children: t(`marivo.presentation.refresh-data-${refreshStatus}`) }), _jsx(IconButton, { label: t('marivo.presentation.refresh-data-dismiss'), variant: "ghost", onClick: () => setRefreshStatus(undefined), children: _jsx(X, { "aria-hidden": "true" }) })] })), status === 'success' && (_jsxs("div", { className: "rt-export-notice", role: "status", children: [_jsx(CheckCircle2, { "aria-hidden": "true" }), t('marivo.presentation.export-html-started')] })), status === 'failed' && (_jsxs("div", { className: "rt-export-notice rt-export-notice-error", children: [_jsx(CircleAlert, { "aria-hidden": "true" }), _jsx("span", { role: "alert", children: t('marivo.presentation.export-html-failed') }), _jsx(IconButton, { label: t('marivo.presentation.export-html-dismiss'), variant: "ghost", onClick: dismiss, children: _jsx(X, { "aria-hidden": "true" }) })] })), publication && publication.kind !== 'working' && (_jsxs("div", { className: `rt-export-notice${publication.kind === 'success' ? '' : ' rt-export-notice-error'}`, children: [publication.kind === 'success' ? (_jsx(CheckCircle2, { "aria-hidden": "true" })) : (_jsx(CircleAlert, { "aria-hidden": "true" })), _jsxs("div", { className: "rt-publishing-details", children: [_jsx("span", { role: publication.kind === 'success' ? 'status' : 'alert', children: t(`marivo.presentation.publish-report-${publication.kind}`) }), publication.kind === 'success' && (_jsx("a", { href: publication.url, target: "_blank", rel: "noopener noreferrer", children: t('marivo.presentation.open-published-report') })), publication.kind === 'credentials-missing' && (_jsx(Button, { variant: "link", type: "button", onClick: () => {
75
+ if (!isCurrent())
76
+ return;
77
+ publishing.dismiss();
78
+ onOpenPublishingCredentials();
79
+ }, children: t('marivo.presentation.open-publishing-credentials') }))] }), _jsx(IconButton, { label: t('marivo.presentation.publish-report-dismiss'), variant: "ghost", onClick: publishing.dismiss, children: _jsx(X, { "aria-hidden": "true" }) })] }))] }));
54
80
  }
@@ -0,0 +1,107 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import { z } from 'zod';
3
+ import { savedReportRef } from "../../presentation/contracts/report-ref.js";
4
+ import { publishingCredentialResultSchema, REPORT_PUBLISHING_CHANNEL, } from "../../report-publishing/credential-contract.js";
5
+ const publicationResult = z.discriminatedUnion('ok', [
6
+ z.object({
7
+ ok: z.literal(true),
8
+ value: z.object({
9
+ url: z.url({ protocol: /^https?$/ }),
10
+ workspaceId: z.string(),
11
+ reportId: z.string(),
12
+ buildId: z.string(),
13
+ }),
14
+ }),
15
+ z.object({ ok: z.literal(false), error: z.object({ message: z.string() }) }),
16
+ ]);
17
+ const failures = {
18
+ 'report-publishing-credentials-missing': 'credentials-missing',
19
+ 'report-publishing-config-changed': 'config-changed',
20
+ 'report-publishing-disabled': 'config-changed',
21
+ 'report-publishing-upload-unconfirmed': 'unconfirmed',
22
+ };
23
+ export function useReportPublishing({ rpc, document, signal, isCurrent, }) {
24
+ const [config, setConfig] = useState();
25
+ const [status, setStatus] = useState();
26
+ const active = useRef();
27
+ useEffect(() => () => active.current?.abort(), []);
28
+ const { workspaceId, reportId, buildId } = document;
29
+ async function run(operation) {
30
+ if (active.current || signal.aborted || !isCurrent())
31
+ return;
32
+ const controller = new AbortController();
33
+ active.current = controller;
34
+ try {
35
+ await operation(AbortSignal.any([signal, controller.signal]));
36
+ }
37
+ finally {
38
+ active.current = undefined;
39
+ }
40
+ }
41
+ const canUpdate = (operationSignal) => !operationSignal.aborted && isCurrent();
42
+ async function check() {
43
+ await run(async (operationSignal) => {
44
+ setConfig(undefined);
45
+ try {
46
+ const response = publishingCredentialResultSchema.parse(await rpc.call(REPORT_PUBLISHING_CHANNEL, 'describe', { workspaceId }, operationSignal));
47
+ if (canUpdate(operationSignal))
48
+ setConfig(response.ok ? response.value : 'unavailable');
49
+ }
50
+ catch {
51
+ if (canUpdate(operationSignal))
52
+ setConfig('unavailable');
53
+ }
54
+ });
55
+ }
56
+ async function publish() {
57
+ if (!config || config === 'unavailable' || !config.enabled)
58
+ return;
59
+ await run(async (operationSignal) => {
60
+ if (config.fields.some((field) => field.required && !field.configured)) {
61
+ setStatus({ kind: 'credentials-missing' });
62
+ return;
63
+ }
64
+ setStatus({ kind: 'working' });
65
+ try {
66
+ const response = publicationResult.parse(await rpc.call(REPORT_PUBLISHING_CHANNEL, 'publish', {
67
+ workspaceId,
68
+ configId: config.configId,
69
+ ref: savedReportRef({ workspaceId, reportId, buildId }),
70
+ }, operationSignal));
71
+ if (!canUpdate(operationSignal))
72
+ return;
73
+ if (!response.ok) {
74
+ const code = response.error.message;
75
+ setStatus({
76
+ kind: Object.hasOwn(failures, code)
77
+ ? failures[code]
78
+ : 'failed',
79
+ });
80
+ return;
81
+ }
82
+ const receipt = response.value;
83
+ if (receipt.workspaceId !== workspaceId ||
84
+ receipt.reportId !== reportId ||
85
+ receipt.buildId !== buildId)
86
+ throw new Error('publication-owner-mismatch');
87
+ setStatus({ kind: 'success', url: receipt.url });
88
+ }
89
+ catch {
90
+ if (canUpdate(operationSignal))
91
+ setStatus({ kind: 'unconfirmed' });
92
+ }
93
+ finally {
94
+ if (!operationSignal.aborted)
95
+ setStatus((current) => (current?.kind === 'working' ? undefined : current));
96
+ }
97
+ });
98
+ }
99
+ const mode = config === 'unavailable'
100
+ ? 'unavailable'
101
+ : config?.enabled
102
+ ? 'publish'
103
+ : config
104
+ ? 'download'
105
+ : 'checking';
106
+ return { mode, status, check, publish, dismiss: () => setStatus(undefined) };
107
+ }
@@ -22,6 +22,9 @@ export const rightTabStyles = `
22
22
  .rt-export-notice>svg{width:1rem;height:1rem;flex:none;color:var(--primary)}
23
23
  .rt-export-notice-error>svg{color:var(--destructive)}
24
24
  .rt-export-notice button{width:1.75rem;height:1.75rem;flex:none;margin:-.375rem -.375rem -.375rem 0}
25
+ .rt-publishing-details{display:flex;flex-direction:column;align-items:flex-start;gap:.25rem;min-width:0;overflow-wrap:anywhere}
26
+ .rt-publishing-details a{color:var(--primary);text-decoration:underline}
27
+ .rt-publishing-details button{width:auto;height:auto;margin:0;padding:0;white-space:normal;text-align:start}
25
28
  .rt-notice{display:flex;align-items:center;flex-wrap:wrap;gap:.75rem;margin-bottom:1rem}
26
29
  .rt-page .pd-catalog{padding:0;overflow:auto}
27
30
  .rt-page .pd-history{position:static;max-height:300px;margin-bottom:1rem;width:100%}
@@ -686,6 +686,9 @@ export const COMPILED_UI_STYLES = `/*! tailwindcss v4.3.4 | MIT License | https:
686
686
  :where(.mv-ui, .mv-ui *).text-balance {
687
687
  text-wrap: balance;
688
688
  }
689
+ :where(.mv-ui, .mv-ui *).break-words {
690
+ overflow-wrap: break-word;
691
+ }
689
692
  :where(.mv-ui, .mv-ui *).whitespace-normal {
690
693
  white-space: normal;
691
694
  }