chore: action response view refactor (#29031)
## Description Refactor PR for action response view and action execution saga #### PR fixes following issue(s) Refactor PR for https://github.com/appsmithorg/appsmith-ee/pull/2936 #### 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 - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] 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 --------- Co-authored-by: Rishabh-Rathod <rishabh.rathod@appsmith.com>
This commit is contained in:
parent
ffcf12d8e5
commit
dea2fd736c
|
|
@ -228,11 +228,16 @@ export const executePluginActionRequest = (payload: { id: string }) => ({
|
||||||
payload: payload,
|
payload: payload,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const executePluginActionSuccess = (payload: {
|
export interface ExecutePluginActionSuccessPayload {
|
||||||
id: string;
|
id: string;
|
||||||
response: ActionResponse;
|
response: ActionResponse;
|
||||||
isPageLoad?: boolean;
|
isPageLoad?: boolean;
|
||||||
}) => ({
|
isActionCreatedInApp: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const executePluginActionSuccess = (
|
||||||
|
payload: ExecutePluginActionSuccessPayload,
|
||||||
|
) => ({
|
||||||
type: ReduxActionTypes.EXECUTE_PLUGIN_ACTION_SUCCESS,
|
type: ReduxActionTypes.EXECUTE_PLUGIN_ACTION_SUCCESS,
|
||||||
payload: payload,
|
payload: payload,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,10 @@ import type { ActionResponse } from "api/ActionAPI";
|
||||||
import type { ExecuteErrorPayload } from "constants/AppsmithActionConstants/ActionConstants";
|
import type { ExecuteErrorPayload } from "constants/AppsmithActionConstants/ActionConstants";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import type { Action } from "entities/Action";
|
import type { Action } from "entities/Action";
|
||||||
import type { UpdateActionPropertyActionPayload } from "actions/pluginActionActions";
|
import type {
|
||||||
|
ExecutePluginActionSuccessPayload,
|
||||||
|
UpdateActionPropertyActionPayload,
|
||||||
|
} from "actions/pluginActionActions";
|
||||||
|
|
||||||
export interface ActionData {
|
export interface ActionData {
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
|
|
@ -173,8 +176,10 @@ export const handlers = {
|
||||||
},
|
},
|
||||||
[ReduxActionTypes.EXECUTE_PLUGIN_ACTION_SUCCESS]: (
|
[ReduxActionTypes.EXECUTE_PLUGIN_ACTION_SUCCESS]: (
|
||||||
draftMetaState: Array<ActionData | PartialActionData>,
|
draftMetaState: Array<ActionData | PartialActionData>,
|
||||||
action: ReduxAction<{ id: string; response: ActionResponse }>,
|
action: ReduxAction<ExecutePluginActionSuccessPayload>,
|
||||||
) => {
|
) => {
|
||||||
|
if (!action.payload.isActionCreatedInApp) return;
|
||||||
|
|
||||||
const foundAction = draftMetaState.find((stateAction) => {
|
const foundAction = draftMetaState.find((stateAction) => {
|
||||||
return stateAction.config.id === action.payload.id;
|
return stateAction.config.id === action.payload.id;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
5
app/client/src/ce/utils/getIsActionCreatedInApp.ts
Normal file
5
app/client/src/ce/utils/getIsActionCreatedInApp.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
import type { Action } from "entities/Action";
|
||||||
|
|
||||||
|
export function getIsActionCreatedInApp(action: Action) {
|
||||||
|
return !!action;
|
||||||
|
}
|
||||||
|
|
@ -1,19 +1,14 @@
|
||||||
import type { PropsWithChildren, RefObject } from "react";
|
import type { PropsWithChildren, RefObject } from "react";
|
||||||
import React, { useCallback, useRef, useState } from "react";
|
import React, { useCallback, useRef, useState } from "react";
|
||||||
import { connect, useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import type { RouteComponentProps } from "react-router";
|
|
||||||
import { withRouter } from "react-router";
|
|
||||||
import ReactJson from "react-json-view";
|
import ReactJson from "react-json-view";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import type { AppState } from "@appsmith/reducers";
|
|
||||||
import type { ActionResponse } from "api/ActionAPI";
|
import type { ActionResponse } from "api/ActionAPI";
|
||||||
import { formatBytes } from "utils/helpers";
|
import { formatBytes } from "utils/helpers";
|
||||||
import type { APIEditorRouteParams } from "constants/routes";
|
|
||||||
import type { SourceEntity } from "entities/AppsmithConsole";
|
import type { SourceEntity } from "entities/AppsmithConsole";
|
||||||
import LOG_TYPE from "entities/AppsmithConsole/logtype";
|
import LOG_TYPE from "entities/AppsmithConsole/logtype";
|
||||||
import { ENTITY_TYPE } from "entities/AppsmithConsole";
|
import { ENTITY_TYPE } from "entities/AppsmithConsole";
|
||||||
import ReadOnlyEditor from "components/editorComponents/ReadOnlyEditor";
|
import ReadOnlyEditor from "components/editorComponents/ReadOnlyEditor";
|
||||||
import { getActionResponses } from "@appsmith/selectors/entitiesSelector";
|
|
||||||
import { isArray, isEmpty, isString } from "lodash";
|
import { isArray, isEmpty, isString } from "lodash";
|
||||||
import {
|
import {
|
||||||
CHECK_REQUEST_BODY,
|
CHECK_REQUEST_BODY,
|
||||||
|
|
@ -25,7 +20,7 @@ import {
|
||||||
DEBUGGER_ERRORS,
|
DEBUGGER_ERRORS,
|
||||||
} from "@appsmith/constants/messages";
|
} from "@appsmith/constants/messages";
|
||||||
import { Text as BlueprintText } from "@blueprintjs/core";
|
import { Text as BlueprintText } from "@blueprintjs/core";
|
||||||
import type { EditorTheme } from "./CodeEditor/EditorConfig";
|
import { EditorTheme } from "./CodeEditor/EditorConfig";
|
||||||
import NoResponseSVG from "assets/images/no-response.svg";
|
import NoResponseSVG from "assets/images/no-response.svg";
|
||||||
import DebuggerLogs from "./Debugger/DebuggerLogs";
|
import DebuggerLogs from "./Debugger/DebuggerLogs";
|
||||||
import ErrorLogs from "./Debugger/Errors";
|
import ErrorLogs from "./Debugger/Errors";
|
||||||
|
|
@ -38,11 +33,11 @@ import EntityBottomTabs from "./EntityBottomTabs";
|
||||||
import { DEBUGGER_TAB_KEYS } from "./Debugger/helpers";
|
import { DEBUGGER_TAB_KEYS } from "./Debugger/helpers";
|
||||||
import Table from "pages/Editor/QueryEditor/Table";
|
import Table from "pages/Editor/QueryEditor/Table";
|
||||||
import { API_RESPONSE_TYPE_OPTIONS } from "constants/ApiEditorConstants/CommonApiConstants";
|
import { API_RESPONSE_TYPE_OPTIONS } from "constants/ApiEditorConstants/CommonApiConstants";
|
||||||
import type { UpdateActionPropertyActionPayload } from "actions/pluginActionActions";
|
|
||||||
import { setActionResponseDisplayFormat } from "actions/pluginActionActions";
|
import { setActionResponseDisplayFormat } from "actions/pluginActionActions";
|
||||||
import { isHtml } from "./utils";
|
import { isHtml } from "./utils";
|
||||||
import {
|
import {
|
||||||
getDebuggerSelectedTab,
|
getDebuggerSelectedTab,
|
||||||
|
getErrorCount,
|
||||||
getResponsePaneHeight,
|
getResponsePaneHeight,
|
||||||
} from "selectors/debuggerSelectors";
|
} from "selectors/debuggerSelectors";
|
||||||
import { ActionExecutionResizerHeight } from "pages/Editor/APIEditor/constants";
|
import { ActionExecutionResizerHeight } from "pages/Editor/APIEditor/constants";
|
||||||
|
|
@ -179,29 +174,17 @@ const ResponseBodyContainer = styled.div`
|
||||||
display: grid;
|
display: grid;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
interface ReduxStateProps {
|
interface Props {
|
||||||
responses: Record<string, ActionResponse | undefined>;
|
currentActionConfig?: Action;
|
||||||
isRunning: Record<string, boolean>;
|
theme?: EditorTheme;
|
||||||
errorCount: number;
|
apiName: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
onRunClick: () => void;
|
||||||
|
responseDataTypes: { key: string; title: string }[];
|
||||||
|
responseDisplayFormat: { title: string; value: string };
|
||||||
|
actionResponse?: ActionResponse;
|
||||||
|
isRunning: boolean;
|
||||||
}
|
}
|
||||||
interface ReduxDispatchProps {
|
|
||||||
updateActionResponseDisplayFormat: ({
|
|
||||||
field,
|
|
||||||
id,
|
|
||||||
value,
|
|
||||||
}: UpdateActionPropertyActionPayload) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
type Props = ReduxStateProps &
|
|
||||||
ReduxDispatchProps &
|
|
||||||
RouteComponentProps<APIEditorRouteParams> & {
|
|
||||||
theme?: EditorTheme;
|
|
||||||
apiName: string;
|
|
||||||
disabled?: boolean;
|
|
||||||
onRunClick: () => void;
|
|
||||||
responseDataTypes: { key: string; title: string }[];
|
|
||||||
responseDisplayFormat: { title: string; value: string };
|
|
||||||
};
|
|
||||||
|
|
||||||
const StatusCodeText = styled(BaseText)<PropsWithChildren<{ code: string }>>`
|
const StatusCodeText = styled(BaseText)<PropsWithChildren<{ code: string }>>`
|
||||||
color: ${(props) =>
|
color: ${(props) =>
|
||||||
|
|
@ -313,31 +296,21 @@ export const NoResponse = (props: NoResponseProps) => (
|
||||||
|
|
||||||
function ApiResponseView(props: Props) {
|
function ApiResponseView(props: Props) {
|
||||||
const {
|
const {
|
||||||
|
actionResponse = EMPTY_RESPONSE,
|
||||||
|
currentActionConfig,
|
||||||
disabled,
|
disabled,
|
||||||
match: {
|
isRunning,
|
||||||
params: { apiId },
|
|
||||||
},
|
|
||||||
responseDataTypes,
|
responseDataTypes,
|
||||||
responseDisplayFormat,
|
responseDisplayFormat,
|
||||||
responses,
|
theme = EditorTheme.LIGHT,
|
||||||
updateActionResponseDisplayFormat,
|
|
||||||
} = props;
|
} = props;
|
||||||
let response: ActionResponse = EMPTY_RESPONSE;
|
const hasFailed = actionResponse.statusCode
|
||||||
let isRunning = false;
|
? actionResponse.statusCode[0] !== "2"
|
||||||
let hasFailed = false;
|
: false;
|
||||||
if (apiId && apiId in responses) {
|
|
||||||
response = responses[apiId] || EMPTY_RESPONSE;
|
|
||||||
isRunning = props.isRunning[apiId];
|
|
||||||
hasFailed = response.statusCode ? response.statusCode[0] !== "2" : false;
|
|
||||||
}
|
|
||||||
const actions: Action[] = useSelector((state: AppState) =>
|
|
||||||
state.entities.actions.map((action) => action.config),
|
|
||||||
);
|
|
||||||
const currentActionConfig: Action | undefined = actions.find(
|
|
||||||
(action) => action.id === apiId,
|
|
||||||
);
|
|
||||||
const panelRef: RefObject<HTMLDivElement> = useRef(null);
|
const panelRef: RefObject<HTMLDivElement> = useRef(null);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const errorCount = useSelector(getErrorCount);
|
||||||
|
|
||||||
const onDebugClick = useCallback(() => {
|
const onDebugClick = useCallback(() => {
|
||||||
AnalyticsUtil.logEvent("OPEN_DEBUGGER", {
|
AnalyticsUtil.logEvent("OPEN_DEBUGGER", {
|
||||||
|
|
@ -353,12 +326,12 @@ function ApiResponseView(props: Props) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const messages = response?.messages;
|
const messages = actionResponse?.messages;
|
||||||
let responseHeaders = {};
|
let responseHeaders = {};
|
||||||
|
|
||||||
// if no headers are present in the response, use the default body text.
|
// if no headers are present in the response, use the default body text.
|
||||||
if (response.headers) {
|
if (actionResponse.headers) {
|
||||||
Object.entries(response.headers).forEach(([key, value]) => {
|
Object.entries(actionResponse.headers).forEach(([key, value]) => {
|
||||||
if (isArray(value) && value.length < 2)
|
if (isArray(value) && value.length < 2)
|
||||||
return (responseHeaders = {
|
return (responseHeaders = {
|
||||||
...responseHeaders,
|
...responseHeaders,
|
||||||
|
|
@ -375,17 +348,19 @@ function ApiResponseView(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const onResponseTabSelect = (tab: string) => {
|
const onResponseTabSelect = (tab: string) => {
|
||||||
updateActionResponseDisplayFormat({
|
dispatch(
|
||||||
id: apiId ? apiId : "",
|
setActionResponseDisplayFormat({
|
||||||
field: "responseDisplayFormat",
|
id: currentActionConfig?.id || "",
|
||||||
value: tab,
|
field: "responseDisplayFormat",
|
||||||
});
|
value: tab,
|
||||||
|
}),
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
let filteredResponseDataTypes: { key: string; title: string }[] = [
|
let filteredResponseDataTypes: { key: string; title: string }[] = [
|
||||||
...responseDataTypes,
|
...responseDataTypes,
|
||||||
];
|
];
|
||||||
if (!!response.body && !isArray(response.body)) {
|
if (!!actionResponse.body && !isArray(actionResponse.body)) {
|
||||||
filteredResponseDataTypes = responseDataTypes.filter(
|
filteredResponseDataTypes = responseDataTypes.filter(
|
||||||
(item) => item.key !== API_RESPONSE_TYPE_OPTIONS.TABLE,
|
(item) => item.key !== API_RESPONSE_TYPE_OPTIONS.TABLE,
|
||||||
);
|
);
|
||||||
|
|
@ -403,7 +378,7 @@ function ApiResponseView(props: Props) {
|
||||||
title: dataType.title,
|
title: dataType.title,
|
||||||
panelComponent: responseTabComponent(
|
panelComponent: responseTabComponent(
|
||||||
dataType.key,
|
dataType.key,
|
||||||
response?.body,
|
actionResponse?.body,
|
||||||
responsePaneHeight,
|
responsePaneHeight,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
@ -444,12 +419,12 @@ function ApiResponseView(props: Props) {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// get request timestamp formatted to human readable format.
|
// get request timestamp formatted to human readable format.
|
||||||
const responseState = getUpdateTimestamp(response.request);
|
const responseState = getUpdateTimestamp(actionResponse.request);
|
||||||
// action source for analytics.
|
// action source for analytics.
|
||||||
const actionSource: SourceEntity = {
|
const actionSource: SourceEntity = {
|
||||||
type: ENTITY_TYPE.ACTION,
|
type: ENTITY_TYPE.ACTION,
|
||||||
name: currentActionConfig ? currentActionConfig.name : "API",
|
name: currentActionConfig ? currentActionConfig.name : "API",
|
||||||
id: apiId ? apiId : "",
|
id: currentActionConfig?.id || "",
|
||||||
};
|
};
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{
|
{
|
||||||
|
|
@ -471,16 +446,18 @@ function ApiResponseView(props: Props) {
|
||||||
<ResponseTabErrorContent>
|
<ResponseTabErrorContent>
|
||||||
<ResponseTabErrorDefaultMessage>
|
<ResponseTabErrorDefaultMessage>
|
||||||
Your API failed to execute
|
Your API failed to execute
|
||||||
{response.pluginErrorDetails && ":"}
|
{actionResponse.pluginErrorDetails && ":"}
|
||||||
</ResponseTabErrorDefaultMessage>
|
</ResponseTabErrorDefaultMessage>
|
||||||
{response.pluginErrorDetails && (
|
{actionResponse.pluginErrorDetails && (
|
||||||
<>
|
<>
|
||||||
<div className="t--debugger-log-downstream-message">
|
<div className="t--debugger-log-downstream-message">
|
||||||
{response.pluginErrorDetails.downstreamErrorMessage}
|
{actionResponse.pluginErrorDetails.downstreamErrorMessage}
|
||||||
</div>
|
</div>
|
||||||
{response.pluginErrorDetails.downstreamErrorCode && (
|
{actionResponse.pluginErrorDetails.downstreamErrorCode && (
|
||||||
<LogAdditionalInfo
|
<LogAdditionalInfo
|
||||||
text={response.pluginErrorDetails.downstreamErrorCode}
|
text={
|
||||||
|
actionResponse.pluginErrorDetails.downstreamErrorCode
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|
@ -488,11 +465,11 @@ function ApiResponseView(props: Props) {
|
||||||
<LogHelper
|
<LogHelper
|
||||||
logType={LOG_TYPE.ACTION_EXECUTION_ERROR}
|
logType={LOG_TYPE.ACTION_EXECUTION_ERROR}
|
||||||
name="PluginExecutionError"
|
name="PluginExecutionError"
|
||||||
pluginErrorDetails={response.pluginErrorDetails}
|
pluginErrorDetails={actionResponse.pluginErrorDetails}
|
||||||
source={actionSource}
|
source={actionSource}
|
||||||
/>
|
/>
|
||||||
</ResponseTabErrorContent>
|
</ResponseTabErrorContent>
|
||||||
{response.request && (
|
{actionResponse.request && (
|
||||||
<JsonWrapper
|
<JsonWrapper
|
||||||
className="t--debugger-log-state"
|
className="t--debugger-log-state"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
|
@ -503,7 +480,7 @@ function ApiResponseView(props: Props) {
|
||||||
</ResponseTabErrorContainer>
|
</ResponseTabErrorContainer>
|
||||||
) : (
|
) : (
|
||||||
<ResponseDataContainer>
|
<ResponseDataContainer>
|
||||||
{isEmpty(response.statusCode) ? (
|
{isEmpty(actionResponse.statusCode) ? (
|
||||||
<NoResponse
|
<NoResponse
|
||||||
isButtonDisabled={disabled}
|
isButtonDisabled={disabled}
|
||||||
isQueryRunning={isRunning}
|
isQueryRunning={isRunning}
|
||||||
|
|
@ -511,12 +488,13 @@ function ApiResponseView(props: Props) {
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<ResponseBodyContainer>
|
<ResponseBodyContainer>
|
||||||
{isString(response?.body) && isHtml(response?.body) ? (
|
{isString(actionResponse?.body) &&
|
||||||
|
isHtml(actionResponse?.body) ? (
|
||||||
<ReadOnlyEditor
|
<ReadOnlyEditor
|
||||||
folding
|
folding
|
||||||
height={"100%"}
|
height={"100%"}
|
||||||
input={{
|
input={{
|
||||||
value: response?.body,
|
value: actionResponse?.body,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : responseTabs &&
|
) : responseTabs &&
|
||||||
|
|
@ -536,7 +514,7 @@ function ApiResponseView(props: Props) {
|
||||||
/>
|
/>
|
||||||
{responseTabComponent(
|
{responseTabComponent(
|
||||||
selectedControl || segmentedControlOptions[0]?.value,
|
selectedControl || segmentedControlOptions[0]?.value,
|
||||||
response?.body,
|
actionResponse?.body,
|
||||||
responsePaneHeight,
|
responsePaneHeight,
|
||||||
)}
|
)}
|
||||||
</SegmentedControlContainer>
|
</SegmentedControlContainer>
|
||||||
|
|
@ -569,7 +547,7 @@ function ApiResponseView(props: Props) {
|
||||||
</Callout>
|
</Callout>
|
||||||
)}
|
)}
|
||||||
<ResponseDataContainer>
|
<ResponseDataContainer>
|
||||||
{isEmpty(response.statusCode) ? (
|
{isEmpty(actionResponse.statusCode) ? (
|
||||||
<NoResponse
|
<NoResponse
|
||||||
isButtonDisabled={disabled}
|
isButtonDisabled={disabled}
|
||||||
isQueryRunning={isRunning}
|
isQueryRunning={isRunning}
|
||||||
|
|
@ -593,7 +571,7 @@ function ApiResponseView(props: Props) {
|
||||||
{
|
{
|
||||||
key: DEBUGGER_TAB_KEYS.ERROR_TAB,
|
key: DEBUGGER_TAB_KEYS.ERROR_TAB,
|
||||||
title: createMessage(DEBUGGER_ERRORS),
|
title: createMessage(DEBUGGER_ERRORS),
|
||||||
count: props.errorCount,
|
count: errorCount,
|
||||||
panelComponent: <ErrorLogs />,
|
panelComponent: <ErrorLogs />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -624,48 +602,49 @@ function ApiResponseView(props: Props) {
|
||||||
snapToHeight={ActionExecutionResizerHeight}
|
snapToHeight={ActionExecutionResizerHeight}
|
||||||
/>
|
/>
|
||||||
{isRunning && (
|
{isRunning && (
|
||||||
<ActionExecutionInProgressView actionType="API" theme={props.theme} />
|
<ActionExecutionInProgressView actionType="API" theme={theme} />
|
||||||
)}
|
)}
|
||||||
<TabbedViewWrapper>
|
<TabbedViewWrapper>
|
||||||
{response.statusCode && (
|
{actionResponse.statusCode && (
|
||||||
<ResponseMetaWrapper>
|
<ResponseMetaWrapper>
|
||||||
{response.statusCode && (
|
{actionResponse.statusCode && (
|
||||||
<Flex>
|
<Flex>
|
||||||
<Text type={TextType.P3}>Status: </Text>
|
<Text type={TextType.P3}>Status: </Text>
|
||||||
<StatusCodeText
|
<StatusCodeText
|
||||||
accent="secondary"
|
accent="secondary"
|
||||||
className="t--response-status-code"
|
className="t--response-status-code"
|
||||||
code={response.statusCode.toString()}
|
code={actionResponse.statusCode.toString()}
|
||||||
>
|
>
|
||||||
{response.statusCode}
|
{actionResponse.statusCode}
|
||||||
</StatusCodeText>
|
</StatusCodeText>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
<ResponseMetaInfo>
|
<ResponseMetaInfo>
|
||||||
{response.duration && (
|
{actionResponse.duration && (
|
||||||
<Flex>
|
<Flex>
|
||||||
<Text type={TextType.P3}>Time: </Text>
|
<Text type={TextType.P3}>Time: </Text>
|
||||||
<Text type={TextType.H5}>{response.duration} ms</Text>
|
<Text type={TextType.H5}>{actionResponse.duration} ms</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
{response.size && (
|
{actionResponse.size && (
|
||||||
<Flex>
|
<Flex>
|
||||||
<Text type={TextType.P3}>Size: </Text>
|
<Text type={TextType.P3}>Size: </Text>
|
||||||
<Text type={TextType.H5}>
|
<Text type={TextType.H5}>
|
||||||
{formatBytes(parseInt(response.size))}
|
{formatBytes(parseInt(actionResponse.size))}
|
||||||
</Text>
|
|
||||||
</Flex>
|
|
||||||
)}
|
|
||||||
{!isEmpty(response?.body) && Array.isArray(response?.body) && (
|
|
||||||
<Flex>
|
|
||||||
<Text type={TextType.P3}>Result: </Text>
|
|
||||||
<Text type={TextType.H5}>
|
|
||||||
{`${response?.body.length} Record${
|
|
||||||
response?.body.length > 1 ? "s" : ""
|
|
||||||
}`}
|
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
|
{!isEmpty(actionResponse?.body) &&
|
||||||
|
Array.isArray(actionResponse?.body) && (
|
||||||
|
<Flex>
|
||||||
|
<Text type={TextType.P3}>Result: </Text>
|
||||||
|
<Text type={TextType.H5}>
|
||||||
|
{`${actionResponse?.body.length} Record${
|
||||||
|
actionResponse?.body.length > 1 ? "s" : ""
|
||||||
|
}`}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
</ResponseMetaInfo>
|
</ResponseMetaInfo>
|
||||||
</ResponseMetaWrapper>
|
</ResponseMetaWrapper>
|
||||||
)}
|
)}
|
||||||
|
|
@ -688,25 +667,4 @@ function ApiResponseView(props: Props) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state: AppState): ReduxStateProps => {
|
export default ApiResponseView;
|
||||||
return {
|
|
||||||
responses: getActionResponses(state),
|
|
||||||
isRunning: state.ui.apiPane.isRunning,
|
|
||||||
errorCount: state.ui.debugger.context.errorCount,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch: any): ReduxDispatchProps => ({
|
|
||||||
updateActionResponseDisplayFormat: ({
|
|
||||||
field,
|
|
||||||
id,
|
|
||||||
value,
|
|
||||||
}: UpdateActionPropertyActionPayload) => {
|
|
||||||
dispatch(setActionResponseDisplayFormat({ id, field, value }));
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps,
|
|
||||||
)(withRouter(ApiResponseView));
|
|
||||||
|
|
|
||||||
1
app/client/src/ee/utils/getIsActionCreatedInApp.ts
Normal file
1
app/client/src/ee/utils/getIsActionCreatedInApp.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export * from "ce/utils/getIsActionCreatedInApp";
|
||||||
|
|
@ -8,7 +8,11 @@ import { GRAPHQL_HTTP_METHOD_OPTIONS } from "constants/ApiEditorConstants/GraphQ
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import FormLabel from "components/editorComponents/FormLabel";
|
import FormLabel from "components/editorComponents/FormLabel";
|
||||||
import FormRow from "components/editorComponents/FormRow";
|
import FormRow from "components/editorComponents/FormRow";
|
||||||
import type { PaginationField, SuggestedWidget } from "api/ActionAPI";
|
import type {
|
||||||
|
ActionResponse,
|
||||||
|
PaginationField,
|
||||||
|
SuggestedWidget,
|
||||||
|
} from "api/ActionAPI";
|
||||||
import type { Action, PaginationType } from "entities/Action";
|
import type { Action, PaginationType } from "entities/Action";
|
||||||
import { isGraphqlPlugin } from "entities/Action";
|
import { isGraphqlPlugin } from "entities/Action";
|
||||||
import KeyValueFieldArray from "components/editorComponents/form/fields/KeyValueFieldArray";
|
import KeyValueFieldArray from "components/editorComponents/form/fields/KeyValueFieldArray";
|
||||||
|
|
@ -164,6 +168,7 @@ const MainContainer = styled.div`
|
||||||
/* padding: var(--ads-v2-spaces-7); */
|
/* padding: var(--ads-v2-spaces-7); */
|
||||||
`;
|
`;
|
||||||
export interface CommonFormProps {
|
export interface CommonFormProps {
|
||||||
|
actionResponse?: ActionResponse;
|
||||||
pluginId: string;
|
pluginId: string;
|
||||||
onRunClick: (paginationField?: PaginationField) => void;
|
onRunClick: (paginationField?: PaginationField) => void;
|
||||||
onDeleteClick: () => void;
|
onDeleteClick: () => void;
|
||||||
|
|
@ -508,6 +513,7 @@ function CommonEditorForm(props: CommonFormPropsWithExtraParams) {
|
||||||
actionConfigurationHeaders,
|
actionConfigurationHeaders,
|
||||||
actionConfigurationParams,
|
actionConfigurationParams,
|
||||||
actionName,
|
actionName,
|
||||||
|
actionResponse,
|
||||||
autoGeneratedActionConfigHeaders,
|
autoGeneratedActionConfigHeaders,
|
||||||
closeEditorLink,
|
closeEditorLink,
|
||||||
currentActionDatasourceId,
|
currentActionDatasourceId,
|
||||||
|
|
@ -729,8 +735,11 @@ function CommonEditorForm(props: CommonFormPropsWithExtraParams) {
|
||||||
</TabbedViewContainer>
|
</TabbedViewContainer>
|
||||||
{showDebugger && (
|
{showDebugger && (
|
||||||
<ApiResponseView
|
<ApiResponseView
|
||||||
|
actionResponse={actionResponse}
|
||||||
apiName={actionName}
|
apiName={actionName}
|
||||||
|
currentActionConfig={currentActionConfig}
|
||||||
disabled={!isExecutePermitted}
|
disabled={!isExecutePermitted}
|
||||||
|
isRunning={isRunning}
|
||||||
onRunClick={onRunClick}
|
onRunClick={onRunClick}
|
||||||
responseDataTypes={responseDataTypes}
|
responseDataTypes={responseDataTypes}
|
||||||
responseDisplayFormat={responseDisplayFormat}
|
responseDisplayFormat={responseDisplayFormat}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import classNames from "classnames";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { API_EDITOR_FORM_NAME } from "@appsmith/constants/forms";
|
import { API_EDITOR_FORM_NAME } from "@appsmith/constants/forms";
|
||||||
import type { Action } from "entities/Action";
|
import type { Action } from "entities/Action";
|
||||||
import { EMPTY_RESPONSE } from "components/editorComponents/emptyResponse";
|
|
||||||
import type { AppState } from "@appsmith/reducers";
|
import type { AppState } from "@appsmith/reducers";
|
||||||
import { getApiName } from "selectors/formSelectors";
|
import { getApiName } from "selectors/formSelectors";
|
||||||
import { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig";
|
import { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig";
|
||||||
|
|
@ -22,6 +21,7 @@ import { tailwindLayers } from "constants/Layers";
|
||||||
import VariableEditor from "./VariableEditor";
|
import VariableEditor from "./VariableEditor";
|
||||||
import Pagination from "./Pagination";
|
import Pagination from "./Pagination";
|
||||||
import { ApiEditorContext } from "../ApiEditorContext";
|
import { ApiEditorContext } from "../ApiEditorContext";
|
||||||
|
import { actionResponseDisplayDataFormats } from "pages/Editor/utils";
|
||||||
|
|
||||||
const ResizeableDiv = styled.div`
|
const ResizeableDiv = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -235,37 +235,21 @@ export default connect(
|
||||||
|
|
||||||
let hasResponse = false;
|
let hasResponse = false;
|
||||||
let suggestedWidgets;
|
let suggestedWidgets;
|
||||||
if (apiId) {
|
const actionResponse = getActionData(state, apiId);
|
||||||
const response = getActionData(state, apiId) || EMPTY_RESPONSE;
|
if (actionResponse) {
|
||||||
hasResponse =
|
hasResponse =
|
||||||
!isEmpty(response.statusCode) && response.statusCode[0] === "2";
|
!isEmpty(actionResponse.statusCode) &&
|
||||||
suggestedWidgets = response.suggestedWidgets;
|
actionResponse.statusCode[0] === "2";
|
||||||
|
suggestedWidgets = actionResponse.suggestedWidgets;
|
||||||
}
|
}
|
||||||
|
|
||||||
const actionData = getActionData(state, apiId);
|
const actionData = getActionData(state, apiId);
|
||||||
let responseDisplayFormat: { title: string; value: string };
|
const { responseDataTypes, responseDisplayFormat } =
|
||||||
let responseDataTypes: { key: string; title: string }[];
|
actionResponseDisplayDataFormats(actionData);
|
||||||
if (!!actionData && actionData.responseDisplayFormat) {
|
|
||||||
responseDataTypes = actionData.dataTypes.map((data) => {
|
|
||||||
return {
|
|
||||||
key: data.dataType,
|
|
||||||
title: data.dataType,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
responseDisplayFormat = {
|
|
||||||
title: actionData.responseDisplayFormat,
|
|
||||||
value: actionData.responseDisplayFormat,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
responseDataTypes = [];
|
|
||||||
responseDisplayFormat = {
|
|
||||||
title: "",
|
|
||||||
value: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
actionName,
|
actionName,
|
||||||
|
actionResponse,
|
||||||
apiId,
|
apiId,
|
||||||
httpMethodFromForm,
|
httpMethodFromForm,
|
||||||
actionConfigurationHeaders,
|
actionConfigurationHeaders,
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,18 @@
|
||||||
import React, { useContext } from "react";
|
import React, { useContext } from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
|
import type { RouteComponentProps } from "react-router";
|
||||||
import type { InjectedFormProps } from "redux-form";
|
import type { InjectedFormProps } from "redux-form";
|
||||||
import { reduxForm, formValueSelector } from "redux-form";
|
import { reduxForm, formValueSelector } from "redux-form";
|
||||||
import { POST_BODY_FORMAT_OPTIONS } from "constants/ApiEditorConstants/CommonApiConstants";
|
import { POST_BODY_FORMAT_OPTIONS } from "constants/ApiEditorConstants/CommonApiConstants";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import FormLabel from "components/editorComponents/FormLabel";
|
import FormLabel from "components/editorComponents/FormLabel";
|
||||||
import FormRow from "components/editorComponents/FormRow";
|
import FormRow from "components/editorComponents/FormRow";
|
||||||
import type { PaginationField, BodyFormData, Property } from "api/ActionAPI";
|
import type {
|
||||||
|
PaginationField,
|
||||||
|
BodyFormData,
|
||||||
|
Property,
|
||||||
|
ActionResponse,
|
||||||
|
} from "api/ActionAPI";
|
||||||
import DynamicTextField from "components/editorComponents/form/fields/DynamicTextField";
|
import DynamicTextField from "components/editorComponents/form/fields/DynamicTextField";
|
||||||
import KeyValueFieldArray from "components/editorComponents/form/fields/KeyValueFieldArray";
|
import KeyValueFieldArray from "components/editorComponents/form/fields/KeyValueFieldArray";
|
||||||
import ApiResponseView from "components/editorComponents/ApiResponseView";
|
import ApiResponseView from "components/editorComponents/ApiResponseView";
|
||||||
|
|
@ -18,11 +24,12 @@ import type { PaginationType, Action } from "entities/Action";
|
||||||
import ActionNameEditor from "components/editorComponents/ActionNameEditor";
|
import ActionNameEditor from "components/editorComponents/ActionNameEditor";
|
||||||
import { NameWrapper } from "./CommonEditorForm";
|
import { NameWrapper } from "./CommonEditorForm";
|
||||||
import { BaseButton } from "components/designSystems/appsmith/BaseButton";
|
import { BaseButton } from "components/designSystems/appsmith/BaseButton";
|
||||||
import { getActionData } from "@appsmith/selectors/entitiesSelector";
|
import { getAction, getActionData } from "@appsmith/selectors/entitiesSelector";
|
||||||
import type { AppState } from "@appsmith/reducers";
|
import type { AppState } from "@appsmith/reducers";
|
||||||
import { Icon } from "design-system";
|
import { Icon } from "design-system";
|
||||||
import { showDebuggerFlag } from "selectors/debuggerSelectors";
|
import { showDebuggerFlag } from "selectors/debuggerSelectors";
|
||||||
import { ApiEditorContext } from "./ApiEditorContext";
|
import { ApiEditorContext } from "./ApiEditorContext";
|
||||||
|
import { actionResponseDisplayDataFormats } from "../utils";
|
||||||
|
|
||||||
const Form = styled.form`
|
const Form = styled.form`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -96,13 +103,21 @@ const TabbedViewContainer = styled.div`
|
||||||
padding-top: 12px;
|
padding-top: 12px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
interface APIFormProps {
|
interface APIFormOwnProps {
|
||||||
onRunClick: (paginationField?: PaginationField) => void;
|
apiId: string;
|
||||||
onDeleteClick: () => void;
|
apiName: string;
|
||||||
isRunning: boolean;
|
|
||||||
isDeleting: boolean;
|
|
||||||
paginationType: PaginationType;
|
|
||||||
appName: string;
|
appName: string;
|
||||||
|
isDeleting: boolean;
|
||||||
|
isRunning: boolean;
|
||||||
|
location: RouteComponentProps["location"];
|
||||||
|
onDeleteClick: () => void;
|
||||||
|
onRunClick: (paginationField?: PaginationField) => void;
|
||||||
|
paginationType: PaginationType;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface APIFormProps {
|
||||||
|
actionData?: ActionResponse;
|
||||||
|
currentActionConfig?: Action;
|
||||||
templateId: string;
|
templateId: string;
|
||||||
actionConfiguration?: any;
|
actionConfiguration?: any;
|
||||||
actionConfigurationHeaders?: Property[];
|
actionConfigurationHeaders?: Property[];
|
||||||
|
|
@ -111,18 +126,15 @@ interface APIFormProps {
|
||||||
providerImage: string;
|
providerImage: string;
|
||||||
providerURL: string;
|
providerURL: string;
|
||||||
providerCredentialSteps: string;
|
providerCredentialSteps: string;
|
||||||
location: {
|
|
||||||
pathname: string;
|
|
||||||
};
|
|
||||||
apiName: string;
|
|
||||||
apiId: string;
|
|
||||||
dispatch: any;
|
dispatch: any;
|
||||||
responseDataTypes: { key: string; title: string }[];
|
responseDataTypes: { key: string; title: string }[];
|
||||||
responseDisplayFormat: { title: string; value: string };
|
responseDisplayFormat: { title: string; value: string };
|
||||||
showDebugger: boolean;
|
showDebugger: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = APIFormProps & InjectedFormProps<Action, APIFormProps>;
|
type Props = APIFormProps &
|
||||||
|
InjectedFormProps<Action, APIFormProps & APIFormOwnProps> &
|
||||||
|
APIFormOwnProps;
|
||||||
|
|
||||||
function RapidApiEditorForm(props: Props) {
|
function RapidApiEditorForm(props: Props) {
|
||||||
const {
|
const {
|
||||||
|
|
@ -276,7 +288,10 @@ function RapidApiEditorForm(props: Props) {
|
||||||
</TabbedViewContainer>
|
</TabbedViewContainer>
|
||||||
{showDebugger && (
|
{showDebugger && (
|
||||||
<ApiResponseView
|
<ApiResponseView
|
||||||
|
actionResponse={props.actionData}
|
||||||
apiName={props.apiName}
|
apiName={props.apiName}
|
||||||
|
currentActionConfig={props.currentActionConfig}
|
||||||
|
isRunning={props.isRunning}
|
||||||
onRunClick={onRunClick}
|
onRunClick={onRunClick}
|
||||||
responseDataTypes={responseDataTypes}
|
responseDataTypes={responseDataTypes}
|
||||||
responseDisplayFormat={responseDisplayFormat}
|
responseDisplayFormat={responseDisplayFormat}
|
||||||
|
|
@ -289,7 +304,7 @@ function RapidApiEditorForm(props: Props) {
|
||||||
|
|
||||||
const selector = formValueSelector(API_EDITOR_FORM_NAME);
|
const selector = formValueSelector(API_EDITOR_FORM_NAME);
|
||||||
|
|
||||||
export default connect((state: AppState) => {
|
export default connect((state: AppState, ownProps: APIFormOwnProps) => {
|
||||||
const displayFormat = selector(state, "displayFormat");
|
const displayFormat = selector(state, "displayFormat");
|
||||||
const providerImage = selector(state, "provider.imageUrl");
|
const providerImage = selector(state, "provider.imageUrl");
|
||||||
const providerURL = selector(state, "provider.url");
|
const providerURL = selector(state, "provider.url");
|
||||||
|
|
@ -317,29 +332,17 @@ export default connect((state: AppState) => {
|
||||||
`${actionConfigurationBodyFormData}`,
|
`${actionConfigurationBodyFormData}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const currentActionConfig = getAction(state, ownProps.apiId);
|
||||||
const actionData = getActionData(state, actionConfiguration.id);
|
const actionData = getActionData(state, actionConfiguration.id);
|
||||||
let responseDisplayFormat: { title: string; value: string };
|
const { responseDataTypes, responseDisplayFormat } =
|
||||||
let responseDataTypes: { key: string; title: string }[];
|
actionResponseDisplayDataFormats(actionData, {
|
||||||
if (!!actionData && actionData.responseDisplayFormat) {
|
|
||||||
responseDataTypes = actionData.dataTypes.map((data) => {
|
|
||||||
return {
|
|
||||||
key: data.dataType,
|
|
||||||
title: data.dataType,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
responseDisplayFormat = {
|
|
||||||
title: actionData.responseDisplayFormat,
|
|
||||||
value: actionData.responseDisplayFormat,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
responseDataTypes = [];
|
|
||||||
responseDisplayFormat = {
|
|
||||||
title: "JSON",
|
title: "JSON",
|
||||||
value: "JSON",
|
value: "JSON",
|
||||||
};
|
});
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
actionData,
|
||||||
|
currentActionConfig,
|
||||||
displayFormat,
|
displayFormat,
|
||||||
actionConfiguration,
|
actionConfiguration,
|
||||||
actionConfigurationHeaders,
|
actionConfigurationHeaders,
|
||||||
|
|
@ -353,7 +356,7 @@ export default connect((state: AppState) => {
|
||||||
providerCredentialSteps,
|
providerCredentialSteps,
|
||||||
};
|
};
|
||||||
})(
|
})(
|
||||||
reduxForm<Action, APIFormProps>({
|
reduxForm<Action, APIFormProps & APIFormOwnProps>({
|
||||||
form: API_EDITOR_FORM_NAME,
|
form: API_EDITOR_FORM_NAME,
|
||||||
destroyOnUnmount: false,
|
destroyOnUnmount: false,
|
||||||
})(RapidApiEditorForm),
|
})(RapidApiEditorForm),
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import styled from "styled-components";
|
||||||
import { API_EDITOR_FORM_NAME } from "@appsmith/constants/forms";
|
import { API_EDITOR_FORM_NAME } from "@appsmith/constants/forms";
|
||||||
import type { Action } from "entities/Action";
|
import type { Action } from "entities/Action";
|
||||||
import PostBodyData from "./PostBodyData";
|
import PostBodyData from "./PostBodyData";
|
||||||
import { EMPTY_RESPONSE } from "components/editorComponents/emptyResponse";
|
|
||||||
import type { AppState } from "@appsmith/reducers";
|
import type { AppState } from "@appsmith/reducers";
|
||||||
import { getApiName } from "selectors/formSelectors";
|
import { getApiName } from "selectors/formSelectors";
|
||||||
import { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig";
|
import { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig";
|
||||||
|
|
@ -25,6 +24,7 @@ import CommonEditorForm from "./CommonEditorForm";
|
||||||
import Pagination from "./Pagination";
|
import Pagination from "./Pagination";
|
||||||
import { getCurrentEnvironmentId } from "@appsmith/selectors/environmentSelectors";
|
import { getCurrentEnvironmentId } from "@appsmith/selectors/environmentSelectors";
|
||||||
import { ApiEditorContext } from "./ApiEditorContext";
|
import { ApiEditorContext } from "./ApiEditorContext";
|
||||||
|
import { actionResponseDisplayDataFormats } from "../utils";
|
||||||
|
|
||||||
const NoBodyMessage = styled.div`
|
const NoBodyMessage = styled.div`
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
|
|
@ -162,39 +162,23 @@ export default connect((state: AppState, props: { pluginId: string }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const responses = getActionResponses(state);
|
const responses = getActionResponses(state);
|
||||||
|
const actionResponse = responses[apiId];
|
||||||
let hasResponse = false;
|
let hasResponse = false;
|
||||||
let suggestedWidgets;
|
let suggestedWidgets;
|
||||||
if (apiId && apiId in responses) {
|
if (actionResponse) {
|
||||||
const response = responses[apiId] || EMPTY_RESPONSE;
|
|
||||||
hasResponse =
|
hasResponse =
|
||||||
!isEmpty(response.statusCode) && response.statusCode[0] === "2";
|
!isEmpty(actionResponse.statusCode) &&
|
||||||
suggestedWidgets = response.suggestedWidgets;
|
actionResponse.statusCode[0] === "2";
|
||||||
|
suggestedWidgets = actionResponse.suggestedWidgets;
|
||||||
}
|
}
|
||||||
|
|
||||||
const actionData = getActionData(state, apiId);
|
const actionData = getActionData(state, apiId);
|
||||||
let responseDisplayFormat: { title: string; value: string };
|
const { responseDataTypes, responseDisplayFormat } =
|
||||||
let responseDataTypes: { key: string; title: string }[];
|
actionResponseDisplayDataFormats(actionData);
|
||||||
if (!!actionData && actionData.responseDisplayFormat) {
|
|
||||||
responseDataTypes = actionData.dataTypes.map((data) => {
|
|
||||||
return {
|
|
||||||
key: data.dataType,
|
|
||||||
title: data.dataType,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
responseDisplayFormat = {
|
|
||||||
title: actionData.responseDisplayFormat,
|
|
||||||
value: actionData.responseDisplayFormat,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
responseDataTypes = [];
|
|
||||||
responseDisplayFormat = {
|
|
||||||
title: "",
|
|
||||||
value: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
actionName,
|
actionName,
|
||||||
|
actionResponse,
|
||||||
apiId,
|
apiId,
|
||||||
httpMethodFromForm,
|
httpMethodFromForm,
|
||||||
actionConfigurationHeaders,
|
actionConfigurationHeaders,
|
||||||
|
|
|
||||||
|
|
@ -228,10 +228,10 @@ class QueryEditor extends React.Component<Props> {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<QueryEditorForm
|
<QueryEditorForm
|
||||||
|
actionResponse={responses[actionId]}
|
||||||
dataSources={dataSources}
|
dataSources={dataSources}
|
||||||
datasourceId={this.props.datasourceId}
|
datasourceId={this.props.datasourceId}
|
||||||
editorConfig={editorConfig}
|
editorConfig={editorConfig}
|
||||||
executedQueryData={responses[actionId]}
|
|
||||||
formData={this.props.formData}
|
formData={this.props.formData}
|
||||||
isDeleting={isDeleting}
|
isDeleting={isDeleting}
|
||||||
isRunning={isRunning}
|
isRunning={isRunning}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
import { useContext, useEffect } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import type { RefObject } from "react";
|
import React, { useCallback } from "react";
|
||||||
import React, { useCallback, useRef, useState } from "react";
|
|
||||||
import type { InjectedFormProps } from "redux-form";
|
import type { InjectedFormProps } from "redux-form";
|
||||||
import { Tag } from "@blueprintjs/core";
|
import { Tag } from "@blueprintjs/core";
|
||||||
import { isString, noop } from "lodash";
|
import { noop } from "lodash";
|
||||||
import type { Datasource } from "entities/Datasource";
|
import type { Datasource } from "entities/Datasource";
|
||||||
import { DatasourceStructureContext } from "entities/Datasource";
|
import { DatasourceStructureContext } from "entities/Datasource";
|
||||||
import {
|
import {
|
||||||
|
|
@ -23,12 +22,9 @@ import DropdownField from "components/editorComponents/form/fields/DropdownField
|
||||||
import type { ControlProps } from "components/formControls/BaseControl";
|
import type { ControlProps } from "components/formControls/BaseControl";
|
||||||
import ActionSettings from "pages/Editor/ActionSettings";
|
import ActionSettings from "pages/Editor/ActionSettings";
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import { Text, TextType } from "design-system-old";
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Callout,
|
|
||||||
Icon,
|
Icon,
|
||||||
SegmentedControl,
|
|
||||||
Spinner,
|
Spinner,
|
||||||
Tab,
|
Tab,
|
||||||
TabPanel,
|
TabPanel,
|
||||||
|
|
@ -38,13 +34,8 @@ import {
|
||||||
} from "design-system";
|
} from "design-system";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import FormRow from "components/editorComponents/FormRow";
|
import FormRow from "components/editorComponents/FormRow";
|
||||||
import DebuggerLogs from "components/editorComponents/Debugger/DebuggerLogs";
|
import { ResizerCSS } from "components/editorComponents/Debugger/Resizer";
|
||||||
import ErrorLogs from "components/editorComponents/Debugger/Errors";
|
|
||||||
import Resizable, {
|
|
||||||
ResizerCSS,
|
|
||||||
} from "components/editorComponents/Debugger/Resizer";
|
|
||||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||||
import EntityDeps from "components/editorComponents/Debugger/EntityDependecies";
|
|
||||||
import {
|
import {
|
||||||
checkIfSectionCanRender,
|
checkIfSectionCanRender,
|
||||||
checkIfSectionIsEnabled,
|
checkIfSectionIsEnabled,
|
||||||
|
|
@ -57,11 +48,8 @@ import {
|
||||||
ACTION_EDITOR_REFRESH,
|
ACTION_EDITOR_REFRESH,
|
||||||
CREATE_NEW_DATASOURCE,
|
CREATE_NEW_DATASOURCE,
|
||||||
createMessage,
|
createMessage,
|
||||||
DEBUGGER_ERRORS,
|
|
||||||
DEBUGGER_LOGS,
|
|
||||||
DOCUMENTATION,
|
DOCUMENTATION,
|
||||||
DOCUMENTATION_TOOLTIP,
|
DOCUMENTATION_TOOLTIP,
|
||||||
INSPECT_ENTITY,
|
|
||||||
INVALID_FORM_CONFIGURATION,
|
INVALID_FORM_CONFIGURATION,
|
||||||
NO_DATASOURCE_FOR_QUERY,
|
NO_DATASOURCE_FOR_QUERY,
|
||||||
UNEXPECTED_ERROR,
|
UNEXPECTED_ERROR,
|
||||||
|
|
@ -72,58 +60,27 @@ import { thinScrollbar } from "constants/DefaultTheme";
|
||||||
import ActionRightPane, {
|
import ActionRightPane, {
|
||||||
useEntityDependencies,
|
useEntityDependencies,
|
||||||
} from "components/editorComponents/ActionRightPane";
|
} from "components/editorComponents/ActionRightPane";
|
||||||
import type {
|
import type { ActionResponse } from "api/ActionAPI";
|
||||||
ActionApiResponseReq,
|
|
||||||
PluginErrorDetails,
|
|
||||||
SuggestedWidget,
|
|
||||||
} from "api/ActionAPI";
|
|
||||||
import type { Plugin } from "api/PluginApi";
|
import type { Plugin } from "api/PluginApi";
|
||||||
import { UIComponentTypes } from "api/PluginApi";
|
import { UIComponentTypes } from "api/PluginApi";
|
||||||
import * as Sentry from "@sentry/react";
|
import * as Sentry from "@sentry/react";
|
||||||
import EntityBottomTabs from "components/editorComponents/EntityBottomTabs";
|
|
||||||
import { DEBUGGER_TAB_KEYS } from "components/editorComponents/Debugger/helpers";
|
import { DEBUGGER_TAB_KEYS } from "components/editorComponents/Debugger/helpers";
|
||||||
import { getErrorAsString } from "sagas/ActionExecution/errorUtils";
|
|
||||||
import type { UpdateActionPropertyActionPayload } from "actions/pluginActionActions";
|
|
||||||
import Guide from "pages/Editor/GuidedTour/Guide";
|
import Guide from "pages/Editor/GuidedTour/Guide";
|
||||||
import { inGuidedTour } from "selectors/onboardingSelectors";
|
import { inGuidedTour } from "selectors/onboardingSelectors";
|
||||||
import { EDITOR_TABS, SQL_DATASOURCES } from "constants/QueryEditorConstants";
|
import { EDITOR_TABS, SQL_DATASOURCES } from "constants/QueryEditorConstants";
|
||||||
import type { FormEvalOutput } from "reducers/evaluationReducers/formEvaluationReducer";
|
import type { FormEvalOutput } from "reducers/evaluationReducers/formEvaluationReducer";
|
||||||
import { isValidFormConfig } from "reducers/evaluationReducers/formEvaluationReducer";
|
import { isValidFormConfig } from "reducers/evaluationReducers/formEvaluationReducer";
|
||||||
import {
|
|
||||||
apiReactJsonProps,
|
|
||||||
NoResponse,
|
|
||||||
responseTabComponent,
|
|
||||||
ResponseTabErrorContainer,
|
|
||||||
ResponseTabErrorContent,
|
|
||||||
ResponseTabErrorDefaultMessage,
|
|
||||||
} from "components/editorComponents/ApiResponseView";
|
|
||||||
import { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig";
|
|
||||||
import { getQueryPaneConfigSelectedTabIndex } from "selectors/queryPaneSelectors";
|
import { getQueryPaneConfigSelectedTabIndex } from "selectors/queryPaneSelectors";
|
||||||
import { setQueryPaneConfigSelectedTabIndex } from "actions/queryPaneActions";
|
import { setQueryPaneConfigSelectedTabIndex } from "actions/queryPaneActions";
|
||||||
import { ActionExecutionResizerHeight } from "pages/Editor/APIEditor/constants";
|
import { ActionExecutionResizerHeight } from "pages/Editor/APIEditor/constants";
|
||||||
import { getCurrentAppWorkspace } from "@appsmith/selectors/workspaceSelectors";
|
import { getCurrentAppWorkspace } from "@appsmith/selectors/workspaceSelectors";
|
||||||
import {
|
|
||||||
setDebuggerSelectedTab,
|
|
||||||
setResponsePaneHeight,
|
|
||||||
showDebugger,
|
|
||||||
} from "actions/debuggerActions";
|
|
||||||
import {
|
import {
|
||||||
getDebuggerSelectedTab,
|
getDebuggerSelectedTab,
|
||||||
getErrorCount,
|
|
||||||
getResponsePaneHeight,
|
|
||||||
showDebuggerFlag,
|
showDebuggerFlag,
|
||||||
} from "selectors/debuggerSelectors";
|
} from "selectors/debuggerSelectors";
|
||||||
import LogAdditionalInfo from "components/editorComponents/Debugger/ErrorLogs/components/LogAdditionalInfo";
|
|
||||||
import LogHelper from "components/editorComponents/Debugger/ErrorLogs/components/LogHelper";
|
|
||||||
import { JsonWrapper } from "components/editorComponents/Debugger/ErrorLogs/components/LogCollapseData";
|
|
||||||
import ReactJson from "react-json-view";
|
|
||||||
import { getUpdateTimestamp } from "components/editorComponents/Debugger/ErrorLogs/ErrorLogItem";
|
|
||||||
import LOG_TYPE from "entities/AppsmithConsole/logtype";
|
|
||||||
import type { SourceEntity } from "entities/AppsmithConsole";
|
import type { SourceEntity } from "entities/AppsmithConsole";
|
||||||
import { ENTITY_TYPE as SOURCE_ENTITY_TYPE } from "entities/AppsmithConsole";
|
import { ENTITY_TYPE as SOURCE_ENTITY_TYPE } from "entities/AppsmithConsole";
|
||||||
import { DocsLink, openDoc } from "../../../constants/DocumentationLinks";
|
import { DocsLink, openDoc } from "../../../constants/DocumentationLinks";
|
||||||
import ActionExecutionInProgressView from "components/editorComponents/ActionExecutionInProgressView";
|
|
||||||
import { CloseDebugger } from "components/editorComponents/Debugger/DebuggerTabs";
|
|
||||||
import {
|
import {
|
||||||
getHasCreateDatasourcePermission,
|
getHasCreateDatasourcePermission,
|
||||||
getHasExecuteActionPermission,
|
getHasExecuteActionPermission,
|
||||||
|
|
@ -132,6 +89,8 @@ import {
|
||||||
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
|
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
|
||||||
import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag";
|
import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag";
|
||||||
import { QueryEditorContext } from "./QueryEditorContext";
|
import { QueryEditorContext } from "./QueryEditorContext";
|
||||||
|
import QueryResponseTabView from "./QueryResponseView";
|
||||||
|
import { setDebuggerSelectedTab, showDebugger } from "actions/debuggerActions";
|
||||||
|
|
||||||
const QueryFormContainer = styled.form`
|
const QueryFormContainer = styled.form`
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
@ -178,13 +137,6 @@ const SettingsWrapper = styled.div`
|
||||||
height: 100%;
|
height: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ResultsCount = styled.div`
|
|
||||||
position: absolute;
|
|
||||||
right: ${(props) => props.theme.spaces[17] + 1}px;
|
|
||||||
top: 9px;
|
|
||||||
color: var(--ads-v2-color-fg);
|
|
||||||
`;
|
|
||||||
|
|
||||||
const FieldWrapper = styled.div`
|
const FieldWrapper = styled.div`
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
`;
|
`;
|
||||||
|
|
@ -196,18 +148,6 @@ const SecondaryWrapper = styled.div`
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const HelpSection = styled.div``;
|
|
||||||
|
|
||||||
const ResponseContentWrapper = styled.div<{ isError: boolean }>`
|
|
||||||
overflow-y: clip;
|
|
||||||
display: grid;
|
|
||||||
height: ${(props) => (props.isError ? "" : "100%")};
|
|
||||||
|
|
||||||
${HelpSection} {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const StyledFormRow = styled(FormRow)`
|
export const StyledFormRow = styled(FormRow)`
|
||||||
padding: 0px var(--ads-v2-spaces-7) var(--ads-v2-spaces-5)
|
padding: 0px var(--ads-v2-spaces-7) var(--ads-v2-spaces-5)
|
||||||
var(--ads-v2-spaces-7);
|
var(--ads-v2-spaces-7);
|
||||||
|
|
@ -338,15 +278,7 @@ interface QueryFormProps {
|
||||||
isRunning: boolean;
|
isRunning: boolean;
|
||||||
dataSources: Datasource[];
|
dataSources: Datasource[];
|
||||||
uiComponent: UIComponentTypes;
|
uiComponent: UIComponentTypes;
|
||||||
executedQueryData?: {
|
actionResponse?: ActionResponse;
|
||||||
body: any;
|
|
||||||
isExecutionSuccess?: boolean;
|
|
||||||
messages?: Array<string>;
|
|
||||||
suggestedWidgets?: SuggestedWidget[];
|
|
||||||
readableError?: string;
|
|
||||||
pluginErrorDetails?: PluginErrorDetails;
|
|
||||||
request?: ActionApiResponseReq;
|
|
||||||
};
|
|
||||||
runErrorMessage: string | undefined;
|
runErrorMessage: string | undefined;
|
||||||
location: {
|
location: {
|
||||||
state: any;
|
state: any;
|
||||||
|
|
@ -357,11 +289,6 @@ interface QueryFormProps {
|
||||||
formData: SaaSAction | QueryAction;
|
formData: SaaSAction | QueryAction;
|
||||||
responseDisplayFormat: { title: string; value: string };
|
responseDisplayFormat: { title: string; value: string };
|
||||||
responseDataTypes: { key: string; title: string }[];
|
responseDataTypes: { key: string; title: string }[];
|
||||||
updateActionResponseDisplayFormat: ({
|
|
||||||
field,
|
|
||||||
id,
|
|
||||||
value,
|
|
||||||
}: UpdateActionPropertyActionPayload) => void;
|
|
||||||
datasourceId: string;
|
datasourceId: string;
|
||||||
showCloseEditor: boolean;
|
showCloseEditor: boolean;
|
||||||
}
|
}
|
||||||
|
|
@ -382,10 +309,10 @@ type Props = EditorJSONtoFormProps &
|
||||||
export function EditorJSONtoForm(props: Props) {
|
export function EditorJSONtoForm(props: Props) {
|
||||||
const {
|
const {
|
||||||
actionName,
|
actionName,
|
||||||
|
actionResponse,
|
||||||
dataSources,
|
dataSources,
|
||||||
documentationLink,
|
documentationLink,
|
||||||
editorConfig,
|
editorConfig,
|
||||||
executedQueryData,
|
|
||||||
formName,
|
formName,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
isRunning,
|
isRunning,
|
||||||
|
|
@ -397,7 +324,6 @@ export function EditorJSONtoForm(props: Props) {
|
||||||
runErrorMessage,
|
runErrorMessage,
|
||||||
settingConfig,
|
settingConfig,
|
||||||
uiComponent,
|
uiComponent,
|
||||||
updateActionResponseDisplayFormat,
|
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|
@ -408,16 +334,8 @@ export function EditorJSONtoForm(props: Props) {
|
||||||
saveActionName,
|
saveActionName,
|
||||||
} = useContext(QueryEditorContext);
|
} = useContext(QueryEditorContext);
|
||||||
|
|
||||||
let error = runErrorMessage;
|
|
||||||
let output: Record<string, any>[] | null = null;
|
|
||||||
let hintMessages: Array<string> = [];
|
|
||||||
const panelRef: RefObject<HTMLDivElement> = useRef(null);
|
|
||||||
|
|
||||||
const params = useParams<{ apiId?: string; queryId?: string }>();
|
const params = useParams<{ apiId?: string; queryId?: string }>();
|
||||||
|
|
||||||
// fetch the error count from the store.
|
// fetch the error count from the store.
|
||||||
const errorCount = useSelector(getErrorCount);
|
|
||||||
|
|
||||||
const actions: Action[] = useSelector((state: AppState) =>
|
const actions: Action[] = useSelector((state: AppState) =>
|
||||||
state.entities.actions.map((action) => action.config),
|
state.entities.actions.map((action) => action.config),
|
||||||
);
|
);
|
||||||
|
|
@ -426,6 +344,8 @@ export function EditorJSONtoForm(props: Props) {
|
||||||
(action) => action.id === params.apiId || action.id === params.queryId,
|
(action) => action.id === params.apiId || action.id === params.queryId,
|
||||||
);
|
);
|
||||||
const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled);
|
const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled);
|
||||||
|
const [showResponseOnFirstLoad, setShowResponseOnFirstLoad] =
|
||||||
|
useState<boolean>(false);
|
||||||
|
|
||||||
const isChangePermitted = getHasManageActionPermission(
|
const isChangePermitted = getHasManageActionPermission(
|
||||||
isFeatureEnabled,
|
isFeatureEnabled,
|
||||||
|
|
@ -440,9 +360,6 @@ export function EditorJSONtoForm(props: Props) {
|
||||||
(state: AppState) => getCurrentAppWorkspace(state).userPermissions ?? [],
|
(state: AppState) => getCurrentAppWorkspace(state).userPermissions ?? [],
|
||||||
);
|
);
|
||||||
|
|
||||||
const [showResponseOnFirstLoad, setShowResponseOnFirstLoad] =
|
|
||||||
useState<boolean>(false);
|
|
||||||
|
|
||||||
const canCreateDatasource = getHasCreateDatasourcePermission(
|
const canCreateDatasource = getHasCreateDatasourcePermission(
|
||||||
isFeatureEnabled,
|
isFeatureEnabled,
|
||||||
userWorkspacePermissions,
|
userWorkspacePermissions,
|
||||||
|
|
@ -470,54 +387,24 @@ export function EditorJSONtoForm(props: Props) {
|
||||||
(!actionBody && SQL_DATASOURCES.includes(currentActionPluginName)) ||
|
(!actionBody && SQL_DATASOURCES.includes(currentActionPluginName)) ||
|
||||||
!isExecutePermitted;
|
!isExecutePermitted;
|
||||||
|
|
||||||
// Query is executed even once during the session, show the response data.
|
const dispatch = useDispatch();
|
||||||
if (executedQueryData) {
|
|
||||||
if (!executedQueryData.isExecutionSuccess) {
|
|
||||||
// Pass the error to be shown in the error tab
|
|
||||||
error = executedQueryData.readableError
|
|
||||||
? getErrorAsString(executedQueryData.readableError)
|
|
||||||
: getErrorAsString(executedQueryData.body);
|
|
||||||
} else if (isString(executedQueryData.body)) {
|
|
||||||
//reset error.
|
|
||||||
error = "";
|
|
||||||
try {
|
|
||||||
// Try to parse response as JSON array to be displayed in the Response tab
|
|
||||||
output = JSON.parse(executedQueryData.body);
|
|
||||||
} catch (e) {
|
|
||||||
// In case the string is not a JSON, wrap it in a response object
|
|
||||||
output = [
|
|
||||||
{
|
|
||||||
response: executedQueryData.body,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
//reset error.
|
|
||||||
error = "";
|
|
||||||
output = executedQueryData.body;
|
|
||||||
}
|
|
||||||
if (executedQueryData.messages && executedQueryData.messages.length) {
|
|
||||||
//reset error.
|
|
||||||
error = "";
|
|
||||||
hintMessages = executedQueryData.messages;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// These useEffects are used to open the response tab by default for page load queries
|
// These useEffects are used to open the response tab by default for page load queries
|
||||||
// as for page load queries, query response is available and can be shown in response tab
|
// as for page load queries, query response is available and can be shown in response tab
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// output and responseDisplayFormat is present only when query has response available
|
// actionResponse and responseDisplayFormat is present only when query has response available
|
||||||
if (
|
if (
|
||||||
responseDisplayFormat &&
|
responseDisplayFormat &&
|
||||||
!!responseDisplayFormat?.title &&
|
!!responseDisplayFormat?.title &&
|
||||||
output &&
|
actionResponse &&
|
||||||
|
actionResponse.isExecutionSuccess &&
|
||||||
!showResponseOnFirstLoad
|
!showResponseOnFirstLoad
|
||||||
) {
|
) {
|
||||||
dispatch(showDebugger(true));
|
dispatch(showDebugger(true));
|
||||||
dispatch(setDebuggerSelectedTab(DEBUGGER_TAB_KEYS.RESPONSE_TAB));
|
dispatch(setDebuggerSelectedTab(DEBUGGER_TAB_KEYS.RESPONSE_TAB));
|
||||||
setShowResponseOnFirstLoad(true);
|
setShowResponseOnFirstLoad(true);
|
||||||
}
|
}
|
||||||
}, [responseDisplayFormat, output, showResponseOnFirstLoad]);
|
}, [responseDisplayFormat, actionResponse, showResponseOnFirstLoad]);
|
||||||
|
|
||||||
// When multiple page load queries exist, we want to response tab by default for all of them
|
// When multiple page load queries exist, we want to response tab by default for all of them
|
||||||
// Hence this useEffect will reset showResponseOnFirstLoad flag used to track whether to show response tab or not
|
// Hence this useEffect will reset showResponseOnFirstLoad flag used to track whether to show response tab or not
|
||||||
|
|
@ -527,8 +414,6 @@ export function EditorJSONtoForm(props: Props) {
|
||||||
}
|
}
|
||||||
}, [currentActionConfig?.id]);
|
}, [currentActionConfig?.id]);
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
|
|
||||||
const handleDocumentationClick = (e: React.MouseEvent) => {
|
const handleDocumentationClick = (e: React.MouseEvent) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
openDoc(DocsLink.QUERY, plugin?.documentationLink, plugin?.name);
|
openDoc(DocsLink.QUERY, plugin?.documentationLink, plugin?.name);
|
||||||
|
|
@ -684,63 +569,8 @@ export function EditorJSONtoForm(props: Props) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// get the response pane height from the store.
|
|
||||||
const responsePaneHeight = useSelector(getResponsePaneHeight);
|
|
||||||
// set the response pane height on resize.
|
|
||||||
const setQueryResponsePaneHeight = useCallback((height: number) => {
|
|
||||||
dispatch(setResponsePaneHeight(height));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const responseBodyTabs =
|
|
||||||
responseDataTypes &&
|
|
||||||
responseDataTypes.map((dataType, index) => {
|
|
||||||
return {
|
|
||||||
index: index,
|
|
||||||
key: dataType.key,
|
|
||||||
title: dataType.title,
|
|
||||||
panelComponent: responseTabComponent(
|
|
||||||
dataType.key,
|
|
||||||
output,
|
|
||||||
responsePaneHeight,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const segmentedControlOptions =
|
|
||||||
responseBodyTabs &&
|
|
||||||
responseBodyTabs.map((item) => {
|
|
||||||
return { value: item.key, label: item.title };
|
|
||||||
});
|
|
||||||
|
|
||||||
const [selectedControl, setSelectedControl] = useState(
|
|
||||||
segmentedControlOptions[0]?.value,
|
|
||||||
);
|
|
||||||
|
|
||||||
const onResponseTabSelect = (tabKey: string) => {
|
|
||||||
if (tabKey === DEBUGGER_TAB_KEYS.ERROR_TAB) {
|
|
||||||
AnalyticsUtil.logEvent("OPEN_DEBUGGER", {
|
|
||||||
source: "QUERY_PANE",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
updateActionResponseDisplayFormat({
|
|
||||||
id: currentActionConfig?.id || "",
|
|
||||||
field: "responseDisplayFormat",
|
|
||||||
value: tabKey,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// onResponseTabSelect(selectedControl);
|
// onResponseTabSelect(selectedControl);
|
||||||
|
|
||||||
const selectedTabIndex =
|
|
||||||
responseDataTypes &&
|
|
||||||
responseDataTypes.findIndex(
|
|
||||||
(dataType) => dataType.title === responseDisplayFormat?.title,
|
|
||||||
);
|
|
||||||
|
|
||||||
//Update request timestamp to human readable format.
|
|
||||||
const responseState =
|
|
||||||
executedQueryData && getUpdateTimestamp(executedQueryData.request);
|
|
||||||
|
|
||||||
// action source for analytics.
|
// action source for analytics.
|
||||||
const actionSource: SourceEntity = {
|
const actionSource: SourceEntity = {
|
||||||
type: SOURCE_ENTITY_TYPE.ACTION,
|
type: SOURCE_ENTITY_TYPE.ACTION,
|
||||||
|
|
@ -748,128 +578,6 @@ export function EditorJSONtoForm(props: Props) {
|
||||||
id: currentActionConfig ? currentActionConfig.id : "",
|
id: currentActionConfig ? currentActionConfig.id : "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const responseTabs = [
|
|
||||||
{
|
|
||||||
key: "response",
|
|
||||||
title: "Response",
|
|
||||||
panelComponent: (
|
|
||||||
<ResponseContentWrapper isError={!!error}>
|
|
||||||
{error && (
|
|
||||||
<ResponseTabErrorContainer>
|
|
||||||
<ResponseTabErrorContent>
|
|
||||||
<ResponseTabErrorDefaultMessage>
|
|
||||||
Your query failed to execute
|
|
||||||
{executedQueryData &&
|
|
||||||
(executedQueryData.pluginErrorDetails ||
|
|
||||||
executedQueryData.body) &&
|
|
||||||
":"}
|
|
||||||
</ResponseTabErrorDefaultMessage>
|
|
||||||
{executedQueryData &&
|
|
||||||
(executedQueryData.pluginErrorDetails ? (
|
|
||||||
<>
|
|
||||||
<div data-testid="t--query-error">
|
|
||||||
{
|
|
||||||
executedQueryData.pluginErrorDetails
|
|
||||||
.downstreamErrorMessage
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
{executedQueryData.pluginErrorDetails
|
|
||||||
.downstreamErrorCode && (
|
|
||||||
<LogAdditionalInfo
|
|
||||||
text={
|
|
||||||
executedQueryData.pluginErrorDetails
|
|
||||||
.downstreamErrorCode
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
executedQueryData.body && (
|
|
||||||
<div data-testid="t--query-error">
|
|
||||||
{executedQueryData.body}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
))}
|
|
||||||
<LogHelper
|
|
||||||
logType={LOG_TYPE.ACTION_EXECUTION_ERROR}
|
|
||||||
name="PluginExecutionError"
|
|
||||||
pluginErrorDetails={
|
|
||||||
executedQueryData && executedQueryData.pluginErrorDetails
|
|
||||||
}
|
|
||||||
source={actionSource}
|
|
||||||
/>
|
|
||||||
</ResponseTabErrorContent>
|
|
||||||
{executedQueryData && executedQueryData.request && (
|
|
||||||
<JsonWrapper
|
|
||||||
className="t--debugger-log-state"
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
>
|
|
||||||
<ReactJson src={responseState} {...apiReactJsonProps} />
|
|
||||||
</JsonWrapper>
|
|
||||||
)}
|
|
||||||
</ResponseTabErrorContainer>
|
|
||||||
)}
|
|
||||||
{hintMessages && hintMessages.length > 0 && (
|
|
||||||
<HelpSection>
|
|
||||||
{hintMessages.map((msg, index) => (
|
|
||||||
<Callout key={index} kind="warning">
|
|
||||||
{msg}
|
|
||||||
</Callout>
|
|
||||||
))}
|
|
||||||
</HelpSection>
|
|
||||||
)}
|
|
||||||
{currentActionConfig &&
|
|
||||||
output &&
|
|
||||||
responseBodyTabs &&
|
|
||||||
responseBodyTabs.length > 0 &&
|
|
||||||
selectedTabIndex !== -1 && (
|
|
||||||
<SegmentedControlContainer>
|
|
||||||
<SegmentedControl
|
|
||||||
data-testid="t--response-tab-segmented-control"
|
|
||||||
defaultValue={segmentedControlOptions[0]?.value}
|
|
||||||
isFullWidth={false}
|
|
||||||
onChange={(value) => {
|
|
||||||
setSelectedControl(value);
|
|
||||||
onResponseTabSelect(value);
|
|
||||||
}}
|
|
||||||
options={segmentedControlOptions}
|
|
||||||
value={selectedControl}
|
|
||||||
/>
|
|
||||||
{responseTabComponent(
|
|
||||||
selectedControl || segmentedControlOptions[0]?.value,
|
|
||||||
output,
|
|
||||||
responsePaneHeight,
|
|
||||||
)}
|
|
||||||
</SegmentedControlContainer>
|
|
||||||
)}
|
|
||||||
{!output && !error && (
|
|
||||||
<NoResponse
|
|
||||||
isButtonDisabled={!isExecutePermitted}
|
|
||||||
isQueryRunning={isRunning}
|
|
||||||
onRunClick={responseTabOnRunClick}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</ResponseContentWrapper>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: DEBUGGER_TAB_KEYS.ERROR_TAB,
|
|
||||||
title: createMessage(DEBUGGER_ERRORS),
|
|
||||||
count: errorCount,
|
|
||||||
panelComponent: <ErrorLogs />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: DEBUGGER_TAB_KEYS.LOGS_TAB,
|
|
||||||
title: createMessage(DEBUGGER_LOGS),
|
|
||||||
panelComponent: <DebuggerLogs searchQuery={actionName} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: DEBUGGER_TAB_KEYS.INSPECT_TAB,
|
|
||||||
title: createMessage(INSPECT_ENTITY),
|
|
||||||
panelComponent: <EntityDeps />,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const { hasDependencies } = useEntityDependencies(props.actionName);
|
const { hasDependencies } = useEntityDependencies(props.actionName);
|
||||||
|
|
||||||
const pluginImages = useSelector(getPluginImages);
|
const pluginImages = useSelector(getPluginImages);
|
||||||
|
|
@ -907,18 +615,10 @@ export function EditorJSONtoForm(props: Props) {
|
||||||
|
|
||||||
const selectedResponseTab = useSelector(getDebuggerSelectedTab);
|
const selectedResponseTab = useSelector(getDebuggerSelectedTab);
|
||||||
|
|
||||||
const setSelectedResponseTab = useCallback((tabKey: string) => {
|
|
||||||
dispatch(setDebuggerSelectedTab(tabKey));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// close the debugger
|
|
||||||
//TODO: move this to a common place
|
|
||||||
const onClose = () => dispatch(showDebugger(false));
|
|
||||||
|
|
||||||
// here we check for normal conditions for opening action pane
|
// here we check for normal conditions for opening action pane
|
||||||
// or if any of the flags are true, We should open the actionpane by default.
|
// or if any of the flags are true, We should open the actionpane by default.
|
||||||
const shouldOpenActionPaneByDefault =
|
const shouldOpenActionPaneByDefault =
|
||||||
((hasDependencies || !!output) && !guidedTourEnabled) ||
|
((hasDependencies || !!actionResponse) && !guidedTourEnabled) ||
|
||||||
currentActionPluginName !== PluginName.SMTP;
|
currentActionPluginName !== PluginName.SMTP;
|
||||||
|
|
||||||
// when switching between different redux forms, make sure this redux form has been initialized before rendering anything.
|
// when switching between different redux forms, make sure this redux form has been initialized before rendering anything.
|
||||||
|
|
@ -1068,52 +768,18 @@ export function EditorJSONtoForm(props: Props) {
|
||||||
</TabContainerView>
|
</TabContainerView>
|
||||||
{renderDebugger &&
|
{renderDebugger &&
|
||||||
selectedResponseTab !== DEBUGGER_TAB_KEYS.HEADER_TAB && (
|
selectedResponseTab !== DEBUGGER_TAB_KEYS.HEADER_TAB && (
|
||||||
<TabbedViewContainer
|
<QueryResponseTabView
|
||||||
className="t--query-bottom-pane-container"
|
actionName={actionName}
|
||||||
ref={panelRef}
|
actionResponse={actionResponse}
|
||||||
>
|
actionSource={actionSource}
|
||||||
<Resizable
|
currentActionConfig={currentActionConfig}
|
||||||
initialHeight={responsePaneHeight}
|
isExecutePermitted={isExecutePermitted}
|
||||||
onResizeComplete={(height: number) =>
|
isRunning={isRunning}
|
||||||
setQueryResponsePaneHeight(height)
|
responseDataTypes={responseDataTypes}
|
||||||
}
|
responseDisplayFormat={responseDisplayFormat}
|
||||||
openResizer={isRunning}
|
responseTabOnRunClick={responseTabOnRunClick}
|
||||||
panelRef={panelRef}
|
runErrorMessage={runErrorMessage}
|
||||||
snapToHeight={ActionExecutionResizerHeight}
|
/>
|
||||||
/>
|
|
||||||
{isRunning && (
|
|
||||||
<ActionExecutionInProgressView
|
|
||||||
actionType="query"
|
|
||||||
theme={EditorTheme.LIGHT}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{output && !!output.length && (
|
|
||||||
<ResultsCount>
|
|
||||||
<Text type={TextType.P3}>
|
|
||||||
Result:
|
|
||||||
<Text type={TextType.H5}>{` ${output.length} Record${
|
|
||||||
output.length > 1 ? "s" : ""
|
|
||||||
}`}</Text>
|
|
||||||
</Text>
|
|
||||||
</ResultsCount>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<EntityBottomTabs
|
|
||||||
expandedHeight={`${ActionExecutionResizerHeight}px`}
|
|
||||||
onSelect={setSelectedResponseTab}
|
|
||||||
selectedTabKey={selectedResponseTab}
|
|
||||||
tabs={responseTabs}
|
|
||||||
/>
|
|
||||||
<CloseDebugger
|
|
||||||
className="close-debugger t--close-debugger"
|
|
||||||
isIconButton
|
|
||||||
kind="tertiary"
|
|
||||||
onClick={onClose}
|
|
||||||
size="md"
|
|
||||||
startIcon="close-modal"
|
|
||||||
/>
|
|
||||||
</TabbedViewContainer>
|
|
||||||
)}
|
)}
|
||||||
</SecondaryWrapper>
|
</SecondaryWrapper>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1125,9 +791,9 @@ export function EditorJSONtoForm(props: Props) {
|
||||||
context={DatasourceStructureContext.QUERY_EDITOR}
|
context={DatasourceStructureContext.QUERY_EDITOR}
|
||||||
datasourceId={props.datasourceId}
|
datasourceId={props.datasourceId}
|
||||||
hasConnections={hasDependencies}
|
hasConnections={hasDependencies}
|
||||||
hasResponse={!!output}
|
hasResponse={!!actionResponse}
|
||||||
pluginId={props.pluginId}
|
pluginId={props.pluginId}
|
||||||
suggestedWidgets={executedQueryData?.suggestedWidgets}
|
suggestedWidgets={actionResponse?.suggestedWidgets}
|
||||||
/>
|
/>
|
||||||
</SidebarWrapper>
|
</SidebarWrapper>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import {
|
||||||
import type { EditorJSONtoFormProps } from "./EditorJSONtoForm";
|
import type { EditorJSONtoFormProps } from "./EditorJSONtoForm";
|
||||||
import { EditorJSONtoForm } from "./EditorJSONtoForm";
|
import { EditorJSONtoForm } from "./EditorJSONtoForm";
|
||||||
import { getFormEvaluationState } from "selectors/formSelectors";
|
import { getFormEvaluationState } from "selectors/formSelectors";
|
||||||
|
import { actionResponseDisplayDataFormats } from "../utils";
|
||||||
|
|
||||||
const valueSelector = formValueSelector(QUERY_EDITOR_FORM_NAME);
|
const valueSelector = formValueSelector(QUERY_EDITOR_FORM_NAME);
|
||||||
const mapStateToProps = (state: AppState, props: any) => {
|
const mapStateToProps = (state: AppState, props: any) => {
|
||||||
|
|
@ -20,27 +21,8 @@ const mapStateToProps = (state: AppState, props: any) => {
|
||||||
const pluginId = valueSelector(state, "datasource.pluginId");
|
const pluginId = valueSelector(state, "datasource.pluginId");
|
||||||
const selectedDbId = valueSelector(state, "datasource.id");
|
const selectedDbId = valueSelector(state, "datasource.id");
|
||||||
const actionData = getActionData(state, actionId);
|
const actionData = getActionData(state, actionId);
|
||||||
let responseDisplayFormat: { title: string; value: string };
|
const { responseDataTypes, responseDisplayFormat } =
|
||||||
let responseDataTypes: { key: string; title: string }[];
|
actionResponseDisplayDataFormats(actionData);
|
||||||
|
|
||||||
if (actionData && actionData.responseDisplayFormat) {
|
|
||||||
responseDataTypes = actionData.dataTypes.map((data) => {
|
|
||||||
return {
|
|
||||||
key: data.dataType,
|
|
||||||
title: data.dataType,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
responseDisplayFormat = {
|
|
||||||
title: actionData.responseDisplayFormat,
|
|
||||||
value: actionData.responseDisplayFormat,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
responseDataTypes = [];
|
|
||||||
responseDisplayFormat = {
|
|
||||||
title: "",
|
|
||||||
value: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const responseTypes = getPluginResponseTypes(state);
|
const responseTypes = getPluginResponseTypes(state);
|
||||||
const documentationLinks = getPluginDocumentationLinks(state);
|
const documentationLinks = getPluginDocumentationLinks(state);
|
||||||
|
|
|
||||||
393
app/client/src/pages/Editor/QueryEditor/QueryResponseView.tsx
Normal file
393
app/client/src/pages/Editor/QueryEditor/QueryResponseView.tsx
Normal file
|
|
@ -0,0 +1,393 @@
|
||||||
|
import {
|
||||||
|
setDebuggerSelectedTab,
|
||||||
|
setResponsePaneHeight,
|
||||||
|
showDebugger,
|
||||||
|
} from "actions/debuggerActions";
|
||||||
|
import { CloseDebugger } from "components/editorComponents/Debugger/DebuggerTabs";
|
||||||
|
import EntityBottomTabs from "components/editorComponents/EntityBottomTabs";
|
||||||
|
import React, { useCallback, useRef, useState } from "react";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import { ActionExecutionResizerHeight } from "pages/Editor/APIEditor/constants";
|
||||||
|
import {
|
||||||
|
getDebuggerSelectedTab,
|
||||||
|
getErrorCount,
|
||||||
|
getResponsePaneHeight,
|
||||||
|
} from "selectors/debuggerSelectors";
|
||||||
|
import { Text, TextType } from "design-system-old";
|
||||||
|
import ActionExecutionInProgressView from "components/editorComponents/ActionExecutionInProgressView";
|
||||||
|
import Resizable, {
|
||||||
|
ResizerCSS,
|
||||||
|
} from "components/editorComponents/Debugger/Resizer";
|
||||||
|
import { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig";
|
||||||
|
import { DEBUGGER_TAB_KEYS } from "components/editorComponents/Debugger/helpers";
|
||||||
|
import {
|
||||||
|
DEBUGGER_ERRORS,
|
||||||
|
DEBUGGER_LOGS,
|
||||||
|
INSPECT_ENTITY,
|
||||||
|
createMessage,
|
||||||
|
} from "@appsmith/constants/messages";
|
||||||
|
import DebuggerLogs from "components/editorComponents/Debugger/DebuggerLogs";
|
||||||
|
import ErrorLogs from "components/editorComponents/Debugger/Errors";
|
||||||
|
import { Callout, SegmentedControl } from "design-system";
|
||||||
|
import {
|
||||||
|
NoResponse,
|
||||||
|
ResponseTabErrorContainer,
|
||||||
|
ResponseTabErrorContent,
|
||||||
|
ResponseTabErrorDefaultMessage,
|
||||||
|
apiReactJsonProps,
|
||||||
|
responseTabComponent,
|
||||||
|
} from "components/editorComponents/ApiResponseView";
|
||||||
|
import LogAdditionalInfo from "components/editorComponents/Debugger/ErrorLogs/components/LogAdditionalInfo";
|
||||||
|
import LogHelper from "components/editorComponents/Debugger/ErrorLogs/components/LogHelper";
|
||||||
|
import { JsonWrapper } from "components/editorComponents/Debugger/ErrorLogs/components/LogCollapseData";
|
||||||
|
import ReactJson from "react-json-view";
|
||||||
|
import EntityDeps from "components/editorComponents/Debugger/EntityDependecies";
|
||||||
|
import type { ActionResponse } from "api/ActionAPI";
|
||||||
|
import { getErrorAsString } from "sagas/ActionExecution/errorUtils";
|
||||||
|
import { isString } from "lodash";
|
||||||
|
import LOG_TYPE from "entities/AppsmithConsole/logtype";
|
||||||
|
import type { SourceEntity } from "entities/AppsmithConsole";
|
||||||
|
import { getUpdateTimestamp } from "components/editorComponents/Debugger/ErrorLogs/ErrorLogItem";
|
||||||
|
import type { Action } from "entities/Action";
|
||||||
|
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||||
|
import { setActionResponseDisplayFormat } from "actions/pluginActionActions";
|
||||||
|
|
||||||
|
const HelpSection = styled.div``;
|
||||||
|
|
||||||
|
const ResponseContentWrapper = styled.div<{ isError: boolean }>`
|
||||||
|
overflow-y: clip;
|
||||||
|
display: grid;
|
||||||
|
height: ${(props) => (props.isError ? "" : "100%")};
|
||||||
|
|
||||||
|
${HelpSection} {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ResultsCount = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
right: ${(props) => props.theme.spaces[17] + 1}px;
|
||||||
|
top: 9px;
|
||||||
|
color: var(--ads-v2-color-fg);
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const TabbedViewContainer = styled.div`
|
||||||
|
${ResizerCSS};
|
||||||
|
height: ${ActionExecutionResizerHeight}px;
|
||||||
|
// Minimum height of bottom tabs as it can be resized
|
||||||
|
min-height: 36px;
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--ads-v2-color-bg);
|
||||||
|
border-top: 1px solid var(--ads-v2-color-border);
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const SegmentedControlContainer = styled.div`
|
||||||
|
padding: 0 var(--ads-v2-spaces-7);
|
||||||
|
padding-top: var(--ads-v2-spaces-4);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--ads-v2-spaces-4);
|
||||||
|
overflow-y: clip;
|
||||||
|
overflow-x: scroll;
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface QueryResponseViewProps {
|
||||||
|
actionSource: SourceEntity;
|
||||||
|
responseTabOnRunClick: () => void;
|
||||||
|
currentActionConfig?: Action;
|
||||||
|
isRunning: boolean;
|
||||||
|
actionName: string; // Check what and how to get
|
||||||
|
runErrorMessage?: string;
|
||||||
|
actionResponse?: ActionResponse;
|
||||||
|
responseDataTypes: { key: string; title: string }[];
|
||||||
|
responseDisplayFormat: { title: string; value: string };
|
||||||
|
isExecutePermitted: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function QueryResponseView({
|
||||||
|
actionName,
|
||||||
|
actionResponse,
|
||||||
|
actionSource,
|
||||||
|
currentActionConfig,
|
||||||
|
isExecutePermitted,
|
||||||
|
isRunning,
|
||||||
|
responseDataTypes,
|
||||||
|
responseDisplayFormat,
|
||||||
|
responseTabOnRunClick,
|
||||||
|
runErrorMessage,
|
||||||
|
}: QueryResponseViewProps) {
|
||||||
|
let output: Record<string, any>[] | null = null;
|
||||||
|
|
||||||
|
const responseBodyTabs =
|
||||||
|
responseDataTypes &&
|
||||||
|
responseDataTypes.map((dataType, index) => {
|
||||||
|
return {
|
||||||
|
index: index,
|
||||||
|
key: dataType.key,
|
||||||
|
title: dataType.title,
|
||||||
|
panelComponent: responseTabComponent(
|
||||||
|
dataType.key,
|
||||||
|
output,
|
||||||
|
responsePaneHeight,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const segmentedControlOptions =
|
||||||
|
responseBodyTabs &&
|
||||||
|
responseBodyTabs.map((item) => {
|
||||||
|
return { value: item.key, label: item.title };
|
||||||
|
});
|
||||||
|
|
||||||
|
const panelRef = useRef<HTMLDivElement>(null);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const [selectedControl, setSelectedControl] = useState(
|
||||||
|
segmentedControlOptions[0]?.value,
|
||||||
|
);
|
||||||
|
const selectedResponseTab = useSelector(getDebuggerSelectedTab);
|
||||||
|
const responsePaneHeight = useSelector(getResponsePaneHeight);
|
||||||
|
const errorCount = useSelector(getErrorCount);
|
||||||
|
|
||||||
|
const onResponseTabSelect = (tabKey: string) => {
|
||||||
|
if (tabKey === DEBUGGER_TAB_KEYS.ERROR_TAB) {
|
||||||
|
AnalyticsUtil.logEvent("OPEN_DEBUGGER", {
|
||||||
|
source: "QUERY_PANE",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
dispatch(
|
||||||
|
setActionResponseDisplayFormat({
|
||||||
|
id: currentActionConfig?.id || "",
|
||||||
|
field: "responseDisplayFormat",
|
||||||
|
value: tabKey,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
let error = runErrorMessage;
|
||||||
|
let hintMessages: Array<string> = [];
|
||||||
|
// Update request timestamp to human readable format.
|
||||||
|
const responseState =
|
||||||
|
actionResponse && getUpdateTimestamp(actionResponse.request);
|
||||||
|
|
||||||
|
const selectedTabIndex =
|
||||||
|
responseDataTypes &&
|
||||||
|
responseDataTypes.findIndex(
|
||||||
|
(dataType) => dataType.title === responseDisplayFormat?.title,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Query is executed even once during the session, show the response data.
|
||||||
|
if (actionResponse) {
|
||||||
|
if (!actionResponse.isExecutionSuccess) {
|
||||||
|
// Pass the error to be shown in the error tab
|
||||||
|
error = actionResponse.readableError
|
||||||
|
? getErrorAsString(actionResponse.readableError)
|
||||||
|
: getErrorAsString(actionResponse.body);
|
||||||
|
} else if (isString(actionResponse.body)) {
|
||||||
|
//reset error.
|
||||||
|
error = "";
|
||||||
|
try {
|
||||||
|
// Try to parse response as JSON array to be displayed in the Response tab
|
||||||
|
output = JSON.parse(actionResponse.body);
|
||||||
|
} catch (e) {
|
||||||
|
// In case the string is not a JSON, wrap it in a response object
|
||||||
|
output = [
|
||||||
|
{
|
||||||
|
response: actionResponse.body,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//reset error.
|
||||||
|
error = "";
|
||||||
|
output = actionResponse.body as any;
|
||||||
|
}
|
||||||
|
if (actionResponse.messages && actionResponse.messages.length) {
|
||||||
|
//reset error.
|
||||||
|
error = "";
|
||||||
|
hintMessages = actionResponse.messages;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const setQueryResponsePaneHeight = useCallback((height: number) => {
|
||||||
|
dispatch(setResponsePaneHeight(height));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onClose = () => dispatch(showDebugger(false));
|
||||||
|
const setSelectedResponseTab = useCallback((tabKey: string) => {
|
||||||
|
dispatch(setDebuggerSelectedTab(tabKey));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const responseTabs = [
|
||||||
|
{
|
||||||
|
key: "response",
|
||||||
|
title: "Response",
|
||||||
|
panelComponent: (
|
||||||
|
<ResponseContentWrapper isError={!!error}>
|
||||||
|
{error && (
|
||||||
|
<ResponseTabErrorContainer>
|
||||||
|
<ResponseTabErrorContent>
|
||||||
|
<ResponseTabErrorDefaultMessage>
|
||||||
|
Your query failed to execute
|
||||||
|
{actionResponse &&
|
||||||
|
(actionResponse.pluginErrorDetails ||
|
||||||
|
actionResponse.body) &&
|
||||||
|
":"}
|
||||||
|
</ResponseTabErrorDefaultMessage>
|
||||||
|
{actionResponse &&
|
||||||
|
(actionResponse.pluginErrorDetails ? (
|
||||||
|
<>
|
||||||
|
<div data-testid="t--query-error">
|
||||||
|
{
|
||||||
|
actionResponse.pluginErrorDetails
|
||||||
|
.downstreamErrorMessage
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
{actionResponse.pluginErrorDetails
|
||||||
|
.downstreamErrorCode && (
|
||||||
|
<LogAdditionalInfo
|
||||||
|
text={
|
||||||
|
actionResponse.pluginErrorDetails
|
||||||
|
.downstreamErrorCode
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
actionResponse.body && (
|
||||||
|
<div data-testid="t--query-error">
|
||||||
|
{actionResponse.body}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
))}
|
||||||
|
<LogHelper
|
||||||
|
logType={LOG_TYPE.ACTION_EXECUTION_ERROR}
|
||||||
|
name="PluginExecutionError"
|
||||||
|
pluginErrorDetails={
|
||||||
|
actionResponse && actionResponse.pluginErrorDetails
|
||||||
|
}
|
||||||
|
source={actionSource}
|
||||||
|
/>
|
||||||
|
</ResponseTabErrorContent>
|
||||||
|
{actionResponse && actionResponse.request && (
|
||||||
|
<JsonWrapper
|
||||||
|
className="t--debugger-log-state"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<ReactJson src={responseState} {...apiReactJsonProps} />
|
||||||
|
</JsonWrapper>
|
||||||
|
)}
|
||||||
|
</ResponseTabErrorContainer>
|
||||||
|
)}
|
||||||
|
{hintMessages && hintMessages.length > 0 && (
|
||||||
|
<HelpSection>
|
||||||
|
{hintMessages.map((msg, index) => (
|
||||||
|
<Callout key={index} kind="warning">
|
||||||
|
{msg}
|
||||||
|
</Callout>
|
||||||
|
))}
|
||||||
|
</HelpSection>
|
||||||
|
)}
|
||||||
|
{currentActionConfig &&
|
||||||
|
output &&
|
||||||
|
responseBodyTabs &&
|
||||||
|
responseBodyTabs.length > 0 &&
|
||||||
|
selectedTabIndex !== -1 && (
|
||||||
|
<SegmentedControlContainer>
|
||||||
|
<SegmentedControl
|
||||||
|
data-testid="t--response-tab-segmented-control"
|
||||||
|
defaultValue={segmentedControlOptions[0]?.value}
|
||||||
|
isFullWidth={false}
|
||||||
|
onChange={(value) => {
|
||||||
|
setSelectedControl(value);
|
||||||
|
onResponseTabSelect(value);
|
||||||
|
}}
|
||||||
|
options={segmentedControlOptions}
|
||||||
|
value={selectedControl}
|
||||||
|
/>
|
||||||
|
{responseTabComponent(
|
||||||
|
selectedControl || segmentedControlOptions[0]?.value,
|
||||||
|
output,
|
||||||
|
responsePaneHeight,
|
||||||
|
)}
|
||||||
|
</SegmentedControlContainer>
|
||||||
|
)}
|
||||||
|
{!output && !error && (
|
||||||
|
<NoResponse
|
||||||
|
isButtonDisabled={!isExecutePermitted}
|
||||||
|
isQueryRunning={isRunning}
|
||||||
|
onRunClick={responseTabOnRunClick}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ResponseContentWrapper>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: DEBUGGER_TAB_KEYS.ERROR_TAB,
|
||||||
|
title: createMessage(DEBUGGER_ERRORS),
|
||||||
|
count: errorCount,
|
||||||
|
panelComponent: <ErrorLogs />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: DEBUGGER_TAB_KEYS.LOGS_TAB,
|
||||||
|
title: createMessage(DEBUGGER_LOGS),
|
||||||
|
panelComponent: <DebuggerLogs searchQuery={actionName} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: DEBUGGER_TAB_KEYS.INSPECT_TAB,
|
||||||
|
title: createMessage(INSPECT_ENTITY),
|
||||||
|
panelComponent: <EntityDeps />,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TabbedViewContainer
|
||||||
|
className="t--query-bottom-pane-container"
|
||||||
|
ref={panelRef}
|
||||||
|
>
|
||||||
|
<Resizable
|
||||||
|
initialHeight={responsePaneHeight}
|
||||||
|
onResizeComplete={(height: number) =>
|
||||||
|
setQueryResponsePaneHeight(height)
|
||||||
|
}
|
||||||
|
openResizer={isRunning}
|
||||||
|
panelRef={panelRef}
|
||||||
|
snapToHeight={ActionExecutionResizerHeight}
|
||||||
|
/>
|
||||||
|
{isRunning && (
|
||||||
|
<ActionExecutionInProgressView
|
||||||
|
actionType="query"
|
||||||
|
theme={EditorTheme.LIGHT}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{output && !!output.length && (
|
||||||
|
<ResultsCount>
|
||||||
|
<Text type={TextType.P3}>
|
||||||
|
Result:
|
||||||
|
<Text type={TextType.H5}>{` ${output.length} Record${
|
||||||
|
output.length > 1 ? "s" : ""
|
||||||
|
}`}</Text>
|
||||||
|
</Text>
|
||||||
|
</ResultsCount>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<EntityBottomTabs
|
||||||
|
expandedHeight={`${ActionExecutionResizerHeight}px`}
|
||||||
|
onSelect={setSelectedResponseTab}
|
||||||
|
selectedTabKey={selectedResponseTab}
|
||||||
|
tabs={responseTabs}
|
||||||
|
/>
|
||||||
|
<CloseDebugger
|
||||||
|
className="close-debugger t--close-debugger"
|
||||||
|
isIconButton
|
||||||
|
kind="tertiary"
|
||||||
|
onClick={onClose}
|
||||||
|
size="md"
|
||||||
|
startIcon="close-modal"
|
||||||
|
/>
|
||||||
|
</TabbedViewContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default QueryResponseView;
|
||||||
|
|
@ -18,6 +18,7 @@ import type { URLBuilderParams } from "@appsmith/entities/URLRedirect/URLAssembl
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { getCurrentPageId } from "selectors/editorSelectors";
|
import { getCurrentPageId } from "selectors/editorSelectors";
|
||||||
import type { WidgetCardProps } from "widgets/BaseWidget";
|
import type { WidgetCardProps } from "widgets/BaseWidget";
|
||||||
|
import type { ActionResponse } from "api/ActionAPI";
|
||||||
|
|
||||||
export const draggableElement = (
|
export const draggableElement = (
|
||||||
id: string,
|
id: string,
|
||||||
|
|
@ -303,3 +304,35 @@ export const groupWidgetCardsByTags = (widgetCards: WidgetCardProps[]) => {
|
||||||
export const transformTextToSentenceCase = (s: string) => {
|
export const transformTextToSentenceCase = (s: string) => {
|
||||||
return s.slice(0, 1).toUpperCase() + s.slice(1).toLowerCase();
|
return s.slice(0, 1).toUpperCase() + s.slice(1).toLowerCase();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const actionResponseDisplayDataFormats = (
|
||||||
|
actionData?: ActionResponse,
|
||||||
|
defaultDisplayFormat: { title: string; value: string } = {
|
||||||
|
title: "",
|
||||||
|
value: "",
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
let responseDisplayFormat: { title: string; value: string };
|
||||||
|
let responseDataTypes: { key: string; title: string }[];
|
||||||
|
|
||||||
|
if (actionData && actionData.responseDisplayFormat) {
|
||||||
|
responseDataTypes = actionData.dataTypes.map((data) => {
|
||||||
|
return {
|
||||||
|
key: data.dataType,
|
||||||
|
title: data.dataType,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
responseDisplayFormat = {
|
||||||
|
title: actionData.responseDisplayFormat,
|
||||||
|
value: actionData.responseDisplayFormat,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
responseDataTypes = [];
|
||||||
|
responseDisplayFormat = defaultDisplayFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
responseDataTypes,
|
||||||
|
responseDisplayFormat,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -161,6 +161,7 @@ import {
|
||||||
getCurrentEnvironmentName,
|
getCurrentEnvironmentName,
|
||||||
} from "@appsmith/selectors/environmentSelectors";
|
} from "@appsmith/selectors/environmentSelectors";
|
||||||
import { EVAL_WORKER_ACTIONS } from "@appsmith/workers/Evaluation/evalWorkerActions";
|
import { EVAL_WORKER_ACTIONS } from "@appsmith/workers/Evaluation/evalWorkerActions";
|
||||||
|
import { getIsActionCreatedInApp } from "@appsmith/utils/getIsActionCreatedInApp";
|
||||||
|
|
||||||
enum ActionResponseDataTypes {
|
enum ActionResponseDataTypes {
|
||||||
BINARY = "BINARY",
|
BINARY = "BINARY",
|
||||||
|
|
@ -560,7 +561,7 @@ export default function* executePluginActionTriggerSaga(
|
||||||
});
|
});
|
||||||
const executePluginActionResponse: ExecutePluginActionResponse = yield call(
|
const executePluginActionResponse: ExecutePluginActionResponse = yield call(
|
||||||
executePluginActionSaga,
|
executePluginActionSaga,
|
||||||
action.id,
|
action,
|
||||||
pagination,
|
pagination,
|
||||||
params,
|
params,
|
||||||
);
|
);
|
||||||
|
|
@ -733,11 +734,12 @@ interface RunActionError {
|
||||||
clientDefinedError?: boolean;
|
clientDefinedError?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function* runActionSaga(
|
export function* runActionSaga(
|
||||||
reduxAction: ReduxAction<{
|
reduxAction: ReduxAction<{
|
||||||
id: string;
|
id: string;
|
||||||
paginationField: PaginationField;
|
paginationField?: PaginationField;
|
||||||
skipOpeningDebugger: boolean;
|
skipOpeningDebugger: boolean;
|
||||||
|
action?: Action;
|
||||||
}>,
|
}>,
|
||||||
) {
|
) {
|
||||||
const actionId = reduxAction.payload.id;
|
const actionId = reduxAction.payload.id;
|
||||||
|
|
@ -754,10 +756,12 @@ function* runActionSaga(
|
||||||
const currentEnvDetails: { id: string; name: string } = yield select(
|
const currentEnvDetails: { id: string; name: string } = yield select(
|
||||||
getCurrentEnvironmentDetails,
|
getCurrentEnvironmentDetails,
|
||||||
);
|
);
|
||||||
const actionObject = shouldBeDefined<Action>(
|
const actionObject =
|
||||||
yield select(getAction, actionId),
|
reduxAction.payload.action ||
|
||||||
`action not found for id - ${actionId}`,
|
shouldBeDefined<Action>(
|
||||||
);
|
yield select(getAction, actionId),
|
||||||
|
`action not found for id - ${actionId}`,
|
||||||
|
);
|
||||||
const plugin: Plugin = yield select(getPlugin, actionObject?.pluginId);
|
const plugin: Plugin = yield select(getPlugin, actionObject?.pluginId);
|
||||||
const datasource: Datasource = yield select(
|
const datasource: Datasource = yield select(
|
||||||
getDatasource,
|
getDatasource,
|
||||||
|
|
@ -787,7 +791,7 @@ function* runActionSaga(
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { id, paginationField } = reduxAction.payload;
|
const { paginationField } = reduxAction.payload;
|
||||||
// open response tab in debugger on exection of action.
|
// open response tab in debugger on exection of action.
|
||||||
if (!reduxAction.payload.skipOpeningDebugger) {
|
if (!reduxAction.payload.skipOpeningDebugger) {
|
||||||
yield call(openDebugger);
|
yield call(openDebugger);
|
||||||
|
|
@ -802,7 +806,7 @@ function* runActionSaga(
|
||||||
try {
|
try {
|
||||||
const executePluginActionResponse: ExecutePluginActionResponse = yield call(
|
const executePluginActionResponse: ExecutePluginActionResponse = yield call(
|
||||||
executePluginActionSaga,
|
executePluginActionSaga,
|
||||||
id,
|
actionObject,
|
||||||
paginationField,
|
paginationField,
|
||||||
{},
|
{},
|
||||||
true,
|
true,
|
||||||
|
|
@ -1119,7 +1123,7 @@ function* executePageLoadAction(pageAction: PageAction) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const executePluginActionResponse: ExecutePluginActionResponse =
|
const executePluginActionResponse: ExecutePluginActionResponse =
|
||||||
yield call(executePluginActionSaga, pageAction);
|
yield call(executePluginActionSaga, action);
|
||||||
payload = executePluginActionResponse.payload;
|
payload = executePluginActionResponse.payload;
|
||||||
isError = executePluginActionResponse.isError;
|
isError = executePluginActionResponse.isError;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -1293,24 +1297,12 @@ interface ExecutePluginActionResponse {
|
||||||
* PluginActionExecutionError which needs to be handled by any saga that calls this.
|
* PluginActionExecutionError which needs to be handled by any saga that calls this.
|
||||||
* */
|
* */
|
||||||
function* executePluginActionSaga(
|
function* executePluginActionSaga(
|
||||||
actionOrActionId: PageAction | string,
|
pluginAction: Action,
|
||||||
paginationField?: PaginationField,
|
paginationField?: PaginationField,
|
||||||
params?: Record<string, unknown>,
|
params?: Record<string, unknown>,
|
||||||
isUserInitiated?: boolean,
|
isUserInitiated?: boolean,
|
||||||
) {
|
) {
|
||||||
let pluginAction;
|
const actionId = pluginAction.id;
|
||||||
let actionId;
|
|
||||||
if (isString(actionOrActionId)) {
|
|
||||||
// @ts-expect-error: plugin Action can take many types
|
|
||||||
pluginAction = yield select(getAction, actionOrActionId);
|
|
||||||
actionId = actionOrActionId;
|
|
||||||
} else {
|
|
||||||
pluginAction = shouldBeDefined<Action>(
|
|
||||||
yield select(getAction, actionOrActionId.id),
|
|
||||||
`Action not found for id -> ${actionOrActionId.id}`,
|
|
||||||
);
|
|
||||||
actionId = actionOrActionId.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pluginAction.confirmBeforeExecute) {
|
if (pluginAction.confirmBeforeExecute) {
|
||||||
const modalPayload = {
|
const modalPayload = {
|
||||||
|
|
@ -1392,6 +1384,7 @@ function* executePluginActionSaga(
|
||||||
executePluginActionSuccess({
|
executePluginActionSuccess({
|
||||||
id: actionId,
|
id: actionId,
|
||||||
response: payload,
|
response: payload,
|
||||||
|
isActionCreatedInApp: getIsActionCreatedInApp(pluginAction),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -1454,6 +1447,7 @@ function* executePluginActionSaga(
|
||||||
executePluginActionSuccess({
|
executePluginActionSuccess({
|
||||||
id: actionId,
|
id: actionId,
|
||||||
response: EMPTY_RESPONSE,
|
response: EMPTY_RESPONSE,
|
||||||
|
isActionCreatedInApp: getIsActionCreatedInApp(pluginAction),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
yield put(
|
yield put(
|
||||||
|
|
|
||||||
|
|
@ -15,3 +15,6 @@ export const getApiPaneConfigSelectedTabIndex = (state: AppState) =>
|
||||||
|
|
||||||
export const getApiRightPaneSelectedTab = (state: AppState) =>
|
export const getApiRightPaneSelectedTab = (state: AppState) =>
|
||||||
state.ui.apiPane.selectedRightPaneTab;
|
state.ui.apiPane.selectedRightPaneTab;
|
||||||
|
|
||||||
|
export const getIsRunning = (state: AppState, apiId: string) =>
|
||||||
|
state.ui.apiPane.isRunning[apiId];
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user