chore: action editors refactor (#27972)
## Description The aim of this PR is to make the editors reusable in the Module editor. Changes 1. A wrapper is introduced for API, Query and Curl editors which passes differentiating functions and make the form editors agnostic of pageId and applicationId 2. In order to pass down function, react contexts are added to avoid prop drilling #### PR fixes following issue(s) Fixes #26160 #### Media > A video or a GIF is preferred. when using Loom, don’t embed because it looks like it’s a GIF. instead, just link to the video > > #### Type of change - Chore (housekeeping or task changes that don't impact user perception) ## Testing > #### How Has This Been Tested? > Please describe the tests that you ran to verify your changes. Also list any relevant details for your test configuration. > Delete anything that is not relevant - [ ] Manual - [ ] JUnit - [ ] Jest - [ ] Cypress > > #### Test Plan > Add Testsmith test cases links that relate to this PR > > #### Issues raised during DP testing > Link issues raised during DP testing for better visiblity and tracking (copy link from comments dropped on this PR) > > > ## Checklist: #### Dev activity - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag #### QA activity: - [ ] [Speedbreak features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-) have been covered - [ ] Test plan covers all impacted features and [areas of interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-) - [ ] Test plan has been peer reviewed by project stakeholders and other QA members - [ ] Manually tested functionality on DP - [ ] We had an implementation alignment call with stakeholders post QA Round 2 - [ ] Cypress test cases have been added and approved by SDET/manual QA - [ ] Added `Test Plan Approved` label after Cypress tests were reviewed - [ ] Added `Test Plan Approved` label after JUnit tests were reviewed
This commit is contained in:
parent
20463ee449
commit
d48ac4fd81
|
|
@ -2,18 +2,20 @@ import type { ReduxAction } from "@appsmith/constants/ReduxActionConstants";
|
|||
import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants";
|
||||
import type { Action } from "entities/Action";
|
||||
|
||||
export const changeQuery = (
|
||||
id: string,
|
||||
newQuery?: boolean,
|
||||
action?: Action,
|
||||
): ReduxAction<{
|
||||
export interface ChangeQueryPayload {
|
||||
id: string;
|
||||
packageId?: string;
|
||||
applicationId?: string;
|
||||
pageId?: string;
|
||||
moduleId?: string;
|
||||
newQuery?: boolean;
|
||||
action?: any;
|
||||
}> => {
|
||||
action?: Action;
|
||||
}
|
||||
|
||||
export const changeQuery = (payload: ChangeQueryPayload) => {
|
||||
return {
|
||||
type: ReduxActionTypes.QUERY_PANE_CHANGE,
|
||||
payload: { id, newQuery, action },
|
||||
payload,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
46
app/client/src/components/common/BackToCanvas.tsx
Normal file
46
app/client/src/components/common/BackToCanvas.tsx
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import React, { useCallback, useContext } from "react";
|
||||
import styled from "styled-components";
|
||||
import { Link } from "design-system";
|
||||
|
||||
import history from "utils/history";
|
||||
import WalkthroughContext from "components/featureWalkthrough/walkthroughContext";
|
||||
import { BACK_TO_CANVAS, createMessage } from "@appsmith/constants/messages";
|
||||
import { builderURL } from "@appsmith/RouteBuilder";
|
||||
|
||||
const BackToCanvasLink = styled(Link)`
|
||||
margin-left: ${(props) => props.theme.spaces[1] + 1}px;
|
||||
margin-top: ${(props) => props.theme.spaces[11]}px;
|
||||
margin-bottom: ${(props) => props.theme.spaces[11]}px;
|
||||
`;
|
||||
|
||||
interface BackToCanvasProps {
|
||||
pageId: string;
|
||||
}
|
||||
|
||||
function BackToCanvas({ pageId }: BackToCanvasProps) {
|
||||
const { isOpened: isWalkthroughOpened, popFeature } =
|
||||
useContext(WalkthroughContext) || {};
|
||||
|
||||
const handleCloseWalkthrough = useCallback(() => {
|
||||
if (isWalkthroughOpened && popFeature) {
|
||||
popFeature();
|
||||
}
|
||||
}, [isWalkthroughOpened, popFeature]);
|
||||
|
||||
return (
|
||||
<BackToCanvasLink
|
||||
id="back-to-canvas"
|
||||
kind="secondary"
|
||||
onClick={() => {
|
||||
history.push(builderURL({ pageId }));
|
||||
|
||||
handleCloseWalkthrough();
|
||||
}}
|
||||
startIcon="arrow-left-line"
|
||||
>
|
||||
{createMessage(BACK_TO_CANVAS)}
|
||||
</BackToCanvasLink>
|
||||
);
|
||||
}
|
||||
|
||||
export default BackToCanvas;
|
||||
|
|
@ -21,6 +21,7 @@ import {
|
|||
createMessage,
|
||||
} from "@appsmith/constants/messages";
|
||||
import { getAssetUrl } from "@appsmith/utils/airgapHelpers";
|
||||
import type { ReduxAction } from "@appsmith/constants/ReduxActionConstants";
|
||||
|
||||
const ApiNameWrapper = styled.div<{ page?: string }>`
|
||||
min-width: 50%;
|
||||
|
|
@ -58,6 +59,11 @@ const ApiIconBox = styled.div`
|
|||
margin-right: 8px;
|
||||
flex-shrink: 0;
|
||||
`;
|
||||
|
||||
interface SaveActionNameParams {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
interface ActionNameEditorProps {
|
||||
/*
|
||||
This prop checks if page is API Pane or Query Pane or Curl Pane
|
||||
|
|
@ -67,6 +73,9 @@ interface ActionNameEditorProps {
|
|||
*/
|
||||
page?: string;
|
||||
disabled?: boolean;
|
||||
saveActionName?: (
|
||||
params: SaveActionNameParams,
|
||||
) => ReduxAction<SaveActionNameParams>;
|
||||
}
|
||||
|
||||
function ActionNameEditor(props: ActionNameEditorProps) {
|
||||
|
|
@ -84,7 +93,13 @@ function ActionNameEditor(props: ActionNameEditorProps) {
|
|||
<NameEditorComponent
|
||||
checkForGuidedTour
|
||||
currentActionConfig={currentActionConfig}
|
||||
dispatchAction={saveActionName}
|
||||
/**
|
||||
* This component is used by module editor in EE which uses a different
|
||||
* action to save the name of an action. The current callers of this component
|
||||
* pass the existing saveAction action but as fallback the saveActionName is used here
|
||||
* as a guard.
|
||||
*/
|
||||
dispatchAction={props.saveActionName || saveActionName}
|
||||
>
|
||||
{({
|
||||
forceUpdate,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import React, { useContext, useEffect, useMemo, useRef, useState } from "react";
|
|||
import styled from "styled-components";
|
||||
import { Collapse, Classes as BPClasses } from "@blueprintjs/core";
|
||||
import { Classes, getTypographyByKey } from "design-system-old";
|
||||
import { Divider, Icon, Link, Text } from "design-system";
|
||||
import { Divider, Icon, Text } from "design-system";
|
||||
import SuggestedWidgets from "./SuggestedWidgets";
|
||||
import type { ReactNode, MutableRefObject } from "react";
|
||||
import { useParams } from "react-router";
|
||||
|
|
@ -11,7 +11,6 @@ import { getWidgets } from "sagas/selectors";
|
|||
import type { AppState } from "@appsmith/reducers";
|
||||
import { getDependenciesFromInverseDependencies } from "../Debugger/helpers";
|
||||
import {
|
||||
BACK_TO_CANVAS,
|
||||
BINDINGS_DISABLED_TOOLTIP,
|
||||
BINDING_SECTION_LABEL,
|
||||
createMessage,
|
||||
|
|
@ -23,11 +22,7 @@ import type {
|
|||
SuggestedWidget,
|
||||
SuggestedWidget as SuggestedWidgetsType,
|
||||
} from "api/ActionAPI";
|
||||
import {
|
||||
getCurrentPageId,
|
||||
getPagePermissions,
|
||||
} from "selectors/editorSelectors";
|
||||
import { builderURL } from "@appsmith/RouteBuilder";
|
||||
import { getPagePermissions } from "selectors/editorSelectors";
|
||||
import DatasourceStructureHeader from "pages/Editor/Explorer/Datasources/DatasourceStructureHeader";
|
||||
import {
|
||||
DatasourceStructureContainer as DataStructureList,
|
||||
|
|
@ -55,7 +50,6 @@ import { Tooltip } from "design-system";
|
|||
import { ASSETS_CDN_URL } from "constants/ThirdPartyConstants";
|
||||
import { FEATURE_WALKTHROUGH_KEYS } from "constants/WalkthroughConstants";
|
||||
import { getIsFirstTimeUserOnboardingEnabled } from "selectors/onboardingSelectors";
|
||||
import history from "utils/history";
|
||||
import { SignpostingWalkthroughConfig } from "pages/Editor/FirstTimeUserOnboarding/Utils";
|
||||
import { getAssetUrl } from "@appsmith/utils/airgapHelpers";
|
||||
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
|
||||
|
|
@ -113,12 +107,6 @@ const SideBar = styled.div`
|
|||
}
|
||||
`;
|
||||
|
||||
const BackToCanvasLink = styled(Link)`
|
||||
margin-left: ${(props) => props.theme.spaces[1] + 1}px;
|
||||
margin-top: ${(props) => props.theme.spaces[11]}px;
|
||||
margin-bottom: ${(props) => props.theme.spaces[11]}px;
|
||||
`;
|
||||
|
||||
const Label = styled.span`
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
|
@ -291,6 +279,7 @@ export function useEntityDependencies(actionName: string) {
|
|||
|
||||
function ActionSidebar({
|
||||
actionName,
|
||||
actionRightPaneBackLink,
|
||||
context,
|
||||
datasourceId,
|
||||
hasConnections,
|
||||
|
|
@ -305,16 +294,12 @@ function ActionSidebar({
|
|||
datasourceId: string;
|
||||
pluginId: string;
|
||||
context: DatasourceStructureContext;
|
||||
actionRightPaneBackLink: React.ReactNode;
|
||||
}) {
|
||||
const dispatch = useDispatch();
|
||||
const widgets = useSelector(getWidgets);
|
||||
const pageId = useSelector(getCurrentPageId);
|
||||
const user = useSelector(getCurrentUser);
|
||||
const {
|
||||
isOpened: isWalkthroughOpened,
|
||||
popFeature,
|
||||
pushFeature,
|
||||
} = useContext(WalkthroughContext) || {};
|
||||
const { pushFeature } = useContext(WalkthroughContext) || {};
|
||||
const schemaRef = useRef(null);
|
||||
const params = useParams<{
|
||||
pageId: string;
|
||||
|
|
@ -444,26 +429,9 @@ function ActionSidebar({
|
|||
return <Placeholder>{createMessage(NO_CONNECTIONS)}</Placeholder>;
|
||||
}
|
||||
|
||||
const handleCloseWalkthrough = () => {
|
||||
if (isWalkthroughOpened && popFeature) {
|
||||
popFeature();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<SideBar>
|
||||
<BackToCanvasLink
|
||||
id="back-to-canvas"
|
||||
kind="secondary"
|
||||
onClick={() => {
|
||||
history.push(builderURL({ pageId }));
|
||||
|
||||
handleCloseWalkthrough();
|
||||
}}
|
||||
startIcon="arrow-left-line"
|
||||
>
|
||||
{createMessage(BACK_TO_CANVAS)}
|
||||
</BackToCanvasLink>
|
||||
{actionRightPaneBackLink}
|
||||
|
||||
{showSchema && (
|
||||
<CollapsibleSection
|
||||
|
|
|
|||
65
app/client/src/pages/Editor/APIEditor/ApiEditorContext.tsx
Normal file
65
app/client/src/pages/Editor/APIEditor/ApiEditorContext.tsx
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import type { ReduxAction } from "@appsmith/constants/ReduxActionConstants";
|
||||
import type { PaginationField } from "api/ActionAPI";
|
||||
import React, { createContext, useMemo } from "react";
|
||||
|
||||
interface SaveActionNameParams {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface ApiEditorContextContextProps {
|
||||
moreActionsMenu?: React.ReactNode;
|
||||
handleDeleteClick: () => void;
|
||||
handleRunClick: (paginationField?: PaginationField) => void;
|
||||
actionRightPaneBackLink?: React.ReactNode;
|
||||
settingsConfig: any;
|
||||
saveActionName?: (
|
||||
params: SaveActionNameParams,
|
||||
) => ReduxAction<SaveActionNameParams>;
|
||||
closeEditorLink?: React.ReactNode;
|
||||
}
|
||||
|
||||
type ApiEditorContextProviderProps =
|
||||
React.PropsWithChildren<ApiEditorContextContextProps>;
|
||||
|
||||
export const ApiEditorContext = createContext<ApiEditorContextContextProps>(
|
||||
{} as ApiEditorContextContextProps,
|
||||
);
|
||||
|
||||
export function ApiEditorContextProvider({
|
||||
actionRightPaneBackLink,
|
||||
children,
|
||||
closeEditorLink,
|
||||
handleDeleteClick,
|
||||
handleRunClick,
|
||||
moreActionsMenu,
|
||||
saveActionName,
|
||||
settingsConfig,
|
||||
}: ApiEditorContextProviderProps) {
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
actionRightPaneBackLink,
|
||||
closeEditorLink,
|
||||
handleDeleteClick,
|
||||
handleRunClick,
|
||||
moreActionsMenu,
|
||||
saveActionName,
|
||||
settingsConfig,
|
||||
}),
|
||||
[
|
||||
actionRightPaneBackLink,
|
||||
closeEditorLink,
|
||||
handleDeleteClick,
|
||||
handleRunClick,
|
||||
moreActionsMenu,
|
||||
saveActionName,
|
||||
settingsConfig,
|
||||
],
|
||||
);
|
||||
|
||||
return (
|
||||
<ApiEditorContext.Provider value={value}>
|
||||
{children}
|
||||
</ApiEditorContext.Provider>
|
||||
);
|
||||
}
|
||||
|
|
@ -310,6 +310,7 @@ function ApiRightPane(props: any) {
|
|||
<SomeWrapper>
|
||||
<ActionRightPane
|
||||
actionName={props.actionName}
|
||||
actionRightPaneBackLink={props.actionRightPaneBackLink}
|
||||
context={DatasourceStructureContext.API_EDITOR}
|
||||
datasourceId={props.datasourceId}
|
||||
hasConnections={hasDependencies}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from "react";
|
||||
import React, { useContext, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import {
|
||||
API_EDITOR_TABS,
|
||||
|
|
@ -18,8 +18,6 @@ import type { AppState } from "@appsmith/reducers";
|
|||
import ActionNameEditor from "components/editorComponents/ActionNameEditor";
|
||||
import ActionSettings from "pages/Editor/ActionSettings";
|
||||
import RequestDropdownField from "components/editorComponents/form/fields/RequestDropdownField";
|
||||
import type { ExplorerURLParams } from "@appsmith/pages/Editor/Explorer/helpers";
|
||||
import MoreActionsMenu from "../Explorer/Actions/MoreActionsMenu";
|
||||
import { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig";
|
||||
import { Classes } from "design-system-old";
|
||||
import {
|
||||
|
|
@ -39,7 +37,6 @@ import {
|
|||
API_PANE_DUPLICATE_HEADER,
|
||||
createMessage,
|
||||
} from "@appsmith/constants/messages";
|
||||
import CloseEditor from "components/editorComponents/CloseEditor";
|
||||
import { useParams } from "react-router";
|
||||
import DataSourceList from "./ApiRightPane";
|
||||
import type { Datasource } from "entities/Datasource";
|
||||
|
|
@ -57,10 +54,10 @@ import { DEFAULT_DATASOURCE_NAME } from "constants/ApiEditorConstants/ApiEditorC
|
|||
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
|
||||
import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag";
|
||||
import {
|
||||
getHasDeleteActionPermission,
|
||||
getHasExecuteActionPermission,
|
||||
getHasManageActionPermission,
|
||||
} from "@appsmith/utils/BusinessFeatures/permissionPageHelpers";
|
||||
import { ApiEditorContext } from "./ApiEditorContext";
|
||||
|
||||
const Form = styled.form`
|
||||
position: relative;
|
||||
|
|
@ -206,6 +203,7 @@ type CommonFormPropsWithExtraParams = CommonFormProps & {
|
|||
handleSubmit: any;
|
||||
// defaultSelectedTabIndex
|
||||
defaultTabSelected?: number;
|
||||
closeEditorLink?: React.ReactNode;
|
||||
};
|
||||
|
||||
export const NameWrapper = styled.div`
|
||||
|
|
@ -498,12 +496,15 @@ function CommonEditorForm(props: CommonFormPropsWithExtraParams) {
|
|||
const index = Object.values(API_EDITOR_TABS).indexOf(value);
|
||||
dispatch(setApiPaneConfigSelectedTabIndex(index));
|
||||
};
|
||||
const { actionRightPaneBackLink, moreActionsMenu, saveActionName } =
|
||||
useContext(ApiEditorContext);
|
||||
|
||||
const {
|
||||
actionConfigurationHeaders,
|
||||
actionConfigurationParams,
|
||||
actionName,
|
||||
autoGeneratedActionConfigHeaders,
|
||||
closeEditorLink,
|
||||
currentActionDatasourceId,
|
||||
formName,
|
||||
handleSubmit,
|
||||
|
|
@ -532,7 +533,6 @@ function CommonEditorForm(props: CommonFormPropsWithExtraParams) {
|
|||
const currentActionConfig: Action | undefined = actions.find(
|
||||
(action) => action.id === params.apiId || action.id === params.queryId,
|
||||
);
|
||||
const { pageId } = useParams<ExplorerURLParams>();
|
||||
const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled);
|
||||
const isChangePermitted = getHasManageActionPermission(
|
||||
isFeatureEnabled,
|
||||
|
|
@ -542,10 +542,6 @@ function CommonEditorForm(props: CommonFormPropsWithExtraParams) {
|
|||
isFeatureEnabled,
|
||||
currentActionConfig?.userPermissions,
|
||||
);
|
||||
const isDeletePermitted = getHasDeleteActionPermission(
|
||||
isFeatureEnabled,
|
||||
currentActionConfig?.userPermissions,
|
||||
);
|
||||
|
||||
const plugin = useSelector((state: AppState) =>
|
||||
getPlugin(state, pluginId ?? ""),
|
||||
|
|
@ -577,22 +573,19 @@ function CommonEditorForm(props: CommonFormPropsWithExtraParams) {
|
|||
|
||||
return (
|
||||
<MainContainer>
|
||||
<CloseEditor />
|
||||
{closeEditorLink}
|
||||
<Form onSubmit={handleSubmit(noop)}>
|
||||
<MainConfiguration>
|
||||
<FormRow className="form-row-header">
|
||||
<NameWrapper className="t--nameOfApi">
|
||||
<ActionNameEditor disabled={!isChangePermitted} page="API_PANE" />
|
||||
<ActionNameEditor
|
||||
disabled={!isChangePermitted}
|
||||
page="API_PANE"
|
||||
saveActionName={saveActionName}
|
||||
/>
|
||||
</NameWrapper>
|
||||
<ActionButtons className="t--formActionButtons">
|
||||
<MoreActionsMenu
|
||||
className="t--more-action-menu"
|
||||
id={currentActionConfig ? currentActionConfig.id : ""}
|
||||
isChangePermitted={isChangePermitted}
|
||||
isDeletePermitted={isDeletePermitted}
|
||||
name={currentActionConfig ? currentActionConfig.name : ""}
|
||||
pageId={pageId}
|
||||
/>
|
||||
{moreActionsMenu}
|
||||
<Button
|
||||
className="t--apiFormRunBtn"
|
||||
isDisabled={blockExecution}
|
||||
|
|
@ -742,6 +735,7 @@ function CommonEditorForm(props: CommonFormPropsWithExtraParams) {
|
|||
</SecondaryWrapper>
|
||||
<DataSourceList
|
||||
actionName={actionName}
|
||||
actionRightPaneBackLink={actionRightPaneBackLink}
|
||||
applicationId={props.applicationId}
|
||||
currentActionDatasourceId={currentActionDatasourceId}
|
||||
currentPageId={props.currentPageId}
|
||||
|
|
|
|||
41
app/client/src/pages/Editor/APIEditor/CurlImportEditor.tsx
Normal file
41
app/client/src/pages/Editor/APIEditor/CurlImportEditor.tsx
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import React, { useMemo } from "react";
|
||||
|
||||
import CurlImportForm from "./CurlImportForm";
|
||||
import { createNewApiName } from "utils/AppsmithUtils";
|
||||
import { curlImportSubmitHandler } from "./helpers";
|
||||
import { getActions } from "@appsmith/selectors/entitiesSelector";
|
||||
import { getIsImportingCurl } from "selectors/ui";
|
||||
import { showDebuggerFlag } from "selectors/debuggerSelectors";
|
||||
import { useSelector } from "react-redux";
|
||||
import type { RouteComponentProps } from "react-router";
|
||||
import type { BuilderRouteParams } from "constants/routes";
|
||||
import CloseEditor from "components/editorComponents/CloseEditor";
|
||||
|
||||
type CurlImportEditorProps = RouteComponentProps<BuilderRouteParams>;
|
||||
|
||||
function CurlImportEditor(props: CurlImportEditorProps) {
|
||||
const actions = useSelector(getActions);
|
||||
const { pageId } = props.match.params;
|
||||
|
||||
const showDebugger = useSelector(showDebuggerFlag);
|
||||
const isImportingCurl = useSelector(getIsImportingCurl);
|
||||
|
||||
const initialFormValues = {
|
||||
pageId,
|
||||
name: createNewApiName(actions, pageId),
|
||||
};
|
||||
|
||||
const closeEditorLink = useMemo(() => <CloseEditor />, []);
|
||||
|
||||
return (
|
||||
<CurlImportForm
|
||||
closeEditorLink={closeEditorLink}
|
||||
curlImportSubmitHandler={curlImportSubmitHandler}
|
||||
initialValues={initialFormValues}
|
||||
isImportingCurl={isImportingCurl}
|
||||
showDebugger={showDebugger}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default CurlImportEditor;
|
||||
|
|
@ -1,26 +1,17 @@
|
|||
import React from "react";
|
||||
import type { InjectedFormProps } from "redux-form";
|
||||
import { reduxForm, Form, Field } from "redux-form";
|
||||
import { connect } from "react-redux";
|
||||
import type { RouteComponentProps } from "react-router";
|
||||
import { withRouter } from "react-router";
|
||||
import styled from "styled-components";
|
||||
import type { AppState } from "@appsmith/reducers";
|
||||
import type { ActionDataState } from "@appsmith/reducers/entityReducers/actionsReducer";
|
||||
import { CURL_IMPORT_FORM } from "@appsmith/constants/forms";
|
||||
import type { BuilderRouteParams } from "constants/routes";
|
||||
import type { curlImportFormValues } from "./helpers";
|
||||
import { curlImportSubmitHandler } from "./helpers";
|
||||
import { createNewApiName } from "utils/AppsmithUtils";
|
||||
import CurlLogo from "assets/images/Curl-logo.svg";
|
||||
import CloseEditor from "components/editorComponents/CloseEditor";
|
||||
import { Button } from "design-system";
|
||||
import FormRow from "components/editorComponents/FormRow";
|
||||
import Debugger, {
|
||||
ResizerContentContainer,
|
||||
ResizerMainContainer,
|
||||
} from "../DataSourceEditor/Debugger";
|
||||
import { showDebuggerFlag } from "selectors/debuggerSelectors";
|
||||
|
||||
const MainConfiguration = styled.div`
|
||||
padding: var(--ads-v2-spaces-4) var(--ads-v2-spaces-7);
|
||||
|
|
@ -99,25 +90,28 @@ const MainContainer = styled.div`
|
|||
padding: 0px var(--ads-v2-spaces-7);
|
||||
}
|
||||
`;
|
||||
interface ReduxStateProps {
|
||||
actions: ActionDataState;
|
||||
initialValues: Record<string, unknown>;
|
||||
|
||||
interface OwnProps {
|
||||
isImportingCurl: boolean;
|
||||
showDebugger: boolean;
|
||||
curlImportSubmitHandler: (
|
||||
values: curlImportFormValues,
|
||||
dispatch: any,
|
||||
) => void;
|
||||
initialValues: Record<string, unknown>;
|
||||
closeEditorLink?: React.ReactNode;
|
||||
}
|
||||
|
||||
export type StateAndRouteProps = ReduxStateProps &
|
||||
RouteComponentProps<BuilderRouteParams>;
|
||||
|
||||
type Props = StateAndRouteProps &
|
||||
InjectedFormProps<curlImportFormValues, StateAndRouteProps>;
|
||||
type Props = OwnProps & InjectedFormProps<curlImportFormValues, OwnProps>;
|
||||
|
||||
class CurlImportForm extends React.Component<Props> {
|
||||
render() {
|
||||
const { handleSubmit, isImportingCurl, showDebugger } = this.props;
|
||||
const { closeEditorLink, handleSubmit, isImportingCurl, showDebugger } =
|
||||
this.props;
|
||||
|
||||
return (
|
||||
<MainContainer>
|
||||
<CloseEditor />
|
||||
{closeEditorLink}
|
||||
<MainConfiguration>
|
||||
<FormRow className="form-row-header">
|
||||
<div
|
||||
|
|
@ -171,35 +165,6 @@ class CurlImportForm extends React.Component<Props> {
|
|||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: AppState, props: Props): ReduxStateProps => {
|
||||
const { pageId: destinationPageId } = props.match.params;
|
||||
|
||||
// Debugger render flag
|
||||
const showDebugger = showDebuggerFlag(state);
|
||||
|
||||
if (destinationPageId) {
|
||||
return {
|
||||
actions: state.entities.actions,
|
||||
initialValues: {
|
||||
pageId: destinationPageId,
|
||||
name: createNewApiName(state.entities.actions, destinationPageId),
|
||||
},
|
||||
isImportingCurl: state.ui.imports.isImportingCurl,
|
||||
showDebugger,
|
||||
};
|
||||
}
|
||||
return {
|
||||
actions: state.entities.actions,
|
||||
initialValues: {},
|
||||
isImportingCurl: state.ui.imports.isImportingCurl,
|
||||
showDebugger,
|
||||
};
|
||||
};
|
||||
|
||||
export default withRouter(
|
||||
connect(mapStateToProps)(
|
||||
reduxForm<curlImportFormValues, StateAndRouteProps>({
|
||||
form: CURL_IMPORT_FORM,
|
||||
})(CurlImportForm),
|
||||
),
|
||||
);
|
||||
export default reduxForm<curlImportFormValues, OwnProps>({
|
||||
form: CURL_IMPORT_FORM,
|
||||
})(CurlImportForm);
|
||||
|
|
|
|||
276
app/client/src/pages/Editor/APIEditor/Editor.tsx
Normal file
276
app/client/src/pages/Editor/APIEditor/Editor.tsx
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { submit } from "redux-form";
|
||||
import RestApiEditorForm from "./RestAPIForm";
|
||||
import RapidApiEditorForm from "./RapidApiEditorForm";
|
||||
import type { AppState } from "@appsmith/reducers";
|
||||
import type { RouteComponentProps } from "react-router";
|
||||
import type {
|
||||
ActionData,
|
||||
ActionDataState,
|
||||
} from "@appsmith/reducers/entityReducers/actionsReducer";
|
||||
import _ from "lodash";
|
||||
import { getCurrentApplication } from "@appsmith/selectors/applicationSelectors";
|
||||
import {
|
||||
getActionById,
|
||||
getCurrentApplicationId,
|
||||
getCurrentPageName,
|
||||
} from "selectors/editorSelectors";
|
||||
import type { Plugin } from "api/PluginApi";
|
||||
import type { Action, PaginationType, RapidApiAction } from "entities/Action";
|
||||
import { PluginPackageName } from "entities/Action";
|
||||
import { getApiName } from "selectors/formSelectors";
|
||||
import Spinner from "components/editorComponents/Spinner";
|
||||
import type { CSSProperties } from "styled-components";
|
||||
import styled from "styled-components";
|
||||
import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper";
|
||||
import { changeApi } from "actions/apiPaneActions";
|
||||
import PerformanceTracker, {
|
||||
PerformanceTransactionName,
|
||||
} from "utils/PerformanceTracker";
|
||||
import * as Sentry from "@sentry/react";
|
||||
import EntityNotFoundPane from "pages/Editor/EntityNotFoundPane";
|
||||
import type { ApplicationPayload } from "@appsmith/constants/ReduxActionConstants";
|
||||
import { getPageList, getPlugins } from "@appsmith/selectors/entitiesSelector";
|
||||
import history from "utils/history";
|
||||
import { saasEditorApiIdURL } from "@appsmith/RouteBuilder";
|
||||
import GraphQLEditorForm from "./GraphQL/GraphQLEditorForm";
|
||||
import type { APIEditorRouteParams } from "constants/routes";
|
||||
import { ApiEditorContext } from "./ApiEditorContext";
|
||||
|
||||
const LoadingContainer = styled(CenteredWrapper)`
|
||||
height: 50%;
|
||||
`;
|
||||
|
||||
interface ReduxStateProps {
|
||||
actions: ActionDataState;
|
||||
isRunning: boolean;
|
||||
isDeleting: boolean;
|
||||
isCreating: boolean;
|
||||
apiName: string;
|
||||
currentApplication?: ApplicationPayload;
|
||||
currentPageName: string | undefined;
|
||||
pages: any;
|
||||
plugins: Plugin[];
|
||||
pluginId: any;
|
||||
apiAction: Action | ActionData | RapidApiAction | undefined;
|
||||
paginationType: PaginationType;
|
||||
applicationId: string;
|
||||
}
|
||||
|
||||
interface OwnProps {
|
||||
isEditorInitialized: boolean;
|
||||
}
|
||||
|
||||
interface ReduxActionProps {
|
||||
submitForm: (name: string) => void;
|
||||
changeAPIPage: (apiId: string, isSaas: boolean) => void;
|
||||
}
|
||||
|
||||
function getPackageNameFromPluginId(pluginId: string, plugins: Plugin[]) {
|
||||
const plugin = plugins.find((plugin: Plugin) => plugin.id === pluginId);
|
||||
return plugin?.packageName;
|
||||
}
|
||||
|
||||
type Props = ReduxActionProps &
|
||||
ReduxStateProps &
|
||||
RouteComponentProps<APIEditorRouteParams> &
|
||||
OwnProps;
|
||||
|
||||
class ApiEditor extends React.Component<Props> {
|
||||
static contextType = ApiEditorContext;
|
||||
context!: React.ContextType<typeof ApiEditorContext>;
|
||||
|
||||
componentDidMount() {
|
||||
PerformanceTracker.stopTracking(PerformanceTransactionName.OPEN_ACTION, {
|
||||
actionType: "API",
|
||||
});
|
||||
const type = this.getFormName();
|
||||
if (this.props.match.params.apiId) {
|
||||
this.props.changeAPIPage(this.props.match.params.apiId, type === "SAAS");
|
||||
}
|
||||
}
|
||||
|
||||
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",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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 <EntityNotFoundPane />;
|
||||
}
|
||||
if (isCreating || !isEditorInitialized) {
|
||||
return (
|
||||
<LoadingContainer>
|
||||
<Spinner size={30} />
|
||||
</LoadingContainer>
|
||||
);
|
||||
}
|
||||
|
||||
let formUiComponent: string | undefined;
|
||||
if (apiId) {
|
||||
if (pluginId) {
|
||||
formUiComponent = this.getPluginUiComponentOfId(pluginId, plugins);
|
||||
} else {
|
||||
formUiComponent = this.getPluginUiComponentOfName(plugins);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={formStyles}>
|
||||
{formUiComponent === "ApiEditorForm" && (
|
||||
<RestApiEditorForm
|
||||
apiName={this.props.apiName}
|
||||
appName={
|
||||
this.props.currentApplication
|
||||
? this.props.currentApplication.name
|
||||
: ""
|
||||
}
|
||||
isDeleting={isDeleting}
|
||||
isRunning={isRunning}
|
||||
onDeleteClick={this.context.handleDeleteClick}
|
||||
onRunClick={this.context.handleRunClick}
|
||||
paginationType={paginationType}
|
||||
pluginId={pluginId}
|
||||
settingsConfig={this.context.settingsConfig}
|
||||
/>
|
||||
)}
|
||||
{formUiComponent === "GraphQLEditorForm" && (
|
||||
<GraphQLEditorForm
|
||||
apiName={this.props.apiName}
|
||||
appName={
|
||||
this.props.currentApplication
|
||||
? this.props.currentApplication.name
|
||||
: ""
|
||||
}
|
||||
isDeleting={isDeleting}
|
||||
isRunning={isRunning}
|
||||
match={this.props.match}
|
||||
onDeleteClick={this.context.handleDeleteClick}
|
||||
onRunClick={this.context.handleRunClick}
|
||||
paginationType={paginationType}
|
||||
pluginId={pluginId}
|
||||
settingsConfig={this.context.settingsConfig}
|
||||
/>
|
||||
)}
|
||||
{formUiComponent === "RapidApiEditorForm" && (
|
||||
<RapidApiEditorForm
|
||||
apiId={this.props.match.params.apiId || ""}
|
||||
apiName={this.props.apiName}
|
||||
appName={
|
||||
this.props.currentApplication
|
||||
? this.props.currentApplication.name
|
||||
: ""
|
||||
}
|
||||
isDeleting={isDeleting}
|
||||
isRunning={isRunning}
|
||||
location={this.props.location}
|
||||
onDeleteClick={this.context.handleDeleteClick}
|
||||
onRunClick={this.context.handleRunClick}
|
||||
paginationType={paginationType}
|
||||
/>
|
||||
)}
|
||||
{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 || "",
|
||||
}),
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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", "");
|
||||
return {
|
||||
actions: state.entities.actions,
|
||||
currentApplication: getCurrentApplication(state),
|
||||
currentPageName: getCurrentPageName(state),
|
||||
pages: getPageList(state),
|
||||
apiName: apiName || "",
|
||||
plugins: getPlugins(state),
|
||||
pluginId,
|
||||
paginationType: _.get(apiAction, "actionConfiguration.paginationType"),
|
||||
apiAction,
|
||||
isRunning: isRunning[props.match.params.apiId],
|
||||
isDeleting: isDeleting[props.match.params.apiId],
|
||||
isCreating: isCreating,
|
||||
applicationId: getCurrentApplicationId(state),
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch: any): ReduxActionProps => ({
|
||||
submitForm: (name: string) => dispatch(submit(name)),
|
||||
changeAPIPage: (actionId: string, isSaas: boolean) =>
|
||||
dispatch(changeApi(actionId, isSaas)),
|
||||
});
|
||||
|
||||
export default Sentry.withProfiler(
|
||||
connect(mapStateToProps, mapDispatchToProps)(ApiEditor),
|
||||
);
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useCallback, useRef } from "react";
|
||||
import React, { useCallback, useContext, useRef } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import type { InjectedFormProps } from "redux-form";
|
||||
import { change, formValueSelector, reduxForm } from "redux-form";
|
||||
|
|
@ -21,6 +21,7 @@ import QueryEditor from "./QueryEditor";
|
|||
import { tailwindLayers } from "constants/Layers";
|
||||
import VariableEditor from "./VariableEditor";
|
||||
import Pagination from "./Pagination";
|
||||
import { ApiEditorContext } from "../ApiEditorContext";
|
||||
|
||||
const ResizeableDiv = styled.div`
|
||||
display: flex;
|
||||
|
|
@ -81,6 +82,8 @@ function GraphQLEditorForm(props: Props) {
|
|||
DEFAULT_GRAPHQL_VARIABLE_WIDTH,
|
||||
);
|
||||
|
||||
const { closeEditorLink } = useContext(ApiEditorContext);
|
||||
|
||||
/**
|
||||
* Variable Editor's resizeable handler for the changing of width
|
||||
*/
|
||||
|
|
@ -131,6 +134,7 @@ function GraphQLEditorForm(props: Props) {
|
|||
</ResizeableDiv>
|
||||
</BodyWrapper>
|
||||
}
|
||||
closeEditorLink={closeEditorLink}
|
||||
defaultTabSelected={2}
|
||||
formName={API_EDITOR_FORM_NAME}
|
||||
paginationUIComponent={
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React from "react";
|
||||
import React, { useContext } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import type { InjectedFormProps } from "redux-form";
|
||||
import { reduxForm, formValueSelector } from "redux-form";
|
||||
|
|
@ -22,6 +22,7 @@ import { getActionData } from "@appsmith/selectors/entitiesSelector";
|
|||
import type { AppState } from "@appsmith/reducers";
|
||||
import { Icon } from "design-system";
|
||||
import { showDebuggerFlag } from "selectors/debuggerSelectors";
|
||||
import { ApiEditorContext } from "./ApiEditorContext";
|
||||
|
||||
const Form = styled.form`
|
||||
display: flex;
|
||||
|
|
@ -142,6 +143,8 @@ function RapidApiEditorForm(props: Props) {
|
|||
templateId,
|
||||
} = props;
|
||||
|
||||
const { saveActionName } = useContext(ApiEditorContext);
|
||||
|
||||
const postbodyResponsePresent =
|
||||
templateId &&
|
||||
actionConfiguration &&
|
||||
|
|
@ -158,7 +161,7 @@ function RapidApiEditorForm(props: Props) {
|
|||
<MainConfiguration>
|
||||
<FormRow>
|
||||
<NameWrapper>
|
||||
<ActionNameEditor />
|
||||
<ActionNameEditor saveActionName={saveActionName} />
|
||||
<a
|
||||
className="t--apiDocumentationLink"
|
||||
href={providerURL && `http://${providerURL}`}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React from "react";
|
||||
import React, { useContext } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import type { InjectedFormProps } from "redux-form";
|
||||
import { change, formValueSelector, reduxForm } from "redux-form";
|
||||
|
|
@ -24,6 +24,7 @@ import type { CommonFormProps } from "./CommonEditorForm";
|
|||
import CommonEditorForm from "./CommonEditorForm";
|
||||
import Pagination from "./Pagination";
|
||||
import { getCurrentEnvironmentId } from "@appsmith/selectors/environmentSelectors";
|
||||
import { ApiEditorContext } from "./ApiEditorContext";
|
||||
|
||||
const NoBodyMessage = styled.div`
|
||||
margin-top: 20px;
|
||||
|
|
@ -42,6 +43,7 @@ type APIFormProps = {
|
|||
type Props = APIFormProps & InjectedFormProps<Action, APIFormProps>;
|
||||
|
||||
function ApiEditorForm(props: Props) {
|
||||
const { closeEditorLink } = useContext(ApiEditorContext);
|
||||
const { actionName, httpMethodFromForm } = props;
|
||||
const allowPostBody = httpMethodFromForm;
|
||||
const theme = EditorTheme.LIGHT;
|
||||
|
|
@ -58,6 +60,7 @@ function ApiEditorForm(props: Props) {
|
|||
</NoBodyMessage>
|
||||
)
|
||||
}
|
||||
closeEditorLink={closeEditorLink}
|
||||
formName={API_EDITOR_FORM_NAME}
|
||||
paginationUIComponent={
|
||||
<Pagination
|
||||
|
|
|
|||
|
|
@ -1,318 +1,129 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { submit } from "redux-form";
|
||||
import RestApiEditorForm from "./RestAPIForm";
|
||||
import RapidApiEditorForm from "./RapidApiEditorForm";
|
||||
import { deleteAction, runAction } from "actions/pluginActionActions";
|
||||
import type { PaginationField } from "api/ActionAPI";
|
||||
import type { AppState } from "@appsmith/reducers";
|
||||
import React, { useCallback, useMemo } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import type { RouteComponentProps } from "react-router";
|
||||
import type {
|
||||
ActionData,
|
||||
ActionDataState,
|
||||
} from "@appsmith/reducers/entityReducers/actionsReducer";
|
||||
import _ from "lodash";
|
||||
import { getCurrentApplication } from "@appsmith/selectors/applicationSelectors";
|
||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||
|
||||
import {
|
||||
getActionById,
|
||||
getCurrentApplicationId,
|
||||
getCurrentPageName,
|
||||
getIsEditorInitialized,
|
||||
} from "selectors/editorSelectors";
|
||||
import type { Plugin } from "api/PluginApi";
|
||||
import type { Action, PaginationType, RapidApiAction } from "entities/Action";
|
||||
import { PluginPackageName } from "entities/Action";
|
||||
import { getApiName } from "selectors/formSelectors";
|
||||
import Spinner from "components/editorComponents/Spinner";
|
||||
import type { CSSProperties } from "styled-components";
|
||||
import styled from "styled-components";
|
||||
import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper";
|
||||
import { changeApi } from "actions/apiPaneActions";
|
||||
getPageList,
|
||||
getPluginSettingConfigs,
|
||||
getPlugins,
|
||||
} from "@appsmith/selectors/entitiesSelector";
|
||||
import { deleteAction, runAction } from "actions/pluginActionActions";
|
||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||
import Editor from "./Editor";
|
||||
import BackToCanvas from "components/common/BackToCanvas";
|
||||
import MoreActionsMenu from "../Explorer/Actions/MoreActionsMenu";
|
||||
import { getIsEditorInitialized } from "selectors/editorSelectors";
|
||||
import { getAction } from "@appsmith/selectors/entitiesSelector";
|
||||
import type { APIEditorRouteParams } 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 { ApiEditorContextProvider } from "./ApiEditorContext";
|
||||
import type { PaginationField } from "api/ActionAPI";
|
||||
import { get } from "lodash";
|
||||
import PerformanceTracker, {
|
||||
PerformanceTransactionName,
|
||||
} from "utils/PerformanceTracker";
|
||||
import * as Sentry from "@sentry/react";
|
||||
import EntityNotFoundPane from "pages/Editor/EntityNotFoundPane";
|
||||
import type { ApplicationPayload } from "@appsmith/constants/ReduxActionConstants";
|
||||
import {
|
||||
getPageList,
|
||||
getPlugins,
|
||||
getPluginSettingConfigs,
|
||||
} from "@appsmith/selectors/entitiesSelector";
|
||||
import history from "utils/history";
|
||||
import { saasEditorApiIdURL } from "@appsmith/RouteBuilder";
|
||||
import GraphQLEditorForm from "./GraphQL/GraphQLEditorForm";
|
||||
import CloseEditor from "components/editorComponents/CloseEditor";
|
||||
|
||||
const LoadingContainer = styled(CenteredWrapper)`
|
||||
height: 50%;
|
||||
`;
|
||||
|
||||
interface ReduxStateProps {
|
||||
actions: ActionDataState;
|
||||
isRunning: boolean;
|
||||
isDeleting: boolean;
|
||||
isCreating: boolean;
|
||||
apiName: string;
|
||||
currentApplication?: ApplicationPayload;
|
||||
currentPageName: string | undefined;
|
||||
pages: any;
|
||||
plugins: Plugin[];
|
||||
pluginId: any;
|
||||
settingsConfig: any;
|
||||
apiAction: Action | ActionData | RapidApiAction | undefined;
|
||||
paginationType: PaginationType;
|
||||
isEditorInitialized: boolean;
|
||||
applicationId: string;
|
||||
}
|
||||
interface ReduxActionProps {
|
||||
submitForm: (name: string) => void;
|
||||
runAction: (id: string, paginationField?: PaginationField) => void;
|
||||
deleteAction: (id: string, name: string) => void;
|
||||
changeAPIPage: (apiId: string, isSaas: boolean) => void;
|
||||
}
|
||||
type ApiEditorWrapperProps = RouteComponentProps<APIEditorRouteParams>;
|
||||
|
||||
function getPageName(pages: any, pageId: string) {
|
||||
const page = pages.find((page: any) => page.pageId === pageId);
|
||||
return page ? page.pageName : "";
|
||||
}
|
||||
|
||||
function getPackageNameFromPluginId(pluginId: string, plugins: Plugin[]) {
|
||||
const plugin = plugins.find((plugin: Plugin) => plugin.id === pluginId);
|
||||
return plugin?.packageName;
|
||||
}
|
||||
function ApiEditorWrapper(props: ApiEditorWrapperProps) {
|
||||
const { apiId = "", pageId } = props.match.params;
|
||||
const dispatch = useDispatch();
|
||||
const isEditorInitialized = useSelector(getIsEditorInitialized);
|
||||
const action = useSelector((state) => getAction(state, apiId));
|
||||
const apiName = action?.name || "";
|
||||
const pluginId = get(action, "pluginId", "");
|
||||
const datasourceId = action?.datasource.id || "";
|
||||
const plugins = useSelector(getPlugins);
|
||||
const pages = useSelector(getPageList);
|
||||
const pageName = getPageName(pages, pageId);
|
||||
const settingsConfig = useSelector((state) =>
|
||||
getPluginSettingConfigs(state, pluginId),
|
||||
);
|
||||
const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled);
|
||||
|
||||
type Props = ReduxActionProps &
|
||||
ReduxStateProps &
|
||||
RouteComponentProps<{ apiId: string; pageId: string }>;
|
||||
const isChangePermitted = getHasManageActionPermission(
|
||||
isFeatureEnabled,
|
||||
action?.userPermissions,
|
||||
);
|
||||
const isDeletePermitted = getHasDeleteActionPermission(
|
||||
isFeatureEnabled,
|
||||
action?.userPermissions,
|
||||
);
|
||||
|
||||
class ApiEditor extends React.Component<Props> {
|
||||
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(
|
||||
() => (
|
||||
<MoreActionsMenu
|
||||
className="t--more-action-menu"
|
||||
id={action ? action.id : ""}
|
||||
isChangePermitted={isChangePermitted}
|
||||
isDeletePermitted={isDeletePermitted}
|
||||
name={action ? action.name : ""}
|
||||
pageId={pageId}
|
||||
/>
|
||||
),
|
||||
[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 <EntityNotFoundPane />;
|
||||
}
|
||||
if (isCreating || !isEditorInitialized) {
|
||||
return (
|
||||
<LoadingContainer>
|
||||
<Spinner size={30} />
|
||||
</LoadingContainer>
|
||||
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 <BackToCanvas pageId={pageId} />;
|
||||
}, [pageId]);
|
||||
|
||||
return (
|
||||
<div style={formStyles}>
|
||||
{formUiComponent === "ApiEditorForm" && (
|
||||
<RestApiEditorForm
|
||||
apiName={this.props.apiName}
|
||||
appName={
|
||||
this.props.currentApplication
|
||||
? this.props.currentApplication.name
|
||||
: ""
|
||||
}
|
||||
isDeleting={isDeleting}
|
||||
isRunning={isRunning}
|
||||
onDeleteClick={this.handleDeleteClick}
|
||||
onRunClick={this.handleRunClick}
|
||||
paginationType={paginationType}
|
||||
pluginId={pluginId}
|
||||
settingsConfig={this.props.settingsConfig}
|
||||
/>
|
||||
)}
|
||||
{formUiComponent === "GraphQLEditorForm" && (
|
||||
<GraphQLEditorForm
|
||||
apiName={this.props.apiName}
|
||||
appName={
|
||||
this.props.currentApplication
|
||||
? this.props.currentApplication.name
|
||||
: ""
|
||||
}
|
||||
isDeleting={isDeleting}
|
||||
isRunning={isRunning}
|
||||
match={this.props.match}
|
||||
onDeleteClick={this.handleDeleteClick}
|
||||
onRunClick={this.handleRunClick}
|
||||
paginationType={paginationType}
|
||||
pluginId={pluginId}
|
||||
settingsConfig={this.props.settingsConfig}
|
||||
/>
|
||||
)}
|
||||
{formUiComponent === "RapidApiEditorForm" && (
|
||||
<RapidApiEditorForm
|
||||
apiId={this.props.match.params.apiId}
|
||||
apiName={this.props.apiName}
|
||||
appName={
|
||||
this.props.currentApplication
|
||||
? this.props.currentApplication.name
|
||||
: ""
|
||||
}
|
||||
isDeleting={isDeleting}
|
||||
isRunning={isRunning}
|
||||
location={this.props.location}
|
||||
onDeleteClick={this.handleDeleteClick}
|
||||
onRunClick={this.handleRunClick}
|
||||
paginationType={paginationType}
|
||||
/>
|
||||
)}
|
||||
{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,
|
||||
}),
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
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(() => <CloseEditor />, []);
|
||||
|
||||
return (
|
||||
<ApiEditorContextProvider
|
||||
actionRightPaneBackLink={actionRightPaneBackLink}
|
||||
closeEditorLink={closeEditorLink}
|
||||
handleDeleteClick={handleDeleteClick}
|
||||
handleRunClick={handleRunClick}
|
||||
moreActionsMenu={moreActionsMenu}
|
||||
settingsConfig={settingsConfig}
|
||||
>
|
||||
<Editor {...props} isEditorInitialized={isEditorInitialized} />
|
||||
</ApiEditorContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
|
|||
352
app/client/src/pages/Editor/QueryEditor/Editor.tsx
Normal file
352
app/client/src/pages/Editor/QueryEditor/Editor.tsx
Normal file
|
|
@ -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<string, string>;
|
||||
pluginId: string | undefined;
|
||||
pluginIds: Array<string> | 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<QueryEditorRouteParams>;
|
||||
type OwnProps = StateAndRouteProps & {
|
||||
isEditorInitialized: boolean;
|
||||
settingsConfig: any;
|
||||
};
|
||||
type Props = ReduxDispatchProps & ReduxStateProps & OwnProps;
|
||||
|
||||
class QueryEditor extends React.Component<Props> {
|
||||
static contextType = QueryEditorContext;
|
||||
context!: React.ContextType<typeof QueryEditorContext>;
|
||||
|
||||
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 <EntityNotFoundPane goBackFn={onEntityNotFoundBackClick} />;
|
||||
}
|
||||
|
||||
if (!pluginIds?.length) {
|
||||
return (
|
||||
<EmptyStateContainer>{"Plugin is not installed"}</EmptyStateContainer>
|
||||
);
|
||||
}
|
||||
|
||||
if (isCreating || !isEditorInitialized) {
|
||||
return (
|
||||
<LoadingContainer>
|
||||
<Spinner size={30} />
|
||||
</LoadingContainer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<QueryEditorForm
|
||||
dataSources={dataSources}
|
||||
datasourceId={this.props.datasourceId}
|
||||
editorConfig={editorConfig}
|
||||
executedQueryData={responses[actionId]}
|
||||
formData={this.props.formData}
|
||||
isDeleting={isDeleting}
|
||||
isRunning={isRunning}
|
||||
location={this.props.location}
|
||||
onCreateDatasourceClick={onCreateDatasourceClick}
|
||||
onDeleteClick={this.handleDeleteClick}
|
||||
onRunClick={this.handleRunClick}
|
||||
pluginId={this.props.pluginId}
|
||||
runErrorMessage={runErrorMessage[actionId]}
|
||||
settingConfig={this.props.settingsConfig}
|
||||
uiComponent={uiComponent}
|
||||
updateActionResponseDisplayFormat={updateActionResponseDisplayFormat}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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<Action | undefined, Action>[] = 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);
|
||||
|
|
@ -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<string, any>[] | null = null;
|
||||
let hintMessages: Array<string> = [];
|
||||
|
|
@ -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<ExplorerURLParams>();
|
||||
|
||||
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 && <CloseEditor />}
|
||||
{!guidedTourEnabled && closeEditorLink}
|
||||
{guidedTourEnabled && <Guide className="query-page" />}
|
||||
<QueryFormContainer onSubmit={handleSubmit(noop)}>
|
||||
<StyledFormRow>
|
||||
<NameWrapper>
|
||||
<ActionNameEditor disabled={!isChangePermitted} />
|
||||
<ActionNameEditor
|
||||
disabled={!isChangePermitted}
|
||||
saveActionName={saveActionName}
|
||||
/>
|
||||
</NameWrapper>
|
||||
<ActionsWrapper>
|
||||
<MoreActionsMenu
|
||||
className="t--more-action-menu"
|
||||
id={currentActionConfig ? currentActionConfig.id : ""}
|
||||
isChangePermitted={isChangePermitted}
|
||||
isDeletePermitted={isDeletePermitted}
|
||||
name={currentActionConfig ? currentActionConfig.name : ""}
|
||||
pageId={pageId}
|
||||
/>
|
||||
{moreActionsMenu}
|
||||
<DropdownSelect>
|
||||
<DropdownField
|
||||
className={"t--switch-datasource"}
|
||||
|
|
@ -1113,6 +1109,7 @@ export function EditorJSONtoForm(props: Props) {
|
|||
<SidebarWrapper show={shouldOpenActionPaneByDefault}>
|
||||
<ActionRightPane
|
||||
actionName={actionName}
|
||||
actionRightPaneBackLink={actionRightPaneBackLink}
|
||||
context={DatasourceStructureContext.QUERY_EDITOR}
|
||||
datasourceId={props.datasourceId}
|
||||
hasConnections={hasDependencies}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
import type { ReduxAction } from "@appsmith/constants/ReduxActionConstants";
|
||||
import React, { createContext, useMemo } from "react";
|
||||
|
||||
interface SaveActionNameParams {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface QueryEditorContextContextProps {
|
||||
moreActionsMenu?: React.ReactNode;
|
||||
onCreateDatasourceClick?: () => void;
|
||||
onEntityNotFoundBackClick?: () => void;
|
||||
changeQueryPage?: (queryId: string) => void;
|
||||
actionRightPaneBackLink?: React.ReactNode;
|
||||
saveActionName?: (
|
||||
params: SaveActionNameParams,
|
||||
) => ReduxAction<SaveActionNameParams>;
|
||||
closeEditorLink?: React.ReactNode;
|
||||
}
|
||||
|
||||
type QueryEditorContextProviderProps =
|
||||
React.PropsWithChildren<QueryEditorContextContextProps>;
|
||||
|
||||
export const QueryEditorContext = createContext<QueryEditorContextContextProps>(
|
||||
{} 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 (
|
||||
<QueryEditorContext.Provider value={value}>
|
||||
{children}
|
||||
</QueryEditorContext.Provider>
|
||||
);
|
||||
}
|
||||
|
|
@ -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<QueryEditorRouteParams>;
|
||||
|
||||
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<string, string>;
|
||||
pluginId: string | undefined;
|
||||
pluginIds: Array<string> | 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<QueryEditorRouteParams>;
|
||||
const moreActionsMenu = useMemo(
|
||||
() => (
|
||||
<MoreActionsMenu
|
||||
className="t--more-action-menu"
|
||||
id={action ? action.id : ""}
|
||||
isChangePermitted={isChangePermitted}
|
||||
isDeletePermitted={isDeletePermitted}
|
||||
name={action ? action.name : ""}
|
||||
pageId={pageId}
|
||||
/>
|
||||
),
|
||||
[action?.id, action?.name, isChangePermitted, isDeletePermitted, pageId],
|
||||
);
|
||||
|
||||
type Props = StateAndRouteProps & ReduxDispatchProps & ReduxStateProps;
|
||||
const actionRightPaneBackLink = useMemo(() => {
|
||||
return <BackToCanvas pageId={pageId} />;
|
||||
}, [pageId]);
|
||||
|
||||
class QueryEditor extends React.Component<Props> {
|
||||
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<Props> {
|
|||
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 <EntityNotFoundPane goBackFn={goToDatasourcePage} />;
|
||||
}
|
||||
|
||||
if (!pluginIds?.length) {
|
||||
return (
|
||||
<EmptyStateContainer>{"Plugin is not installed"}</EmptyStateContainer>
|
||||
);
|
||||
}
|
||||
|
||||
if (isCreating || !isEditorInitialized) {
|
||||
return (
|
||||
<LoadingContainer>
|
||||
<Spinner size={30} />
|
||||
</LoadingContainer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<QueryEditorForm
|
||||
dataSources={dataSources}
|
||||
datasourceId={this.props.datasourceId}
|
||||
editorConfig={editorConfig}
|
||||
executedQueryData={responses[actionId]}
|
||||
formData={this.props.formData}
|
||||
isDeleting={isDeleting}
|
||||
isRunning={isRunning}
|
||||
location={this.props.location}
|
||||
onCreateDatasourceClick={this.onCreateDatasourceClick}
|
||||
onDeleteClick={this.handleDeleteClick}
|
||||
onRunClick={this.handleRunClick}
|
||||
pluginId={this.props.pluginId}
|
||||
runErrorMessage={runErrorMessage[actionId]}
|
||||
settingConfig={settingConfig}
|
||||
uiComponent={uiComponent}
|
||||
updateActionResponseDisplayFormat={updateActionResponseDisplayFormat}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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<Action | undefined, Action>[] = diff(
|
||||
action,
|
||||
initialValues,
|
||||
),
|
||||
[pageId, history, integrationEditorURL],
|
||||
);
|
||||
|
||||
const allPlugins = getPlugins(state);
|
||||
let uiComponent = UIComponentTypes.DbEditorForm;
|
||||
if (!!pluginId) uiComponent = getUIComponent(pluginId, allPlugins);
|
||||
const closeEditorLink = useMemo(() => <CloseEditor />, []);
|
||||
|
||||
const currentEnvDetails = getCurrentEnvironmentDetails(state);
|
||||
return (
|
||||
<QueryEditorContextProvider
|
||||
actionRightPaneBackLink={actionRightPaneBackLink}
|
||||
changeQueryPage={changeQueryPage}
|
||||
closeEditorLink={closeEditorLink}
|
||||
moreActionsMenu={moreActionsMenu}
|
||||
onCreateDatasourceClick={onCreateDatasourceClick}
|
||||
onEntityNotFoundBackClick={onEntityNotFoundBackClick}
|
||||
>
|
||||
<Editor
|
||||
{...props}
|
||||
isEditorInitialized={isEditorInitialized}
|
||||
settingsConfig={settingsConfig}
|
||||
/>
|
||||
</QueryEditorContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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() {
|
|||
/>
|
||||
|
||||
<SentryRoute
|
||||
component={CurlImportForm}
|
||||
component={CurlImportEditor}
|
||||
exact
|
||||
path={`${path}${CURL_IMPORT_PAGE_PATH}`}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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<ChangeQueryPayload>) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -54,3 +54,6 @@ export const getDatasourceCollapsibleState = createSelector(
|
|||
return datasourceCollapsibleState[key];
|
||||
},
|
||||
);
|
||||
|
||||
export const getIsImportingCurl = (state: AppState) =>
|
||||
state.ui.imports.isImportingCurl;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user