From c6bec5a3c5a7528385b0c39bbbb379ad99679db5 Mon Sep 17 00:00:00 2001 From: wadii Date: Tue, 14 Jul 2026 15:01:14 +0200 Subject: [PATCH 1/5] feat: ClickHouse warehouse connection types in frontend API layer --- frontend/common/types/requests.ts | 6 ++++-- frontend/common/types/responses.ts | 10 +++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/frontend/common/types/requests.ts b/frontend/common/types/requests.ts index fe7053e9cfbb..68a5694a4b6f 100644 --- a/frontend/common/types/requests.ts +++ b/frontend/common/types/requests.ts @@ -1032,7 +1032,8 @@ export type Req = { environmentId: string warehouse_type: string name?: string - config?: Record + config?: Record + credentials?: { password: string } } deleteWarehouseConnection: { environmentId: string; id: number } testWarehouseConnection: { environmentId: string; id: number } @@ -1040,7 +1041,8 @@ export type Req = { environmentId: string id: number name?: string - config?: Record + config?: Record + credentials?: { password: string } } getExperiments: PagedRequest<{ environmentId: string diff --git a/frontend/common/types/responses.ts b/frontend/common/types/responses.ts index fa51e33faee2..d4382e37562c 100644 --- a/frontend/common/types/responses.ts +++ b/frontend/common/types/responses.ts @@ -1282,12 +1282,20 @@ export type SnowflakeConfig = { user: string } +export type ClickHouseConfig = { + host: string + port: number + database: string + username: string + secure: boolean +} + export type WarehouseConnection = { id: number warehouse_type: WarehouseType status: WarehouseConnectionStatus name: string - config: SnowflakeConfig | Record + config: SnowflakeConfig | ClickHouseConfig | Record created_at: string total_events_received: number | null unique_events_count: number | null From 62a58de0b4896c2addbc3501c8fcc26f568be91b Mon Sep 17 00:00:00 2001 From: wadii Date: Tue, 14 Jul 2026 15:02:38 +0200 Subject: [PATCH 2/5] feat: ClickHouse warehouse config form --- .../warehouse-tab/ClickHouseConfigForm.tsx | 190 ++++++++++++++++++ .../__tests__/clickhouseConfig.test.ts | 75 +++++++ .../tabs/warehouse-tab/clickhouseConfig.ts | 60 ++++++ 3 files changed, 325 insertions(+) create mode 100644 frontend/web/components/pages/environment-settings/tabs/warehouse-tab/ClickHouseConfigForm.tsx create mode 100644 frontend/web/components/pages/environment-settings/tabs/warehouse-tab/__tests__/clickhouseConfig.test.ts create mode 100644 frontend/web/components/pages/environment-settings/tabs/warehouse-tab/clickhouseConfig.ts diff --git a/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/ClickHouseConfigForm.tsx b/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/ClickHouseConfigForm.tsx new file mode 100644 index 000000000000..6ecced0fb0f5 --- /dev/null +++ b/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/ClickHouseConfigForm.tsx @@ -0,0 +1,190 @@ +import React, { FC, useState } from 'react' +import Button from 'components/base/forms/Button' +import Input from 'components/base/forms/Input' +import Switch from 'components/Switch' +import ErrorMessage from 'components/ErrorMessage' +import { ClickHouseConfig } from 'common/types/responses' +import { + buildClickHousePayload, + CLICKHOUSE_DEFAULTS, + ClickHouseFormData, + ClickHouseFormState, + isClickHouseFormValid, +} from './clickhouseConfig' +import './ConfigForm.scss' + +type ClickHouseConfigFormProps = { + onSave: (data: ClickHouseFormData) => Promise + onCancel: () => void + isEdit?: boolean + initialConfig?: ClickHouseConfig + initialName?: string +} + +const getButtonLabel = (isEdit: boolean, isSaving: boolean): string => { + if (isSaving) return isEdit ? 'Saving...' : 'Creating...' + return isEdit ? 'Save changes' : 'Save and continue' +} + +const ClickHouseConfigForm: FC = ({ + initialConfig, + initialName = '', + isEdit = false, + onCancel, + onSave, +}) => { + const defaults = { ...CLICKHOUSE_DEFAULTS, ...initialConfig } + const [name, setName] = useState(initialName) + const [host, setHost] = useState(defaults.host) + const [port, setPort] = useState(String(defaults.port)) + const [database, setDatabase] = useState(defaults.database) + const [username, setUsername] = useState(defaults.username) + const [password, setPassword] = useState('') + const [secure, setSecure] = useState(defaults.secure) + const [isSaving, setIsSaving] = useState(false) + const [error, setError] = useState(false) + + const form: ClickHouseFormState = { + database, + host, + name, + password, + port, + secure, + username, + } + const isValid = isClickHouseFormValid(form, isEdit) + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + if (!isValid) return + + setIsSaving(true) + setError(false) + try { + await onSave(buildClickHousePayload(form)) + } catch { + setError(true) + setIsSaving(false) + } + } + + return ( +
+
+
+ + ) => + setHost(e.target.value) + } + placeholder='your-instance.clickhouse.cloud' + disabled={isEdit} + /> + + {isEdit + ? "Host can't be changed. To use a different ClickHouse instance, disconnect and create a new connection." + : 'The hostname of your ClickHouse instance, without protocol or port.'} + +
+ +
+ + ) => + setName(e.target.value) + } + placeholder='e.g. Production ClickHouse' + /> +
+ +
+
+ + ) => + setPort(e.target.value) + } + placeholder='9440' + /> +
+
+ + ) => + setDatabase(e.target.value) + } + placeholder='flagsmith' + /> +
+
+ +
+ + ) => + setUsername(e.target.value) + } + placeholder='default' + /> +
+ +
+ + ) => + setPassword(e.target.value) + } + type='password' + placeholder={isEdit ? '••••••••' : 'Password'} + /> + {isEdit && ( + + Leave blank to keep the current password. + + )} +
+ +
+
+ + +
+
+ + {error && ( + + )} + +
+ + +
+
+
+ ) +} + +ClickHouseConfigForm.displayName = 'ClickHouseConfigForm' +export default ClickHouseConfigForm diff --git a/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/__tests__/clickhouseConfig.test.ts b/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/__tests__/clickhouseConfig.test.ts new file mode 100644 index 000000000000..b3e2418a8251 --- /dev/null +++ b/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/__tests__/clickhouseConfig.test.ts @@ -0,0 +1,75 @@ +import { + buildClickHousePayload, + ClickHouseFormState, + isClickHouseFormValid, + isValidPort, +} from 'components/pages/environment-settings/tabs/warehouse-tab/clickhouseConfig' + +const validForm: ClickHouseFormState = { + database: 'flagsmith', + host: 'ch.example.com', + name: 'Production ClickHouse', + password: 'hunter2', + port: '9440', + secure: true, + username: 'default', +} + +describe('isValidPort', () => { + it('accepts ports within 1-65535', () => { + expect(isValidPort('1')).toBe(true) + expect(isValidPort('9440')).toBe(true) + expect(isValidPort('65535')).toBe(true) + }) + + it('rejects out-of-range or non-numeric ports', () => { + expect(isValidPort('0')).toBe(false) + expect(isValidPort('65536')).toBe(false) + expect(isValidPort('abc')).toBe(false) + expect(isValidPort('94.4')).toBe(false) + expect(isValidPort('')).toBe(false) + }) +}) + +describe('isClickHouseFormValid', () => { + it('accepts a complete form on create', () => { + expect(isClickHouseFormValid(validForm, false)).toBe(true) + }) + + it.each(['name', 'host', 'database', 'username', 'password'] as const)( + 'rejects a form with empty %s on create', + (field) => { + expect(isClickHouseFormValid({ ...validForm, [field]: '' }, false)).toBe( + false, + ) + }, + ) + + it('allows an empty password on edit (keeps the stored one)', () => { + expect(isClickHouseFormValid({ ...validForm, password: '' }, true)).toBe( + true, + ) + }) +}) + +describe('buildClickHousePayload', () => { + it('builds config with a numeric port and separates credentials', () => { + expect(buildClickHousePayload(validForm)).toEqual({ + config: { + database: 'flagsmith', + host: 'ch.example.com', + port: 9440, + secure: true, + username: 'default', + }, + credentials: { password: 'hunter2' }, + name: 'Production ClickHouse', + }) + }) + + it('omits credentials when the password is blank (edit keeps stored)', () => { + expect( + buildClickHousePayload({ ...validForm, password: '' }).credentials, + ).toBeUndefined() + }) +}) diff --git a/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/clickhouseConfig.ts b/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/clickhouseConfig.ts new file mode 100644 index 000000000000..6c08a06bdcce --- /dev/null +++ b/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/clickhouseConfig.ts @@ -0,0 +1,60 @@ +import { ClickHouseConfig } from 'common/types/responses' + +export const CLICKHOUSE_DEFAULTS: ClickHouseConfig = { + database: 'flagsmith', + host: '', + port: 9440, + secure: true, + username: 'default', +} + +export type ClickHouseFormState = { + name: string + host: string + port: string + database: string + username: string + password: string + secure: boolean +} + +export type ClickHouseFormData = { + name: string + config: ClickHouseConfig + credentials?: { password: string } +} + +export const isValidPort = (port: string): boolean => { + const value = Number(port) + return ( + /^\d+$/.test(port) && + Number.isInteger(value) && + value >= 1 && + value <= 65535 + ) +} + +export const isClickHouseFormValid = ( + form: ClickHouseFormState, + isEdit: boolean, +): boolean => + !!form.name && + !!form.host && + isValidPort(form.port) && + !!form.database && + !!form.username && + (isEdit || !!form.password) + +export const buildClickHousePayload = ( + form: ClickHouseFormState, +): ClickHouseFormData => ({ + config: { + database: form.database, + host: form.host, + port: Number(form.port), + secure: form.secure, + username: form.username, + }, + credentials: form.password ? { password: form.password } : undefined, + name: form.name, +}) From ed0f7243e4851c2a21816e097667e7126772eaa2 Mon Sep 17 00:00:00 2001 From: wadii Date: Tue, 14 Jul 2026 15:05:10 +0200 Subject: [PATCH 3/5] feat: ClickHouse warehouse setup flow behind clickhouse_warehouse flag --- .../warehouse-tab/WarehouseConnectionCard.tsx | 11 ++- .../tabs/warehouse-tab/WarehouseSetup.tsx | 33 ++++++++- .../tabs/warehouse-tab/index.tsx | 71 ++++++++++++++++++- 3 files changed, 108 insertions(+), 7 deletions(-) diff --git a/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/WarehouseConnectionCard.tsx b/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/WarehouseConnectionCard.tsx index 225ae8791711..8b32adc3d26a 100644 --- a/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/WarehouseConnectionCard.tsx +++ b/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/WarehouseConnectionCard.tsx @@ -16,6 +16,7 @@ type WarehouseConnectionCardProps = { onDelete: () => void onEdit?: () => void onSendTestEvent: () => void + onTestConnection?: () => void isSendingTestEvent: boolean isLoadingStats?: boolean } @@ -46,6 +47,7 @@ const WarehouseConnectionCard: FC = ({ onDelete, onEdit, onSendTestEvent, + onTestConnection, }) => { const typeLabel = connection.warehouse_type !== 'flagsmith' @@ -90,6 +92,10 @@ const WarehouseConnectionCard: FC = ({ 'account_identifier' in connection.config && connection.config.account_identifier && `: ${connection.config.account_identifier}`} + {connection.config && + 'host' in connection.config && + connection.config.host && + `: ${connection.config.host}`} )} @@ -139,9 +145,10 @@ const WarehouseConnectionCard: FC = ({ id='warehouse-connection-test' theme='outline' size='small' - disabled + onClick={onTestConnection} + disabled={!onTestConnection || isSendingTestEvent} > - Test connection + {isSendingTestEvent ? 'Testing...' : 'Test connection'} )} {isFlagsmith && !isPending && !isConnected && ( diff --git a/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/WarehouseSetup.tsx b/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/WarehouseSetup.tsx index 3c0946b5601b..bbaaf64d06df 100644 --- a/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/WarehouseSetup.tsx +++ b/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/WarehouseSetup.tsx @@ -5,25 +5,32 @@ import { WarehouseType } from 'common/types/responses' import { ConfigFormData } from './ConfigForm' import SelectableCard from 'components/base/SelectableCard' import ConfigForm from './ConfigForm' +import Utils from 'common/utils/utils' +import ClickHouseConfigForm from './ClickHouseConfigForm' +import { ClickHouseFormData } from './clickhouseConfig' import './WarehouseSetup.scss' type WarehouseSetupProps = { onEnableFlagsmith: () => void onCreateSnowflake: (data: ConfigFormData) => Promise + onCreateClickHouse: (data: ClickHouseFormData) => Promise isCreating: boolean } type WarehouseTypeOption = WarehouseType | 'bigquery' | 'databricks' -const CONFIGURABLE_TYPES: WarehouseTypeOption[] = ['flagsmith'] - const WarehouseSetup: FC = ({ isCreating, + onCreateClickHouse, onCreateSnowflake, onEnableFlagsmith, }) => { const [selectedType, setSelectedType] = useState('flagsmith') + const clickhouseEnabled = Utils.getFlagsmithHasFeature('clickhouse_warehouse') + const configurableTypes: WarehouseTypeOption[] = clickhouseEnabled + ? ['flagsmith', 'clickhouse'] + : ['flagsmith'] return (
@@ -46,6 +53,19 @@ const WarehouseSetup: FC = ({ onClick={() => setSelectedType('flagsmith')} />
+
+ } + title='ClickHouse' + description='Open-source OLAP database' + selected={selectedType === 'clickhouse'} + onClick={() => clickhouseEnabled && setSelectedType('clickhouse')} + disabled={!clickhouseEnabled} + /> + {!clickhouseEnabled && ( + Coming Soon + )} +
} @@ -109,7 +129,14 @@ const WarehouseSetup: FC = ({ /> )} - {!CONFIGURABLE_TYPES.includes(selectedType) && ( + {selectedType === 'clickhouse' && ( + setSelectedType('flagsmith')} + /> + )} + + {!configurableTypes.includes(selectedType) && (

Coming soon. This warehouse type is not yet available. diff --git a/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/index.tsx b/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/index.tsx index 7eac70be8241..6aac16d5588d 100644 --- a/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/index.tsx +++ b/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/index.tsx @@ -6,11 +6,13 @@ import { useTestWarehouseConnectionMutation, useUpdateWarehouseConnectionMutation, } from 'common/services/useWarehouseConnection' -import { SnowflakeConfig } from 'common/types/responses' +import { ClickHouseConfig, SnowflakeConfig } from 'common/types/responses' import WarehouseConnectionCard from './WarehouseConnectionCard' import WarehouseSetup from './WarehouseSetup' import WarehouseSetupSkeleton from './WarehouseSetupSkeleton' import ConfigForm from './ConfigForm' +import ClickHouseConfigForm from './ClickHouseConfigForm' +import { ClickHouseFormData } from './clickhouseConfig' import sendWarehouseTestEvent from './sendWarehouseTestEvent' import { getWarehousePollingInterval } from './warehousePolling' @@ -56,6 +58,7 @@ const WarehouseTab: FC = ({ environmentId }) => { }, [baseConnection, connectionsWithStats]) const connectionId = connection?.id const connectionStatus = connection?.status + const connectionType = connection?.warehouse_type const [testConnection, { isLoading: isSendingTestEvent }] = useTestWarehouseConnectionMutation() @@ -65,6 +68,7 @@ const WarehouseTab: FC = ({ environmentId }) => { }, [connection]) useEffect(() => { + if (connectionType !== 'flagsmith') return const interval = getWarehousePollingInterval(connectionStatus) if (!interval || connectionId === undefined) return testConnection({ environmentId, id: connectionId }) @@ -72,7 +76,13 @@ const WarehouseTab: FC = ({ environmentId }) => { testConnection({ environmentId, id: connectionId }) }, interval) return () => clearInterval(timer) - }, [connectionStatus, connectionId, environmentId, testConnection]) + }, [ + connectionStatus, + connectionId, + connectionType, + environmentId, + testConnection, + ]) const handleEnableFlagsmith = () => { openConfirm({ @@ -122,6 +132,43 @@ const WarehouseTab: FC = ({ environmentId }) => { }) } + const handleCreateClickHouse = (data: ClickHouseFormData) => + createConnection({ + environmentId, + warehouse_type: 'clickhouse', + ...data, + }) + .unwrap() + .then(() => toast('Warehouse connection created')) + + const handleUpdateClickHouse = (data: ClickHouseFormData) => { + if (!connection) return Promise.reject() + return updateConnection({ + environmentId, + id: connection.id, + ...data, + }) + .unwrap() + .then(() => { + setEditing(false) + toast('Warehouse connection updated') + }) + } + + const handleTestConnection = () => { + if (!connection) return + testConnection({ environmentId, id: connection.id }) + .unwrap() + .then((result) => { + if (result.status === 'connected') { + toast('Connection verified') + } else { + toast('Connection failed — check your credentials', 'danger') + } + }) + .catch(() => toast('Failed to test connection', 'danger')) + } + const handleDelete = () => { if (!connection) return deleteConnection({ environmentId, id: connection.id }) @@ -169,6 +216,7 @@ const WarehouseTab: FC = ({ environmentId }) => {

@@ -189,6 +237,20 @@ const WarehouseTab: FC = ({ environmentId }) => { ) } + if (editing && connection.warehouse_type === 'clickhouse') { + return ( +
+ setEditing(false)} + /> +
+ ) + } + return (
= ({ environmentId }) => { : undefined } onSendTestEvent={handleSendTestEvent} + onTestConnection={ + connection.warehouse_type === 'clickhouse' + ? handleTestConnection + : undefined + } isSendingTestEvent={isSendingTestEvent} isLoadingStats={isFetchingStats} /> From 69343677cdbc1f52d6f95588af0228173bccad7e Mon Sep 17 00:00:00 2001 From: wadii Date: Thu, 16 Jul 2026 12:38:27 +0200 Subject: [PATCH 4/5] feat: surface warehouse connection status detail in the frontend --- frontend/common/types/responses.ts | 1 + .../tabs/warehouse-tab/WarehouseConnectionCard.tsx | 6 +++++- .../pages/environment-settings/tabs/warehouse-tab/index.tsx | 6 +++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/frontend/common/types/responses.ts b/frontend/common/types/responses.ts index d4382e37562c..cf827b186d6b 100644 --- a/frontend/common/types/responses.ts +++ b/frontend/common/types/responses.ts @@ -1294,6 +1294,7 @@ export type WarehouseConnection = { id: number warehouse_type: WarehouseType status: WarehouseConnectionStatus + status_detail: string | null name: string config: SnowflakeConfig | ClickHouseConfig | Record created_at: string diff --git a/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/WarehouseConnectionCard.tsx b/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/WarehouseConnectionCard.tsx index 8b32adc3d26a..d80e6946b8a1 100644 --- a/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/WarehouseConnectionCard.tsx +++ b/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/WarehouseConnectionCard.tsx @@ -82,7 +82,11 @@ const WarehouseConnectionCard: FC = ({ } place='top' > - {STATUS_LABEL[connection.status]} + {connection.status === 'errored' && connection.status_detail + ? `${STATUS_LABEL[connection.status]}: ${ + connection.status_detail + }` + : STATUS_LABEL[connection.status]}
{typeLabel && ( diff --git a/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/index.tsx b/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/index.tsx index 6aac16d5588d..47ca1e466cda 100644 --- a/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/index.tsx +++ b/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/index.tsx @@ -163,7 +163,11 @@ const WarehouseTab: FC = ({ environmentId }) => { if (result.status === 'connected') { toast('Connection verified') } else { - toast('Connection failed — check your credentials', 'danger') + toast( + result.status_detail || + 'Connection failed — check your credentials', + 'danger', + ) } }) .catch(() => toast('Failed to test connection', 'danger')) From aa07e2f96920f737bd7d273d498188f208d23fd0 Mon Sep 17 00:00:00 2001 From: wadii Date: Mon, 20 Jul 2026 11:48:04 +0200 Subject: [PATCH 5/5] fix: address review feedback on warehouse frontend - Only render Test connection button when the handler is provided (backend only supports ClickHouse, not Snowflake) - Add missing id attributes on ClickHouse form buttons for E2E - Extract shared getButtonLabel helper from both config forms - Extract WarehouseConfigValue named type in requests.ts - Extract WarehouseConfigResponse named type in responses.ts --- frontend/common/types/requests.ts | 6 ++++-- frontend/common/types/responses.ts | 7 ++++++- .../tabs/warehouse-tab/ClickHouseConfigForm.tsx | 15 +++++++++------ .../tabs/warehouse-tab/ConfigForm.tsx | 6 +----- .../warehouse-tab/WarehouseConnectionCard.tsx | 4 ++-- .../tabs/warehouse-tab/warehouseFormUtils.ts | 4 ++++ 6 files changed, 26 insertions(+), 16 deletions(-) create mode 100644 frontend/web/components/pages/environment-settings/tabs/warehouse-tab/warehouseFormUtils.ts diff --git a/frontend/common/types/requests.ts b/frontend/common/types/requests.ts index 68a5694a4b6f..a2a5b2874f93 100644 --- a/frontend/common/types/requests.ts +++ b/frontend/common/types/requests.ts @@ -147,6 +147,8 @@ export interface PipelineStageRequest { actions: StageActionRequest[] } +type WarehouseConfigValue = string | number | boolean + export type Req = { getFeatureCodeReferences: { projectId: number @@ -1032,7 +1034,7 @@ export type Req = { environmentId: string warehouse_type: string name?: string - config?: Record + config?: Record credentials?: { password: string } } deleteWarehouseConnection: { environmentId: string; id: number } @@ -1041,7 +1043,7 @@ export type Req = { environmentId: string id: number name?: string - config?: Record + config?: Record credentials?: { password: string } } getExperiments: PagedRequest<{ diff --git a/frontend/common/types/responses.ts b/frontend/common/types/responses.ts index cf827b186d6b..1d17aec30d32 100644 --- a/frontend/common/types/responses.ts +++ b/frontend/common/types/responses.ts @@ -1290,13 +1290,18 @@ export type ClickHouseConfig = { secure: boolean } +export type WarehouseConfigResponse = + | SnowflakeConfig + | ClickHouseConfig + | Record + export type WarehouseConnection = { id: number warehouse_type: WarehouseType status: WarehouseConnectionStatus status_detail: string | null name: string - config: SnowflakeConfig | ClickHouseConfig | Record + config: WarehouseConfigResponse created_at: string total_events_received: number | null unique_events_count: number | null diff --git a/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/ClickHouseConfigForm.tsx b/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/ClickHouseConfigForm.tsx index 6ecced0fb0f5..66692d2a74e9 100644 --- a/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/ClickHouseConfigForm.tsx +++ b/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/ClickHouseConfigForm.tsx @@ -11,6 +11,7 @@ import { ClickHouseFormState, isClickHouseFormValid, } from './clickhouseConfig' +import { getButtonLabel } from './warehouseFormUtils' import './ConfigForm.scss' type ClickHouseConfigFormProps = { @@ -21,11 +22,6 @@ type ClickHouseConfigFormProps = { initialName?: string } -const getButtonLabel = (isEdit: boolean, isSaving: boolean): string => { - if (isSaving) return isEdit ? 'Saving...' : 'Creating...' - return isEdit ? 'Save changes' : 'Save and continue' -} - const ClickHouseConfigForm: FC = ({ initialConfig, initialName = '', @@ -169,10 +165,17 @@ const ClickHouseConfigForm: FC = ({ )}
- diff --git a/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/warehouseFormUtils.ts b/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/warehouseFormUtils.ts new file mode 100644 index 000000000000..79d787eea6be --- /dev/null +++ b/frontend/web/components/pages/environment-settings/tabs/warehouse-tab/warehouseFormUtils.ts @@ -0,0 +1,4 @@ +export const getButtonLabel = (isEdit: boolean, isSaving: boolean): string => { + if (isSaving) return isEdit ? 'Saving...' : 'Creating...' + return isEdit ? 'Save changes' : 'Save and continue' +}