ToolJet/frontend/src/Editor/QueryManager/Components/DataSourcePicker.jsx
Akshay 9316b763fc
Refactor: Workflows feature is configurable (#13235)
* refactor: Disable workflows for cloud edition

* chore: update submodule to latest commit
2025-07-08 14:07:42 +05:30

233 lines
9.1 KiB
JavaScript

import React, { useState, useEffect } from 'react';
import Plus from '@/_ui/Icon/solidIcons/Plus';
import Information from '@/_ui/Icon/solidIcons/Information';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { getWorkspaceId, decodeEntities } from '@/_helpers/utils';
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
import { SearchBox as SearchBox2 } from '@/_components/SearchBox';
import DataSourceIcon from './DataSourceIcon';
import { isEmpty } from 'lodash';
import { Col, Container, Row } from 'react-bootstrap';
import { useDataQueriesActions } from '@/_stores/dataQueriesStore';
import { useQueryPanelActions } from '@/_stores/queryPanelStore';
import { Tooltip } from 'react-tooltip';
import { canCreateDataSource } from '@/_helpers';
import SolidIcon from '@/_ui/Icon/SolidIcons';
import { isWorkflowsFeatureEnabled } from '@/modules/common/helpers/utils';
import '../queryManager.theme.scss';
function DataSourcePicker({ dataSources, sampleDataSource, staticDataSources, darkMode, globalDataSources }) {
const allUserDefinedSources = [...dataSources, ...globalDataSources];
const [searchTerm, setSearchTerm] = useState();
const [filteredUserDefinedDataSources, setFilteredUserDefinedDataSources] = useState(allUserDefinedSources);
const navigate = useNavigate();
const { createDataQuery } = useDataQueriesActions();
const { setPreviewData } = useQueryPanelActions();
const docLink = 'sampledb.com';
const handleChangeDataSource = (source) => {
createDataQuery(source);
setPreviewData(null);
};
useEffect(() => {
if (searchTerm) {
const formattedSearchTerm = searchTerm.toLowerCase();
const filteredResults = allUserDefinedSources.filter(
({ name, kind }) =>
name.toLowerCase().includes(formattedSearchTerm) || kind.toLowerCase().includes(formattedSearchTerm)
);
setFilteredUserDefinedDataSources(filteredResults);
} else {
setFilteredUserDefinedDataSources(allUserDefinedSources);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchTerm, globalDataSources, dataSources]);
const handleAddClick = () => {
const workspaceId = getWorkspaceId();
navigate(`/${workspaceId}/data-sources`);
};
const workflowsEnabled = isWorkflowsFeatureEnabled();
return (
<>
<h4 className="w-100 text-center" data-cy={'label-select-datasource'} style={{ fontWeight: 500 }}>
Connect to a Data source
</h4>
<p className="mb-3" style={{ textAlign: 'center' }}>
Select a Data source to start creating a new query. To know more about queries in ToolJet, you can read our
&nbsp;
<a
data-cy="querymanager-doc-link"
target="_blank"
href="https://docs.tooljet.ai/docs/app-builder/query-panel"
rel="noreferrer"
>
documentation
</a>
</p>
<div>
<label className="form-label" data-cy={`landing-page-label-default`}>
Default
</label>
<div className="query-datasource-card-container d-flex justify-content-between mb-3 mt-2">
{staticDataSources.map((source) => {
if (!workflowsEnabled && source.kind === 'workflows') return null;
return (
<ButtonSolid
key={`${source.id}-${source.kind}`}
variant="tertiary"
size="sm"
onClick={() => {
handleChangeDataSource(source);
}}
className="text-truncate"
data-cy={`${source.kind.toLowerCase().replace(/\s+/g, '-')}-add-query-card`}
>
<DataSourceIcon source={source} height={14} /> {source.shortName}
</ButtonSolid>
);
})}
</div>
{!!sampleDataSource && (
<div>
<label className="form-label sample-db-data-query-picker-form-label" data-cy={`landing-page-label-default`}>
Sample data sources
</label>
<div className="query-datasource-card-container d-flex justify-content-between mb-3 mt-2">
<ButtonSolid
key={`${sampleDataSource.id}-${sampleDataSource.kind}`}
variant="tertiary"
size="sm"
onClick={() => {
handleChangeDataSource(sampleDataSource);
}}
className="text-truncate"
data-cy={`${sampleDataSource.kind.toLowerCase().replace(/\s+/g, '-')}-sample-db-add-query-card`}
>
<DataSourceIcon source={sampleDataSource} height={14} />{' '}
{sampleDataSource.kind == 'postgresql' ? 'PostgreSQL' : 'ToolJetDB'}
</ButtonSolid>
</div>
{/* Info icon */}
<div className="open-doc-link-container">
<div className="col-md-1 info-btn">
<SolidIcon name="informationcircle" fill="#3E63DD" />
</div>
<div className="col-md-11">
<div className="message" data-cy="warning-text">
<p>
This is a shared resource and may show varying data due to real-time updates. It&apos;s reset daily
for some consistency, but please note it&apos;s designed for user exploration, not production
use.&nbsp;
<a onClick={handleAddClick} target="_blank" rel="noopener noreferrer" className="opn-git-btn">
Explore available data sources
</a>{' '}
<SolidIcon name="open" width={'8'} height={'8'} viewBox={'0 0 10 10'} />
</p>
</div>
</div>
</div>
</div>
)}
<div className="d-flex d-flex justify-content-between">
<label className="form-label py-1" style={{ width: 'auto' }} data-cy={`label-avilable-ds`}>
{`Available Data sources ${!isEmpty(allUserDefinedSources) ? '(' + allUserDefinedSources.length + ')' : 0}`}
</label>
{canCreateDataSource() && (
<ButtonSolid
size="sm"
variant="ghostBlue"
onClick={handleAddClick}
data-cy={`landing-page-add-new-ds-button`}
>
<Plus style={{ height: '16px' }} fill="var(--indigo9)" />
Add new
</ButtonSolid>
)}
</div>
{isEmpty(allUserDefinedSources) ? (
<EmptyDataSourceBanner />
) : (
<Container className="p-0">
{allUserDefinedSources.length > 4 && (
<SearchBox
onSearch={setSearchTerm}
darkMode={darkMode}
searchTerm={searchTerm}
dataCy={`gds-querymanager`}
/>
)}
<Row className="mt-2">
{filteredUserDefinedDataSources.map((source) => (
<Col sm="6" key={source.id} className="ps-1">
<ButtonSolid
key={`${source.id}-${source.kind}`}
variant="ghostBlack"
size="sm"
className="font-weight-400 py-3 mb-1 w-100 justify-content-start"
onClick={() => {
handleChangeDataSource(source);
}}
data-tooltip-id="tooltip-for-query-panel-ds-picker-btn"
data-tooltip-content={decodeEntities(source.name)}
data-cy={`${String(source.name).toLowerCase().replace(/\s+/g, '-')}-add-query-card`}
>
<DataSourceIcon source={source} height={14} styles={{ minWidth: 14 }} />
<span className="text-truncate">{decodeEntities(source.name)}</span>
<Tooltip id="tooltip-for-query-panel-ds-picker-btn" className="tooltip" />
</ButtonSolid>
</Col>
))}
</Row>
</Container>
)}
</div>
</>
);
}
const EmptyDataSourceBanner = () => (
<div className="bg-slate3 p-3 d-flex align-items-center lh-lg mt-2" style={{ borderRadius: '6px' }}>
<div className="me-2">
<Information fill="var(--slate9)" />
</div>
<div>No Data sources have been added yet.</div>
</div>
);
const SearchBox = ({ onSearch, darkMode, searchTerm, dataCy }) => {
const { t } = useTranslation();
return (
<Row>
<Col className="mt-2 mb-2">
<SearchBox2
width="100%"
type="text"
className={`form-control ${darkMode && 'dark-theme-placeholder'}`}
placeholder={t('globals.search', 'Search') + '...'}
value={searchTerm}
callBack={(e) => onSearch(e.target.value)}
onClearCallback={() => onSearch('')}
dataCy={dataCy}
/>
{/* <span
className="position-absolute"
style={{ top: '50%', transform: 'translate(0%, -50%)', paddingLeft: '10px' }}
>
<Search style={{ width: '16px' }} />
</span> */}
</Col>
</Row>
);
};
export default DataSourcePicker;