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-19 16:49:57 +00:00
import { Redis } from './Redis' ;
2021-04-22 10:59:14 +00:00
import { Googlesheets } from './Googlesheets' ;
2021-04-08 13:57:24 +00:00
import SelectSearch , { fuzzySearch } from 'react-select-search' ;
2021-04-26 07:13:20 +00:00
import ReactTooltip from 'react-tooltip' ;
2021-04-28 08:06:14 +00:00
import { Elasticsearch } from './Elasticsearch' ;
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 ,
2021-04-19 16:49:57 +00:00
Firestore ,
2021-04-22 10:59:14 +00:00
Redis ,
2021-04-28 08:06:14 +00:00
Googlesheets ,
Elasticsearch
2021-04-06 04:38:36 +00:00
}
2021-04-04 17:07:03 +00:00
2021-04-29 16:39:09 +00:00
const queryNameRegex = new RegExp ( "^[A-Za-z0-9_-]*$" ) ;
2021-04-04 17:07:03 +00:00
const staticDataSources = [
{ 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-19 16:49:57 +00:00
} ,
'redis' : {
query : 'PING'
2021-04-08 01:20:30 +00:00
} ,
'mysql' : {
2021-04-13 16:52:51 +00:00
} ,
'firestore' : {
path : '' ,
2021-04-28 08:06:14 +00:00
} ,
'elasticsearch' : {
query : '' ,
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-22 10:59:14 +00:00
} ,
'googlesheets' : {
operation : 'read'
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-26 07:13:20 +00:00
addingQuery : props . addingQuery ,
editingQuery : props . editingQuery ,
2021-04-26 18:08:49 +00:00
queryPaneHeight : props . queryPaneHeight ,
currentState : props . currentState
2021-04-26 07:13:20 +00:00
} , ( ) => {
if ( this . props . mode === 'edit' ) {
const source = props . dataSources . find ( source => source . id === selectedQuery . data _source _id )
this . setState ( {
options : selectedQuery . options ,
selectedDataSource : source ,
selectedQuery ,
2021-04-29 16:39:09 +00:00
queryName : selectedQuery . name
2021-04-26 07:13:20 +00:00
} )
} else {
this . setState ( {
options : { } ,
selectedDataSource : null ,
selectedQuery : null ,
} )
}
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 ) ;
2021-04-29 16:39:09 +00:00
this . setState ( { selectedDataSource : source , options : defaultOptions [ source . kind ] , queryName : this . computeQueryName ( 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-29 16:39:09 +00:00
validateQueryName = ( ) => {
const { queryName , dataQueries , mode , selectedQuery } = this . state ;
if ( mode === 'create' ) {
return ( dataQueries . find ( query => query . name === queryName ) === undefined ) && queryNameRegex . test ( queryName ) ;
} else {
const existingQuery = dataQueries . find ( query => query . name === queryName ) ;
if ( existingQuery ) {
return ( existingQuery . id === selectedQuery . id ) && queryNameRegex . test ( queryName ) ;
} else {
queryNameRegex . test ( queryName ) ;
}
}
}
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 = ( ) => {
2021-04-29 16:39:09 +00:00
const { appId , options , selectedDataSource , mode , queryName } = this . state ;
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
2021-04-29 16:39:09 +00:00
const isQueryNameValid = this . validateQueryName ( ) ;
if ( ! isQueryNameValid ) {
toast . error ( 'Invalid query name. Should be unique and only include letters, numbers and underscore.' , { hideProgressBar : true , position : "bottom-center" , } ) ;
return ;
}
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-29 16:39:09 +00:00
dataqueryService . update ( this . state . selectedQuery . id , queryName , options ) . then ( ( data ) => {
toast . success ( 'Query Updated' , { hideProgressBar : true , position : "bottom-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-29 16:39:09 +00:00
dataqueryService . create ( appId , queryName , kind , options , dataSourceId ) . then ( ( data ) => {
toast . success ( 'Query Added' , { hideProgressBar : true , position : "bottom-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-26 07:13:20 +00:00
const {
dataSources ,
selectedDataSource ,
mode ,
currentTab ,
isUpdating ,
isCreating ,
addingQuery ,
editingQuery ,
selectedQuery ,
2021-04-26 18:08:49 +00:00
queryPaneHeight ,
2021-04-29 16:39:09 +00:00
currentState ,
queryName
2021-04-26 07:13:20 +00:00
} = 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-26 07:13:20 +00:00
< div className = "query-manager" key = { selectedQuery ? selectedQuery . id : '' } >
2021-04-26 18:27:37 +00:00
< ReactTooltip type = "dark" effect = "solid" delayShow = { 250 } / >
2021-04-03 11:14:15 +00:00
< div className = "row header" >
2021-04-07 06:26:19 +00:00
< div className = "col" >
2021-04-26 07:13:20 +00:00
{ ( addingQuery || editingQuery ) &&
< div className = "nav-header" >
< ul className = "nav nav-tabs" data - bs - toggle = "tabs" >
< li className = "nav-item" >
< a
onClick = { ( ) => this . switchCurrentTab ( 1 ) }
className = { currentTab === 1 ? 'nav-link active' : 'nav-link' }
>
& nbsp ; General
< / a >
< / li >
< li className = "nav-item" >
< a
onClick = { ( ) => this . switchCurrentTab ( 2 ) }
className = { currentTab === 2 ? 'nav-link active' : 'nav-link' }
>
& nbsp ; Advanced
< / a >
< / li >
< / ul >
< / div >
}
2021-04-03 11:14:15 +00:00
< / div >
2021-04-29 16:39:09 +00:00
{ ( addingQuery || editingQuery ) &&
2021-04-29 16:56:33 +00:00
< div className = "col query-name-field" >
< div className = "input-icon" style = { { width : '160px' } } >
< input
type = "text"
onChange = { ( e ) => this . setState ( { queryName : e . target . value } ) }
class = "form-control-plaintext form-control-plaintext-sm mt-1"
value = { queryName }
style = { { width : '160px' } }
autoFocus
/ >
< span class = "input-icon-addon" >
< img src = "https://www.svgrepo.com/show/149235/edit.svg" width = "12" height = "12" / >
< / span >
< / div >
2021-04-29 16:39:09 +00:00
< / div >
}
2021-04-07 06:26:19 +00:00
< div className = "col-auto" >
2021-04-26 17:54:41 +00:00
{ ( ( addingQuery || editingQuery ) && selectedQuery ) &&
2021-04-26 18:27:37 +00:00
< span
data - tip = "NOTE: Query should be saved before running."
2021-04-26 18:08:49 +00:00
onClick = { ( ) => this . props . runQuery ( selectedQuery . id , selectedQuery . name ) }
className = { ` btn btn-secondary m-1 float-right1 ${ currentState . queries [ selectedQuery . name ] . isLoading === true ? ' btn-loading' : '' } ` }
>
2021-04-26 17:54:41 +00:00
Run
2021-04-26 18:27:37 +00:00
< / span >
2021-04-26 17:54:41 +00:00
}
2021-04-26 07:13:20 +00:00
{ ( addingQuery || editingQuery ) &&
< button onClick = { this . createOrUpdateDataQuery } disabled = { buttonDisabled } className = "btn btn-primary m-1 float-right" >
{ buttonText }
< / button >
}
{ queryPaneHeight === '30%' ?
2021-04-26 18:27:37 +00:00
< span className = "btn btn-light m-1" onClick = { this . props . toggleQueryPaneHeight } data - tip = "Maximize query editor" >
2021-04-26 07:13:20 +00:00
< img src = "https://www.svgrepo.com/show/129993/expand.svg" width = "12" height = "12" / >
2021-04-26 18:27:37 +00:00
< / span >
2021-04-26 07:13:20 +00:00
:
2021-04-26 18:27:37 +00:00
< span className = "btn btn-light m-1" onClick = { this . props . toggleQueryPaneHeight } data - tip = "Minimize query editor" >
2021-04-26 07:13:20 +00:00
< img src = "https://www.svgrepo.com/show/310476/arrow-minimize.svg" width = "12" height = "12" / >
2021-04-26 18:27:37 +00:00
< / span >
2021-04-26 07:13:20 +00:00
}
2021-04-03 11:14:15 +00:00
< / div >
< / div >
2021-04-26 07:13:20 +00:00
{ ( addingQuery || editingQuery ) &&
< div >
{ currentTab === 1 &&
< div className = "row row-deck p-3" >
{ ( dataSources && mode === 'create' ) &&
< div className = "datasource-picker mb-2" >
< label className = "form-label col-md-2" > Datasource < / label >
< 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"
/ >
< / div >
}
{ selectedDataSource &&
< div >
< ElementToRender
options = { this . state . options }
optionsChanged = { this . optionsChanged }
/ >
< / div >
}
2021-04-07 06:26:19 +00:00
< / div >
}
2021-04-26 07:13:20 +00:00
{ currentTab === 2 &&
< div className = "advanced-options-container p-2 m-2" >
< label className = "form-check form-switch" >
< input
className = "form-check-input"
type = "checkbox"
onClick = { ( ) => this . toggleOption ( 'runOnPageLoad' ) }
checked = { this . state . options . runOnPageLoad }
/ >
< span className = "form-check-label" > Run this query on page load ? < / span >
< / label >
< 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
2021-04-26 07:13:20 +00:00
< hr / >
2021-04-11 03:14:29 +00:00
2021-04-26 07:13:20 +00:00
< 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-11 03:14:29 +00:00
< / div >
2021-04-26 07:13:20 +00:00
}
< / div >
}
2021-04-03 11:14:15 +00:00
< / div >
)
}
}
2021-04-04 06:46:53 +00:00
export { QueryManager } ;