Line data Source code
1 : import * as React from 'react';
2 : import type * as dashql from '@ankoh/dashql-core';
3 : import type { EditorView } from '@codemirror/view';
4 :
5 : import { useAppConfig } from '../../config/app_config.js';
6 : import { ConnectionHealth, type ConnectionState } from '../../connections/connection_state.js';
7 : import { ConnectorType } from '../../connections/connector_info.js';
8 : import { useQueryExecutor, useQueryState } from '../../connections/query_executor.js';
9 : import { useLogger } from '../../platform/logger/logger_provider.js';
10 : import { useStorageReader } from '../../storage/storage_provider.js';
11 : import { displayPath } from '../../storage/session_locator.js';
12 : import { STORAGE_SCRIPTS_FOLDER } from '../../storage/storage_backend.js';
13 : import { useConnectionLabeler } from '../connection_labels.js';
14 : import { DELETE_SCRIPT, SET_SCRIPT_TEXT, SET_SCRIPT_VIS_SPEC, type ScriptData, type SessionScripts } from '../scripts/session_scripts.js';
15 : import type { ModifySessionScripts } from '../scripts/session_scripts_registry.js';
16 : import { runSessionScript } from '../scripts/rerun_query.js';
17 : import { useSessionExecution } from '../scripts/session_execution_provider.js';
18 : import { renameScript, type RenameDraftReferences } from '../scripts/script_rename.js';
19 : import { formatScriptEditor, formattingDialectForConnector, getKeywordCase, isScriptFormattable } from '../scripts/script_format.js';
20 : import type { ScriptStorage } from '../scripts/script_action_menu.js';
21 : import { scriptConnectionView, usePickScriptConnection } from '../scripts/script_connection_picker.js';
22 : import type { ScriptReferenceReport } from '../scripts/script_reference_check.js';
23 : import { FeedEntryHeader } from './feed_entry_header.js';
24 :
25 : export interface ScriptEntryHeaderProps {
26 : sessionScripts: SessionScripts;
27 : modifySessionScripts: ModifySessionScripts;
28 : scriptKey: number;
29 : /// The connections that the scripts run on
30 : scriptConnections: ReadonlyMap<string, ConnectionState>;
31 : /// The checked script references of the script
32 : scriptReferences: ScriptReferenceReport | null;
33 : /// The function that renames the references in the draft of the composer
34 : draftRenameRef?: React.RefObject<RenameDraftReferences | null>;
35 : /// The editor of the script, formatting needs it
36 : editorView?: EditorView | null;
37 : /// Delete the script, the session deletes it by default
38 : onDelete?: (script: ScriptData) => void;
39 : /// Show the snippets button, true by default. The boxes of the dataflow leave it to the editor dialog.
40 : snippets?: boolean;
41 : /// Open the script in an editor, an edit button left of the script menu offers it
42 : onEdit?: () => void;
43 : className?: string;
44 : }
45 :
46 : /// The header of a feed entry for a script of a session, outside of the feed: in the boxes of the dataflow and in
47 : /// the script editor dialog. The header runs, renames, formats and deletes the script itself.
48 1 : export const ScriptEntryHeader: React.FC<ScriptEntryHeaderProps> = React.memo((props: ScriptEntryHeaderProps) => {
49 1 : const { sessionScripts, modifySessionScripts } = props;
50 1 : const sessionId = sessionScripts.sessionId;
51 1 : const scriptData = sessionScripts.scripts[props.scriptKey];
52 1 : const fileName = scriptData?.fileName ?? 'script.sql';
53 1 : const config = useAppConfig();
54 1 : const logger = useLogger();
55 1 : const executeQuery = useQueryExecutor();
56 1 : const storageReader = useStorageReader();
57 1 : const sessionExecution = useSessionExecution(sessionId);
58 1 : const queryState = useQueryState(sessionId, scriptData?.latestQueryId ?? null);
59 :
60 : // The connection of the script, labeled like the feed labels it
61 1 : const labeler = useConnectionLabeler();
62 1 : const connectionState = scriptData != null ? props.scriptConnections.get(scriptData.connection.connectionId) ?? null : null;
63 1 : const connection = React.useMemo(
64 1 : () => connectionState == null ? null : scriptConnectionView(connectionState, labeler(connectionState)),
65 1 : [connectionState, labeler],
66 1 : );
67 1 : const online = connectionState?.connectionHealth === ConnectionHealth.ONLINE;
68 1 : const pickConnection = usePickScriptConnection(sessionId, modifySessionScripts);
69 :
70 1 : const scriptDebugMode = config?.settings?.scriptDebugMode ?? false;
71 1 : const formattingDebugMode = config?.settings?.formattingDebugMode ?? false;
72 1 : const keywordCase = getKeywordCase(config?.settings);
73 1 : const connectorType = scriptData?.connection.connectorInfo.connectorType;
74 1 : const formattingDialect = formattingDialectForConnector(connectorType);
75 1 : const isFormattable = React.useMemo(
76 1 : () => isScriptFormattable(scriptData ?? null, formattingDialect),
77 1 : [scriptData?.scriptSession, scriptData?.editorUpdate?.textVersion, formattingDialect],
78 1 : );
79 1 : const editorView = props.editorView ?? null;
80 1 : const format = React.useCallback((mode: dashql.buffers.formatting.FormattingMode) => {
81 0 : formatScriptEditor(editorView, scriptData ?? null, mode, (text) => {
82 0 : modifySessionScripts({ type: SET_SCRIPT_TEXT, value: { scriptKey: props.scriptKey, text, withDiff: true } });
83 0 : }, formattingDebugMode, keywordCase, formattingDialect);
84 1 : }, [editorView, scriptData, props.scriptKey, modifySessionScripts, formattingDebugMode, keywordCase, formattingDialect]);
85 :
86 1 : const execute = React.useCallback(() => {
87 0 : if (scriptData == null || !online || sessionExecution.runActive) return;
88 0 : runSessionScript(sessionScripts, scriptData, executeQuery, modifySessionScripts, logger);
89 1 : }, [scriptData, online, sessionExecution.runActive, sessionScripts, executeQuery, modifySessionScripts, logger]);
90 1 : const rename = React.useCallback((name: string) => {
91 0 : void renameScript(sessionScripts, modifySessionScripts, fileName, name, props.draftRenameRef?.current);
92 1 : }, [sessionScripts, modifySessionScripts, fileName, props.draftRenameRef]);
93 1 : const remove = React.useCallback(() => {
94 0 : if (scriptData == null) return;
95 0 : if (props.onDelete != null) props.onDelete(scriptData);
96 0 : else modifySessionScripts({ type: DELETE_SCRIPT, value: fileName });
97 1 : }, [scriptData, props.onDelete, modifySessionScripts, fileName]);
98 : // A snippet replaces the text of the script, a script with text shows the replacement as a diff to accept or reject
99 1 : const applySnippet = React.useCallback((text: string) => {
100 0 : const priorText = scriptData?.scriptSession.getText() ?? '';
101 0 : modifySessionScripts({ type: SET_SCRIPT_TEXT, value: { scriptKey: props.scriptKey, text, withDiff: priorText.trim().length > 0 } });
102 1 : }, [scriptData, props.scriptKey, modifySessionScripts]);
103 1 : const setVisSpec = React.useCallback((spec: string) => {
104 0 : modifySessionScripts({ type: SET_SCRIPT_VIS_SPEC, value: { scriptKey: props.scriptKey, spec } });
105 1 : }, [props.scriptKey, modifySessionScripts]);
106 1 : const storage = React.useMemo<ScriptStorage>(() => ({
107 1 : fileName,
108 1 : resolvePath: () => `${displayPath(sessionId, storageReader.getSessionLocation(sessionId))}/${STORAGE_SCRIPTS_FOLDER}/${fileName}`,
109 1 : onRename: rename,
110 1 : }), [fileName, sessionId, storageReader, rename]);
111 :
112 1 : return (
113 1 : <FeedEntryHeader
114 1 : sessionId={sessionId}
115 1 : scriptData={scriptData}
116 1 : scriptFileName={fileName}
117 1 : connection={connection}
118 1 : connectorType={connectorType ?? ConnectorType.HYPER}
119 1 : queryState={queryState}
120 1 : focused
121 1 : canExecute={scriptData != null && online}
122 1 : scriptDebugMode={scriptDebugMode}
123 1 : isFormattable={isFormattable}
124 1 : canFormat={editorView != null}
125 1 : scriptReferences={props.scriptReferences}
126 : // There is no composer outside of the feed
127 1 : canTarget={false}
128 1 : onTarget={NO_TARGET}
129 1 : onExecute={execute}
130 1 : onPickConnection={connectionId => pickConnection(fileName, connectionId)}
131 1 : onRename={rename}
132 1 : onDelete={remove}
133 1 : onEdit={props.onEdit}
134 1 : onFormat={format}
135 1 : onApplySnippet={props.snippets === false ? undefined : applySnippet}
136 1 : onSetVisSpec={setVisSpec}
137 1 : storage={storage}
138 1 : className={props.className}
139 1 : />
140 : );
141 1 : });
142 :
143 1 : const NO_TARGET = () => {};
|