diff --git a/app/client/src/actions/queryPaneActions.ts b/app/client/src/actions/queryPaneActions.ts new file mode 100644 index 0000000000..6c3344f719 --- /dev/null +++ b/app/client/src/actions/queryPaneActions.ts @@ -0,0 +1,53 @@ +import { ReduxActionTypes, ReduxAction } from "constants/ReduxActionConstants"; +import { RestAction } from "api/ActionAPI"; + +export const createQueryRequest = (payload: Partial) => { + return { + type: ReduxActionTypes.CREATE_QUERY_INIT, + payload, + }; +}; + +export const deleteQuery = (payload: { id: string }) => { + return { + type: ReduxActionTypes.DELETE_QUERY_INIT, + payload, + }; +}; + +export const deleteQuerySuccess = (payload: { id: string }) => { + return { + type: ReduxActionTypes.DELETE_QUERY_SUCCESS, + payload, + }; +}; + +export const executeQuery = (payload: { + action: RestAction; + actionId: string; +}) => { + return { + type: ReduxActionTypes.EXECUTE_QUERY_REQUEST, + payload, + }; +}; + +export const initQueryPane = ( + pluginType: string, + urlId?: string, +): ReduxAction<{ pluginType: string; id?: string }> => { + return { + type: ReduxActionTypes.INIT_QUERY_PANE, + payload: { id: urlId, pluginType }, + }; +}; + +export const changeQuery = ( + id: string, + pluginType: string, +): ReduxAction<{ id: string; pluginType: string }> => { + return { + type: ReduxActionTypes.QUERY_PANE_CHANGE, + payload: { id, pluginType }, + }; +}; diff --git a/app/client/src/api/ActionAPI.tsx b/app/client/src/api/ActionAPI.tsx index a1605af5c5..ea29393ddb 100644 --- a/app/client/src/api/ActionAPI.tsx +++ b/app/client/src/api/ActionAPI.tsx @@ -108,6 +108,10 @@ export interface ExecuteActionRequest extends APIRequest { paginationField?: PaginationField; } +export interface ExecuteQueryRequest extends APIRequest { + action: Pick | Omit; +} + export interface ExecuteActionResponse extends ApiResponse { actionId: string; data: any; @@ -200,6 +204,10 @@ class ActionAPI extends API { static moveAction(moveRequest: MoveActionRequest) { return API.put(ActionAPI.url + "/move", moveRequest); } + + static executeQuery(executeAction: any): AxiosPromise { + return API.post(ActionAPI.url + "/execute", executeAction); + } } export default ActionAPI; diff --git a/app/client/src/assets/icons/menu/queries.svg b/app/client/src/assets/icons/menu/queries.svg new file mode 100644 index 0000000000..44abd92bf2 --- /dev/null +++ b/app/client/src/assets/icons/menu/queries.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/client/src/assets/icons/widget/slash.svg b/app/client/src/assets/icons/widget/slash.svg new file mode 100644 index 0000000000..cd450db315 --- /dev/null +++ b/app/client/src/assets/icons/widget/slash.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/client/src/components/designSystems/appsmith/TextInputComponent.tsx b/app/client/src/components/designSystems/appsmith/TextInputComponent.tsx index 37aa50d7fe..7ac45d9798 100644 --- a/app/client/src/components/designSystems/appsmith/TextInputComponent.tsx +++ b/app/client/src/components/designSystems/appsmith/TextInputComponent.tsx @@ -76,7 +76,7 @@ export interface TextInputProps extends IInputGroupProps { /** Additional classname */ className?: string; type?: string; - refHandler?: (ref: HTMLInputElement | null) => void; + refHandler?: any; noValidate?: boolean; readonly?: boolean; } diff --git a/app/client/src/components/editorComponents/CodeEditor.tsx b/app/client/src/components/editorComponents/CodeEditor.tsx index 0fb14743a8..df76370def 100644 --- a/app/client/src/components/editorComponents/CodeEditor.tsx +++ b/app/client/src/components/editorComponents/CodeEditor.tsx @@ -3,6 +3,7 @@ import cm from "codemirror"; import styled from "styled-components"; import "codemirror/lib/codemirror.css"; import "codemirror/theme/monokai.css"; + require("codemirror/mode/javascript/javascript"); const Wrapper = styled.div<{ height: number }>` @@ -24,13 +25,14 @@ class CodeEditor extends React.Component { componentDidMount(): void { if (this.textArea.current) { const readOnly = !this.props.input.onChange; + this.editor = cm.fromTextArea(this.textArea.current, { mode: { name: "javascript", json: true }, value: this.props.input.value, readOnly, - lineNumbers: true, tabSize: 2, indentWithTabs: true, + lineNumbers: true, lineWrapping: true, }); this.editor.setSize(null, this.props.height); diff --git a/app/client/src/components/editorComponents/DynamicAutocompleteInput.tsx b/app/client/src/components/editorComponents/DynamicAutocompleteInput.tsx index cdfc8754f8..d2d73fab29 100644 --- a/app/client/src/components/editorComponents/DynamicAutocompleteInput.tsx +++ b/app/client/src/components/editorComponents/DynamicAutocompleteInput.tsx @@ -10,6 +10,7 @@ import "codemirror/addon/hint/javascript-hint"; import "codemirror/addon/display/placeholder"; import "codemirror/addon/edit/closebrackets"; import "codemirror/addon/display/autorefresh"; +import "codemirror/addon/mode/multiplex"; import { getDataTreeForAutocomplete } from "selectors/dataTreeSelectors"; import { AUTOCOMPLETE_MATCH_REGEX } from "constants/BindingsConstants"; import ErrorTooltip from "components/editorComponents/ErrorTooltip"; @@ -21,6 +22,42 @@ import { DataTree } from "entities/DataTree/dataTreeFactory"; import { Theme } from "constants/DefaultTheme"; import AnalyticsUtil from "utils/AnalyticsUtil"; require("codemirror/mode/javascript/javascript"); +require("codemirror/mode/sql/sql"); +require("codemirror/addon/hint/sql-hint"); + +CodeMirror.defineMode("sql-js", function(config) { + // eslint-disable-next-line @typescript-eslint/ban-ts-ignore + // @ts-ignore + return CodeMirror.multiplexingMode( + CodeMirror.getMode(config, "text/x-sql"), + { + open: "{{", + close: "}}", + mode: CodeMirror.getMode(config, { + name: "javascript", + globalVars: true, + }), + }, + // .. more multiplexed styles can follow here + ); +}); + +CodeMirror.defineMode("js-js", function(config) { + // eslint-disable-next-line @typescript-eslint/ban-ts-ignore + // @ts-ignore + return CodeMirror.multiplexingMode( + CodeMirror.getMode(config, { name: "javascript", json: true }), + { + open: "{{", + close: "}}", + mode: CodeMirror.getMode(config, { + name: "javascript", + globalVars: true, + }), + }, + // .. more multiplexed styles can follow here + ); +}); const getBorderStyle = ( props: { theme: Theme } & { @@ -208,9 +245,12 @@ export type DynamicAutocompleteInputProps = { showLineNumbers?: boolean; allowTabIndent?: boolean; singleLine: boolean; - disabled?: boolean; + mode?: string | object; + className?: string; leftImage?: string; + disabled?: boolean; link?: string; + baseMode?: string | object; }; type Props = ReduxStateProps & @@ -249,7 +289,7 @@ class DynamicAutocompleteInput extends Component { }; if (!this.props.allowTabIndent) extraKeys["Tab"] = false; this.editor = CodeMirror.fromTextArea(this.textArea.current, { - mode: { name: "javascript", globalVars: true }, + mode: this.props.mode || { name: "javascript", globalVars: true }, viewportMargin: 10, tabSize: 2, indentWithTabs: true, @@ -259,6 +299,7 @@ class DynamicAutocompleteInput extends Component { autoCloseBrackets: true, ...options, }); + this.editor.on("change", _.debounce(this.handleChange, 300)); this.editor.on("keyup", this.handleAutocompleteVisibility); this.editor.on("focus", this.handleEditorFocus); @@ -286,6 +327,7 @@ class DynamicAutocompleteInput extends Component { if (this.editor) { this.editor.refresh(); if (!this.state.isFocused) { + const currentMode = this.editor.getOption("mode"); const editorValue = this.editor.getValue(); let inputValue = this.props.input.value; // Safe update of value of the editor when value updated outside the editor @@ -295,6 +337,10 @@ class DynamicAutocompleteInput extends Component { if ((!!inputValue || inputValue === "") && inputValue !== editorValue) { this.editor.setValue(inputValue); } + + if (currentMode !== this.props.mode) { + this.editor.setOption("mode", this.props?.mode); + } } else { // Update the dynamic bindings for autocomplete if (prevProps.dynamicData !== this.props.dynamicData) { @@ -367,6 +413,11 @@ class DynamicAutocompleteInput extends Component { !cm.state.completionActive && AUTOCOMPLETE_CLOSE_KEY_CODES.indexOf(event.code) === -1; + if (this.props.baseMode) { + // https://github.com/codemirror/CodeMirror/issues/5249#issue-295565980 + cm.doc.modeOption = this.props.baseMode; + } + if (shouldShow) { AnalyticsUtil.logEvent("AUTO_COMPELTE_SHOW", {}); this.setState({ @@ -398,7 +449,7 @@ class DynamicAutocompleteInput extends Component { }; render() { - const { input, meta, theme, singleLine, disabled } = this.props; + const { input, meta, theme, singleLine, disabled, className } = this.props; const hasError = !!(meta && meta.error); let showError = false; if (this.editor) { @@ -413,6 +464,7 @@ class DynamicAutocompleteInput extends Component { singleLine={singleLine} isFocused={this.state.isFocused} disabled={disabled} + className={className} > diff --git a/app/client/src/components/editorComponents/Sidebar.tsx b/app/client/src/components/editorComponents/Sidebar.tsx index 675487f42f..cb94cdd65e 100644 --- a/app/client/src/components/editorComponents/Sidebar.tsx +++ b/app/client/src/components/editorComponents/Sidebar.tsx @@ -8,12 +8,15 @@ import { PAGE_LIST_EDITOR_URL, DATA_SOURCES_EDITOR_URL, DATA_SOURCES_EDITOR_ID_URL, + QUERIES_EDITOR_URL, + QUERIES_EDITOR_ID_URL, getCurlImportPageURL, API_EDITOR_URL_WITH_SELECTED_PAGE_ID, getProviderTemplatesURL, } from "constants/routes"; import WidgetSidebar from "pages/Editor/WidgetSidebar"; +import QuerySidebar from "pages/Editor/QuerySidebar"; import DataSourceSidebar from "pages/Editor/DataSourceSidebar"; import ApiSidebar from "pages/Editor/ApiSidebar"; import PageListSidebar from "pages/Editor/PageListSidebar"; @@ -73,11 +76,23 @@ export const Sidebar = () => { component={ApiSidebar} name={"ApiSidebar"} /> + + = { }, }; +export const API_CONSTANT = "API"; export const DEFAULT_PROVIDER_OPTION = "Business Software"; export const POST_BODY_FORMATS = ["application/json", "x-www-form-urlencoded"]; diff --git a/app/client/src/constants/ReduxActionConstants.tsx b/app/client/src/constants/ReduxActionConstants.tsx index 5df0bc482a..de3fd9cbec 100644 --- a/app/client/src/constants/ReduxActionConstants.tsx +++ b/app/client/src/constants/ReduxActionConstants.tsx @@ -28,6 +28,8 @@ export const ReduxActionTypes: { [key: string]: string } = { LOAD_API_RESPONSE: "LOAD_API_RESPONSE", LOAD_QUERY_RESPONSE: "LOAD_QUERY_RESPONSE", RUN_API_REQUEST: "RUN_API_REQUEST", + INIT_API_PANE: "INIT_API_PANE", + API_PANE_CHANGE_API: "API_PANE_CHANGE_API", RUN_API_SUCCESS: "RUN_API_SUCCESS", EXECUTE_ACTION: "EXECUTE_ACTION", EXECUTE_ACTION_SUCCESS: "EXECUTE_ACTION_SUCCESS", @@ -57,6 +59,7 @@ export const ReduxActionTypes: { [key: string]: string } = { UPDATE_ACTION_SUCCESS: "UPDATE_ACTION_SUCCESS", DELETE_ACTION_INIT: "DELETE_ACTION_INIT", DELETE_ACTION_SUCCESS: "DELETE_ACTION_SUCCESS", + CREATE_QUERY_INIT: "CREATE_QUERY_INIT", FETCH_DATASOURCES_INIT: "FETCH_DATASOURCES_INIT", FETCH_DATASOURCES_SUCCESS: "FETCH_DATASOURCES_SUCCESS", CREATE_DATASOURCE_INIT: "CREATE_DATASOURCE_INIT", @@ -86,11 +89,11 @@ export const ReduxActionTypes: { [key: string]: string } = { CREATE_APPLICATION_SUCCESS: "CREATE_APPLICATION_SUCCESS", UPDATE_WIDGET_PROPERTY_VALIDATION: "UPDATE_WIDGET_PROPERTY_VALIDATION", HIDE_PROPERTY_PANE: "HIDE_PROPERTY_PANE", - INIT_API_PANE: "INIT_API_PANE", - API_PANE_CHANGE_API: "API_PANE_CHANGE_API", INIT_DATASOURCE_PANE: "INIT_DATASOURCE_PANE", + INIT_QUERY_PANE: "INIT_QUERY_PANE", UPDATE_API_DRAFT: "UPDATE_API_DRAFT", DELETE_API_DRAFT: "DELETE_API_DRAFT", + QUERY_PANE_CHANGE: "QUERY_PANE_CHANGE", UPDATE_ROUTES_PARAMS: "UPDATE_ROUTES_PARAMS", PERSIST_USER_SESSION: "PERSIST_USER_SESSION", LOGIN_USER_INIT: "LOGIN_USER_INIT", @@ -175,6 +178,11 @@ export const ReduxActionTypes: { [key: string]: string } = { FETCH_PROVIDER_TEMPLATES_SUCCESS: "FETCH_PROVIDER_TEMPLATES_SUCCESS", ADD_API_TO_PAGE_INIT: "ADD_API_TO_PAGE_INIT", ADD_API_TO_PAGE_SUCCESS: "ADD_API_TO_PAGE_SUCCESS", + DELETE_QUERY_INIT: "DELETE_QUERY_INIT", + DELETE_QUERY_SUCCESS: "DELETE_QUERY_SUCCESS", + EXECUTE_QUERY_REQUEST: "EXECUTE_QUERY_REQUEST", + RUN_QUERY_SUCCESS: "RUN_QUERY_SUCCESS", + CLEAR_PREVIOUSLY_EXECUTED_QUERY: "CLEAR_PREVIOUSLY_EXECUTED_QUERY", FETCH_PROVIDERS_CATEGORIES_INIT: "FETCH_PROVIDERS_CATEGORIES_INIT", FETCH_PROVIDERS_CATEGORIES_SUCCESS: "FETCH_PROVIDERS_CATEGORIES_SUCCESS", FETCH_PROVIDERS_WITH_CATEGORY_INIT: "FETCH_PROVIDERS_WITH_CATEGORY_INIT", @@ -260,6 +268,7 @@ export const ReduxActionErrorTypes: { [key: string]: string } = { MOVE_ACTION_ERROR: "MOVE_ACTION_ERROR", COPY_ACTION_ERROR: "COPY_ACTION_ERROR", DELETE_PAGE_ERROR: "DELETE_PAGE_ERROR", + RUN_QUERY_ERROR: "RUN_QUERY_ERROR", DELETE_APPLICATION_ERROR: "DELETE_APPLICATION_ERROR", SET_DEFAULT_APPLICATION_PAGE_ERROR: "SET_DEFAULT_APPLICATION_PAGE_ERROR", CREATE_ORGANIZATION_ERROR: "CREATE_ORGANIZATION_ERROR", diff --git a/app/client/src/constants/forms.ts b/app/client/src/constants/forms.ts index 58e9204d08..05ebf45d7b 100644 --- a/app/client/src/constants/forms.ts +++ b/app/client/src/constants/forms.ts @@ -13,6 +13,8 @@ export const CREATE_PASSWORD_FORM_NAME = "CreatePasswordForm"; export const CREATE_ORGANIZATION_FORM_NAME = "CreateOrganizationForm"; export const CURL_IMPORT_FORM = "CurlImportForm"; + +export const QUERY_EDITOR_FORM_NAME = "QueryEditorForm"; export const API_HOME_SCREEN_FORM = "APIHomeScreenForm"; export const DATASOURCE_DB_FORM = "DatasourceDBForm"; diff --git a/app/client/src/constants/routes.ts b/app/client/src/constants/routes.ts index b2f459d0c1..42e187ed5e 100644 --- a/app/client/src/constants/routes.ts +++ b/app/client/src/constants/routes.ts @@ -1,5 +1,6 @@ import { MenuIcons } from "icons/MenuIcons"; import { FeatureFlagEnum } from "utils/featureFlags"; + export const BASE_URL = "/"; export const ORG_URL = "/org"; export const APPLICATIONS_URL = `/applications`; @@ -29,6 +30,12 @@ export type ProviderViewerRouteParams = { providerId: string; }; +export type QueryEditorRouteParams = { + applicationId: string; + pageId: string; + queryId: string; +}; + export const BUILDER_BASE_URL = (applicationId = ":applicationId"): string => `/applications/${applicationId}`; @@ -66,6 +73,17 @@ export const DATA_SOURCES_EDITOR_ID_URL = ( ): string => `${DATA_SOURCES_EDITOR_URL(applicationId, pageId)}/${datasourceId}`; +export const QUERIES_EDITOR_URL = ( + applicationId = ":applicationId", + pageId = ":pageId", +): string => `${BUILDER_PAGE_URL(applicationId, pageId)}/queries`; + +export const QUERIES_EDITOR_ID_URL = ( + applicationId = ":applicationId", + pageId = ":pageId", + queryId = ":queryId", +): string => `${QUERIES_EDITOR_URL(applicationId, pageId)}/${queryId}`; + export const API_EDITOR_ID_URL = ( applicationId = ":applicationId", pageId = ":pageId", @@ -124,6 +142,17 @@ export const getProviderTemplatesURL = ( providerId = ":providerId", ): string => `${API_EDITOR_URL(applicationId, pageId)}/provider/${providerId}`; +export const QUERY_EDITOR_URL_WITH_SELECTED_PAGE_ID = ( + applicationId = ":applicationId", + pageId = ":pageId", + selectedPageId = ":importTo", +): string => { + return `${BUILDER_PAGE_URL( + applicationId, + pageId, + )}/queries?importTo=${selectedPageId}`; +}; + export const EDITOR_ROUTES = [ { icon: MenuIcons.WIDGETS_ICON, @@ -139,6 +168,14 @@ export const EDITOR_ROUTES = [ title: "APIs", exact: false, }, + { + icon: MenuIcons.QUERIES_ICON, + className: "t--nav-link-query-editor", + path: QUERIES_EDITOR_URL, + title: "Queries", + exact: false, + flag: FeatureFlagEnum.QueryPane, + }, { icon: MenuIcons.DATASOURCES_ICON, className: "t--nav-link-datasource-editor", diff --git a/app/client/src/icons/MenuIcons.tsx b/app/client/src/icons/MenuIcons.tsx index 52064854a8..c7a2b86318 100644 --- a/app/client/src/icons/MenuIcons.tsx +++ b/app/client/src/icons/MenuIcons.tsx @@ -5,6 +5,7 @@ import { ReactComponent as ApisIcon } from "assets/icons/menu/api.svg"; import { ReactComponent as OrgIcon } from "assets/icons/menu/org.svg"; import { ReactComponent as PagesIcon } from "assets/icons/menu/pages.svg"; import { ReactComponent as DataSourcesIcon } from "assets/icons/menu/data-sources.svg"; +import { ReactComponent as QueriesIcon } from "assets/icons/menu/queries.svg"; import { ReactComponent as HomepageIcon } from "assets/icons/menu/homepage.svg"; /* eslint-disable react/display-name */ @@ -36,6 +37,11 @@ export const MenuIcons: { ), + QUERIES_ICON: (props: IconProps) => ( + + + + ), HOMEPAGE_ICON: (props: IconProps) => ( diff --git a/app/client/src/pages/Editor/QueryEditor/Form.tsx b/app/client/src/pages/Editor/QueryEditor/Form.tsx new file mode 100644 index 0000000000..5944cf81e9 --- /dev/null +++ b/app/client/src/pages/Editor/QueryEditor/Form.tsx @@ -0,0 +1,455 @@ +import React, { useState, useRef, useEffect } from "react"; +import { + reduxForm, + InjectedFormProps, + Field, + FormSubmitHandler, +} from "redux-form"; +import { + GridComponent, + ColumnsDirective, + ColumnDirective, +} from "@syncfusion/ej2-react-grids"; +import ReactJson from "react-json-view"; +import styled, { createGlobalStyle } from "styled-components"; +import { Popover } from "@blueprintjs/core"; +import history from "utils/history"; +import DynamicAutocompleteInput from "components/editorComponents/DynamicAutocompleteInput"; +import { DATA_SOURCES_EDITOR_URL } from "constants/routes"; +import TemplateMenu from "./TemplateMenu"; +import Spinner from "components/editorComponents/Spinner"; +import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper"; +import Button from "components/editorComponents/Button"; +import FormRow from "components/editorComponents/FormRow"; +import TextField from "components/editorComponents/form/fields/TextField"; +import DropdownField from "components/editorComponents/form/fields/DropdownField"; +import { BaseButton } from "components/designSystems/blueprint/ButtonComponent"; +import { Datasource } from "api/DatasourcesApi"; +import { RestAction } from "api/ActionAPI"; +import { QUERY_EDITOR_FORM_NAME } from "constants/forms"; +import { PLUGIN_PACKAGE_POSTGRES } from "constants/QueryEditorConstants"; +import "@syncfusion/ej2-react-grids/styles/material.css"; + +const QueryFormContainer = styled.div` + font-size: 20px; + padding: 20px 32px; + width: 100%; + max-height: 93vh; + a { + font-size: 14px; + line-height: 20px; + margin-top: 15px; + } + + .textAreaStyles { + border-radius: 4px; + border: 1px solid #d0d7dd; + font-size: 14px; + height: calc(100vh / 3); + } + .statementTextArea { + font-size: 14px; + line-height: 20px; + color: #2e3d49; + margin-top: 15px; + } + + && { + .CodeMirror-lines { + padding: 16px 20px; + } + } + + .queryInput { + max-width: 30%; + padding-right: 10px; + } + span.bp3-popover-target { + display: initial !important; + } +`; + +const ActionButtons = styled.div` + flex: 1; + margin-left: 10px; +`; + +const ActionButton = styled(BaseButton)` + &&& { + max-width: 72px; + margin: 0 5px; + min-height: 30px; + } +`; + +const ResponseContainer = styled.div` + margin-top: 20px; +`; + +const ResponseContent = styled.div` + height: calc( + 100vh - (100vh / 3) - 150px - ${props => props.theme.headerHeight} + ); + overflow: auto; +`; + +const DropdownSelect = styled.div` + font-size: 14px; +`; + +const NoDataSourceContainer = styled.div` + align-items: center; + display: flex; + flex-direction: column; + margin-top: 62px; + flex: 1; + .font18 { + width: 50%; + text-align: center; + margin-bottom: 23px; + font-size: 18px; + color: #2e3d49; + } +`; + +const TooltipStyles = createGlobalStyle` + .helper-tooltip{ + width: 378px; + .bp3-popover { + height: 137px; + max-width: 378px; + box-shadow: none; + display: inherit !important; + .bp3-popover-arrow { + display: block; + fill: none; + } + .bp3-popover-arrow-fill { + fill: #23292E; + } + .bp3-popover-content { + padding: 15px; + background-color: #23292E; + color: #fff; + text-align: left; + border-radius: 4px; + text-transform: initial; + font-weight: 500; + font-size: 16px; + line-height: 20px; + } + .popoverBtn { + float: right; + margin-top: 25px; + } + .popuptext { + padding-right: 30px; + } + } + } +`; + +const TableHeader = styled.div` + font-weight: 500; + font-size: 14px; + font-family: "DM Sans"; + color: #2e3d49; +`; + +const LoadingContainer = styled(CenteredWrapper)` + height: 50%; +`; + +const StyledGridComponent = styled(GridComponent)` + &&& { + .e-altrow { + background-color: #fafafa; + } + .e-active { + background: #cccccc; + } + .e-gridcontent { + max-height: calc( + 100vh - (100vh / 3) - 150px - 49px - + ${props => props.theme.headerHeight} + ); + overflow: auto; + } + } +`; + +type QueryFormProps = { + isCreating: boolean; + onDeleteClick: () => void; + onSaveClick: () => void; + onRunClick: () => void; + createTemplate: (template: any, name: string) => void; + onSubmit: FormSubmitHandler; + isDeleting: boolean; + allowSave: boolean; + isSaving: boolean; + isRunning: boolean; + dataSources: Datasource[]; + DATASOURCES_OPTIONS: any; + executedQueryData: any; + applicationId: string; + selectedPluginPackage: string; + pageId: string; + location: { + state: any; + }; +}; + +export type StateAndRouteProps = QueryFormProps; + +type Props = StateAndRouteProps & + InjectedFormProps; + +const QueryEditorForm: React.FC = (props: Props) => { + const { + handleSubmit, + allowSave, + isDeleting, + isSaving, + isRunning, + onSaveClick, + onRunClick, + onDeleteClick, + DATASOURCES_OPTIONS, + pageId, + applicationId, + dataSources, + executedQueryData, + selectedPluginPackage, + createTemplate, + isCreating, + } = props; + + const [showTemplateMenu, setMenuVisibility] = useState(true); + + const isSQL = selectedPluginPackage === PLUGIN_PACKAGE_POSTGRES; + const isNewQuery = props.location.state?.newQuery ?? false; + let queryOutput = { body: [{ "": "" }] }; + const inputEl = useRef(); + + if (executedQueryData) { + if (isSQL && executedQueryData.body.length) { + queryOutput = executedQueryData; + } + } + + useEffect(() => { + if (isNewQuery) { + inputEl.current?.select(); + } + }, [isNewQuery]); + + if (isCreating) { + return ( + + + + ); + } + return ( + +
+ + + + + + + + {dataSources.length === 0 ? ( + <> + + + +
+

+ You don’t have a Data Source to run this query +

+
+
+ + ) : ( + + )} + +
+
+
+

Query Statement

+ {isSQL ? ( + + PostgreSQL docs + + ) : ( + + Mongo docs + + )} +
+ {isNewQuery && showTemplateMenu ? ( + { + const name = isSQL + ? "actionConfiguration.query.cmd" + : "actionConfiguration.query"; + + setMenuVisibility(false); + createTemplate(templateString, name); + }} + selectedPluginPackage={selectedPluginPackage} + /> + ) : isSQL ? ( + + ) : ( + { + try { + return JSON.parse(value); + } catch (e) { + return value; + } + }} + /> + )} + + + {dataSources.length === 0 && ( + +

+ Seems like you don’t have any Datasouces to create a query +

+