import React from 'react'; import Skeleton from 'react-loading-skeleton'; import { datasourceService, pluginsService, globalDatasourceService, libraryAppService } from '@/_services'; import cx from 'classnames'; import { Modal, Button, Tab, Row, Col, ListGroup, ModalBody } from 'react-bootstrap'; import { toast } from 'react-hot-toast'; import { cn } from '@/lib/utils'; import { getSvgIcon } from '@/_helpers/appUtils'; import { TestConnection } from './TestConnection'; import { getWorkspaceId, deepEqual, returnDevelopmentEnv, decodeEntities } from '@/_helpers/utils'; import { getSubpath } from '@/_helpers/routes'; import { DataBaseSources, ApiSources, DataSourceTypes, SourceComponent, SourceComponents, CloudStorageSources, } from '../../../common/components/DataSourceComponents'; import { CopyToClipboard } from 'react-copy-to-clipboard'; import config from 'config'; import { capitalize, isEmpty } from 'lodash'; import { Card } from '@/_ui/Card'; import { withTranslation, useTranslation } from 'react-i18next'; import { camelizeKeys, decamelizeKeys, decamelize } from 'humps'; import { ButtonSolid } from '@/_ui/AppButton/AppButton'; import SolidIcon from '@/_ui/Icon/SolidIcons'; import { useAppVersionStore } from '@/_stores/appVersionStore'; import { ConfirmDialog, ToolTip } from '@/_components'; import { shallow } from 'zustand/shallow'; import { useDataSourcesStore } from '@/_stores/dataSourcesStore'; import { withRouter } from '@/_hoc/withRouter'; import useGlobalDatasourceUnsavedChanges from '@/_hooks/useGlobalDatasourceUnsavedChanges'; import { LicenseTooltip } from '@/LicenseTooltip'; import { DATA_SOURCE_TYPE } from '@/_helpers/constants'; import './dataSourceManager.theme.scss'; import { canUpdateDataSource } from '@/_helpers'; import DataSourceSchemaManager from '@/_helpers/dataSourceSchemaManager'; import MultiEnvTabs from './MultiEnvTabs'; import { generateCypressDataCy } from '../../../common/helpers/cypressHelpers'; import posthogHelper from '@/modules/common/helpers/posthogHelper'; import SampleDataSourceBody from './SampleDataSourceBody'; class DataSourceManagerComponent extends React.Component { constructor(props) { super(props); let selectedDataSource = null; let dataSourceSchema = null; let selectedDataSourceIcon = null; let options = {}; let dataSourceMeta = {}; let datasourceName = ''; if (props.selectedDataSource) { selectedDataSource = props.selectedDataSource; options = selectedDataSource.options; dataSourceMeta = this.getDataSourceMeta(selectedDataSource); dataSourceSchema = props.selectedDataSource?.plugin?.manifestFile?.data; selectedDataSourceIcon = props.selectDataSource?.plugin?.iconFile.data; datasourceName = props.selectedDataSource?.name; } this.state = { showModal: true, appId: props.appId, selectedDataSource, dataSourceSchema, selectedDataSourceIcon, options, dataSourceMeta, isSaving: false, isCopied: false, queryString: null, plugins: [], filteredDatasources: [], activeDatasourceList: '#alldatasources', suggestingDatasources: false, scope: props?.scope, modalProps: props?.modalProps ?? {}, showBackButton: props?.showBackButton ?? true, defaultOptions: {}, pluginsLoaded: false, dataSourceConfirmModalProps: { isOpen: false, dataSource: null, category: null }, addingDataSource: false, createdDataSource: null, unsavedChangesModal: false, datasourceName, creatingApp: false, validationError: [], validationMessages: {}, showValidationErrors: false, }; } componentDidMount() { this.setState({ appId: this.props.appId, }); pluginsService .findAll() .then(({ data = [] }) => { this.setState({ plugins: data, pluginsLoaded: true }); }) .catch((error) => { this.setState({ pluginsLoaded: true }); toast.error(error?.message || 'failed to fetch plugins'); }); } componentDidUpdate(prevProps) { this.props.setGlobalDataSourceStatus({ saveAction: this.createDataSource }); if (prevProps.selectedDataSource !== this.props.selectedDataSource) { let dataSourceMeta = this.getDataSourceMeta(this.props.selectedDataSource); this.setState({ selectedDataSource: this.props.selectedDataSource, options: this.props.selectedDataSource?.options, dataSourceMeta, dataSourceSchema: this.props.selectedDataSource?.plugin?.manifestFile?.data, selectedDataSourceIcon: this.props.selectedDataSource?.plugin?.iconFile?.data, connectionTestError: null, datasourceName: this.props.selectedDataSource?.name, validationMessages: {}, validationError: [], showValidationErrors: false, }); } } getDataSourceMeta = (dataSource) => { if (!dataSource) return {}; if (dataSource?.pluginId) { let dataSourceMeta = camelizeKeys(dataSource?.plugin?.manifestFile?.data.source); dataSourceMeta.options = decamelizeKeys(dataSourceMeta.options); return dataSourceMeta; } return DataSourceTypes.find((source) => source?.kind === dataSource?.kind); }; selectDataSource = (source, category) => { posthogHelper.captureEvent('choose_datasource', { dataSource: source?.kind, category, appId: this.state.appId, }); this.hideModal(); this.setState( { dataSourceMeta: source.manifestFile?.data?.source ?? source, selectedDataSource: source.manifestFile?.data?.source ?? source, options: source?.defaults ?? source?.options, selectedDataSourceIcon: source.iconFile?.data, name: source.manifestFile?.data?.source?.kind ?? source.kind, dataSourceSchema: source.manifestFile?.data, selectedDataSourcePluginId: source.id, datasourceName: source.name, validationMessages: {}, validationError: [], showValidationErrors: false, }, () => this.createDataSource() ); }; onNameChanged = (newName) => { this.setState({ selectedDataSource: { ...this.state.selectedDataSource, name: newName, }, }); }; onExit = () => { !this.state.selectedDataSource?.id && this.props.environmentChanged(returnDevelopmentEnv(this.props.environments)); this.setState({ dataSourceMeta: {}, selectedDataSource: null, options: {}, connectionTestError: null, queryString: null, filteredDatasources: [], activeDatasourceList: '#alldatasources', }); }; setStateAsync = (state) => { return new Promise((resolve) => { this.setState(state, resolve); }); }; optionchanged = (option, value) => { const stateToUpdate = { connectionTestError: null, options: { ...this.state.options, [option]: { value: value }, }, }; return this.setStateAsync(stateToUpdate); }; resetOptions = () => { return this.setStateAsync({ connectionTestError: null, options: this.state.defaultOptions, }); }; hideModal = (ds = null) => { this.onExit(); this.props.hideModal(ds); }; resetDataSourceConfirmModal = () => { this.setState({ dataSourceConfirmModalProps: { isOpen: false, dataSource: null, category: null, }, }); }; createDataSource = () => { const { appId, options, selectedDataSource, selectedDataSourcePluginId, dataSourceMeta, dataSourceSchema, validationMessages, validationError, } = this.state; if (!isEmpty(validationMessages)) { const validationMessageArray = Object.values(validationMessages); this.setState({ validationError: validationMessageArray, showValidationErrors: true }); toast.error( this.props.t( 'editor.queryManager.dataSourceManager.toast.error.validationFailed', 'Validation failed. Please check your inputs.' ), { position: 'top-center' } ); if (validationMessageArray.length > 0) { return false; } } const OAuthDs = [ 'slack', 'zendesk', 'googlesheets', 'salesforce', 'googlecalendar', 'microsoft_graph', 'hubspot', 'gmail', ]; const name = selectedDataSource.name; const kind = selectedDataSource?.kind; const pluginId = selectedDataSourcePluginId; const appVersionId = useAppVersionStore?.getState()?.editingVersion?.id; const currentAppEnvironmentId = this.props.currentAppEnvironmentId ?? this.props.currentEnvironment?.id; const scope = this.state?.scope || selectedDataSource?.scope; posthogHelper.captureEvent('save_connection_datasource', { dataSource: kind, appId }); //posthog event const parsedOptions = Object?.keys(options)?.map((key) => { let keyMeta = dataSourceMeta.options[key]; let isEncrypted = false; if (keyMeta) { isEncrypted = keyMeta.encrypted; } // to resolve any casing mis-match if (decamelize(key) !== key) { const newKey = decamelize(key); isEncrypted = dataSourceMeta.options[newKey]?.encrypted; } return { key: key, value: options[key].value, encrypted: isEncrypted, ...(!options[key]?.value && { credential_id: options[key]?.credential_id }), }; }); if (OAuthDs.includes(kind)) { const value = localStorage.getItem('OAuthCode'); parsedOptions.push({ key: 'code', value, encrypted: false }); } if (name.trim() !== '') { let service = scope === 'global' ? globalDatasourceService : datasourceService; if (selectedDataSource.id) { this.setState({ isSaving: true }); this.props.setGlobalDataSourceStatus({ isSaving: true, isEditing: false }); service .save({ id: selectedDataSource.id, name, options: parsedOptions, app_id: appId, environment_id: currentAppEnvironmentId, }) .then(() => { this.props.updateSelectedDatasource && this.props.updateSelectedDatasource(selectedDataSource.name); this.setState({ isSaving: false }); this.hideModal(selectedDataSource); toast.success( this.props.t('editor.queryManager.dataSourceManager.toast.success.dataSourceSaved', 'Data Source Saved'), { position: 'top-center' } ); this.props.dataSourcesChanged(false, selectedDataSource); this.props.globalDataSourcesChanged && this.props.globalDataSourcesChanged(); this.props.setGlobalDataSourceStatus({ isSaving: false, isEditing: false }); scope === 'local' && this.hideModal(); }) .catch(({ error }) => { this.setState({ isSaving: false }); this.hideModal(selectedDataSource); error && toast.error(error, { position: 'top-center' }); this.resetDataSourceConfirmModal(); this.props.setGlobalDataSourceStatus({ isSaving: false, isEditing: false }); }); } else { this.setState({ isSaving: true, addingDataSource: true }); service .create({ plugin_id: pluginId, name, kind, options: parsedOptions, app_id: appId, app_version_id: appVersionId, scope, environment_id: currentAppEnvironmentId, }) .then((data) => { this.setState({ isSaving: false, addingDataSource: false }); this.props.updateSelectedDatasource && this.props.updateSelectedDatasource(name); this.hideModal(selectedDataSource); toast.success( this.props.t('editor.queryManager.dataSourceManager.toast.success.dataSourceAdded', 'Data Source Added'), { position: 'top-center' } ); this.props.dataSourcesChanged(false, data); this.props.globalDataSourcesChanged && this.props.globalDataSourcesChanged(); this.resetDataSourceConfirmModal(); }) .catch(({ error }) => { this.setState({ isSaving: false, addingDataSource: false }); this.hideModal(); error && toast.error(error, { position: 'top-center' }); this.resetDataSourceConfirmModal(); }); } } else { toast.error( this.props.t( 'editor.queryManager.dataSourceManager.toast.error.noEmptyDsName', 'The name of datasource should not be empty' ), { position: 'top-center' } ); } }; handleSearch = (searchQuery, activeDatasourceList) => { this.setState({ queryString: searchQuery }); const arr = []; const filteredDatasources = this.datasourcesGroups().filter((group) => group.key === activeDatasourceList)[0].list; filteredDatasources.forEach((datasource) => { if (datasource.name.toLowerCase().includes(searchQuery.toLowerCase())) { arr.push(datasource); } }); this.setState({ filteredDatasources: arr }); }; handleBackToAllDatasources = () => { this.setState({ queryString: null, filteredDatasources: [], activeDatasourceList: '#alldatasources', }); }; updateSuggestedDatasources = () => { this.setState({ suggestingDatasources: true, activeDatasourceList: '#' }); }; checkShouldRenderFooterComponent = (datasourceKind, datasourceOptions) => { switch (datasourceKind) { case 'googlesheets': { return datasourceOptions?.authentication_type?.value === 'service_account' ? true : false; } default: return true; } }; setValidationMessages = (errors, schema, interactedFields) => { const errorMap = errors.reduce((acc, error) => { // Get property name from either required error or dataPath const property = error.keyword === 'required' ? error.params.missingProperty : error.dataPath?.replace(/^[./]/, '') || error.instancePath?.replace(/^[./]/, ''); if (property) { const propertySchema = schema.properties?.[property]; const propertyTitle = propertySchema?.title; acc[property] = error.keyword === 'required' ? `${propertyTitle} is required` : `${propertyTitle} ${error.message}`; } return acc; }, {}); this.setState({ validationMessages: errorMap }); const filteredValidationBanner = interactedFields ? Object.keys(this.state.validationMessages) .filter((key) => interactedFields.has(key)) .reduce((result, key) => { result.push(this.state.validationMessages[key]); return result; }, []) : Object.values(this.state.validationMessages); this.setState({ validationError: filteredValidationBanner }); }; renderSourceComponent = (kind, isPlugin = false) => { const { options, isSaving, showValidationErrors } = this.state; const sourceComponentName = kind?.charAt(0).toUpperCase() + kind?.slice(1); const ComponentToRender = isPlugin ? SourceComponent : SourceComponents[sourceComponentName] || SourceComponent; return ( this.setState({ options })} optionchanged={this.optionchanged} createDataSource={this.createDataSource} options={options} isSaving={isSaving} hideModal={this.hideModal} selectedDataSource={this.state.selectedDataSource} isEditMode={!isEmpty(this.state.selectedDataSource)} currentAppEnvironmentId={this.props.currentEnvironment?.id} validationMessages={this.state.validationMessages} setValidationMessages={this.setValidationMessages} clearValidationMessages={() => this.setState({ validationMessages: {} })} setDefaultOptions={this.setDefaultOptions} showValidationErrors={showValidationErrors} clearValidationErrorBanner={() => this.setState({ validationError: [] })} elementsProps={this.props.formProps?.[kind]} /> ); }; setDefaultOptions = (defaults) => { this.setState({ defaultOptions: defaults, }); }; onConnectionTestFailed = (data) => { this.setState({ connectionTestError: data }); }; segregateDataSources = (suggestingDatasources, darkMode) => { const datasources = this.datasourcesGroups(); const handleOnSelect = (activekey) => { if (suggestingDatasources) { this.setState({ suggestingDatasources: false }); } this.setState({ activeDatasourceList: activekey }); }; const goBacktoAllDatasources = () => { this.setState({ suggestingDatasources: false }); this.handleBackToAllDatasources(); }; const datasourceSuggestionUI = () => { return (
); }; return ( handleOnSelect(activekey)} id="list-group-tabs-example" defaultActiveKey={this.state.activeDatasourceList} > {this.renderSidebarList()}
{suggestingDatasources ? (

{this.props.t('editor.queryManager.dataSourceManager.suggestDataSource', 'Suggest Datasource')}

{datasourceSuggestionUI()}
) : ( <>
{datasources.map((datasource) => ( {datasource.renderDatasources()} ))} )} {!suggestingDatasources && this.state.queryString && this.state.filteredDatasources.length === 0 && (
)}
); }; datasourcesGroups = () => { const allDataSourcesList = { databases: DataBaseSources, apis: ApiSources, cloudStorages: CloudStorageSources, plugins: this.state.plugins, filteredDatasources: this.state.filteredDatasources, }; const dataSourceList = [ { type: 'All Datasources', key: '#alldatasources', list: [ ...allDataSourcesList.databases, ...allDataSourcesList.apis, ...allDataSourcesList.cloudStorages, ...allDataSourcesList.plugins, ], renderDatasources: () => this.renderCardGroup(allDataSourcesList, 'All Datasources'), }, { type: 'Databases', key: '#databases', list: allDataSourcesList.databases, renderDatasources: () => this.renderCardGroup(allDataSourcesList.databases, 'Databases'), }, { type: 'APIs', key: '#apis', list: allDataSourcesList.apis, renderDatasources: () => this.renderCardGroup(allDataSourcesList.apis, 'APIs'), }, { type: 'Cloud Storage', key: '#cloudstorage', list: allDataSourcesList.cloudStorages, renderDatasources: () => this.renderCardGroup(allDataSourcesList.cloudStorages, 'Cloud Storages'), }, { type: 'Plugins', key: '#plugins', list: allDataSourcesList.plugins, renderDatasources: () => this.renderCardGroup(allDataSourcesList.plugins, 'Plugins'), }, { type: 'Filtered Datasources', key: '#filtereddatasources', list: allDataSourcesList.filteredDatasources, renderDatasources: () => this.renderCardGroup(this.state.filteredDatasources, this.state.activeDatasourceList), }, ]; return dataSourceList; }; renderSidebarList = () => { const dataSourceList = this.datasourcesGroups().splice(0, 5); const updateSuggestionState = () => { this.updateSuggestedDatasources(); }; return ( <> {dataSourceList.map((datasource) => ( {`${datasource.type} (${datasource.list.length})`} ))}

{this.props.t( 'editor.queryManager.dataSourceManager.noResultFound', `Don't see what you were looking for?` )}
{this.props.t('editor.queryManager.dataSourceManager.suggest', 'Suggest')}

); }; createSampleApp = () => { let _self = this; _self.setState({ creatingApp: true }); libraryAppService .createSampleApp() .then((data) => { const workspaceId = getWorkspaceId(); const subpath = getSubpath(); const path = subpath ? `${subpath}/${workspaceId}/apps/${data.app[0].id}` : `/${workspaceId}/apps/${data.app[0].id}`; window.open(path, '_blank'); toast.success('App created successfully!'); _self.setState({ creatingApp: false }); }) .catch((errorResponse) => { _self.setState({ creatingApp: false }); const message = errorResponse?.error; toast.error(message); }); }; renderSampleDBModal = () => { const { creatingApp } = this.state; return ( ); }; renderCardGroup = (source, type) => { const openDataSourceConfirmModal = (dataSource) => this.setState({ dataSourceConfirmModalProps: { isOpen: true, dataSource, category: type, }, }); if (this.state.queryString && this.state.queryString.length > 0) { const filteredDatasources = this.state.filteredDatasources.map((datasource) => { const src = datasource?.iconFile?.data ? `data:image/svg+xml;base64,${datasource.iconFile?.data}` : datasource?.kind?.toLowerCase(); return { ...datasource, src, title: datasource.name, }; }); return ( <>

{type}

{filteredDatasources.map((item) => ( openDataSourceConfirmModal(item)} usePluginIcon={isEmpty(item?.iconFile?.data)} height="35px" width="35px" /> ))}
); } if (type === 'All Datasources') { const databases = source.databases.map((datasource) => { return { ...datasource, src: datasource?.kind?.toLowerCase(), title: datasource.name, }; }); const apis = source.apis.map((datasource) => { return { ...datasource, src: datasource?.kind?.toLowerCase(), title: datasource.name, }; }); const cloudStorages = source.cloudStorages.map((datasource) => { return { ...datasource, src: datasource?.kind?.toLowerCase(), title: datasource.name, }; }); return ( <>

{'Databases'}

{databases.map((item) => ( openDataSourceConfirmModal(item)} usePluginIcon={true} height="35px" width="35px" /> ))}

{'APIs'}

{apis.map((item) => ( openDataSourceConfirmModal(item)} usePluginIcon={true} height="35px" width="35px" /> ))}

{'Cloud Storages'}

{cloudStorages.map((item) => ( openDataSourceConfirmModal(item)} usePluginIcon={true} height="35px" width="35px" /> ))}
); } const datasources = source.map((datasource) => { const src = datasource?.iconFile?.data ? `data:image/svg+xml;base64,${datasource.iconFile?.data}` : datasource?.kind?.toLowerCase(); return { ...datasource, src, title: datasource.name, }; }); return ( <>

{type}

{datasources.map((item) => ( openDataSourceConfirmModal(item)} usePluginIcon={isEmpty(item?.iconFile?.data)} height="35px" width="35px" /> ))}
); }; renderEnvironmentsTab = (selectedDataSource) => { const multiEnvironmentEnabled = this.props?.featureAccess?.multiEnvironment; const isTrial = this.props?.featureAccess?.licenseStatus?.licenseType === 'trial'; const licenseValid = !this.props?.featureAccess?.licenseStatus?.isExpired && this.props?.featureAccess?.licenseStatus?.isLicenseValid; return ( selectedDataSource && this.props.environments?.length > 1 && ( ) ); }; render() { const { classes } = this.props; const { dataSourceMeta, selectedDataSource, selectedDataSourceIcon, options, isSaving, connectionTestError, isCopied, dataSourceSchema, pluginsLoaded, dataSourceConfirmModalProps, addingDataSource, datasourceName, validationError, validationMessages, } = this.state; const isPlugin = dataSourceSchema ? true : false; const createSelectedDataSource = (dataSource, category) => { this.selectDataSource(dataSource, category); }; const isSampleDb = selectedDataSource?.type === DATA_SOURCE_TYPE.SAMPLE; const sampleDBmodalBodyStyle = isSampleDb ? { padding: '56px 32px 64px 32px', borderBottom: '1px solid #E6E8EB' } : {}; const sampleDBmodalFooterStyle = isSampleDb ? { paddingTop: '8px' } : {}; const isSaveDisabled = selectedDataSource ? (deepEqual(options, selectedDataSource?.options, ['encrypted']) && selectedDataSource?.name === datasourceName) || !isEmpty(validationMessages) : true; this.props.setGlobalDataSourceStatus({ isEditing: !isSaveDisabled }); const docLink = isSampleDb ? 'https://docs.tooljet.com/docs/data-sources/sample-data-sources' : selectedDataSource?.pluginId && selectedDataSource.pluginId.trim() !== '' ? `https://docs.tooljet.com/docs/marketplace/plugins/marketplace-plugin-${selectedDataSource?.kind}/` : `https://docs.tooljet.com/docs/data-sources/${selectedDataSource?.kind}`; const OAuthDs = [ 'slack', 'zendesk', 'googlesheets', 'salesforce', 'googlecalendar', 'snowflake', 'microsoft_graph', 'hubspot', 'gmail', ]; const shouldRenderFooterComponent = this.checkShouldRenderFooterComponent(selectedDataSource?.kind, options); return ( pluginsLoaded && (
{selectedDataSource && this.props.showBackButton && (
this.setState({ selectedDataSource: false }, () => this.onExit())} >
)} {selectedDataSource && !isSampleDb ? (
{getSvgIcon(dataSourceMeta?.kind?.toLowerCase(), 35, 35, selectedDataSourceIcon)}
this.onNameChanged(e.target.value)} className="form-control-plaintext form-control-plaintext-sm color-slate12 tw-border-x tw-border-y" value={decodeEntities(selectedDataSource.name)} style={{ width: '160px' }} data-cy="data-source-name-input-field" autoFocus autoComplete="off" disabled={!canUpdateDataSource(selectedDataSource.id)} /> {!this.props.isEditing && ( )}
) : (
{' '} Sample data source
)} {!selectedDataSource && ( {this.props.t('editor.queryManager.dataSourceManager.addNewDataSource', 'Add new datasource')} )}
{!this.props.isEditing && !this.props.hideCloseIcon && ( this.hideModal()} > )}
{this.props.tags && this.props.tags.map((tag) => { if (tag === 'AI') { return (
{tag}
); } })}
{!isSampleDb && ( )}
{this.props.environmentLoading ? ( ) : ( <> {selectedDataSource && !isSampleDb ? (
{this.renderSourceComponent(selectedDataSource.kind, isPlugin)}
) : ( selectedDataSource && isSampleDb && this.renderSampleDBModal() )} {!selectedDataSource && this.segregateDataSources(this.state.suggestingDatasources, this.props.darkMode)}
{selectedDataSource && !dataSourceMeta.customTesting && shouldRenderFooterComponent && (!OAuthDs.includes(selectedDataSource?.kind) || !( options?.auth_type?.value === 'oauth2' && options?.grant_type?.value === 'authorization_code' )) && ( {selectedDataSource && !isSampleDb && (

{this.props.t( 'editor.queryManager.dataSourceManager.whiteListIP', 'Please white-list our IP address if the data source is not publicly accessible.' )}

{isCopied ? (
{this.props.t('editor.queryManager.dataSourceManager.copied', 'Copied')}
) : ( { this.setState({ isCopied: true }); }} > {this.props.t('editor.queryManager.dataSourceManager.copy', 'Copy')} )}
)} {connectionTestError && (
{connectionTestError.message}
)} {validationError && validationError.length > 0 && (
{validationError.map((error, index) => (
{error}
))}
)}
{!isSampleDb && (
{this.props.t('globals.save', 'Save')}
)}
)} {!dataSourceMeta?.hideSave && selectedDataSource && dataSourceMeta.customTesting && (!OAuthDs.includes(selectedDataSource?.kind) || !( options?.auth_type?.value === 'oauth2' && options?.grant_type?.value === 'authorization_code' )) && (
{isSaving ? this.props.t('editor.queryManager.dataSourceManager.saving' + '...', 'Saving...') : this.props.t('globals.save', 'Save')}
)} )}
createSelectedDataSource(dataSourceConfirmModalProps.dataSource, dataSourceConfirmModalProps?.category) } onCancel={this.resetDataSourceConfirmModal} confirmButtonText={'Add datasource'} confirmButtonType="primary" cancelButtonType="tertiary" backdropClassName="datasource-selection-confirm-backdrop" confirmButtonLoading={addingDataSource} />
) ); } } const EmptyStateContainer = ({ suggestionUI = false, queryString, handleBackToAllDatasources, darkMode, placeholder, }) => { const { t } = useTranslation(); const [inputValue, set] = React.useState(() => ''); const [status, setStatus] = React.useState(false); const handleSend = () => { if (inputValue) { setStatus(true); //send value to backend } }; React.useEffect(() => { setStatus(false); }, [queryString]); return (
{queryString && !suggestionUI && (

{t( `editor.queryManager.dataSourceManager.noResultsFor + "${queryString}"`, `No results for "${queryString}"` )}

)}
{status ? (

{t('editor.queryManager.dataSourceManager.noteTaken', `Thank you, we've taken a note of that!`)}

) : (
set(e.target.value)} onKeyDown={(event) => { if (event.key === 'Enter') { handleSend(); } }} />
)}
); }; const SearchBoxContainer = ({ onChange, onClear, queryString, activeDatasourceList, dataCy, scope }) => { const [searchText, setSearchText] = React.useState(queryString ?? ''); const { t } = useTranslation(); const handleChange = (e) => { setSearchText(e.target.value); onChange(e.target.value, activeDatasourceList); }; const clearSearch = () => { setSearchText(''); onClear(); }; React.useEffect(() => { if (searchText.length > 0) { onChange(searchText, activeDatasourceList); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [activeDatasourceList]); React.useEffect(() => { if (queryString === null) { setSearchText(''); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [queryString]); React.useEffect(() => { if (searchText === '') { onClear(); } let element = document.querySelector('.input-icon .form-control:not(:first-child)'); if (scope === 'global') { element = document.querySelector('.input-icon .form-control'); } if (searchText && element) { element.style.paddingLeft = '0.5rem'; } return () => { element && (element.style.paddingLeft = '2.5rem'); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [searchText]); return (
{searchText.length === 0 && ( )} {searchText.length > 0 && ( )}
); }; const DataSourceLoader = () => { const generateLoaders = (max = 7) => { const arr = []; for (let i = 1; i < max; i++) { arr.push(
); } return arr; }; return
{generateLoaders()}
; }; const withStore = (Component) => (props) => { const { setGlobalDataSourceStatus } = useDataSourcesStore( (state) => ({ setGlobalDataSourceStatus: state.actions.setGlobalDataSourceStatus, }), shallow ); const { handleActions } = useGlobalDatasourceUnsavedChanges(); return ; }; export const DataSourceManager = withTranslation()(withRouter(withStore(DataSourceManagerComponent)));