From 1431d4996f41771ea69cc49449c0dce24bea8cee Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Tue, 8 Sep 2026 20:35:39 +0800 Subject: [PATCH] fix(settings): centralize draft normalization and use 60s query timeout --- .../connection/ConnectionDialog.vue | 27 +- .../editor/EditorSettingsDialog.vue | 448 +++++------------- .../connectionDialogTimeouts.spec.ts | 2 +- .../lib/__tests__/sql/queryTimeout.spec.ts | 6 +- .../connectionSubmitNormalization.spec.ts | 34 ++ .../connectionSubmitNormalization.ts | 28 ++ .../src/lib/connection/timeoutLimits.ts | 2 +- .../desktop/src/lib/imports/datagripImport.ts | 3 +- apps/desktop/src/lib/imports/dbeaverImport.ts | 3 +- apps/desktop/src/lib/imports/navicatImport.ts | 3 +- .../applyEditorSettingsDraft.spec.ts | 17 + .../lib/settings/applyEditorSettingsDraft.ts | 12 + apps/desktop/src/lib/sql/queryTimeout.ts | 5 +- .../stores/__tests__/settingsStore.spec.ts | 2 +- apps/desktop/src/stores/connectionStore.ts | 3 +- apps/desktop/src/stores/settingsStore.ts | 4 +- 16 files changed, 239 insertions(+), 360 deletions(-) create mode 100644 apps/desktop/src/lib/connection/__tests__/connectionSubmitNormalization.spec.ts create mode 100644 apps/desktop/src/lib/connection/connectionSubmitNormalization.ts create mode 100644 apps/desktop/src/lib/settings/__tests__/applyEditorSettingsDraft.spec.ts create mode 100644 apps/desktop/src/lib/settings/applyEditorSettingsDraft.ts diff --git a/apps/desktop/src/components/connection/ConnectionDialog.vue b/apps/desktop/src/components/connection/ConnectionDialog.vue index f0367e2dbb..07ee65f710 100644 --- a/apps/desktop/src/components/connection/ConnectionDialog.vue +++ b/apps/desktop/src/components/connection/ConnectionDialog.vue @@ -36,7 +36,7 @@ import DatabaseIcon from "@/components/icons/DatabaseIcon.vue"; import * as api from "@/lib/backend/api"; import { isTauriRuntime } from "@/lib/backend/tauriRuntime"; import { applyMeilisearchBasePathToExternalConfig, applyParsedConnectionUrl, normalizeMongoConnectionString, parseConnectionUrl } from "@/lib/connection/connectionUrl"; -import { MAX_CONNECT_TIMEOUT_SECS, MAX_QUERY_TIMEOUT_SECS } from "@/lib/connection/timeoutLimits"; +import { DEFAULT_QUERY_TIMEOUT_SECS, MAX_CONNECT_TIMEOUT_SECS, MAX_QUERY_TIMEOUT_SECS } from "@/lib/connection/timeoutLimits"; import { buildOracleTnsConnectionString, normalizeOracleTnsAdminPath, parseOracleTnsConnectionString } from "@/lib/connection/oracleTnsConnection"; import { connectionDeepLinkServiceHydrationValue, parseConnectionDeepLink, parseServiceConnectionUrl, type ConnectionDeepLinkDraft } from "@/lib/connection/connectionDeepLink"; import { connectionUrlPlaceholder as getUrlPlaceholder } from "@/lib/connection/connectionPresentation"; @@ -107,6 +107,7 @@ import { import { buildDraftVisibleDatabasesConnectionId, connectionCanChooseVisibleDatabases, initialVisibleDatabaseSelection, visibleObjectFiltersNeedReset } from "@/lib/connection/connectionVisibleDatabases"; import { canSaveVisibleDatabaseSelection, connectionUsesVisibleSchemaFilter, filterDatabaseNamesForVisiblePicker, filterSchemaNamesForVisiblePicker, normalizeVisibleDatabaseSelection, buildDraftVisibleSchemasConnectionId, normalizeVisibleSchemaSelection } from "@/lib/database/visibleDatabases"; import { isSchemaAware, isSingleDatabase } from "@/lib/database/databaseFeatureSupport"; +import { normalizeConnectionScope, normalizeConnectionTimeouts } from "@/lib/connection/connectionSubmitNormalization"; import { databaseConnectionFormKind } from "@/lib/database/databaseDriverManifest"; import VisibleSchemasDialog from "@/components/sidebar/VisibleSchemasDialog.vue"; import CloudflareD1ConnectionFields from "@/components/connection/CloudflareD1ConnectionFields.vue"; @@ -2517,7 +2518,7 @@ watch( transport_layers: transportLayersForConfig(legacyConfig), connect_timeout_secs: config.connect_timeout_inherit === true ? settingsStore.editorSettings.globalConnectTimeoutSecs : config.connect_timeout_secs || 10, connect_timeout_inherit: config.connect_timeout_inherit === true, - query_timeout_secs: config.query_timeout_inherit === true ? settingsStore.editorSettings.globalQueryTimeoutSecs : (config.query_timeout_secs ?? 30), + query_timeout_secs: config.query_timeout_inherit === true ? settingsStore.editorSettings.globalQueryTimeoutSecs : (config.query_timeout_secs ?? DEFAULT_QUERY_TIMEOUT_SECS), query_timeout_inherit: config.query_timeout_inherit === true, idle_timeout_secs: config.idle_timeout_secs ?? 60, keepalive_interval_secs: config.keepalive_interval_secs ?? 30, @@ -3854,13 +3855,7 @@ function connectionConfigForSubmit(id: string, generatedName = ""): ConnectionCo // service, SID, and descriptor JDBC strings exactly as before. config.connection_string = undefined; } - config.connect_timeout_secs = config.connect_timeout_inherit === true ? normalizeGlobalConnectTimeoutSecs(editGlobalConnectTimeoutSecs.value) : normalizeGlobalConnectTimeoutSecs(config.connect_timeout_secs); - const queryTimeout = Number(config.query_timeout_secs); - config.query_timeout_secs = config.query_timeout_inherit === true ? normalizeGlobalQueryTimeoutSecs(editGlobalQueryTimeoutSecs.value) : normalizeGlobalQueryTimeoutSecs(queryTimeout); - const idleTimeout = Number(config.idle_timeout_secs); - config.idle_timeout_secs = Number.isFinite(idleTimeout) && idleTimeout >= 0 ? idleTimeout : 60; - const keepaliveInterval = Number(config.keepalive_interval_secs); - config.keepalive_interval_secs = Number.isFinite(keepaliveInterval) && keepaliveInterval >= 0 ? keepaliveInterval : 30; + normalizeConnectionTimeouts(config, editGlobalConnectTimeoutSecs.value, editGlobalQueryTimeoutSecs.value); if (config.db_type === "manticoresearch") { config.url_params = ""; } @@ -3903,19 +3898,7 @@ function connectionConfigForSubmit(id: string, generatedName = ""): ConnectionCo .replace(/^[;]|[;]$/g, "") .trim(); } - if (!config.one_time) config.one_time = undefined; - if (!config.read_only) config.read_only = undefined; - // Save-password is a positive default: only an explicit unchecked state (false) - // is persisted; anything else keeps the current behavior. - config.save_password = config.save_password !== false; - if ((isSingleDatabase(config.db_type) || config.db_type === "mq" || config.db_type === "mqtt") && config.production_databases?.length) { - // Single-database / MQ drivers expose no independently selectable database list for PROD scope. - config.is_production = true; - config.production_databases = []; - } - if (!config.is_production) config.is_production = undefined; - config.production_databases = [...new Set((config.production_databases || []).map((database) => database.trim()).filter(Boolean))]; - if (!config.production_databases.length) config.production_databases = undefined; + normalizeConnectionScope(config); if (form.value.db_type === "mq") { const mqConfig = buildMqAdminConfig(); config.external_config = mqConfig; diff --git a/apps/desktop/src/components/editor/EditorSettingsDialog.vue b/apps/desktop/src/components/editor/EditorSettingsDialog.vue index 58693204d1..a1a63295e6 100644 --- a/apps/desktop/src/components/editor/EditorSettingsDialog.vue +++ b/apps/desktop/src/components/editor/EditorSettingsDialog.vue @@ -211,6 +211,7 @@ import { type EditorSettingsDraft, type EditorSettingsDraftKey, } from "@/lib/settings/editorSettingsDraft"; +import { applyEditorSettingsDraftToRefs, type EditorSettingsDraftRefMap } from "@/lib/settings/applyEditorSettingsDraft"; import { serializeSettingsTransfer, sortTransferCategories, transferCategoryForKey, type SettingsTransferCategoryId } from "@/lib/settings/settingsTransfer"; import { useConnectionStore } from "@/stores/connectionStore"; import { useSavedSqlStore } from "@/stores/savedSqlStore"; @@ -1299,333 +1300,132 @@ function syncEditorSettingsDraftFromStore() { // every other in-progress edit untouched. The base snapshot is intentionally // left untouched: changed values stay unapplied until the user clicks Apply, // exactly like hand-edited draft values. +const editorSettingsDraftRefs: EditorSettingsDraftRefMap = { + fontFamily: editFontFamily, + fontSize: editFontSize, + tableFontFamily: editTableFontFamily, + uiFontFamily: editUiFontFamily, + uiScale: editUiScale, + theme: editTheme, + backgroundImage: editBackgroundImage, + customThemes: editCustomThemes, + activeCustomThemeId: editActiveCustomThemeId, + executeMode: editExecuteMode, + executeAllOnBlankLine: editExecuteAllOnBlankLine, + showExecutionTargetPicker: editShowExecutionTargetPicker, + showStatementRunButtons: editShowStatementRunButtons, + showLineNumbers: editShowLineNumbers, + showCurrentStatementFrame: editShowCurrentStatementFrame, + showInsertValueHints: editShowInsertValueHints, + autoAliasTables: editAutoAliasTables, + insertSpaceAfterCompletion: editInsertSpaceAfterCompletion, + sortCompletionColumnsAlphabetically: editSortCompletionColumnsAlphabetically, + selectFirstCompletionOnOpen: editSelectFirstCompletionOnOpen, + wordWrap: editWordWrap, + vimModeEnabled: editVimModeEnabled, + autoCloseBrackets: editAutoCloseBrackets, + sqlSemanticDiagnosticsMode: editSqlSemanticDiagnosticsMode, + confirmDangerousSqlExecution: editConfirmDangerousSqlExecution, + confirmUnsavedSqlClose: editConfirmUnsavedSqlClose, + appCloseUnsavedTabsMode: editAppCloseUnsavedTabsMode, + savedSqlOpenTargetMode: editSavedSqlOpenTargetMode, + appLayout: editAppLayout, + tabLayout: editTabLayout, + tabPlacement: editTabPlacement, + tabGroupMode: editTabGroupMode, + tabSortMode: editTabSortMode, + showColumnCommentsInHeader: editShowColumnCommentsInHeader, + showColumnTypesInHeader: editShowColumnTypesInHeader, + dataGridShowTransposeFieldMetadata: editDataGridShowTransposeFieldMetadata, + colorizeDataGridCellTypes: editColorizeDataGridCellTypes, + dataGridTypeColorSchemes: editDataGridTypeColorSchemes, + activeDataGridTypeColorSchemeId: editActiveDataGridTypeColorSchemeId, + showIndexIndicatorsInHeader: editShowIndexIndicatorsInHeader, + compactColumnHeaderActions: editCompactColumnHeaderActions, + dataGridQuickEntry: editDataGridQuickEntry, + dataGridFilterEditorView: editDataGridFilterEditorView, + dataGridAutoHideFilterBuilder: editDataGridAutoHideFilterBuilder, + dataGridTextFilterPanelHeight: editDataGridTextFilterPanelHeight, + multiStatementDefaultView: editMultiStatementDefaultView, + dataGridAutoTransposeSingleRow: editDataGridAutoTransposeSingleRow, + dataGridCellDetailButtonVisible: editDataGridCellDetailButtonVisible, + dataGridCrosshairHighlight: editDataGridCrosshairHighlight, + pageSize: editPageSize, + tableOpenPageSize: editTableOpenPageSize, + queryResultMaxRowsEnabled: editQueryResultMaxRowsEnabled, + queryResultMaxRows: editQueryResultMaxRows, + externalSqlEditorMaxMb: editExternalSqlEditorMaxMb, + infiniteScroll: editInfiniteScroll, + regexMaxMatchCount: editRegexMaxMatchCount, + autoCalculateTotalRows: editAutoCalculateTotalRows, + flatteningMultiLineText: editFlatteningMultiLineText, + shortcuts: editShortcuts, + sqlFormatter: editSqlFormatter, + sidebarActivation: editSidebarActivation, + sidebarObjectDisplay: editSidebarObjectDisplay, + routineSourceOpenMode: editRoutineSourceOpenMode, + sidebarTableSearchEnabled: editSidebarTableSearchEnabled, + autoSelectActiveSidebarNode: editAutoSelectActiveSidebarNode, + sidebarBrowseObjectsOnDatabaseActivation: editSidebarBrowseObjectsOnDatabaseActivation, + openTabsRestoreMode: editOpenTabsRestoreMode, + disconnectTabHandlingMode: editDisconnectTabHandlingMode, + dataTabReuseMode: editDataTabReuseMode, + openDataTabsNextToActive: editOpenDataTabsNextToActive, + prefillNewQueryWithSelect: editPrefillNewQueryWithSelect, + generateSqlIncludeDatabaseName: editGenerateSqlIncludeDatabaseName, + generateSqlQuoteIdentifiers: editGenerateSqlQuoteIdentifiers, + formatSqlOnSqlFileSave: editFormatSqlOnSqlFileSave, + showTableDdlHoverPreview: editShowTableDdlHoverPreview, + updateNotificationsEnabled: editUpdateNotificationsEnabled, + sidebarObjectInfoMode: editSidebarObjectInfoMode, + sidebarAllowHorizontalScroll: editSidebarAllowHorizontalScroll, + sidebarShowTooltips: editSidebarShowTooltips, + sidebarIndent: editSidebarIndent, + sidebarFontSize: editSidebarFontSize, + sidebarHiddenTablePrefixes: editSidebarHiddenTablePrefixes, + sidebarCopyTableNameSeparator: editSidebarCopyTableNameSeparator, + sidebarCopyTableNameIncludeSchema: editSidebarCopyTableNameIncludeSchema, + redisKeyTemplates: editRedisKeyTemplates, + exportBatchSize: editExportBatchSize, + csvQuoteMode: editCsvQuoteMode, + exportRowLimitEnabled: editExportRowLimitEnabled, + exportRowLimit: editExportRowLimit, + queryExportKeysetOptimizationEnabled: editQueryExportKeysetOptimizationEnabled, + globalDateTimeDisplayFormat: editGlobalDateTimeDisplayFormat, + globalDateTimeExportFormat: editGlobalDateTimeExportFormat, + globalDateTimeImportFormat: editGlobalDateTimeImportFormat, + updateDownloadSource: editUpdateDownloadSource, + toolbarItems: editToolbarItems, + snippets: editSnippets, + sqlShortcuts: editSqlShortcuts, + sqlVariableSubstitutionEnabled: editSqlVariableSubstitutionEnabled, + sqlVariableSyntaxOverrides: editSqlVariableSyntaxOverrides, + continueOnErrorOnBatch: editContinueOnErrorOnBatch, + clickTableNavigationTarget: editClickTableNavigationTarget, + completionTriggerMode: editCompletionTriggerMode, + defaultTransactionMode: editDefaultTransactionMode, + tableColumnTemplateFields: editTableColumnTemplateRows, +}; + function applyEditorSettingsKeysToRefs(draft: EditorSettingsDraft, keys: readonly EditorSettingsDraftKey[]) { - for (const key of keys) { - switch (key) { - case "fontFamily": - editFontFamily.value = draft.fontFamily; - break; - case "fontSize": - editFontSize.value = draft.fontSize; - break; - case "tableFontFamily": - editTableFontFamily.value = draft.tableFontFamily; - break; - case "uiFontFamily": - editUiFontFamily.value = draft.uiFontFamily; - break; - case "uiScale": - editUiScale.value = draft.uiScale; - break; - case "theme": - editTheme.value = draft.theme; - break; - case "customThemes": - editCustomThemes.value = [...draft.customThemes]; - break; - case "activeCustomThemeId": - editActiveCustomThemeId.value = draft.activeCustomThemeId; - break; - case "executeMode": - editExecuteMode.value = draft.executeMode; - break; - case "executeAllOnBlankLine": - editExecuteAllOnBlankLine.value = draft.executeAllOnBlankLine; - break; - case "showExecutionTargetPicker": - editShowExecutionTargetPicker.value = draft.showExecutionTargetPicker; - break; - case "showStatementRunButtons": - editShowStatementRunButtons.value = draft.showStatementRunButtons; - break; - case "showLineNumbers": - editShowLineNumbers.value = draft.showLineNumbers; - break; - case "showCurrentStatementFrame": - editShowCurrentStatementFrame.value = draft.showCurrentStatementFrame; - break; - case "showInsertValueHints": - editShowInsertValueHints.value = draft.showInsertValueHints; - break; - case "autoAliasTables": - editAutoAliasTables.value = draft.autoAliasTables; - break; - case "insertSpaceAfterCompletion": - editInsertSpaceAfterCompletion.value = draft.insertSpaceAfterCompletion; - break; - case "sortCompletionColumnsAlphabetically": - editSortCompletionColumnsAlphabetically.value = draft.sortCompletionColumnsAlphabetically; - break; - case "selectFirstCompletionOnOpen": - editSelectFirstCompletionOnOpen.value = draft.selectFirstCompletionOnOpen; - break; - case "wordWrap": - editWordWrap.value = draft.wordWrap; - break; - case "vimModeEnabled": - editVimModeEnabled.value = draft.vimModeEnabled; - break; - case "autoCloseBrackets": - editAutoCloseBrackets.value = draft.autoCloseBrackets; - break; - case "sqlSemanticDiagnosticsMode": - editSqlSemanticDiagnosticsMode.value = draft.sqlSemanticDiagnosticsMode; - editSqlSemanticDiagnosticsEnabled.value = draft.sqlSemanticDiagnosticsMode !== "disabled"; - break; - case "confirmDangerousSqlExecution": - editConfirmDangerousSqlExecution.value = draft.confirmDangerousSqlExecution; - break; - case "confirmUnsavedSqlClose": - editConfirmUnsavedSqlClose.value = draft.confirmUnsavedSqlClose; - break; - case "appCloseUnsavedTabsMode": - editAppCloseUnsavedTabsMode.value = draft.appCloseUnsavedTabsMode; - break; - case "savedSqlOpenTargetMode": - editSavedSqlOpenTargetMode.value = draft.savedSqlOpenTargetMode; - break; - case "appLayout": - editAppLayout.value = draft.appLayout; - break; - case "tabLayout": - editTabLayout.value = draft.tabLayout; - break; - case "tabPlacement": - editTabPlacement.value = draft.tabPlacement; - break; - case "tabGroupMode": - editTabGroupMode.value = draft.tabGroupMode; - break; - case "tabSortMode": - editTabSortMode.value = draft.tabSortMode; - break; - case "showColumnCommentsInHeader": - editShowColumnCommentsInHeader.value = draft.showColumnCommentsInHeader; - break; - case "showColumnTypesInHeader": - editShowColumnTypesInHeader.value = draft.showColumnTypesInHeader; - break; - case "dataGridShowTransposeFieldMetadata": - editDataGridShowTransposeFieldMetadata.value = draft.dataGridShowTransposeFieldMetadata; - break; - case "colorizeDataGridCellTypes": - editColorizeDataGridCellTypes.value = draft.colorizeDataGridCellTypes; - break; - case "dataGridTypeColorSchemes": - editDataGridTypeColorSchemes.value = cloneDataGridTypeColorSchemes(draft.dataGridTypeColorSchemes); - break; - case "activeDataGridTypeColorSchemeId": - editActiveDataGridTypeColorSchemeId.value = draft.activeDataGridTypeColorSchemeId; - break; - case "showIndexIndicatorsInHeader": - editShowIndexIndicatorsInHeader.value = draft.showIndexIndicatorsInHeader; - break; - case "compactColumnHeaderActions": - editCompactColumnHeaderActions.value = draft.compactColumnHeaderActions; - break; - case "dataGridQuickEntry": - editDataGridQuickEntry.value = draft.dataGridQuickEntry; - break; - case "dataGridFilterEditorView": - editDataGridFilterEditorView.value = draft.dataGridFilterEditorView; - break; - case "dataGridAutoHideFilterBuilder": - editDataGridAutoHideFilterBuilder.value = draft.dataGridAutoHideFilterBuilder; - break; - case "dataGridTextFilterPanelHeight": - editDataGridTextFilterPanelHeight.value = draft.dataGridTextFilterPanelHeight; - break; - case "multiStatementDefaultView": - editMultiStatementDefaultView.value = draft.multiStatementDefaultView; - break; - case "dataGridAutoTransposeSingleRow": - editDataGridAutoTransposeSingleRow.value = draft.dataGridAutoTransposeSingleRow; - break; - case "dataGridCellDetailButtonVisible": - editDataGridCellDetailButtonVisible.value = draft.dataGridCellDetailButtonVisible; - break; - case "dataGridCrosshairHighlight": - editDataGridCrosshairHighlight.value = draft.dataGridCrosshairHighlight; - break; - case "pageSize": - editPageSize.value = draft.pageSize; - break; - case "tableOpenPageSize": - editTableOpenPageSize.value = draft.tableOpenPageSize; - break; - case "queryResultMaxRowsEnabled": - editQueryResultMaxRowsEnabled.value = draft.queryResultMaxRowsEnabled; - break; - case "queryResultMaxRows": - editQueryResultMaxRows.value = draft.queryResultMaxRows; - break; - case "externalSqlEditorMaxMb": - editExternalSqlEditorMaxMb.value = draft.externalSqlEditorMaxMb; - break; - case "infiniteScroll": - editInfiniteScroll.value = draft.infiniteScroll; - break; - case "regexMaxMatchCount": - editRegexMaxMatchCount.value = draft.regexMaxMatchCount; - break; - case "autoCalculateTotalRows": - editAutoCalculateTotalRows.value = draft.autoCalculateTotalRows; - break; - case "flatteningMultiLineText": - editFlatteningMultiLineText.value = draft.flatteningMultiLineText; - break; - case "tableColumnTemplateFields": - editTableColumnTemplateRows.value = tableColumnTemplateRowsFromSettings(draft.tableColumnTemplateFields); - break; - case "shortcuts": - editShortcuts.value = normalizeShortcutSettings(draft.shortcuts); - break; - case "sqlFormatter": - editSqlFormatter.value = normalizeSqlFormatterSettings(draft.sqlFormatter); - sqlFormatterConfigValid.value = true; - break; - case "sidebarActivation": - editSidebarActivation.value = draft.sidebarActivation; - break; - case "sidebarObjectDisplay": - editSidebarObjectDisplay.value = draft.sidebarObjectDisplay; - break; - case "routineSourceOpenMode": - editRoutineSourceOpenMode.value = draft.routineSourceOpenMode; - break; - case "sidebarTableSearchEnabled": - editSidebarTableSearchEnabled.value = draft.sidebarTableSearchEnabled; - break; - case "autoSelectActiveSidebarNode": - editAutoSelectActiveSidebarNode.value = draft.autoSelectActiveSidebarNode; - break; - case "sidebarBrowseObjectsOnDatabaseActivation": - editSidebarBrowseObjectsOnDatabaseActivation.value = draft.sidebarBrowseObjectsOnDatabaseActivation; - break; - case "openTabsRestoreMode": - editOpenTabsRestoreMode.value = draft.openTabsRestoreMode; - break; - case "disconnectTabHandlingMode": - editDisconnectTabHandlingMode.value = draft.disconnectTabHandlingMode; - break; - case "dataTabReuseMode": - editDataTabReuseMode.value = draft.dataTabReuseMode; - break; - case "openDataTabsNextToActive": - editOpenDataTabsNextToActive.value = draft.openDataTabsNextToActive; - break; - case "prefillNewQueryWithSelect": - editPrefillNewQueryWithSelect.value = draft.prefillNewQueryWithSelect; - break; - case "generateSqlIncludeDatabaseName": - editGenerateSqlIncludeDatabaseName.value = draft.generateSqlIncludeDatabaseName; - break; - case "generateSqlQuoteIdentifiers": - editGenerateSqlQuoteIdentifiers.value = draft.generateSqlQuoteIdentifiers; - break; - case "formatSqlOnSqlFileSave": - editFormatSqlOnSqlFileSave.value = draft.formatSqlOnSqlFileSave; - break; - case "showTableDdlHoverPreview": - editShowTableDdlHoverPreview.value = draft.showTableDdlHoverPreview; - break; - case "updateNotificationsEnabled": - editUpdateNotificationsEnabled.value = draft.updateNotificationsEnabled; - break; - case "sidebarObjectInfoMode": - editSidebarObjectInfoMode.value = draft.sidebarObjectInfoMode; - break; - case "sidebarAllowHorizontalScroll": - editSidebarAllowHorizontalScroll.value = draft.sidebarAllowHorizontalScroll; - break; - case "sidebarShowTooltips": - editSidebarShowTooltips.value = draft.sidebarShowTooltips; - break; - case "sidebarIndent": - editSidebarIndent.value = draft.sidebarIndent; - break; - case "sidebarFontSize": - editSidebarFontSize.value = draft.sidebarFontSize; - break; - case "sidebarHiddenTablePrefixes": - editSidebarHiddenTablePrefixes.value = draft.sidebarHiddenTablePrefixes.join("\n"); - break; - case "sidebarCopyTableNameSeparator": - editSidebarCopyTableNameSeparator.value = draft.sidebarCopyTableNameSeparator; - break; - case "sidebarCopyTableNameIncludeSchema": - editSidebarCopyTableNameIncludeSchema.value = draft.sidebarCopyTableNameIncludeSchema; - break; - case "redisKeyTemplates": - editRedisKeyTemplates.value = normalizeRedisKeyTemplates(draft.redisKeyTemplates).join("\n"); - break; - case "exportBatchSize": - editExportBatchSize.value = draft.exportBatchSize; - break; - case "csvQuoteMode": - editCsvQuoteMode.value = draft.csvQuoteMode; - break; - case "exportRowLimitEnabled": - editExportRowLimitEnabled.value = draft.exportRowLimitEnabled; - break; - case "exportRowLimit": - editExportRowLimit.value = draft.exportRowLimit; - break; - case "queryExportKeysetOptimizationEnabled": - editQueryExportKeysetOptimizationEnabled.value = draft.queryExportKeysetOptimizationEnabled; - break; - case "globalDateTimeDisplayFormat": - editGlobalDateTimeDisplayFormat.value = draft.globalDateTimeDisplayFormat; - break; - case "globalDateTimeExportFormat": - editGlobalDateTimeExportFormat.value = draft.globalDateTimeExportFormat; - break; - case "globalDateTimeImportFormat": - editGlobalDateTimeImportFormat.value = draft.globalDateTimeImportFormat; - break; - case "updateDownloadSource": - editUpdateDownloadSource.value = draft.updateDownloadSource; - break; - case "toolbarItems": - editToolbarItems.value = { ...draft.toolbarItems }; - break; - case "snippets": - editSnippets.value = draft.snippets.map(editableSnippet); - break; - case "sqlShortcuts": - editSqlShortcuts.value = draft.sqlShortcuts.map(editableSqlShortcut); - break; - case "sqlVariableSubstitutionEnabled": - editSqlVariableSubstitutionEnabled.value = draft.sqlVariableSubstitutionEnabled; - break; - case "sqlVariableSyntaxOverrides": - editSqlVariableSyntaxOverrides.value = normalizeSqlVariableSyntaxOverrides(draft.sqlVariableSyntaxOverrides); - break; - case "continueOnErrorOnBatch": - editContinueOnErrorOnBatch.value = draft.continueOnErrorOnBatch; - break; - case "clickTableNavigationTarget": - editClickTableNavigationTarget.value = draft.clickTableNavigationTarget; - break; - case "completionTriggerMode": - editCompletionTriggerMode.value = draft.completionTriggerMode; - break; - case "defaultTransactionMode": - editDefaultTransactionMode.value = draft.defaultTransactionMode; - break; - case "backgroundImage": - editBackgroundImage.value = cloneBackgroundImageDraft(draft.backgroundImage); - break; - default: { - // Compile-time exhaustiveness over EDITOR_SETTINGS_DRAFT_KEYS: adding a - // draft key without a ref assignment fails the build here. - const unhandled: never = key; - void unhandled; - } - } + applyEditorSettingsDraftToRefs(draft, keys, editorSettingsDraftRefs, { + customThemes: (value) => [...(value as CustomTheme[])], + dataGridTypeColorSchemes: (value) => cloneDataGridTypeColorSchemes(value as DataGridTypeColorScheme[]), + tableColumnTemplateFields: (value) => tableColumnTemplateRowsFromSettings(value as string[]), + shortcuts: (value) => normalizeShortcutSettings(value as Parameters[0]), + sqlFormatter: (value) => normalizeSqlFormatterSettings(value as SqlFormatterSettings), + sidebarHiddenTablePrefixes: (value) => (value as string[]).join("\n"), + redisKeyTemplates: (value) => normalizeRedisKeyTemplates(value as string[]).join("\n"), + toolbarItems: (value) => ({ ...(value as EditorSettings["toolbarItems"]) }), + snippets: (value) => (value as SqlSnippet[]).map(editableSnippet), + sqlShortcuts: (value) => (value as SqlShortcutAction[]).map(editableSqlShortcut), + sqlVariableSyntaxOverrides: (value) => normalizeSqlVariableSyntaxOverrides(value as EditorSettings["sqlVariableSyntaxOverrides"]), + backgroundImage: (value) => cloneBackgroundImageDraft(value as BackgroundImageSettings), + }); + if (keys.includes("sqlSemanticDiagnosticsMode")) { + editSqlSemanticDiagnosticsEnabled.value = editSqlSemanticDiagnosticsMode.value !== "disabled"; } } - // Sync from store when dialog opens watch( () => settingsVisible.value, diff --git a/apps/desktop/src/lib/__tests__/connection/connectionDialogTimeouts.spec.ts b/apps/desktop/src/lib/__tests__/connection/connectionDialogTimeouts.spec.ts index 3b86f91b45..e3fa3ef4b4 100644 --- a/apps/desktop/src/lib/__tests__/connection/connectionDialogTimeouts.spec.ts +++ b/apps/desktop/src/lib/__tests__/connection/connectionDialogTimeouts.spec.ts @@ -21,7 +21,7 @@ describe("ConnectionDialog timeout controls", () => { it("restores the default when the connection timeout input is cleared", () => { expect(normalizeConnectTimeoutSecs("")).toBe(DEFAULT_CONNECT_TIMEOUT_SECS); - expect(dialogSource).toContain("normalizeGlobalConnectTimeoutSecs(config.connect_timeout_secs)"); + expect(dialogSource).toContain("normalizeConnectionTimeouts(config"); expect(dialogSource).not.toContain("Number(config.connect_timeout_secs)"); }); diff --git a/apps/desktop/src/lib/__tests__/sql/queryTimeout.spec.ts b/apps/desktop/src/lib/__tests__/sql/queryTimeout.spec.ts index 312fa73e1b..3ba82cc154 100644 --- a/apps/desktop/src/lib/__tests__/sql/queryTimeout.spec.ts +++ b/apps/desktop/src/lib/__tests__/sql/queryTimeout.spec.ts @@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest"; import { CONCURRENT_INDEX_QUERY_TIMEOUT_SECS, frontendQueryTimeoutDelayMs, frontendQueryTimeoutSecsForSql, queryTimeoutSecsForConcurrentIndex, queryTimeoutSecsForConnection } from "@/lib/sql/queryTimeout"; describe("queryTimeout", () => { - it("gives CREATE INDEX CONCURRENTLY a dedicated long budget instead of the 30s default", () => { + it("gives CREATE INDEX CONCURRENTLY a dedicated long budget instead of the 60s default", () => { expect(CONCURRENT_INDEX_QUERY_TIMEOUT_SECS).toBe(1800); expect(CONCURRENT_INDEX_QUERY_TIMEOUT_SECS).toBeGreaterThan(30); expect(frontendQueryTimeoutDelayMs(CONCURRENT_INDEX_QUERY_TIMEOUT_SECS)).toBe(1_800_000); @@ -33,7 +33,7 @@ describe("queryTimeout", () => { it("keeps the existing frontend guard for other database types", () => { expect(frontendQueryTimeoutSecsForSql("SELECT * FROM sample_records LIMIT 2000", "mysql", 30)).toBe(60); - expect(queryTimeoutSecsForConnection({ query_timeout_secs: undefined })).toBe(30); + expect(queryTimeoutSecsForConnection({ query_timeout_secs: undefined })).toBe(60); }); it("does not schedule frontend timeouts beyond the browser timer limit", () => { @@ -49,6 +49,6 @@ describe("queryTimeout", () => { }); it("falls back safely when an inherited global timeout is invalid", () => { - expect(queryTimeoutSecsForConnection({ query_timeout_inherit: true }, Number.NaN)).toBe(30); + expect(queryTimeoutSecsForConnection({ query_timeout_inherit: true }, Number.NaN)).toBe(60); }); }); diff --git a/apps/desktop/src/lib/connection/__tests__/connectionSubmitNormalization.spec.ts b/apps/desktop/src/lib/connection/__tests__/connectionSubmitNormalization.spec.ts new file mode 100644 index 0000000000..94467b4473 --- /dev/null +++ b/apps/desktop/src/lib/connection/__tests__/connectionSubmitNormalization.spec.ts @@ -0,0 +1,34 @@ +import { describe, expect, it } from "vitest"; +import { normalizeConnectionScope, normalizeConnectionTimeouts } from "../connectionSubmitNormalization"; +import type { ConnectionConfig } from "@/types/database"; + +function config(overrides: Partial = {}): ConnectionConfig { + return { + id: "test", + name: "Test", + db_type: "postgres", + host: "localhost", + port: 5432, + username: "user", + password: "password", + ...overrides, + }; +} + +describe("connection submit normalization", () => { + it("normalizes invalid timeout values and inherited global values", () => { + const inherited = config({ connect_timeout_inherit: true, query_timeout_inherit: true, idle_timeout_secs: -1, keepalive_interval_secs: Number.NaN }); + normalizeConnectionTimeouts(inherited, 12, 34); + expect(inherited).toMatchObject({ connect_timeout_secs: 12, query_timeout_secs: 34, idle_timeout_secs: 60, keepalive_interval_secs: 30 }); + + const invalid = config({ query_timeout_secs: Number.NaN, idle_timeout_secs: -1, keepalive_interval_secs: -2 }); + normalizeConnectionTimeouts(invalid, 12, 34); + expect(invalid).toMatchObject({ query_timeout_secs: 60, idle_timeout_secs: 60, keepalive_interval_secs: 30 }); + }); + + it("canonicalizes scope flags and production database names", () => { + const value = config({ db_type: "postgres", one_time: false, read_only: false, save_password: undefined, production_databases: [" app ", "app", ""] }); + normalizeConnectionScope(value); + expect(value).toMatchObject({ one_time: undefined, read_only: undefined, save_password: true, production_databases: ["app"] }); + }); +}); diff --git a/apps/desktop/src/lib/connection/connectionSubmitNormalization.ts b/apps/desktop/src/lib/connection/connectionSubmitNormalization.ts new file mode 100644 index 0000000000..48118f6aed --- /dev/null +++ b/apps/desktop/src/lib/connection/connectionSubmitNormalization.ts @@ -0,0 +1,28 @@ +import { isSingleDatabase } from "@/lib/database/databaseFeatureSupport"; +import { normalizeGlobalConnectTimeoutSecs, normalizeGlobalQueryTimeoutSecs } from "@/stores/settingsStore"; +import type { ConnectionConfig } from "@/types/database"; + +export function normalizeConnectionTimeouts(config: ConnectionConfig, connectTimeoutSecs: number, queryTimeoutSecs: number): void { + config.connect_timeout_secs = config.connect_timeout_inherit === true ? normalizeGlobalConnectTimeoutSecs(connectTimeoutSecs) : normalizeGlobalConnectTimeoutSecs(config.connect_timeout_secs); + const queryTimeout = Number(config.query_timeout_secs); + config.query_timeout_secs = config.query_timeout_inherit === true ? normalizeGlobalQueryTimeoutSecs(queryTimeoutSecs) : normalizeGlobalQueryTimeoutSecs(queryTimeout); + const idleTimeout = Number(config.idle_timeout_secs); + config.idle_timeout_secs = Number.isFinite(idleTimeout) && idleTimeout >= 0 ? idleTimeout : 60; + const keepaliveInterval = Number(config.keepalive_interval_secs); + config.keepalive_interval_secs = Number.isFinite(keepaliveInterval) && keepaliveInterval >= 0 ? keepaliveInterval : 30; +} + +export function normalizeConnectionScope(config: ConnectionConfig): void { + if (!config.one_time) config.one_time = undefined; + if (!config.read_only) config.read_only = undefined; + // Persist only an explicit opt-out; absent/true keeps the existing save-password behavior. + config.save_password = config.save_password !== false; + if ((isSingleDatabase(config.db_type) || config.db_type === "mq" || config.db_type === "mqtt") && config.production_databases?.length) { + // These connection types do not expose independently selectable production databases. + config.is_production = true; + config.production_databases = []; + } + if (!config.is_production) config.is_production = undefined; + config.production_databases = [...new Set((config.production_databases || []).map((database) => database.trim()).filter(Boolean))]; + if (!config.production_databases.length) config.production_databases = undefined; +} diff --git a/apps/desktop/src/lib/connection/timeoutLimits.ts b/apps/desktop/src/lib/connection/timeoutLimits.ts index 3725d24928..288d2cda27 100644 --- a/apps/desktop/src/lib/connection/timeoutLimits.ts +++ b/apps/desktop/src/lib/connection/timeoutLimits.ts @@ -1,6 +1,6 @@ export const DEFAULT_CONNECT_TIMEOUT_SECS = 10; export const MAX_CONNECT_TIMEOUT_SECS = 300; -export const DEFAULT_QUERY_TIMEOUT_SECS = 30; +export const DEFAULT_QUERY_TIMEOUT_SECS = 60; export const MAX_QUERY_TIMEOUT_SECS = 3600; export function normalizeConnectTimeoutSecs(value: unknown): number { diff --git a/apps/desktop/src/lib/imports/datagripImport.ts b/apps/desktop/src/lib/imports/datagripImport.ts index 9972a05db7..096ee00853 100644 --- a/apps/desktop/src/lib/imports/datagripImport.ts +++ b/apps/desktop/src/lib/imports/datagripImport.ts @@ -1,6 +1,7 @@ import type { ConnectionConfig, DatabaseType, SidebarLayout } from "@/types/database"; import { uuid } from "@/lib/common/utils"; import { buildSidebarLayoutFromFolderPaths } from "@/lib/sidebar/sidebarLayout"; +import { DEFAULT_QUERY_TIMEOUT_SECS } from "@/lib/connection/timeoutLimits"; declare var process: { env: Record } | undefined; @@ -462,7 +463,7 @@ function buildConnection(fragment: DataSourceFragment): ConnectionConfig { color: "", transport_layers: [], connect_timeout_secs: 10, - query_timeout_secs: 30, + query_timeout_secs: DEFAULT_QUERY_TIMEOUT_SECS, ssl: false, oracle_connection_type: profile.dbType === "oracle" ? parsed.oracleConnectionType || "service_name" : undefined, connection_string: profile.dbType === "jdbc" || profile.dbType === "mongodb" ? fragment.jdbcUrl.replace(/^jdbc:/i, "") : undefined, diff --git a/apps/desktop/src/lib/imports/dbeaverImport.ts b/apps/desktop/src/lib/imports/dbeaverImport.ts index 33aec127ee..79a284e54a 100644 --- a/apps/desktop/src/lib/imports/dbeaverImport.ts +++ b/apps/desktop/src/lib/imports/dbeaverImport.ts @@ -2,6 +2,7 @@ import type { ConnectionConfig, DatabaseType, SidebarLayout } from "@/types/data import { uuid } from "@/lib/common/utils"; import { JDBCX_JDBC_DRIVER_CLASS } from "@/lib/database/jdbcxBuiltinDriver"; import { buildSidebarLayoutFromFolderPaths } from "@/lib/sidebar/sidebarLayout"; +import { DEFAULT_QUERY_TIMEOUT_SECS } from "@/lib/connection/timeoutLimits"; type PartialConnection = Omit; @@ -264,7 +265,7 @@ function buildConnection(entry: DbeaverConnectionEntry, credentials: ReturnType< color: getString(config.color || config["connection-color"]), transport_layers: [], connect_timeout_secs: 10, - query_timeout_secs: 30, + query_timeout_secs: DEFAULT_QUERY_TIMEOUT_SECS, ssl: false, oracle_connection_type: profile.dbType === "oracle" ? parsedUrl.oracleConnectionType || "service_name" : undefined, connection_string: profile.dbType === "jdbc" || profile.dbType === "mongodb" ? url || undefined : undefined, diff --git a/apps/desktop/src/lib/imports/navicatImport.ts b/apps/desktop/src/lib/imports/navicatImport.ts index 05554cf2b0..05732c0ce5 100644 --- a/apps/desktop/src/lib/imports/navicatImport.ts +++ b/apps/desktop/src/lib/imports/navicatImport.ts @@ -1,5 +1,6 @@ import type { ConnectionConfig, DatabaseType, SshTunnelConfig } from "@/types/database"; import { uuid } from "@/lib/common/utils"; +import { DEFAULT_QUERY_TIMEOUT_SECS } from "@/lib/connection/timeoutLimits"; type PartialConnection = Omit; @@ -336,7 +337,7 @@ async function parseConnection(node: ParsedNode): Promise { + it("applies only the requested keys and preserves custom transforms", () => { + const fontSize = ref(12); + const theme = ref("light"); + const draft = { fontSize: 16, theme: "dark" } as EditorSettingsDraft; + + applyEditorSettingsDraftToRefs(draft, ["fontSize"], { fontSize, theme }, { fontSize: (value) => Number(value) + 1 }); + + expect(fontSize.value).toBe(17); + expect(theme.value).toBe("light"); + }); +}); diff --git a/apps/desktop/src/lib/settings/applyEditorSettingsDraft.ts b/apps/desktop/src/lib/settings/applyEditorSettingsDraft.ts new file mode 100644 index 0000000000..e4a95d1132 --- /dev/null +++ b/apps/desktop/src/lib/settings/applyEditorSettingsDraft.ts @@ -0,0 +1,12 @@ +import type { Ref } from "vue"; +import type { EditorSettingsDraft, EditorSettingsDraftKey } from "./editorSettingsDraft"; + +export type EditorSettingsDraftRefMap = Record; + +export function applyEditorSettingsDraftToRefs(draft: EditorSettingsDraft, keys: readonly EditorSettingsDraftKey[], refs: EditorSettingsDraftRefMap, transform: Partial unknown>> = {}) { + for (const key of keys) { + const target = refs[key]; + const value = draft[key] as unknown; + target.value = transform[key] ? transform[key]!(value) : value; + } +} diff --git a/apps/desktop/src/lib/sql/queryTimeout.ts b/apps/desktop/src/lib/sql/queryTimeout.ts index 44165b4d58..40deadd266 100644 --- a/apps/desktop/src/lib/sql/queryTimeout.ts +++ b/apps/desktop/src/lib/sql/queryTimeout.ts @@ -1,15 +1,16 @@ import { splitSqlStatementRanges } from "@/lib/sql/sqlStatementRanges"; import { tokenizeSqlSemantic } from "@/lib/sql/semantic/tokens"; +import { DEFAULT_QUERY_TIMEOUT_SECS } from "@/lib/connection/timeoutLimits"; import type { ConnectionConfig, DatabaseType } from "@/types/database"; -export const DEFAULT_QUERY_TIMEOUT_SECS = 30; +export { DEFAULT_QUERY_TIMEOUT_SECS } from "@/lib/connection/timeoutLimits"; /** * Query timeout (seconds) the table structure editor applies when the change * script contains a PostgreSQL `CREATE INDEX CONCURRENTLY`. * * Concurrent builds scan the whole table while only taking `ShareUpdateExclusiveLock`, - * so they routinely run far beyond the default 30s query timeout — the default + * so they routinely run far beyond the default 60s query timeout — the default * budget would cancel a legitimate build and leave an INVALID index behind. * PostgreSQL itself does not impose a build deadline * (https://www.postgresql.org/docs/current/sql-createindex.html). This is the diff --git a/apps/desktop/src/stores/__tests__/settingsStore.spec.ts b/apps/desktop/src/stores/__tests__/settingsStore.spec.ts index 98049621c3..8eb6cc459b 100644 --- a/apps/desktop/src/stores/__tests__/settingsStore.spec.ts +++ b/apps/desktop/src/stores/__tests__/settingsStore.spec.ts @@ -423,7 +423,7 @@ describe("normalizeEditorSettings", () => { it("normalizes the global query timeout and inherited connection ids", () => { expect(normalizeEditorSettings({}).globalConnectTimeoutSecs).toBe(10); expect(normalizeEditorSettings({ globalConnectTimeoutSecs: 0 }).globalConnectTimeoutSecs).toBe(1); - expect(normalizeEditorSettings({}).globalQueryTimeoutSecs).toBe(30); + expect(normalizeEditorSettings({}).globalQueryTimeoutSecs).toBe(60); expect(normalizeEditorSettings({ queryTimeoutSecs: 45 } as any).globalQueryTimeoutSecs).toBe(45); expect(normalizeEditorSettings({ globalQueryTimeoutSecs: -1 }).globalQueryTimeoutSecs).toBe(0); expect(normalizeEditorSettings({ globalQueryTimeoutSecs: 301 }).globalQueryTimeoutSecs).toBe(301); diff --git a/apps/desktop/src/stores/connectionStore.ts b/apps/desktop/src/stores/connectionStore.ts index a21d76fd86..e57b09c35c 100644 --- a/apps/desktop/src/stores/connectionStore.ts +++ b/apps/desktop/src/stores/connectionStore.ts @@ -2,6 +2,7 @@ import { defineStore } from "pinia"; import { uuid } from "@/lib/common/utils"; import { containsHan, orderedSubsequenceSpan, pinyinFirstLetters } from "@/lib/common/pinyin"; import { ref, computed, watch, markRaw } from "vue"; +import { DEFAULT_QUERY_TIMEOUT_SECS } from "@/lib/connection/timeoutLimits"; import type { ColumnInfo, CompletionAssistantCandidate, @@ -1377,7 +1378,7 @@ export const useConnectionStore = defineStore("connection", () => { show_system_schemas: config.show_system_schemas === true, connect_timeout_secs: connectTimeoutInherit ? settingsStore.editorSettings.globalConnectTimeoutSecs : config.connect_timeout_secs || 10, connect_timeout_inherit: connectTimeoutInherit, - query_timeout_secs: queryTimeoutInherit ? settingsStore.editorSettings.globalQueryTimeoutSecs : (config.query_timeout_secs ?? 30), + query_timeout_secs: queryTimeoutInherit ? settingsStore.editorSettings.globalQueryTimeoutSecs : (config.query_timeout_secs ?? DEFAULT_QUERY_TIMEOUT_SECS), query_timeout_inherit: queryTimeoutInherit, idle_timeout_secs: config.idle_timeout_secs ?? 60, keepalive_interval_secs: config.keepalive_interval_secs ?? DEFAULT_KEEPALIVE_INTERVAL_SECS, diff --git a/apps/desktop/src/stores/settingsStore.ts b/apps/desktop/src/stores/settingsStore.ts index 1e5435aabb..1a98e50eac 100644 --- a/apps/desktop/src/stores/settingsStore.ts +++ b/apps/desktop/src/stores/settingsStore.ts @@ -13,7 +13,7 @@ import { DATA_GRID_TYPE_COLOR_SCHEME_AUTO_ID, type DataGridTypeColorScheme, norm import { normalizeResultPageSize } from "@/lib/dataGrid/paginationPageSize"; import { DEFAULT_QUERY_RESULT_MAX_ROWS, normalizeQueryResultMaxRows } from "@/lib/dataGrid/queryResultRowLimit"; import { normalizeExternalSqlEditorMaxMb } from "@/lib/sql/sqlFileOpen"; -import { normalizeConnectTimeoutSecs, normalizeQueryTimeoutSecs } from "@/lib/connection/timeoutLimits"; +import { DEFAULT_QUERY_TIMEOUT_SECS, normalizeConnectTimeoutSecs, normalizeQueryTimeoutSecs } from "@/lib/connection/timeoutLimits"; import { needsTabNavigationHistoryShortcutMigration, normalizeShortcutSettings, type ShortcutSettings } from "@/lib/editor/shortcutRegistry"; import type { SavedSqlOpenTargetMode } from "@/lib/savedSql/savedSqlExecutionTarget"; import type { ConnectionListSortMode } from "@/lib/sidebar/connectionListSort"; @@ -972,7 +972,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = { executeAllOnBlankLine: false, globalConnectTimeoutSecs: 10, connectTimeoutInheritConnectionIds: [], - globalQueryTimeoutSecs: 30, + globalQueryTimeoutSecs: DEFAULT_QUERY_TIMEOUT_SECS, queryTimeoutInheritConnectionIds: [], timeoutInheritanceMigrationVersion: 2, showExecutionTargetPicker: false,