2021-04-03 11:14:15 +00:00
|
|
|
import React from 'react';
|
|
|
|
|
import { dataqueryService, authenticationService } from '@/_services';
|
|
|
|
|
import { ToastContainer, toast } from 'react-toastify';
|
|
|
|
|
import 'react-toastify/dist/ReactToastify.css';
|
2021-04-06 04:38:36 +00:00
|
|
|
import { Restapi } from './Restapi';
|
|
|
|
|
import { Mysql } from './Mysql';
|
|
|
|
|
import { Postgresql } from './Postgresql';
|
2021-04-08 01:20:30 +00:00
|
|
|
import { Stripe } from './Stripe';
|
2021-04-13 16:52:51 +00:00
|
|
|
import { Firestore } from './Firestore';
|
2021-04-08 13:57:24 +00:00
|
|
|
import SelectSearch, { fuzzySearch } from 'react-select-search';
|
2021-04-06 04:38:36 +00:00
|
|
|
|
|
|
|
|
const allSources = {
|
|
|
|
|
Restapi,
|
|
|
|
|
Mysql,
|
2021-04-08 01:20:30 +00:00
|
|
|
Postgresql,
|
2021-04-13 16:52:51 +00:00
|
|
|
Stripe,
|
|
|
|
|
Firestore
|
2021-04-06 04:38:36 +00:00
|
|
|
}
|
2021-04-04 17:07:03 +00:00
|
|
|
|
|
|
|
|
const staticDataSources = [
|
|
|
|
|
{ kind: 'js-code', id: 'js-code', name: 'Custom JS Code' },
|
|
|
|
|
{ kind: 'restapi', id: 'restapi', name: 'REST API' },
|
|
|
|
|
]
|
|
|
|
|
|
2021-04-08 01:20:30 +00:00
|
|
|
const defaultOptions = {
|
2021-04-04 17:07:03 +00:00
|
|
|
'postgresql': {
|
|
|
|
|
|
2021-04-08 01:20:30 +00:00
|
|
|
},
|
|
|
|
|
'mysql': {
|
|
|
|
|
|
2021-04-13 16:52:51 +00:00
|
|
|
},
|
|
|
|
|
'firestore': {
|
|
|
|
|
path: '',
|
2021-04-14 04:26:44 +00:00
|
|
|
|
2021-04-04 17:07:03 +00:00
|
|
|
},
|
|
|
|
|
'restapi': {
|
|
|
|
|
method: 'GET',
|
|
|
|
|
url: null,
|
|
|
|
|
url_params: [ ['', ''] ],
|
|
|
|
|
headers: [ ['', ''] ],
|
|
|
|
|
body: [ ['', ''] ],
|
2021-04-08 01:20:30 +00:00
|
|
|
},
|
|
|
|
|
'stripe': {
|
2021-04-04 17:07:03 +00:00
|
|
|
}
|
|
|
|
|
}
|
2021-04-03 11:14:15 +00:00
|
|
|
|
|
|
|
|
class QueryManager extends React.Component {
|
|
|
|
|
constructor(props) {
|
|
|
|
|
super(props);
|
|
|
|
|
|
|
|
|
|
this.state = {
|
2021-04-07 05:02:44 +00:00
|
|
|
|
2021-04-03 11:14:15 +00:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-07 05:02:44 +00:00
|
|
|
setStateFromProps = (props) => {
|
|
|
|
|
const selectedQuery = props.selectedQuery;
|
2021-04-07 04:14:40 +00:00
|
|
|
|
2021-04-07 05:02:44 +00:00
|
|
|
this.setState({
|
|
|
|
|
appId: props.appId,
|
|
|
|
|
dataSources: props.dataSources,
|
|
|
|
|
dataQueries: props.dataQueries,
|
2021-04-07 06:26:19 +00:00
|
|
|
mode: props.mode,
|
|
|
|
|
currentTab: 1,
|
2021-04-07 05:02:44 +00:00
|
|
|
});
|
2021-04-07 04:14:40 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
if(this.props.mode === 'edit') {
|
2021-04-07 07:03:03 +00:00
|
|
|
const source = props.dataSources.find(source => source.id === selectedQuery.data_source_id)
|
2021-04-07 04:14:40 +00:00
|
|
|
|
|
|
|
|
this.setState({
|
|
|
|
|
options: selectedQuery.options,
|
2021-04-07 07:03:03 +00:00
|
|
|
selectedDataSource: source,
|
|
|
|
|
selectedQuery
|
2021-04-07 04:14:40 +00:00
|
|
|
})
|
2021-04-07 05:02:44 +00:00
|
|
|
} else {
|
|
|
|
|
this.setState({
|
|
|
|
|
options: {},
|
|
|
|
|
})
|
2021-04-07 04:14:40 +00:00
|
|
|
}
|
2021-04-07 05:02:44 +00:00
|
|
|
}
|
2021-04-07 04:14:40 +00:00
|
|
|
|
2021-04-07 05:02:44 +00:00
|
|
|
componentWillReceiveProps(nextProps) {
|
|
|
|
|
this.setStateFromProps(nextProps);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
componentDidMount() {
|
|
|
|
|
this.setStateFromProps(this.props);
|
2021-04-03 11:14:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
changeDataSource = (sourceId) => {
|
2021-04-04 17:07:03 +00:00
|
|
|
const source = [...this.state.dataSources, ...staticDataSources].find(source => source.id === sourceId);
|
|
|
|
|
this.setState({ selectedDataSource: source, options: defaultOptions[source.kind] });
|
2021-04-03 11:14:15 +00:00
|
|
|
}
|
|
|
|
|
|
2021-04-07 06:26:19 +00:00
|
|
|
switchCurrentTab = (tab) => {
|
|
|
|
|
this.setState({
|
|
|
|
|
currentTab: tab
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-04 06:26:23 +00:00
|
|
|
computeQueryName = (kind) => {
|
|
|
|
|
const { dataQueries } = this.state;
|
|
|
|
|
const currentQueriesForKind = dataQueries.filter(query => query.kind === kind);
|
|
|
|
|
let found = false;
|
|
|
|
|
let name = '';
|
2021-04-07 06:26:19 +00:00
|
|
|
let currentNumber = currentQueriesForKind.length + 1;
|
2021-04-06 03:14:52 +00:00
|
|
|
|
2021-04-04 06:26:23 +00:00
|
|
|
while(!found) {
|
|
|
|
|
name = `${kind}${currentNumber}`;
|
|
|
|
|
if(dataQueries.find(query => query.name === name) === undefined) {
|
|
|
|
|
found = true;
|
|
|
|
|
}
|
2021-04-06 03:14:52 +00:00
|
|
|
currentNumber = currentNumber + 1
|
2021-04-04 06:26:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return name;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-07 04:14:40 +00:00
|
|
|
createOrUpdateDataQuery = () => {
|
|
|
|
|
const { appId, options, selectedDataSource, mode } = this.state;
|
2021-04-04 06:26:23 +00:00
|
|
|
const name = this.computeQueryName(selectedDataSource.kind);
|
2021-04-03 11:14:15 +00:00
|
|
|
const kind = selectedDataSource.kind;
|
2021-04-06 03:14:52 +00:00
|
|
|
const dataSourceId = selectedDataSource.id;
|
2021-04-07 04:14:40 +00:00
|
|
|
|
|
|
|
|
if ( mode === 'edit') {
|
2021-04-16 13:44:54 +00:00
|
|
|
this.setState({ isUpdating: true });
|
2021-04-07 04:14:40 +00:00
|
|
|
dataqueryService.update(this.state.selectedQuery.id, options).then((data) => {
|
|
|
|
|
toast.success('Datasource Updated', { hideProgressBar: true, position: "top-center", });
|
2021-04-16 13:44:54 +00:00
|
|
|
this.setState({ isUpdating: false });
|
2021-04-07 04:14:40 +00:00
|
|
|
this.props.dataQueriesChanged();
|
|
|
|
|
});
|
|
|
|
|
} else {
|
2021-04-16 13:44:54 +00:00
|
|
|
this.setState({ isCreating: true });
|
2021-04-07 04:14:40 +00:00
|
|
|
dataqueryService.create(appId, name, kind, options, dataSourceId).then((data) => {
|
|
|
|
|
toast.success('Datasource Added', { hideProgressBar: true, position: "top-center", });
|
2021-04-16 13:44:54 +00:00
|
|
|
this.setState({ isCreating: false });
|
2021-04-07 04:14:40 +00:00
|
|
|
this.props.dataQueriesChanged();
|
|
|
|
|
});
|
|
|
|
|
}
|
2021-04-03 11:14:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
optionchanged = (option, value) => {
|
|
|
|
|
this.setState( { options: { ...this.state.options, [option]: value } } );
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-04 17:07:03 +00:00
|
|
|
optionsChanged = (newOptions) => {
|
|
|
|
|
this.setState({ options: newOptions });
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-07 07:03:03 +00:00
|
|
|
toggleOption = (option) => {
|
|
|
|
|
const currentValue = this.state.options[option] ? this.state.options[option] : false;
|
|
|
|
|
this.optionchanged(option, !currentValue);
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-08 13:57:24 +00:00
|
|
|
renderDataSourceOption = (props, option, snapshot, className) => {
|
|
|
|
|
return (
|
|
|
|
|
<button {...props} className={className} type="button">
|
|
|
|
|
<div className="row">
|
|
|
|
|
<div className="col-md-9">
|
|
|
|
|
<span className="text-muted mx-2">{option.name}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</button>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-03 11:14:15 +00:00
|
|
|
render() {
|
2021-04-16 13:44:54 +00:00
|
|
|
const { dataSources, selectedDataSource, mode, currentTab, isUpdating, isCreating } = this.state;
|
2021-04-03 11:14:15 +00:00
|
|
|
|
2021-04-06 04:38:36 +00:00
|
|
|
let ElementToRender = '';
|
|
|
|
|
|
|
|
|
|
if(selectedDataSource) {
|
|
|
|
|
const sourcecomponentName = selectedDataSource.kind.charAt(0).toUpperCase() + selectedDataSource.kind.slice(1);
|
|
|
|
|
ElementToRender = allSources[sourcecomponentName];
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-16 13:44:54 +00:00
|
|
|
let buttonText = mode === 'edit' ? 'Save' : 'Create';
|
|
|
|
|
const buttonDisabled = isUpdating || isCreating;
|
|
|
|
|
|
|
|
|
|
if(isUpdating) { buttonText = 'Saving...'}
|
|
|
|
|
if(isCreating) { buttonText = 'Creating...'}
|
|
|
|
|
|
2021-04-03 11:14:15 +00:00
|
|
|
return (
|
2021-04-07 06:26:19 +00:00
|
|
|
<div className="query-manager">
|
2021-04-03 11:14:15 +00:00
|
|
|
|
|
|
|
|
<div className="row header">
|
2021-04-07 06:26:19 +00:00
|
|
|
<div className="col">
|
|
|
|
|
<div className="nav-header">
|
|
|
|
|
<ul className="nav nav-tabs" data-bs-toggle="tabs">
|
2021-04-09 15:59:27 +00:00
|
|
|
<li className="nav-item">
|
2021-04-07 06:26:19 +00:00
|
|
|
<a
|
|
|
|
|
onClick={() => this.switchCurrentTab(1)}
|
|
|
|
|
className={currentTab === 1 ? 'nav-link active' : 'nav-link'}
|
|
|
|
|
>
|
|
|
|
|
General
|
|
|
|
|
</a>
|
|
|
|
|
</li>
|
|
|
|
|
<li className="nav-item">
|
|
|
|
|
<a
|
|
|
|
|
onClick={() => this.switchCurrentTab(2)}
|
|
|
|
|
className={currentTab === 2 ? 'nav-link active' : 'nav-link'}
|
|
|
|
|
>
|
|
|
|
|
Advanced
|
|
|
|
|
</a>
|
|
|
|
|
</li>
|
|
|
|
|
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
2021-04-03 11:14:15 +00:00
|
|
|
</div>
|
2021-04-07 06:26:19 +00:00
|
|
|
<div className="col-auto">
|
2021-04-08 06:23:39 +00:00
|
|
|
<button className="btn btn-light" onClick={this.props.toggleQueryPaneHeight}>
|
|
|
|
|
<img src="https://www.svgrepo.com/show/129993/expand.svg" width="12" height="12"/>
|
|
|
|
|
</button>
|
2021-04-16 13:44:54 +00:00
|
|
|
<button onClick={this.createOrUpdateDataQuery} disabled={buttonDisabled} className="btn btn-primary m-1 float-right">
|
|
|
|
|
{ buttonText }
|
2021-04-07 04:14:40 +00:00
|
|
|
</button>
|
2021-04-03 11:14:15 +00:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2021-04-07 06:26:19 +00:00
|
|
|
{currentTab === 1 &&
|
2021-04-09 15:59:27 +00:00
|
|
|
<div className="row row-deck p-3">
|
2021-04-07 06:26:19 +00:00
|
|
|
{(dataSources && mode ==='create') &&
|
2021-04-08 13:57:24 +00:00
|
|
|
<div className="datasource-picker mb-2">
|
2021-04-09 15:59:27 +00:00
|
|
|
<label className="form-label col-md-2">Datasource</label>
|
2021-04-08 13:57:24 +00:00
|
|
|
<SelectSearch
|
|
|
|
|
options={[
|
|
|
|
|
...dataSources.map(source => { return { name: source.name, value: source.id } }),
|
|
|
|
|
...staticDataSources.map(source => { return {name: source.name, value: source.id} }),
|
|
|
|
|
]}
|
|
|
|
|
value={selectedDataSource ? selectedDataSource.id : ''}
|
|
|
|
|
search={true}
|
|
|
|
|
onChange={(value) => this.changeDataSource(value) }
|
|
|
|
|
filterOptions={fuzzySearch}
|
|
|
|
|
renderOption={this.renderDataSourceOption}
|
|
|
|
|
placeholder="Select a data source"
|
|
|
|
|
/>
|
2021-04-07 06:26:19 +00:00
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{selectedDataSource &&
|
|
|
|
|
<div>
|
2021-04-06 04:38:36 +00:00
|
|
|
|
2021-04-18 09:29:44 +00:00
|
|
|
<ElementToRender
|
2021-04-07 06:26:19 +00:00
|
|
|
options={this.state.options}
|
|
|
|
|
optionsChanged={this.optionsChanged}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
}
|
2021-04-03 11:14:15 +00:00
|
|
|
|
2021-04-07 07:03:03 +00:00
|
|
|
{currentTab === 2 &&
|
2021-04-09 15:59:27 +00:00
|
|
|
<div className="advanced-options-container p-2 m-2">
|
|
|
|
|
<label className="form-check form-switch">
|
2021-04-07 07:03:03 +00:00
|
|
|
<input
|
2021-04-09 15:59:27 +00:00
|
|
|
className="form-check-input"
|
2021-04-07 07:03:03 +00:00
|
|
|
type="checkbox"
|
|
|
|
|
onClick={() => this.toggleOption('runOnPageLoad')}
|
|
|
|
|
checked={this.state.options.runOnPageLoad}
|
|
|
|
|
/>
|
2021-04-09 15:59:27 +00:00
|
|
|
<span className="form-check-label">Run this query on page load?</span>
|
2021-04-07 07:03:03 +00:00
|
|
|
</label>
|
2021-04-10 04:33:00 +00:00
|
|
|
<label className="form-check form-switch">
|
|
|
|
|
<input
|
|
|
|
|
className="form-check-input"
|
|
|
|
|
type="checkbox"
|
|
|
|
|
onClick={() => this.toggleOption('requestConfirmation')}
|
|
|
|
|
checked={this.state.options.requestConfirmation}
|
|
|
|
|
/>
|
|
|
|
|
<span className="form-check-label">Request confirmation before running query?</span>
|
|
|
|
|
</label>
|
2021-04-11 03:14:29 +00:00
|
|
|
|
|
|
|
|
<hr/>
|
|
|
|
|
|
|
|
|
|
<label className="form-check form-switch">
|
|
|
|
|
<input
|
|
|
|
|
className="form-check-input"
|
|
|
|
|
type="checkbox"
|
|
|
|
|
onClick={() => this.toggleOption('showSuccessNotification')}
|
|
|
|
|
checked={this.state.options.showSuccessNotification}
|
|
|
|
|
/>
|
|
|
|
|
<span className="form-check-label">Show notification on success?</span>
|
|
|
|
|
|
|
|
|
|
</label>
|
|
|
|
|
|
|
|
|
|
<div class="row mt-3">
|
|
|
|
|
<div class="col-auto">
|
|
|
|
|
<label class="form-label p-2">Success Message</label>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="col">
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
disabled={!this.state.options.showSuccessNotification}
|
|
|
|
|
value={this.state.options.successMessage}
|
|
|
|
|
onChange={(e) => this.optionchanged('successMessage', e.target.value)}
|
|
|
|
|
placeholder="Query ran successfully"
|
|
|
|
|
class="form-control"
|
|
|
|
|
value={this.state.options.successMessage}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<hr/>
|
|
|
|
|
|
|
|
|
|
<div class="row mt-3">
|
|
|
|
|
<div class="col-auto">
|
|
|
|
|
<label class="form-label p-2">Notification duration (s)</label>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="col">
|
|
|
|
|
<input
|
|
|
|
|
type="number"
|
|
|
|
|
disabled={!this.state.options.showSuccessNotification}
|
|
|
|
|
value={this.state.options.notificationDuration}
|
|
|
|
|
onChange={(e) => this.optionchanged('notificationDuration', e.target.value)}
|
|
|
|
|
placeholder={5}
|
|
|
|
|
class="form-control"
|
|
|
|
|
value={this.state.options.notificationDuration}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2021-04-07 07:03:03 +00:00
|
|
|
</div>
|
|
|
|
|
}
|
2021-04-03 11:14:15 +00:00
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-04 06:46:53 +00:00
|
|
|
export { QueryManager };
|