{
- componentDidMount() {
- PerformanceTracker.stopTracking(PerformanceTransactionName.OPEN_ACTION, {
- actionType: "API",
- });
- const type = this.getFormName();
- this.props.changeAPIPage(this.props.match.params.apiId, type === "SAAS");
- }
- handleDeleteClick = () => {
- const pageName = getPageName(
- this.props.pages,
- this.props.match.params.pageId,
- );
- AnalyticsUtil.logEvent("DELETE_API_CLICK", {
- apiName: this.props.apiName,
- apiID: this.props.match.params.apiId,
- pageName: pageName,
- });
- this.props.deleteAction(this.props.match.params.apiId, this.props.apiName);
- };
+ const moreActionsMenu = useMemo(
+ () => (
+
+ ),
+ [action?.id, action?.name, isChangePermitted, isDeletePermitted, pageId],
+ );
- getFormName = () => {
- const plugins = this.props.plugins;
- const pluginId = this.props.pluginId;
- const plugin =
- plugins &&
- plugins.find((plug) => {
- if (plug.id === pluginId) return plug;
- });
- return plugin && plugin.type;
- };
-
- componentDidUpdate(prevProps: Props) {
- if (prevProps.isRunning && !this.props.isRunning) {
- PerformanceTracker.stopTracking(PerformanceTransactionName.RUN_API_CLICK);
- }
- if (prevProps.match.params.apiId !== this.props.match.params.apiId) {
- const type = this.getFormName();
- this.props.changeAPIPage(this.props.match.params.apiId, type === "SAAS");
- }
- }
-
- handleRunClick = (paginationField?: PaginationField) => {
- const pageName = getPageName(
- this.props.pages,
- this.props.match.params.pageId,
- );
- const pluginName = this.props.plugins.find(
- (plugin) => plugin.id === this.props.pluginId,
- )?.name;
- PerformanceTracker.startTracking(PerformanceTransactionName.RUN_API_CLICK, {
- apiId: this.props.match.params.apiId,
- });
- AnalyticsUtil.logEvent("RUN_API_CLICK", {
- apiName: this.props.apiName,
- apiID: this.props.match.params.apiId,
- pageName: pageName,
- datasourceId: (this.props?.apiAction as any)?.datasource?.id,
- pluginName: pluginName,
- isMock: false, // as mock db exists only for postgres and mongo plugins
- });
- this.props.runAction(this.props.match.params.apiId, paginationField);
- };
-
- getPluginUiComponentOfId = (
- id: string,
- plugins: Plugin[],
- ): string | undefined => {
- const plugin = plugins.find((plugin) => plugin.id === id);
- if (!plugin) return undefined;
- return plugin.uiComponent;
- };
-
- getPluginUiComponentOfName = (plugins: Plugin[]): string | undefined => {
- const plugin = plugins.find(
- (plugin) => plugin.packageName === PluginPackageName.REST_API,
- );
- if (!plugin) return undefined;
- return plugin.uiComponent;
- };
-
- render() {
- const {
- isCreating,
- isDeleting,
- isEditorInitialized,
- isRunning,
- match: {
- params: { apiId },
- },
- paginationType,
- pluginId,
- plugins,
- } = this.props;
- if (!pluginId && apiId) {
- return ;
- }
- if (isCreating || !isEditorInitialized) {
- return (
-
-
-
+ const handleRunClick = useCallback(
+ (paginationField?: PaginationField) => {
+ const pluginName = plugins.find((plugin) => plugin.id === pluginId)?.name;
+ PerformanceTracker.startTracking(
+ PerformanceTransactionName.RUN_API_CLICK,
+ {
+ apiId,
+ },
);
- }
+ AnalyticsUtil.logEvent("RUN_API_CLICK", {
+ apiName,
+ apiID: apiId,
+ pageName: pageName,
+ datasourceId,
+ pluginName: pluginName,
+ isMock: false, // as mock db exists only for postgres and mongo plugins
+ });
+ dispatch(runAction(apiId, paginationField));
+ },
+ [apiId, apiName, pageName, getPageName, plugins, pluginId, datasourceId],
+ );
- let formUiComponent: string | undefined;
- if (apiId) {
- if (pluginId) {
- formUiComponent = this.getPluginUiComponentOfId(pluginId, plugins);
- } else {
- formUiComponent = this.getPluginUiComponentOfName(plugins);
- }
- }
+ const actionRightPaneBackLink = useMemo(() => {
+ return ;
+ }, [pageId]);
- return (
-
- {formUiComponent === "ApiEditorForm" && (
-
- )}
- {formUiComponent === "GraphQLEditorForm" && (
-
- )}
- {formUiComponent === "RapidApiEditorForm" && (
-
- )}
- {formUiComponent === "SaaSEditorForm" &&
- history.push(
- saasEditorApiIdURL({
- pageId: this.props.match.params.pageId,
- pluginPackageName:
- getPackageNameFromPluginId(
- this.props.pluginId,
- this.props.plugins,
- ) ?? "",
- apiId: this.props.match.params.apiId,
- }),
- )}
-
- );
- }
+ const handleDeleteClick = useCallback(() => {
+ AnalyticsUtil.logEvent("DELETE_API_CLICK", {
+ apiName,
+ apiID: apiId,
+ pageName,
+ });
+ dispatch(deleteAction({ id: apiId, name: apiName }));
+ }, [getPageName, pages, pageId, apiName]);
+
+ const closeEditorLink = useMemo(() => , []);
+
+ return (
+
+
+
+ );
}
-const formStyles: CSSProperties = {
- position: "relative",
- height: "100%",
- display: "flex",
- flexDirection: "column",
-};
-
-const mapStateToProps = (state: AppState, props: any): ReduxStateProps => {
- const apiAction = getActionById(state, props);
- const apiName = getApiName(state, props.match.params.apiId);
- const { isCreating, isDeleting, isRunning } = state.ui.apiPane;
- const pluginId = _.get(apiAction, "pluginId", "");
- const settingsConfig = getPluginSettingConfigs(state, pluginId);
- return {
- actions: state.entities.actions,
- currentApplication: getCurrentApplication(state),
- currentPageName: getCurrentPageName(state),
- pages: getPageList(state),
- apiName: apiName || "",
- plugins: getPlugins(state),
- pluginId,
- settingsConfig,
- paginationType: _.get(apiAction, "actionConfiguration.paginationType"),
- apiAction,
- isRunning: isRunning[props.match.params.apiId],
- isDeleting: isDeleting[props.match.params.apiId],
- isCreating: isCreating,
- isEditorInitialized: getIsEditorInitialized(state),
- applicationId: getCurrentApplicationId(state),
- };
-};
-
-const mapDispatchToProps = (dispatch: any): ReduxActionProps => ({
- submitForm: (name: string) => dispatch(submit(name)),
- runAction: (id: string, paginationField?: PaginationField) =>
- dispatch(runAction(id, paginationField)),
- deleteAction: (id: string, name: string) =>
- dispatch(deleteAction({ id, name })),
- changeAPIPage: (actionId: string, isSaas: boolean) =>
- dispatch(changeApi(actionId, isSaas)),
-});
-
-export default Sentry.withProfiler(
- connect(mapStateToProps, mapDispatchToProps)(ApiEditor),
-);
+export default ApiEditorWrapper;
diff --git a/app/client/src/pages/Editor/QueryEditor/Editor.tsx b/app/client/src/pages/Editor/QueryEditor/Editor.tsx
new file mode 100644
index 0000000000..789a427956
--- /dev/null
+++ b/app/client/src/pages/Editor/QueryEditor/Editor.tsx
@@ -0,0 +1,352 @@
+import React from "react";
+import type { RouteComponentProps } from "react-router";
+import { connect } from "react-redux";
+import { getFormValues } from "redux-form";
+import styled from "styled-components";
+import type { QueryEditorRouteParams } from "constants/routes";
+import QueryEditorForm from "./Form";
+import type { UpdateActionPropertyActionPayload } from "actions/pluginActionActions";
+import {
+ deleteAction,
+ runAction,
+ setActionResponseDisplayFormat,
+ setActionProperty,
+} from "actions/pluginActionActions";
+import type { AppState } from "@appsmith/reducers";
+import { getCurrentApplicationId } from "selectors/editorSelectors";
+import { QUERY_EDITOR_FORM_NAME } from "@appsmith/constants/forms";
+import type { Plugin } from "api/PluginApi";
+import { UIComponentTypes } from "api/PluginApi";
+import type { Datasource } from "entities/Datasource";
+import {
+ getPluginIdsOfPackageNames,
+ getPlugins,
+ getAction,
+ getActionResponses,
+ getDatasourceByPluginId,
+ getDBAndRemoteDatasources,
+} from "@appsmith/selectors/entitiesSelector";
+import { PLUGIN_PACKAGE_DBS } from "constants/QueryEditorConstants";
+import type { QueryAction, SaaSAction } from "entities/Action";
+import Spinner from "components/editorComponents/Spinner";
+import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper";
+import PerformanceTracker, {
+ PerformanceTransactionName,
+} from "utils/PerformanceTracker";
+import AnalyticsUtil from "utils/AnalyticsUtil";
+import { initFormEvaluations } from "@appsmith/actions/evaluationActions";
+import { getUIComponent } from "./helpers";
+import type { Diff } from "deep-diff";
+import { diff } from "deep-diff";
+import EntityNotFoundPane from "pages/Editor/EntityNotFoundPane";
+import { getConfigInitialValues } from "components/formControls/utils";
+import { merge } from "lodash";
+import { getPathAndValueFromActionDiffObject } from "../../../utils/getPathAndValueFromActionDiffObject";
+import { getCurrentEnvironmentDetails } from "@appsmith/selectors/environmentSelectors";
+import { QueryEditorContext } from "./QueryEditorContext";
+
+const EmptyStateContainer = styled.div`
+ display: flex;
+ height: 100%;
+ font-size: 20px;
+`;
+
+const LoadingContainer = styled(CenteredWrapper)`
+ height: 50%;
+`;
+
+interface ReduxDispatchProps {
+ runAction: (actionId: string) => void;
+ deleteAction: (id: string, name: string) => void;
+ initFormEvaluation: (
+ editorConfig: any,
+ settingConfig: any,
+ formId: string,
+ ) => void;
+ updateActionResponseDisplayFormat: ({
+ field,
+ id,
+ value,
+ }: UpdateActionPropertyActionPayload) => void;
+ setActionProperty: (
+ actionId: string,
+ propertyName: string,
+ value: string,
+ ) => void;
+}
+
+interface ReduxStateProps {
+ plugins: Plugin[];
+ dataSources: Datasource[];
+ isRunning: boolean;
+ isDeleting: boolean;
+ formData: QueryAction | SaaSAction;
+ runErrorMessage: Record;
+ pluginId: string | undefined;
+ pluginIds: Array | undefined;
+ responses: any;
+ isCreating: boolean;
+ editorConfig: any;
+ uiComponent: UIComponentTypes;
+ applicationId: string;
+ actionId: string;
+ actionObjectDiff?: any;
+ isSaas: boolean;
+ datasourceId?: string;
+ currentEnvironmentId: string;
+ currentEnvironmentName: string;
+}
+
+type StateAndRouteProps = RouteComponentProps;
+type OwnProps = StateAndRouteProps & {
+ isEditorInitialized: boolean;
+ settingsConfig: any;
+};
+type Props = ReduxDispatchProps & ReduxStateProps & OwnProps;
+
+class QueryEditor extends React.Component {
+ static contextType = QueryEditorContext;
+ context!: React.ContextType;
+
+ constructor(props: Props) {
+ super(props);
+ // Call the first evaluations when the page loads
+ // call evaluations only for queries and not google sheets (which uses apiId)
+ if (this.props.match.params.queryId) {
+ this.props.initFormEvaluation(
+ this.props.editorConfig,
+ this.props.settingsConfig,
+ this.props.match.params.queryId,
+ );
+ }
+ }
+
+ componentDidMount() {
+ // if the current action is non existent, do not dispatch change query page action
+ // this action should only be dispatched when switching from an existent action.
+ if (!this.props.pluginId) return;
+ this.context?.changeQueryPage?.(this.props.actionId);
+
+ // fixes missing where key issue by populating the action with a where object when the component is mounted.
+ if (this.props.isSaas) {
+ const { path = "", value = "" } = {
+ ...getPathAndValueFromActionDiffObject(this.props.actionObjectDiff),
+ };
+ if (value && path) {
+ this.props.setActionProperty(this.props.actionId, path, value);
+ }
+ }
+
+ PerformanceTracker.stopTracking(PerformanceTransactionName.OPEN_ACTION, {
+ actionType: "QUERY",
+ });
+ }
+
+ handleDeleteClick = () => {
+ const { formData } = this.props;
+
+ this.props.deleteAction(this.props.actionId, formData.name);
+ };
+
+ handleRunClick = () => {
+ const { dataSources } = this.props;
+ const datasource = dataSources.find(
+ (datasource) => datasource.id === this.props.datasourceId,
+ );
+ const pluginName = this.props.plugins.find(
+ (plugin) => plugin.id === this.props.pluginId,
+ )?.name;
+ PerformanceTracker.startTracking(
+ PerformanceTransactionName.RUN_QUERY_CLICK,
+ { actionId: this.props.actionId },
+ );
+ AnalyticsUtil.logEvent("RUN_QUERY_CLICK", {
+ actionId: this.props.actionId,
+ dataSourceSize: dataSources.length,
+ environmentId: this.props.currentEnvironmentId,
+ environmentName: this.props.currentEnvironmentName,
+ pluginName: pluginName,
+ datasourceId: datasource?.id,
+ isMock: !!datasource?.isMock,
+ });
+ this.props.runAction(this.props.actionId);
+ };
+
+ componentDidUpdate(prevProps: Props) {
+ if (prevProps.isRunning === true && this.props.isRunning === false) {
+ PerformanceTracker.stopTracking(
+ PerformanceTransactionName.RUN_QUERY_CLICK,
+ );
+ }
+ // Update the page when the queryID is changed by changing the
+ // URL or selecting new query from the query pane
+ // reusing same logic for changing query panes for switching query editor datasources, since the operations are similar.
+ if (
+ prevProps.actionId !== this.props.actionId ||
+ prevProps.pluginId !== this.props.pluginId
+ ) {
+ this.context?.changeQueryPage?.(this.props.actionId);
+ }
+ }
+
+ render() {
+ const {
+ actionId,
+ dataSources,
+ editorConfig,
+ isCreating,
+ isDeleting,
+ isEditorInitialized,
+ isRunning,
+ pluginId,
+ pluginIds,
+ responses,
+ runErrorMessage,
+ uiComponent,
+ updateActionResponseDisplayFormat,
+ } = this.props;
+ const { onCreateDatasourceClick, onEntityNotFoundBackClick } = this.context;
+
+ // if the action can not be found, generate a entity not found page
+ if (!pluginId && actionId) {
+ return ;
+ }
+
+ if (!pluginIds?.length) {
+ return (
+ {"Plugin is not installed"}
+ );
+ }
+
+ if (isCreating || !isEditorInitialized) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+ );
+ }
+}
+
+const mapStateToProps = (state: AppState, props: OwnProps): ReduxStateProps => {
+ const { apiId, queryId } = props.match.params;
+ const actionId = queryId || apiId || "";
+ const { runErrorMessage } = state.ui.queryPane;
+ const { plugins } = state.entities;
+
+ const { editorConfigs } = plugins;
+
+ const action = getAction(state, actionId) as QueryAction | SaaSAction;
+ const formData = getFormValues(QUERY_EDITOR_FORM_NAME)(state) as
+ | QueryAction
+ | SaaSAction;
+ let pluginId;
+ if (action) {
+ pluginId = action.pluginId;
+ }
+
+ let editorConfig: any;
+
+ if (editorConfigs && pluginId) {
+ editorConfig = editorConfigs[pluginId];
+ }
+
+ const initialValues = {};
+
+ if (editorConfig) {
+ merge(initialValues, getConfigInitialValues(editorConfig));
+ }
+
+ if (props.settingsConfig) {
+ merge(initialValues, getConfigInitialValues(props.settingsConfig));
+ }
+
+ // initialValues contains merge of action, editorConfig, settingsConfig and will be passed to redux form
+ merge(initialValues, action);
+
+ // @ts-expect-error: Types are not available
+ const actionObjectDiff: undefined | Diff[] = diff(
+ action,
+ initialValues,
+ );
+
+ const allPlugins = getPlugins(state);
+ let uiComponent = UIComponentTypes.DbEditorForm;
+ if (!!pluginId) uiComponent = getUIComponent(pluginId, allPlugins);
+
+ const currentEnvDetails = getCurrentEnvironmentDetails(state);
+
+ return {
+ actionId,
+ currentEnvironmentId: currentEnvDetails?.id || "",
+ currentEnvironmentName: currentEnvDetails?.name || "",
+ pluginId,
+ plugins: allPlugins,
+ runErrorMessage,
+ pluginIds: getPluginIdsOfPackageNames(state, PLUGIN_PACKAGE_DBS),
+ dataSources: !!apiId
+ ? getDatasourceByPluginId(state, action?.pluginId)
+ : getDBAndRemoteDatasources(state),
+ responses: getActionResponses(state),
+ isRunning: state.ui.queryPane.isRunning[actionId],
+ isDeleting: state.ui.queryPane.isDeleting[actionId],
+ isSaas: !!apiId,
+ formData,
+ editorConfig,
+ isCreating: state.ui.apiPane.isCreating,
+ uiComponent,
+ applicationId: getCurrentApplicationId(state),
+ actionObjectDiff,
+ datasourceId: action?.datasource?.id,
+ };
+};
+
+const mapDispatchToProps = (dispatch: any): ReduxDispatchProps => ({
+ deleteAction: (id: string, name: string) =>
+ dispatch(deleteAction({ id, name })),
+ runAction: (actionId: string) => dispatch(runAction(actionId)),
+ initFormEvaluation: (
+ editorConfig: any,
+ settingsConfig: any,
+ formId: string,
+ ) => {
+ dispatch(initFormEvaluations(editorConfig, settingsConfig, formId));
+ },
+ updateActionResponseDisplayFormat: ({
+ field,
+ id,
+ value,
+ }: UpdateActionPropertyActionPayload) => {
+ dispatch(setActionResponseDisplayFormat({ id, field, value }));
+ },
+ setActionProperty: (
+ actionId: string,
+ propertyName: string,
+ value: string,
+ ) => {
+ dispatch(setActionProperty({ actionId, propertyName, value }));
+ },
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(QueryEditor);
diff --git a/app/client/src/pages/Editor/QueryEditor/EditorJSONtoForm.tsx b/app/client/src/pages/Editor/QueryEditor/EditorJSONtoForm.tsx
index 56ed44c91b..161d35461c 100644
--- a/app/client/src/pages/Editor/QueryEditor/EditorJSONtoForm.tsx
+++ b/app/client/src/pages/Editor/QueryEditor/EditorJSONtoForm.tsx
@@ -1,3 +1,4 @@
+import { useContext } from "react";
import type { RefObject } from "react";
import React, { useCallback, useRef, useState } from "react";
import type { InjectedFormProps } from "redux-form";
@@ -42,7 +43,6 @@ import Resizable, {
ResizerCSS,
} from "components/editorComponents/Debugger/Resizer";
import AnalyticsUtil from "utils/AnalyticsUtil";
-import CloseEditor from "components/editorComponents/CloseEditor";
import EntityDeps from "components/editorComponents/Debugger/EntityDependecies";
import {
checkIfSectionCanRender,
@@ -67,8 +67,6 @@ import {
} from "@appsmith/constants/messages";
import { useParams } from "react-router";
import type { AppState } from "@appsmith/reducers";
-import type { ExplorerURLParams } from "@appsmith/pages/Editor/Explorer/helpers";
-import MoreActionsMenu from "../Explorer/Actions/MoreActionsMenu";
import { thinScrollbar } from "constants/DefaultTheme";
import ActionRightPane, {
useEntityDependencies,
@@ -132,12 +130,12 @@ import { DatasourceStructureContext } from "../Explorer/Datasources/DatasourceSt
import { selectFeatureFlags } from "@appsmith/selectors/featureFlagsSelectors";
import {
getHasCreateDatasourcePermission,
- getHasDeleteActionPermission,
getHasExecuteActionPermission,
getHasManageActionPermission,
} from "@appsmith/utils/BusinessFeatures/permissionPageHelpers";
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag";
+import { QueryEditorContext } from "./QueryEditorContext";
const QueryFormContainer = styled.form`
flex: 1;
@@ -368,6 +366,7 @@ interface QueryFormProps {
value,
}: UpdateActionPropertyActionPayload) => void;
datasourceId: string;
+ showCloseEditor: boolean;
}
interface ReduxProps {
@@ -404,6 +403,13 @@ export function EditorJSONtoForm(props: Props) {
updateActionResponseDisplayFormat,
} = props;
+ const {
+ actionRightPaneBackLink,
+ closeEditorLink,
+ moreActionsMenu,
+ saveActionName,
+ } = useContext(QueryEditorContext);
+
let error = runErrorMessage;
let output: Record[] | null = null;
let hintMessages: Array = [];
@@ -421,8 +427,6 @@ export function EditorJSONtoForm(props: Props) {
const currentActionConfig: Action | undefined = actions.find(
(action) => action.id === params.apiId || action.id === params.queryId,
);
- const { pageId } = useParams();
-
const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled);
const isChangePermitted = getHasManageActionPermission(
@@ -433,10 +437,6 @@ export function EditorJSONtoForm(props: Props) {
isFeatureEnabled,
currentActionConfig?.userPermissions,
);
- const isDeletePermitted = getHasDeleteActionPermission(
- isFeatureEnabled,
- currentActionConfig?.userPermissions,
- );
const userWorkspacePermissions = useSelector(
(state: AppState) => getCurrentAppWorkspace(state).userPermissions ?? [],
@@ -913,22 +913,18 @@ export function EditorJSONtoForm(props: Props) {
return (
<>
- {!guidedTourEnabled && }
+ {!guidedTourEnabled && closeEditorLink}
{guidedTourEnabled && }
-
+
-
+ {moreActionsMenu}
void;
+ onEntityNotFoundBackClick?: () => void;
+ changeQueryPage?: (queryId: string) => void;
+ actionRightPaneBackLink?: React.ReactNode;
+ saveActionName?: (
+ params: SaveActionNameParams,
+ ) => ReduxAction;
+ closeEditorLink?: React.ReactNode;
+}
+
+type QueryEditorContextProviderProps =
+ React.PropsWithChildren;
+
+export const QueryEditorContext = createContext(
+ {} as QueryEditorContextContextProps,
+);
+
+export function QueryEditorContextProvider({
+ actionRightPaneBackLink,
+ changeQueryPage,
+ children,
+ closeEditorLink,
+ moreActionsMenu,
+ onCreateDatasourceClick,
+ onEntityNotFoundBackClick,
+ saveActionName,
+}: QueryEditorContextProviderProps) {
+ const value = useMemo(
+ () => ({
+ actionRightPaneBackLink,
+ changeQueryPage,
+ closeEditorLink,
+ moreActionsMenu,
+ onCreateDatasourceClick,
+ onEntityNotFoundBackClick,
+ saveActionName,
+ }),
+ [
+ actionRightPaneBackLink,
+ changeQueryPage,
+ closeEditorLink,
+ moreActionsMenu,
+ onCreateDatasourceClick,
+ onEntityNotFoundBackClick,
+ saveActionName,
+ ],
+ );
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/app/client/src/pages/Editor/QueryEditor/index.tsx b/app/client/src/pages/Editor/QueryEditor/index.tsx
index 3e12a871a6..5c8df76a5a 100644
--- a/app/client/src/pages/Editor/QueryEditor/index.tsx
+++ b/app/client/src/pages/Editor/QueryEditor/index.tsx
@@ -1,199 +1,87 @@
-import React from "react";
+import React, { useCallback, useMemo } from "react";
+import { useDispatch, useSelector } from "react-redux";
import type { RouteComponentProps } from "react-router";
-import { connect } from "react-redux";
-import { getFormValues } from "redux-form";
-import styled from "styled-components";
-import type { QueryEditorRouteParams } from "constants/routes";
-import { INTEGRATION_TABS } from "constants/routes";
+
+import AnalyticsUtil from "utils/AnalyticsUtil";
+import Editor from "./Editor";
import history from "utils/history";
-import QueryEditorForm from "./Form";
-import type { UpdateActionPropertyActionPayload } from "actions/pluginActionActions";
-import {
- deleteAction,
- runAction,
- setActionResponseDisplayFormat,
- setActionProperty,
-} from "actions/pluginActionActions";
-import type { AppState } from "@appsmith/reducers";
+import MoreActionsMenu from "../Explorer/Actions/MoreActionsMenu";
+import BackToCanvas from "components/common/BackToCanvas";
+import { INTEGRATION_TABS } from "constants/routes";
import {
getCurrentApplicationId,
+ getCurrentPageId,
getIsEditorInitialized,
} from "selectors/editorSelectors";
-import { QUERY_EDITOR_FORM_NAME } from "@appsmith/constants/forms";
-import type { Plugin } from "api/PluginApi";
-import { UIComponentTypes } from "api/PluginApi";
-import type { Datasource } from "entities/Datasource";
-import {
- getPluginIdsOfPackageNames,
- getPlugins,
- getAction,
- getActionResponses,
- getDatasourceByPluginId,
- getDBAndRemoteDatasources,
-} from "@appsmith/selectors/entitiesSelector";
-import { PLUGIN_PACKAGE_DBS } from "constants/QueryEditorConstants";
-import type { QueryAction, SaaSAction } from "entities/Action";
-import Spinner from "components/editorComponents/Spinner";
-import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper";
import { changeQuery } from "actions/queryPaneActions";
-import PerformanceTracker, {
- PerformanceTransactionName,
-} from "utils/PerformanceTracker";
-import AnalyticsUtil from "utils/AnalyticsUtil";
-import { initFormEvaluations } from "@appsmith/actions/evaluationActions";
-import { getUIComponent } from "./helpers";
-import type { Diff } from "deep-diff";
-import { diff } from "deep-diff";
-import EntityNotFoundPane from "pages/Editor/EntityNotFoundPane";
-import { integrationEditorURL } from "@appsmith/RouteBuilder";
-import { getConfigInitialValues } from "components/formControls/utils";
-import { merge } from "lodash";
-import { getPathAndValueFromActionDiffObject } from "../../../utils/getPathAndValueFromActionDiffObject";
import { DatasourceCreateEntryPoints } from "constants/Datasource";
-import { getCurrentEnvironmentDetails } from "@appsmith/selectors/environmentSelectors";
+import {
+ getAction,
+ getPluginSettingConfigs,
+} from "@appsmith/selectors/entitiesSelector";
+import { integrationEditorURL } from "@appsmith/RouteBuilder";
+import { QueryEditorContextProvider } from "./QueryEditorContext";
+import type { QueryEditorRouteParams } from "constants/routes";
+import {
+ getHasDeleteActionPermission,
+ getHasManageActionPermission,
+} from "@appsmith/utils/BusinessFeatures/permissionPageHelpers";
+import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag";
+import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
+import CloseEditor from "components/editorComponents/CloseEditor";
-const EmptyStateContainer = styled.div`
- display: flex;
- height: 100%;
- font-size: 20px;
-`;
+type QueryEditorProps = RouteComponentProps;
-const LoadingContainer = styled(CenteredWrapper)`
- height: 50%;
-`;
+function QueryEditor(props: QueryEditorProps) {
+ const { apiId, queryId } = props.match.params;
+ const actionId = queryId || apiId;
+ const dispatch = useDispatch();
+ const action = useSelector((state) => getAction(state, actionId || ""));
+ const pluginId = action?.pluginId || "";
+ const isEditorInitialized = useSelector(getIsEditorInitialized);
+ const applicationId: string = useSelector(getCurrentApplicationId);
+ const pageId: string = useSelector(getCurrentPageId);
+ const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled);
+ const settingsConfig = useSelector((state) =>
+ getPluginSettingConfigs(state, pluginId),
+ );
-interface ReduxDispatchProps {
- runAction: (actionId: string) => void;
- deleteAction: (id: string, name: string) => void;
- changeQueryPage: (queryId: string) => void;
- initFormEvaluation: (
- editorConfig: any,
- settingConfig: any,
- formId: string,
- ) => void;
- updateActionResponseDisplayFormat: ({
- field,
- id,
- value,
- }: UpdateActionPropertyActionPayload) => void;
- setActionProperty: (
- actionId: string,
- propertyName: string,
- value: string,
- ) => void;
-}
+ const isDeletePermitted = getHasDeleteActionPermission(
+ isFeatureEnabled,
+ action?.userPermissions,
+ );
-interface ReduxStateProps {
- plugins: Plugin[];
- dataSources: Datasource[];
- isRunning: boolean;
- isDeleting: boolean;
- formData: QueryAction | SaaSAction;
- runErrorMessage: Record;
- pluginId: string | undefined;
- pluginIds: Array | undefined;
- responses: any;
- isCreating: boolean;
- editorConfig: any;
- settingConfig: any;
- isEditorInitialized: boolean;
- uiComponent: UIComponentTypes;
- applicationId: string;
- actionId: string;
- actionObjectDiff?: any;
- isSaas: boolean;
- datasourceId?: string;
- currentEnvironmentId: string;
- currentEnvironmentName: string;
-}
+ const isChangePermitted = getHasManageActionPermission(
+ isFeatureEnabled,
+ action?.userPermissions,
+ );
-type StateAndRouteProps = RouteComponentProps;
+ const moreActionsMenu = useMemo(
+ () => (
+
+ ),
+ [action?.id, action?.name, isChangePermitted, isDeletePermitted, pageId],
+ );
-type Props = StateAndRouteProps & ReduxDispatchProps & ReduxStateProps;
+ const actionRightPaneBackLink = useMemo(() => {
+ return ;
+ }, [pageId]);
-class QueryEditor extends React.Component {
- constructor(props: Props) {
- super(props);
- // Call the first evaluations when the page loads
- // call evaluations only for queries and not google sheets (which uses apiId)
- if (this.props.match.params.queryId) {
- this.props.initFormEvaluation(
- this.props.editorConfig,
- this.props.settingConfig,
- this.props.match.params.queryId,
- );
- }
- }
+ const changeQueryPage = useCallback(
+ (queryId: string) => {
+ dispatch(changeQuery({ id: queryId, pageId, applicationId }));
+ },
+ [pageId, applicationId],
+ );
- componentDidMount() {
- // if the current action is non existent, do not dispatch change query page action
- // this action should only be dispatched when switching from an existent action.
- if (!this.props.pluginId) return;
- this.props.changeQueryPage(this.props.actionId);
-
- // fixes missing where key issue by populating the action with a where object when the component is mounted.
- if (this.props.isSaas) {
- const { path = "", value = "" } = {
- ...getPathAndValueFromActionDiffObject(this.props.actionObjectDiff),
- };
- if (value && path) {
- this.props.setActionProperty(this.props.actionId, path, value);
- }
- }
-
- PerformanceTracker.stopTracking(PerformanceTransactionName.OPEN_ACTION, {
- actionType: "QUERY",
- });
- }
-
- handleDeleteClick = () => {
- const { formData } = this.props;
- this.props.deleteAction(this.props.actionId, formData.name);
- };
-
- handleRunClick = () => {
- const { dataSources } = this.props;
- const datasource = dataSources.find(
- (datasource) => datasource.id === this.props.datasourceId,
- );
- const pluginName = this.props.plugins.find(
- (plugin) => plugin.id === this.props.pluginId,
- )?.name;
- PerformanceTracker.startTracking(
- PerformanceTransactionName.RUN_QUERY_CLICK,
- { actionId: this.props.actionId },
- );
- AnalyticsUtil.logEvent("RUN_QUERY_CLICK", {
- actionId: this.props.actionId,
- dataSourceSize: dataSources.length,
- environmentId: this.props.currentEnvironmentId,
- environmentName: this.props.currentEnvironmentName,
- pluginName: pluginName,
- datasourceId: datasource?.id,
- isMock: !!datasource?.isMock,
- });
- this.props.runAction(this.props.actionId);
- };
-
- componentDidUpdate(prevProps: Props) {
- if (prevProps.isRunning === true && this.props.isRunning === false) {
- PerformanceTracker.stopTracking(
- PerformanceTransactionName.RUN_QUERY_CLICK,
- );
- }
- // Update the page when the queryID is changed by changing the
- // URL or selecting new query from the query pane
- // reusing same logic for changing query panes for switching query editor datasources, since the operations are similar.
- if (
- prevProps.actionId !== this.props.actionId ||
- prevProps.pluginId !== this.props.pluginId
- ) {
- this.props.changeQueryPage(this.props.actionId);
- }
- }
-
- onCreateDatasourceClick = () => {
- const { pageId } = this.props.match.params;
+ const onCreateDatasourceClick = useCallback(() => {
history.push(
integrationEditorURL({
pageId,
@@ -205,187 +93,44 @@ class QueryEditor extends React.Component {
AnalyticsUtil.logEvent("NAVIGATE_TO_CREATE_NEW_DATASOURCE_PAGE", {
entryPoint,
});
- };
+ }, [
+ pageId,
+ history,
+ integrationEditorURL,
+ DatasourceCreateEntryPoints,
+ AnalyticsUtil,
+ ]);
- render() {
- const {
- actionId,
- dataSources,
- editorConfig,
- isCreating,
- isDeleting,
- isEditorInitialized,
- isRunning,
- pluginId,
- pluginIds,
- responses,
- runErrorMessage,
- settingConfig,
- uiComponent,
- updateActionResponseDisplayFormat,
- } = this.props;
- const { pageId } = this.props.match.params;
-
- // custom function to return user to integrations page if action is not found
- const goToDatasourcePage = () =>
+ // custom function to return user to integrations page if action is not found
+ const onEntityNotFoundBackClick = useCallback(
+ () =>
history.push(
integrationEditorURL({
pageId,
selectedTab: INTEGRATION_TABS.ACTIVE,
}),
- );
-
- // if the action can not be found, generate a entity not found page
- if (!pluginId && actionId) {
- return ;
- }
-
- if (!pluginIds?.length) {
- return (
- {"Plugin is not installed"}
- );
- }
-
- if (isCreating || !isEditorInitialized) {
- return (
-
-
-
- );
- }
-
- return (
-
- );
- }
-}
-
-const mapStateToProps = (state: AppState, props: any): ReduxStateProps => {
- const { apiId, queryId } = props.match.params;
- const actionId = queryId || apiId;
- const { runErrorMessage } = state.ui.queryPane;
- const { plugins } = state.entities;
-
- const { editorConfigs, settingConfigs } = plugins;
-
- const action = getAction(state, actionId) as QueryAction | SaaSAction;
- const formData = getFormValues(QUERY_EDITOR_FORM_NAME)(state) as
- | QueryAction
- | SaaSAction;
- let pluginId;
- if (action) {
- pluginId = action.pluginId;
- }
-
- let editorConfig: any;
-
- if (editorConfigs && pluginId) {
- editorConfig = editorConfigs[pluginId];
- }
-
- let settingConfig: any;
-
- if (settingConfigs && pluginId) {
- settingConfig = settingConfigs[pluginId];
- }
-
- const initialValues = {};
-
- if (editorConfig) {
- merge(initialValues, getConfigInitialValues(editorConfig));
- }
-
- if (settingConfig) {
- merge(initialValues, getConfigInitialValues(settingConfig));
- }
-
- // initialValues contains merge of action, editorConfig, settingsConfig and will be passed to redux form
- merge(initialValues, action);
-
- // @ts-expect-error: Types are not available
- const actionObjectDiff: undefined | Diff[] = diff(
- action,
- initialValues,
+ ),
+ [pageId, history, integrationEditorURL],
);
- const allPlugins = getPlugins(state);
- let uiComponent = UIComponentTypes.DbEditorForm;
- if (!!pluginId) uiComponent = getUIComponent(pluginId, allPlugins);
+ const closeEditorLink = useMemo(() => , []);
- const currentEnvDetails = getCurrentEnvironmentDetails(state);
+ return (
+
+
+
+ );
+}
- return {
- actionId,
- currentEnvironmentId: currentEnvDetails?.id || "",
- currentEnvironmentName: currentEnvDetails?.name || "",
- pluginId,
- plugins: allPlugins,
- runErrorMessage,
- pluginIds: getPluginIdsOfPackageNames(state, PLUGIN_PACKAGE_DBS),
- dataSources: !!apiId
- ? getDatasourceByPluginId(state, action?.pluginId)
- : getDBAndRemoteDatasources(state),
- responses: getActionResponses(state),
- isRunning: state.ui.queryPane.isRunning[actionId],
- isDeleting: state.ui.queryPane.isDeleting[actionId],
- isSaas: !!apiId,
- formData,
- editorConfig,
- settingConfig,
- isCreating: state.ui.apiPane.isCreating,
- isEditorInitialized: getIsEditorInitialized(state),
- uiComponent,
- applicationId: getCurrentApplicationId(state),
- actionObjectDiff,
- datasourceId: action?.datasource?.id,
- };
-};
-
-const mapDispatchToProps = (dispatch: any): ReduxDispatchProps => ({
- deleteAction: (id: string, name: string) =>
- dispatch(deleteAction({ id, name })),
- runAction: (actionId: string) => dispatch(runAction(actionId)),
- changeQueryPage: (queryId: string) => {
- dispatch(changeQuery(queryId));
- },
- initFormEvaluation: (
- editorConfig: any,
- settingsConfig: any,
- formId: string,
- ) => {
- dispatch(initFormEvaluations(editorConfig, settingsConfig, formId));
- },
- updateActionResponseDisplayFormat: ({
- field,
- id,
- value,
- }: UpdateActionPropertyActionPayload) => {
- dispatch(setActionResponseDisplayFormat({ id, field, value }));
- },
- setActionProperty: (
- actionId: string,
- propertyName: string,
- value: string,
- ) => {
- dispatch(setActionProperty({ actionId, propertyName, value }));
- },
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(QueryEditor);
+export default QueryEditor;
diff --git a/app/client/src/pages/Editor/routes.tsx b/app/client/src/pages/Editor/routes.tsx
index 4061162b99..ce92d313df 100644
--- a/app/client/src/pages/Editor/routes.tsx
+++ b/app/client/src/pages/Editor/routes.tsx
@@ -6,7 +6,6 @@ import IntegrationEditor from "./IntegrationEditor";
import QueryEditor from "./QueryEditor";
import JSEditor from "./JSEditor";
import GeneratePage from "./GeneratePage";
-import CurlImportForm from "./APIEditor/CurlImportForm";
import ProviderTemplates from "./APIEditor/ProviderTemplates";
import {
API_EDITOR_ID_PATH,
@@ -27,6 +26,7 @@ import * as Sentry from "@sentry/react";
import { SaaSEditorRoutes } from "./SaaSEditor/routes";
import OnboardingChecklist from "./FirstTimeUserOnboarding/Checklist";
import { DatasourceEditorRoutes } from "pages/routes";
+import CurlImportEditor from "./APIEditor/CurlImportEditor";
const SentryRoute = Sentry.withSentryRouting(Route);
@@ -91,7 +91,7 @@ function EditorsRouter() {
/>
diff --git a/app/client/src/sagas/QueryPaneSagas.ts b/app/client/src/sagas/QueryPaneSagas.ts
index b11783ca03..0ee5d21735 100644
--- a/app/client/src/sagas/QueryPaneSagas.ts
+++ b/app/client/src/sagas/QueryPaneSagas.ts
@@ -24,10 +24,7 @@ import {
} from "@appsmith/constants/forms";
import history from "utils/history";
import { APPLICATIONS_URL, INTEGRATION_TABS } from "constants/routes";
-import {
- getCurrentApplicationId,
- getCurrentPageId,
-} from "selectors/editorSelectors";
+import { getCurrentPageId } from "selectors/editorSelectors";
import { autofill, change, initialize, reset } from "redux-form";
import {
getAction,
@@ -86,25 +83,28 @@ import type { FeatureFlags } from "@appsmith/entities/FeatureFlag";
import { selectFeatureFlags } from "@appsmith/selectors/featureFlagsSelectors";
import { isGACEnabled } from "@appsmith/utils/planHelpers";
import { getHasManageActionPermission } from "@appsmith/utils/BusinessFeatures/permissionPageHelpers";
+import type { ChangeQueryPayload } from "actions/queryPaneActions";
// Called whenever the query being edited is changed via the URL or query pane
-function* changeQuerySaga(actionPayload: ReduxAction<{ id: string }>) {
- const { id } = actionPayload.payload;
+function* changeQuerySaga(actionPayload: ReduxAction) {
+ const { applicationId, id, moduleId, packageId, pageId } =
+ actionPayload.payload;
let configInitialValues = {};
- const applicationId: string = yield select(getCurrentApplicationId);
- const pageId: string = yield select(getCurrentPageId);
- if (!applicationId || !pageId) {
+
+ if (!(packageId && moduleId) && !(applicationId && pageId)) {
history.push(APPLICATIONS_URL);
return;
}
const action: Action | undefined = yield select(getAction, id);
if (!action) {
- history.push(
- integrationEditorURL({
- pageId,
- selectedTab: INTEGRATION_TABS.ACTIVE,
- }),
- );
+ if (pageId) {
+ history.push(
+ integrationEditorURL({
+ pageId,
+ selectedTab: INTEGRATION_TABS.ACTIVE,
+ }),
+ );
+ }
return;
}
diff --git a/app/client/src/selectors/ui.tsx b/app/client/src/selectors/ui.tsx
index f806158ae4..244296e038 100644
--- a/app/client/src/selectors/ui.tsx
+++ b/app/client/src/selectors/ui.tsx
@@ -54,3 +54,6 @@ export const getDatasourceCollapsibleState = createSelector(
return datasourceCollapsibleState[key];
},
);
+
+export const getIsImportingCurl = (state: AppState) =>
+ state.ui.imports.isImportingCurl;