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,
|
||||
});
|
||||
|
||||
export const executePluginActionSuccess = (payload: {
|
||||
export interface ExecutePluginActionSuccessPayload {
|
||||
id: string;
|
||||
response: ActionResponse;
|
||||
isPageLoad?: boolean;
|
||||
}) => ({
|
||||
isActionCreatedInApp: boolean;
|
||||
}
|
||||
|
||||
export const executePluginActionSuccess = (
|
||||
payload: ExecutePluginActionSuccessPayload,
|
||||
) => ({
|
||||
type: ReduxActionTypes.EXECUTE_PLUGIN_ACTION_SUCCESS,
|
||||
payload: payload,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,7 +8,10 @@ import type { ActionResponse } from "api/ActionAPI";
|
|||
import type { ExecuteErrorPayload } from "constants/AppsmithActionConstants/ActionConstants";
|
||||
import _ from "lodash";
|
||||
import type { Action } from "entities/Action";
|
||||
import type { UpdateActionPropertyActionPayload } from "actions/pluginActionActions";
|
||||
import type {
|
||||
ExecutePluginActionSuccessPayload,
|
||||
UpdateActionPropertyActionPayload,
|
||||
} from "actions/pluginActionActions";
|
||||
|
||||
export interface ActionData {
|
||||
isLoading: boolean;
|
||||
|
|
@ -173,8 +176,10 @@ export const handlers = {
|
|||
},
|
||||
[ReduxActionTypes.EXECUTE_PLUGIN_ACTION_SUCCESS]: (
|
||||
draftMetaState: Array<ActionData | PartialActionData>,
|
||||
action: ReduxAction<{ id: string; response: ActionResponse }>,
|
||||
action: ReduxAction<ExecutePluginActionSuccessPayload>,
|
||||
) => {
|
||||
if (!action.payload.isActionCreatedInApp) return;
|
||||
|
||||
const foundAction = draftMetaState.find((stateAction) => {
|
||||
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 React, { useCallback, useRef, useState } from "react";
|
||||
import { connect, useDispatch, useSelector } from "react-redux";
|
||||
import type { RouteComponentProps } from "react-router";
|
||||
import { withRouter } from "react-router";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import ReactJson from "react-json-view";
|
||||
import styled from "styled-components";
|
||||
import type { AppState } from "@appsmith/reducers";
|
||||
import type { ActionResponse } from "api/ActionAPI";
|
||||
import { formatBytes } from "utils/helpers";
|
||||
import type { APIEditorRouteParams } from "constants/routes";
|
||||
import type { SourceEntity } from "entities/AppsmithConsole";
|
||||
import LOG_TYPE from "entities/AppsmithConsole/logtype";
|
||||
import { ENTITY_TYPE } from "entities/AppsmithConsole";
|
||||
import ReadOnlyEditor from "components/editorComponents/ReadOnlyEditor";
|
||||
import { getActionResponses } from "@appsmith/selectors/entitiesSelector";
|
||||
import { isArray, isEmpty, isString } from "lodash";
|
||||
import {
|
||||
CHECK_REQUEST_BODY,
|
||||
|
|
@ -25,7 +20,7 @@ import {
|
|||
DEBUGGER_ERRORS,
|
||||
} from "@appsmith/constants/messages";
|
||||
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 DebuggerLogs from "./Debugger/DebuggerLogs";
|
||||
import ErrorLogs from "./Debugger/Errors";
|
||||
|
|
@ -38,11 +33,11 @@ import EntityBottomTabs from "./EntityBottomTabs";
|
|||
import { DEBUGGER_TAB_KEYS } from "./Debugger/helpers";
|
||||
import Table from "pages/Editor/QueryEditor/Table";
|
||||
import { API_RESPONSE_TYPE_OPTIONS } from "constants/ApiEditorConstants/CommonApiConstants";
|
||||
import type { UpdateActionPropertyActionPayload } from "actions/pluginActionActions";
|
||||
import { setActionResponseDisplayFormat } from "actions/pluginActionActions";
|
||||
import { isHtml } from "./utils";
|
||||
import {
|
||||
getDebuggerSelectedTab,
|
||||
getErrorCount,
|
||||
getResponsePaneHeight,
|
||||
} from "selectors/debuggerSelectors";
|
||||
import { ActionExecutionResizerHeight } from "pages/Editor/APIEditor/constants";
|
||||
|
|
@ -179,29 +174,17 @@ const ResponseBodyContainer = styled.div`
|
|||
display: grid;
|
||||
`;
|
||||
|
||||
interface ReduxStateProps {
|
||||
responses: Record<string, ActionResponse | undefined>;
|
||||
isRunning: Record<string, boolean>;
|
||||
errorCount: number;
|
||||
interface Props {
|
||||
currentActionConfig?: Action;
|
||||
theme?: EditorTheme;
|
||||
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 }>>`
|
||||
color: ${(props) =>
|
||||
|
|
@ -313,31 +296,21 @@ export const NoResponse = (props: NoResponseProps) => (
|
|||
|
||||
function ApiResponseView(props: Props) {
|
||||
const {
|
||||
actionResponse = EMPTY_RESPONSE,
|
||||
currentActionConfig,
|
||||
disabled,
|
||||
match: {
|
||||
params: { apiId },
|
||||
},
|
||||
isRunning,
|
||||
responseDataTypes,
|
||||
responseDisplayFormat,
|
||||
responses,
|
||||
updateActionResponseDisplayFormat,
|
||||
theme = EditorTheme.LIGHT,
|
||||
} = props;
|
||||
let response: ActionResponse = EMPTY_RESPONSE;
|
||||
let isRunning = false;
|
||||
let hasFailed = 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 hasFailed = actionResponse.statusCode
|
||||
? actionResponse.statusCode[0] !== "2"
|
||||
: false;
|
||||
|
||||
const panelRef: RefObject<HTMLDivElement> = useRef(null);
|
||||
const dispatch = useDispatch();
|
||||
const errorCount = useSelector(getErrorCount);
|
||||
|
||||
const onDebugClick = useCallback(() => {
|
||||
AnalyticsUtil.logEvent("OPEN_DEBUGGER", {
|
||||
|
|
@ -353,12 +326,12 @@ function ApiResponseView(props: Props) {
|
|||
});
|
||||
};
|
||||
|
||||
const messages = response?.messages;
|
||||
const messages = actionResponse?.messages;
|
||||
let responseHeaders = {};
|
||||
|
||||
// if no headers are present in the response, use the default body text.
|
||||
if (response.headers) {
|
||||
Object.entries(response.headers).forEach(([key, value]) => {
|
||||
if (actionResponse.headers) {
|
||||
Object.entries(actionResponse.headers).forEach(([key, value]) => {
|
||||
if (isArray(value) && value.length < 2)
|
||||
return (responseHeaders = {
|
||||
...responseHeaders,
|
||||
|
|
@ -375,17 +348,19 @@ function ApiResponseView(props: Props) {
|
|||
}
|
||||
|
||||
const onResponseTabSelect = (tab: string) => {
|
||||
updateActionResponseDisplayFormat({
|
||||
id: apiId ? apiId : "",
|
||||
field: "responseDisplayFormat",
|
||||
value: tab,
|
||||
});
|
||||
dispatch(
|
||||
setActionResponseDisplayFormat({
|
||||
id: currentActionConfig?.id || "",
|
||||
field: "responseDisplayFormat",
|
||||
value: tab,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
let filteredResponseDataTypes: { key: string; title: string }[] = [
|
||||
...responseDataTypes,
|
||||
];
|
||||
if (!!response.body && !isArray(response.body)) {
|
||||
if (!!actionResponse.body && !isArray(actionResponse.body)) {
|
||||
filteredResponseDataTypes = responseDataTypes.filter(
|
||||
(item) => item.key !== API_RESPONSE_TYPE_OPTIONS.TABLE,
|
||||
);
|
||||
|
|
@ -403,7 +378,7 @@ function ApiResponseView(props: Props) {
|
|||
title: dataType.title,
|
||||
panelComponent: responseTabComponent(
|
||||
dataType.key,
|
||||
response?.body,
|
||||
actionResponse?.body,
|
||||
responsePaneHeight,
|
||||
),
|
||||
};
|
||||
|
|
@ -444,12 +419,12 @@ function ApiResponseView(props: Props) {
|
|||
}, []);
|
||||
|
||||
// get request timestamp formatted to human readable format.
|
||||
const responseState = getUpdateTimestamp(response.request);
|
||||
const responseState = getUpdateTimestamp(actionResponse.request);
|
||||
// action source for analytics.
|
||||
const actionSource: SourceEntity = {
|
||||
type: ENTITY_TYPE.ACTION,
|
||||
name: currentActionConfig ? currentActionConfig.name : "API",
|
||||
id: apiId ? apiId : "",
|
||||
id: currentActionConfig?.id || "",
|
||||
};
|
||||
const tabs = [
|
||||
{
|
||||
|
|
@ -471,16 +446,18 @@ function ApiResponseView(props: Props) {
|
|||
<ResponseTabErrorContent>
|
||||
<ResponseTabErrorDefaultMessage>
|
||||
Your API failed to execute
|
||||
{response.pluginErrorDetails && ":"}
|
||||
{actionResponse.pluginErrorDetails && ":"}
|
||||
</ResponseTabErrorDefaultMessage>
|
||||
{response.pluginErrorDetails && (
|
||||
{actionResponse.pluginErrorDetails && (
|
||||
<>
|
||||
<div className="t--debugger-log-downstream-message">
|
||||
{response.pluginErrorDetails.downstreamErrorMessage}
|
||||
{actionResponse.pluginErrorDetails.downstreamErrorMessage}
|
||||
</div>
|
||||
{response.pluginErrorDetails.downstreamErrorCode && (
|
||||
{actionResponse.pluginErrorDetails.downstreamErrorCode && (
|
||||
<LogAdditionalInfo
|
||||
text={response.pluginErrorDetails.downstreamErrorCode}
|
||||
text={
|
||||
actionResponse.pluginErrorDetails.downstreamErrorCode
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
|
@ -488,11 +465,11 @@ function ApiResponseView(props: Props) {
|
|||
<LogHelper
|
||||
logType={LOG_TYPE.ACTION_EXECUTION_ERROR}
|
||||
name="PluginExecutionError"
|
||||
pluginErrorDetails={response.pluginErrorDetails}
|
||||
pluginErrorDetails={actionResponse.pluginErrorDetails}
|
||||
source={actionSource}
|
||||
/>
|
||||
</ResponseTabErrorContent>
|
||||
{response.request && (
|
||||
{actionResponse.request && (
|
||||
<JsonWrapper
|
||||
className="t--debugger-log-state"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
|
|
@ -503,7 +480,7 @@ function ApiResponseView(props: Props) {
|
|||
</ResponseTabErrorContainer>
|
||||
) : (
|
||||
<ResponseDataContainer>
|
||||
{isEmpty(response.statusCode) ? (
|
||||
{isEmpty(actionResponse.statusCode) ? (
|
||||
<NoResponse
|
||||
isButtonDisabled={disabled}
|
||||
isQueryRunning={isRunning}
|
||||
|
|
@ -511,12 +488,13 @@ function ApiResponseView(props: Props) {
|
|||
/>
|
||||
) : (
|
||||
<ResponseBodyContainer>
|
||||
{isString(response?.body) && isHtml(response?.body) ? (
|
||||
{isString(actionResponse?.body) &&
|
||||
isHtml(actionResponse?.body) ? (
|
||||
<ReadOnlyEditor
|
||||
folding
|
||||
height={"100%"}
|
||||
input={{
|
||||
value: response?.body,
|
||||
value: actionResponse?.body,
|
||||
}}
|
||||
/>
|
||||
) : responseTabs &&
|
||||
|
|
@ -536,7 +514,7 @@ function ApiResponseView(props: Props) {
|
|||
/>
|
||||
{responseTabComponent(
|
||||
selectedControl || segmentedControlOptions[0]?.value,
|
||||
response?.body,
|
||||
actionResponse?.body,
|
||||
responsePaneHeight,
|
||||
)}
|
||||
</SegmentedControlContainer>
|
||||
|
|
@ -569,7 +547,7 @@ function ApiResponseView(props: Props) {
|
|||
</Callout>
|
||||
)}
|
||||
<ResponseDataContainer>
|
||||
{isEmpty(response.statusCode) ? (
|
||||
{isEmpty(actionResponse.statusCode) ? (
|
||||
<NoResponse
|
||||
isButtonDisabled={disabled}
|
||||
isQueryRunning={isRunning}
|
||||
|
|
@ -593,7 +571,7 @@ function ApiResponseView(props: Props) {
|
|||
{
|
||||
key: DEBUGGER_TAB_KEYS.ERROR_TAB,
|
||||
title: createMessage(DEBUGGER_ERRORS),
|
||||
count: props.errorCount,
|
||||
count: errorCount,
|
||||
panelComponent: <ErrorLogs />,
|
||||
},
|
||||
{
|
||||
|
|
@ -624,48 +602,49 @@ function ApiResponseView(props: Props) {
|
|||
snapToHeight={ActionExecutionResizerHeight}
|
||||
/>
|
||||
{isRunning && (
|
||||
<ActionExecutionInProgressView actionType="API" theme={props.theme} />
|
||||
<ActionExecutionInProgressView actionType="API" theme={theme} />
|
||||
)}
|
||||
<TabbedViewWrapper>
|
||||
{response.statusCode && (
|
||||
{actionResponse.statusCode && (
|
||||
<ResponseMetaWrapper>
|
||||
{response.statusCode && (
|
||||
{actionResponse.statusCode && (
|
||||
<Flex>
|
||||
<Text type={TextType.P3}>Status: </Text>
|
||||
<StatusCodeText
|
||||
accent="secondary"
|
||||
className="t--response-status-code"
|
||||
code={response.statusCode.toString()}
|
||||
code={actionResponse.statusCode.toString()}
|
||||
>
|
||||
{response.statusCode}
|
||||
{actionResponse.statusCode}
|
||||
</StatusCodeText>
|
||||
</Flex>
|
||||
)}
|
||||
<ResponseMetaInfo>
|
||||
{response.duration && (
|
||||
{actionResponse.duration && (
|
||||
<Flex>
|
||||
<Text type={TextType.P3}>Time: </Text>
|
||||
<Text type={TextType.H5}>{response.duration} ms</Text>
|
||||
<Text type={TextType.H5}>{actionResponse.duration} ms</Text>
|
||||
</Flex>
|
||||
)}
|
||||
{response.size && (
|
||||
{actionResponse.size && (
|
||||
<Flex>
|
||||
<Text type={TextType.P3}>Size: </Text>
|
||||
<Text type={TextType.H5}>
|
||||
{formatBytes(parseInt(response.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" : ""
|
||||
}`}
|
||||
{formatBytes(parseInt(actionResponse.size))}
|
||||
</Text>
|
||||
</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>
|
||||
</ResponseMetaWrapper>
|
||||
)}
|
||||
|
|
@ -688,25 +667,4 @@ function ApiResponseView(props: Props) {
|
|||
);
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: AppState): ReduxStateProps => {
|
||||
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));
|
||||
export default 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 FormLabel from "components/editorComponents/FormLabel";
|
||||
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 { isGraphqlPlugin } from "entities/Action";
|
||||
import KeyValueFieldArray from "components/editorComponents/form/fields/KeyValueFieldArray";
|
||||
|
|
@ -164,6 +168,7 @@ const MainContainer = styled.div`
|
|||
/* padding: var(--ads-v2-spaces-7); */
|
||||
`;
|
||||
export interface CommonFormProps {
|
||||
actionResponse?: ActionResponse;
|
||||
pluginId: string;
|
||||
onRunClick: (paginationField?: PaginationField) => void;
|
||||
onDeleteClick: () => void;
|
||||
|
|
@ -508,6 +513,7 @@ function CommonEditorForm(props: CommonFormPropsWithExtraParams) {
|
|||
actionConfigurationHeaders,
|
||||
actionConfigurationParams,
|
||||
actionName,
|
||||
actionResponse,
|
||||
autoGeneratedActionConfigHeaders,
|
||||
closeEditorLink,
|
||||
currentActionDatasourceId,
|
||||
|
|
@ -729,8 +735,11 @@ function CommonEditorForm(props: CommonFormPropsWithExtraParams) {
|
|||
</TabbedViewContainer>
|
||||
{showDebugger && (
|
||||
<ApiResponseView
|
||||
actionResponse={actionResponse}
|
||||
apiName={actionName}
|
||||
currentActionConfig={currentActionConfig}
|
||||
disabled={!isExecutePermitted}
|
||||
isRunning={isRunning}
|
||||
onRunClick={onRunClick}
|
||||
responseDataTypes={responseDataTypes}
|
||||
responseDisplayFormat={responseDisplayFormat}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import classNames from "classnames";
|
|||
import styled from "styled-components";
|
||||
import { API_EDITOR_FORM_NAME } from "@appsmith/constants/forms";
|
||||
import type { Action } from "entities/Action";
|
||||
import { EMPTY_RESPONSE } from "components/editorComponents/emptyResponse";
|
||||
import type { AppState } from "@appsmith/reducers";
|
||||
import { getApiName } from "selectors/formSelectors";
|
||||
import { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig";
|
||||
|
|
@ -22,6 +21,7 @@ import { tailwindLayers } from "constants/Layers";
|
|||
import VariableEditor from "./VariableEditor";
|
||||
import Pagination from "./Pagination";
|
||||
import { ApiEditorContext } from "../ApiEditorContext";
|
||||
import { actionResponseDisplayDataFormats } from "pages/Editor/utils";
|
||||
|
||||
const ResizeableDiv = styled.div`
|
||||
display: flex;
|
||||
|
|
@ -235,37 +235,21 @@ export default connect(
|
|||
|
||||
let hasResponse = false;
|
||||
let suggestedWidgets;
|
||||
if (apiId) {
|
||||
const response = getActionData(state, apiId) || EMPTY_RESPONSE;
|
||||
const actionResponse = getActionData(state, apiId);
|
||||
if (actionResponse) {
|
||||
hasResponse =
|
||||
!isEmpty(response.statusCode) && response.statusCode[0] === "2";
|
||||
suggestedWidgets = response.suggestedWidgets;
|
||||
!isEmpty(actionResponse.statusCode) &&
|
||||
actionResponse.statusCode[0] === "2";
|
||||
suggestedWidgets = actionResponse.suggestedWidgets;
|
||||
}
|
||||
|
||||
const actionData = getActionData(state, apiId);
|
||||
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 = {
|
||||
title: "",
|
||||
value: "",
|
||||
};
|
||||
}
|
||||
const { responseDataTypes, responseDisplayFormat } =
|
||||
actionResponseDisplayDataFormats(actionData);
|
||||
|
||||
return {
|
||||
actionName,
|
||||
actionResponse,
|
||||
apiId,
|
||||
httpMethodFromForm,
|
||||
actionConfigurationHeaders,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,18 @@
|
|||
import React, { useContext } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import type { RouteComponentProps } from "react-router";
|
||||
import type { InjectedFormProps } from "redux-form";
|
||||
import { reduxForm, formValueSelector } from "redux-form";
|
||||
import { POST_BODY_FORMAT_OPTIONS } from "constants/ApiEditorConstants/CommonApiConstants";
|
||||
import styled from "styled-components";
|
||||
import FormLabel from "components/editorComponents/FormLabel";
|
||||
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 KeyValueFieldArray from "components/editorComponents/form/fields/KeyValueFieldArray";
|
||||
import ApiResponseView from "components/editorComponents/ApiResponseView";
|
||||
|
|
@ -18,11 +24,12 @@ import type { PaginationType, Action } from "entities/Action";
|
|||
import ActionNameEditor from "components/editorComponents/ActionNameEditor";
|
||||
import { NameWrapper } from "./CommonEditorForm";
|
||||
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 { Icon } from "design-system";
|
||||
import { showDebuggerFlag } from "selectors/debuggerSelectors";
|
||||
import { ApiEditorContext } from "./ApiEditorContext";
|
||||
import { actionResponseDisplayDataFormats } from "../utils";
|
||||
|
||||
const Form = styled.form`
|
||||
display: flex;
|
||||
|
|
@ -96,13 +103,21 @@ const TabbedViewContainer = styled.div`
|
|||
padding-top: 12px;
|
||||
`;
|
||||
|
||||
interface APIFormProps {
|
||||
onRunClick: (paginationField?: PaginationField) => void;
|
||||
onDeleteClick: () => void;
|
||||
isRunning: boolean;
|
||||
isDeleting: boolean;
|
||||
paginationType: PaginationType;
|
||||
interface APIFormOwnProps {
|
||||
apiId: string;
|
||||
apiName: 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;
|
||||
actionConfiguration?: any;
|
||||
actionConfigurationHeaders?: Property[];
|
||||
|
|
@ -111,18 +126,15 @@ interface APIFormProps {
|
|||
providerImage: string;
|
||||
providerURL: string;
|
||||
providerCredentialSteps: string;
|
||||
location: {
|
||||
pathname: string;
|
||||
};
|
||||
apiName: string;
|
||||
apiId: string;
|
||||
dispatch: any;
|
||||
responseDataTypes: { key: string; title: string }[];
|
||||
responseDisplayFormat: { title: string; value: string };
|
||||
showDebugger: boolean;
|
||||
}
|
||||
|
||||
type Props = APIFormProps & InjectedFormProps<Action, APIFormProps>;
|
||||
type Props = APIFormProps &
|
||||
InjectedFormProps<Action, APIFormProps & APIFormOwnProps> &
|
||||
APIFormOwnProps;
|
||||
|
||||
function RapidApiEditorForm(props: Props) {
|
||||
const {
|
||||
|
|
@ -276,7 +288,10 @@ function RapidApiEditorForm(props: Props) {
|
|||
</TabbedViewContainer>
|
||||
{showDebugger && (
|
||||
<ApiResponseView
|
||||
actionResponse={props.actionData}
|
||||
apiName={props.apiName}
|
||||
currentActionConfig={props.currentActionConfig}
|
||||
isRunning={props.isRunning}
|
||||
onRunClick={onRunClick}
|
||||
responseDataTypes={responseDataTypes}
|
||||
responseDisplayFormat={responseDisplayFormat}
|
||||
|
|
@ -289,7 +304,7 @@ function RapidApiEditorForm(props: Props) {
|
|||
|
||||
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 providerImage = selector(state, "provider.imageUrl");
|
||||
const providerURL = selector(state, "provider.url");
|
||||
|
|
@ -317,29 +332,17 @@ export default connect((state: AppState) => {
|
|||
`${actionConfigurationBodyFormData}`,
|
||||
);
|
||||
}
|
||||
const currentActionConfig = getAction(state, ownProps.apiId);
|
||||
const actionData = getActionData(state, actionConfiguration.id);
|
||||
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 = {
|
||||
const { responseDataTypes, responseDisplayFormat } =
|
||||
actionResponseDisplayDataFormats(actionData, {
|
||||
title: "JSON",
|
||||
value: "JSON",
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
actionData,
|
||||
currentActionConfig,
|
||||
displayFormat,
|
||||
actionConfiguration,
|
||||
actionConfigurationHeaders,
|
||||
|
|
@ -353,7 +356,7 @@ export default connect((state: AppState) => {
|
|||
providerCredentialSteps,
|
||||
};
|
||||
})(
|
||||
reduxForm<Action, APIFormProps>({
|
||||
reduxForm<Action, APIFormProps & APIFormOwnProps>({
|
||||
form: API_EDITOR_FORM_NAME,
|
||||
destroyOnUnmount: false,
|
||||
})(RapidApiEditorForm),
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import styled from "styled-components";
|
|||
import { API_EDITOR_FORM_NAME } from "@appsmith/constants/forms";
|
||||
import type { Action } from "entities/Action";
|
||||
import PostBodyData from "./PostBodyData";
|
||||
import { EMPTY_RESPONSE } from "components/editorComponents/emptyResponse";
|
||||
import type { AppState } from "@appsmith/reducers";
|
||||
import { getApiName } from "selectors/formSelectors";
|
||||
import { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig";
|
||||
|
|
@ -25,6 +24,7 @@ import CommonEditorForm from "./CommonEditorForm";
|
|||
import Pagination from "./Pagination";
|
||||
import { getCurrentEnvironmentId } from "@appsmith/selectors/environmentSelectors";
|
||||
import { ApiEditorContext } from "./ApiEditorContext";
|
||||
import { actionResponseDisplayDataFormats } from "../utils";
|
||||
|
||||
const NoBodyMessage = styled.div`
|
||||
margin-top: 20px;
|
||||
|
|
@ -162,39 +162,23 @@ export default connect((state: AppState, props: { pluginId: string }) => {
|
|||
}
|
||||
|
||||
const responses = getActionResponses(state);
|
||||
const actionResponse = responses[apiId];
|
||||
let hasResponse = false;
|
||||
let suggestedWidgets;
|
||||
if (apiId && apiId in responses) {
|
||||
const response = responses[apiId] || EMPTY_RESPONSE;
|
||||
if (actionResponse) {
|
||||
hasResponse =
|
||||
!isEmpty(response.statusCode) && response.statusCode[0] === "2";
|
||||
suggestedWidgets = response.suggestedWidgets;
|
||||
!isEmpty(actionResponse.statusCode) &&
|
||||
actionResponse.statusCode[0] === "2";
|
||||
suggestedWidgets = actionResponse.suggestedWidgets;
|
||||
}
|
||||
|
||||
const actionData = getActionData(state, apiId);
|
||||
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 = {
|
||||
title: "",
|
||||
value: "",
|
||||
};
|
||||
}
|
||||
const { responseDataTypes, responseDisplayFormat } =
|
||||
actionResponseDisplayDataFormats(actionData);
|
||||
|
||||
return {
|
||||
actionName,
|
||||
actionResponse,
|
||||
apiId,
|
||||
httpMethodFromForm,
|
||||
actionConfigurationHeaders,
|
||||
|
|
|
|||
|
|
@ -228,10 +228,10 @@ class QueryEditor extends React.Component<Props> {
|
|||
|
||||
return (
|
||||
<QueryEditorForm
|
||||
actionResponse={responses[actionId]}
|
||||
dataSources={dataSources}
|
||||
datasourceId={this.props.datasourceId}
|
||||
editorConfig={editorConfig}
|
||||
executedQueryData={responses[actionId]}
|
||||
formData={this.props.formData}
|
||||
isDeleting={isDeleting}
|
||||
isRunning={isRunning}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import { useContext, useEffect } from "react";
|
||||
import type { RefObject } from "react";
|
||||
import React, { useCallback, useRef, useState } from "react";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import React, { useCallback } from "react";
|
||||
import type { InjectedFormProps } from "redux-form";
|
||||
import { Tag } from "@blueprintjs/core";
|
||||
import { isString, noop } from "lodash";
|
||||
import { noop } from "lodash";
|
||||
import type { Datasource } from "entities/Datasource";
|
||||
import { DatasourceStructureContext } from "entities/Datasource";
|
||||
import {
|
||||
|
|
@ -23,12 +22,9 @@ import DropdownField from "components/editorComponents/form/fields/DropdownField
|
|||
import type { ControlProps } from "components/formControls/BaseControl";
|
||||
import ActionSettings from "pages/Editor/ActionSettings";
|
||||
import log from "loglevel";
|
||||
import { Text, TextType } from "design-system-old";
|
||||
import {
|
||||
Button,
|
||||
Callout,
|
||||
Icon,
|
||||
SegmentedControl,
|
||||
Spinner,
|
||||
Tab,
|
||||
TabPanel,
|
||||
|
|
@ -38,13 +34,8 @@ import {
|
|||
} from "design-system";
|
||||
import styled from "styled-components";
|
||||
import FormRow from "components/editorComponents/FormRow";
|
||||
import DebuggerLogs from "components/editorComponents/Debugger/DebuggerLogs";
|
||||
import ErrorLogs from "components/editorComponents/Debugger/Errors";
|
||||
import Resizable, {
|
||||
ResizerCSS,
|
||||
} from "components/editorComponents/Debugger/Resizer";
|
||||
import { ResizerCSS } from "components/editorComponents/Debugger/Resizer";
|
||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||
import EntityDeps from "components/editorComponents/Debugger/EntityDependecies";
|
||||
import {
|
||||
checkIfSectionCanRender,
|
||||
checkIfSectionIsEnabled,
|
||||
|
|
@ -57,11 +48,8 @@ import {
|
|||
ACTION_EDITOR_REFRESH,
|
||||
CREATE_NEW_DATASOURCE,
|
||||
createMessage,
|
||||
DEBUGGER_ERRORS,
|
||||
DEBUGGER_LOGS,
|
||||
DOCUMENTATION,
|
||||
DOCUMENTATION_TOOLTIP,
|
||||
INSPECT_ENTITY,
|
||||
INVALID_FORM_CONFIGURATION,
|
||||
NO_DATASOURCE_FOR_QUERY,
|
||||
UNEXPECTED_ERROR,
|
||||
|
|
@ -72,58 +60,27 @@ import { thinScrollbar } from "constants/DefaultTheme";
|
|||
import ActionRightPane, {
|
||||
useEntityDependencies,
|
||||
} from "components/editorComponents/ActionRightPane";
|
||||
import type {
|
||||
ActionApiResponseReq,
|
||||
PluginErrorDetails,
|
||||
SuggestedWidget,
|
||||
} from "api/ActionAPI";
|
||||
import type { ActionResponse } from "api/ActionAPI";
|
||||
import type { Plugin } from "api/PluginApi";
|
||||
import { UIComponentTypes } from "api/PluginApi";
|
||||
import * as Sentry from "@sentry/react";
|
||||
import EntityBottomTabs from "components/editorComponents/EntityBottomTabs";
|
||||
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 { inGuidedTour } from "selectors/onboardingSelectors";
|
||||
import { EDITOR_TABS, SQL_DATASOURCES } from "constants/QueryEditorConstants";
|
||||
import type { FormEvalOutput } 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 { setQueryPaneConfigSelectedTabIndex } from "actions/queryPaneActions";
|
||||
import { ActionExecutionResizerHeight } from "pages/Editor/APIEditor/constants";
|
||||
import { getCurrentAppWorkspace } from "@appsmith/selectors/workspaceSelectors";
|
||||
import {
|
||||
setDebuggerSelectedTab,
|
||||
setResponsePaneHeight,
|
||||
showDebugger,
|
||||
} from "actions/debuggerActions";
|
||||
import {
|
||||
getDebuggerSelectedTab,
|
||||
getErrorCount,
|
||||
getResponsePaneHeight,
|
||||
showDebuggerFlag,
|
||||
} 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 { ENTITY_TYPE as SOURCE_ENTITY_TYPE } from "entities/AppsmithConsole";
|
||||
import { DocsLink, openDoc } from "../../../constants/DocumentationLinks";
|
||||
import ActionExecutionInProgressView from "components/editorComponents/ActionExecutionInProgressView";
|
||||
import { CloseDebugger } from "components/editorComponents/Debugger/DebuggerTabs";
|
||||
import {
|
||||
getHasCreateDatasourcePermission,
|
||||
getHasExecuteActionPermission,
|
||||
|
|
@ -132,6 +89,8 @@ import {
|
|||
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
|
||||
import { FEATURE_FLAG } from "@appsmith/entities/FeatureFlag";
|
||||
import { QueryEditorContext } from "./QueryEditorContext";
|
||||
import QueryResponseTabView from "./QueryResponseView";
|
||||
import { setDebuggerSelectedTab, showDebugger } from "actions/debuggerActions";
|
||||
|
||||
const QueryFormContainer = styled.form`
|
||||
flex: 1;
|
||||
|
|
@ -178,13 +137,6 @@ const SettingsWrapper = styled.div`
|
|||
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`
|
||||
margin-top: 15px;
|
||||
`;
|
||||
|
|
@ -196,18 +148,6 @@ const SecondaryWrapper = styled.div`
|
|||
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)`
|
||||
padding: 0px var(--ads-v2-spaces-7) var(--ads-v2-spaces-5)
|
||||
var(--ads-v2-spaces-7);
|
||||
|
|
@ -338,15 +278,7 @@ interface QueryFormProps {
|
|||
isRunning: boolean;
|
||||
dataSources: Datasource[];
|
||||
uiComponent: UIComponentTypes;
|
||||
executedQueryData?: {
|
||||
body: any;
|
||||
isExecutionSuccess?: boolean;
|
||||
messages?: Array<string>;
|
||||
suggestedWidgets?: SuggestedWidget[];
|
||||
readableError?: string;
|
||||
pluginErrorDetails?: PluginErrorDetails;
|
||||
request?: ActionApiResponseReq;
|
||||
};
|
||||
actionResponse?: ActionResponse;
|
||||
runErrorMessage: string | undefined;
|
||||
location: {
|
||||
state: any;
|
||||
|
|
@ -357,11 +289,6 @@ interface QueryFormProps {
|
|||
formData: SaaSAction | QueryAction;
|
||||
responseDisplayFormat: { title: string; value: string };
|
||||
responseDataTypes: { key: string; title: string }[];
|
||||
updateActionResponseDisplayFormat: ({
|
||||
field,
|
||||
id,
|
||||
value,
|
||||
}: UpdateActionPropertyActionPayload) => void;
|
||||
datasourceId: string;
|
||||
showCloseEditor: boolean;
|
||||
}
|
||||
|
|
@ -382,10 +309,10 @@ type Props = EditorJSONtoFormProps &
|
|||
export function EditorJSONtoForm(props: Props) {
|
||||
const {
|
||||
actionName,
|
||||
actionResponse,
|
||||
dataSources,
|
||||
documentationLink,
|
||||
editorConfig,
|
||||
executedQueryData,
|
||||
formName,
|
||||
handleSubmit,
|
||||
isRunning,
|
||||
|
|
@ -397,7 +324,6 @@ export function EditorJSONtoForm(props: Props) {
|
|||
runErrorMessage,
|
||||
settingConfig,
|
||||
uiComponent,
|
||||
updateActionResponseDisplayFormat,
|
||||
} = props;
|
||||
|
||||
const {
|
||||
|
|
@ -408,16 +334,8 @@ export function EditorJSONtoForm(props: Props) {
|
|||
saveActionName,
|
||||
} = 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 }>();
|
||||
|
||||
// fetch the error count from the store.
|
||||
const errorCount = useSelector(getErrorCount);
|
||||
|
||||
const actions: Action[] = useSelector((state: AppState) =>
|
||||
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,
|
||||
);
|
||||
const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled);
|
||||
const [showResponseOnFirstLoad, setShowResponseOnFirstLoad] =
|
||||
useState<boolean>(false);
|
||||
|
||||
const isChangePermitted = getHasManageActionPermission(
|
||||
isFeatureEnabled,
|
||||
|
|
@ -440,9 +360,6 @@ export function EditorJSONtoForm(props: Props) {
|
|||
(state: AppState) => getCurrentAppWorkspace(state).userPermissions ?? [],
|
||||
);
|
||||
|
||||
const [showResponseOnFirstLoad, setShowResponseOnFirstLoad] =
|
||||
useState<boolean>(false);
|
||||
|
||||
const canCreateDatasource = getHasCreateDatasourcePermission(
|
||||
isFeatureEnabled,
|
||||
userWorkspacePermissions,
|
||||
|
|
@ -470,54 +387,24 @@ export function EditorJSONtoForm(props: Props) {
|
|||
(!actionBody && SQL_DATASOURCES.includes(currentActionPluginName)) ||
|
||||
!isExecutePermitted;
|
||||
|
||||
// Query is executed even once during the session, show the response data.
|
||||
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;
|
||||
}
|
||||
}
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// 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
|
||||
useEffect(() => {
|
||||
// output and responseDisplayFormat is present only when query has response available
|
||||
// actionResponse and responseDisplayFormat is present only when query has response available
|
||||
if (
|
||||
responseDisplayFormat &&
|
||||
!!responseDisplayFormat?.title &&
|
||||
output &&
|
||||
actionResponse &&
|
||||
actionResponse.isExecutionSuccess &&
|
||||
!showResponseOnFirstLoad
|
||||
) {
|
||||
dispatch(showDebugger(true));
|
||||
dispatch(setDebuggerSelectedTab(DEBUGGER_TAB_KEYS.RESPONSE_TAB));
|
||||
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
|
||||
// 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]);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleDocumentationClick = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
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);
|
||||
|
||||
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.
|
||||
const actionSource: SourceEntity = {
|
||||
type: SOURCE_ENTITY_TYPE.ACTION,
|
||||
|
|
@ -748,128 +578,6 @@ export function EditorJSONtoForm(props: Props) {
|
|||
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 pluginImages = useSelector(getPluginImages);
|
||||
|
|
@ -907,18 +615,10 @@ export function EditorJSONtoForm(props: Props) {
|
|||
|
||||
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
|
||||
// or if any of the flags are true, We should open the actionpane by default.
|
||||
const shouldOpenActionPaneByDefault =
|
||||
((hasDependencies || !!output) && !guidedTourEnabled) ||
|
||||
((hasDependencies || !!actionResponse) && !guidedTourEnabled) ||
|
||||
currentActionPluginName !== PluginName.SMTP;
|
||||
|
||||
// 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>
|
||||
{renderDebugger &&
|
||||
selectedResponseTab !== DEBUGGER_TAB_KEYS.HEADER_TAB && (
|
||||
<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>
|
||||
<QueryResponseTabView
|
||||
actionName={actionName}
|
||||
actionResponse={actionResponse}
|
||||
actionSource={actionSource}
|
||||
currentActionConfig={currentActionConfig}
|
||||
isExecutePermitted={isExecutePermitted}
|
||||
isRunning={isRunning}
|
||||
responseDataTypes={responseDataTypes}
|
||||
responseDisplayFormat={responseDisplayFormat}
|
||||
responseTabOnRunClick={responseTabOnRunClick}
|
||||
runErrorMessage={runErrorMessage}
|
||||
/>
|
||||
)}
|
||||
</SecondaryWrapper>
|
||||
</div>
|
||||
|
|
@ -1125,9 +791,9 @@ export function EditorJSONtoForm(props: Props) {
|
|||
context={DatasourceStructureContext.QUERY_EDITOR}
|
||||
datasourceId={props.datasourceId}
|
||||
hasConnections={hasDependencies}
|
||||
hasResponse={!!output}
|
||||
hasResponse={!!actionResponse}
|
||||
pluginId={props.pluginId}
|
||||
suggestedWidgets={executedQueryData?.suggestedWidgets}
|
||||
suggestedWidgets={actionResponse?.suggestedWidgets}
|
||||
/>
|
||||
</SidebarWrapper>
|
||||
</Wrapper>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
import type { EditorJSONtoFormProps } from "./EditorJSONtoForm";
|
||||
import { EditorJSONtoForm } from "./EditorJSONtoForm";
|
||||
import { getFormEvaluationState } from "selectors/formSelectors";
|
||||
import { actionResponseDisplayDataFormats } from "../utils";
|
||||
|
||||
const valueSelector = formValueSelector(QUERY_EDITOR_FORM_NAME);
|
||||
const mapStateToProps = (state: AppState, props: any) => {
|
||||
|
|
@ -20,27 +21,8 @@ const mapStateToProps = (state: AppState, props: any) => {
|
|||
const pluginId = valueSelector(state, "datasource.pluginId");
|
||||
const selectedDbId = valueSelector(state, "datasource.id");
|
||||
const actionData = getActionData(state, actionId);
|
||||
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 = {
|
||||
title: "",
|
||||
value: "",
|
||||
};
|
||||
}
|
||||
const { responseDataTypes, responseDisplayFormat } =
|
||||
actionResponseDisplayDataFormats(actionData);
|
||||
|
||||
const responseTypes = getPluginResponseTypes(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 { getCurrentPageId } from "selectors/editorSelectors";
|
||||
import type { WidgetCardProps } from "widgets/BaseWidget";
|
||||
import type { ActionResponse } from "api/ActionAPI";
|
||||
|
||||
export const draggableElement = (
|
||||
id: string,
|
||||
|
|
@ -303,3 +304,35 @@ export const groupWidgetCardsByTags = (widgetCards: WidgetCardProps[]) => {
|
|||
export const transformTextToSentenceCase = (s: string) => {
|
||||
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,
|
||||
} from "@appsmith/selectors/environmentSelectors";
|
||||
import { EVAL_WORKER_ACTIONS } from "@appsmith/workers/Evaluation/evalWorkerActions";
|
||||
import { getIsActionCreatedInApp } from "@appsmith/utils/getIsActionCreatedInApp";
|
||||
|
||||
enum ActionResponseDataTypes {
|
||||
BINARY = "BINARY",
|
||||
|
|
@ -560,7 +561,7 @@ export default function* executePluginActionTriggerSaga(
|
|||
});
|
||||
const executePluginActionResponse: ExecutePluginActionResponse = yield call(
|
||||
executePluginActionSaga,
|
||||
action.id,
|
||||
action,
|
||||
pagination,
|
||||
params,
|
||||
);
|
||||
|
|
@ -733,11 +734,12 @@ interface RunActionError {
|
|||
clientDefinedError?: boolean;
|
||||
}
|
||||
|
||||
function* runActionSaga(
|
||||
export function* runActionSaga(
|
||||
reduxAction: ReduxAction<{
|
||||
id: string;
|
||||
paginationField: PaginationField;
|
||||
paginationField?: PaginationField;
|
||||
skipOpeningDebugger: boolean;
|
||||
action?: Action;
|
||||
}>,
|
||||
) {
|
||||
const actionId = reduxAction.payload.id;
|
||||
|
|
@ -754,10 +756,12 @@ function* runActionSaga(
|
|||
const currentEnvDetails: { id: string; name: string } = yield select(
|
||||
getCurrentEnvironmentDetails,
|
||||
);
|
||||
const actionObject = shouldBeDefined<Action>(
|
||||
yield select(getAction, actionId),
|
||||
`action not found for id - ${actionId}`,
|
||||
);
|
||||
const actionObject =
|
||||
reduxAction.payload.action ||
|
||||
shouldBeDefined<Action>(
|
||||
yield select(getAction, actionId),
|
||||
`action not found for id - ${actionId}`,
|
||||
);
|
||||
const plugin: Plugin = yield select(getPlugin, actionObject?.pluginId);
|
||||
const datasource: Datasource = yield select(
|
||||
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.
|
||||
if (!reduxAction.payload.skipOpeningDebugger) {
|
||||
yield call(openDebugger);
|
||||
|
|
@ -802,7 +806,7 @@ function* runActionSaga(
|
|||
try {
|
||||
const executePluginActionResponse: ExecutePluginActionResponse = yield call(
|
||||
executePluginActionSaga,
|
||||
id,
|
||||
actionObject,
|
||||
paginationField,
|
||||
{},
|
||||
true,
|
||||
|
|
@ -1119,7 +1123,7 @@ function* executePageLoadAction(pageAction: PageAction) {
|
|||
|
||||
try {
|
||||
const executePluginActionResponse: ExecutePluginActionResponse =
|
||||
yield call(executePluginActionSaga, pageAction);
|
||||
yield call(executePluginActionSaga, action);
|
||||
payload = executePluginActionResponse.payload;
|
||||
isError = executePluginActionResponse.isError;
|
||||
} catch (e) {
|
||||
|
|
@ -1293,24 +1297,12 @@ interface ExecutePluginActionResponse {
|
|||
* PluginActionExecutionError which needs to be handled by any saga that calls this.
|
||||
* */
|
||||
function* executePluginActionSaga(
|
||||
actionOrActionId: PageAction | string,
|
||||
pluginAction: Action,
|
||||
paginationField?: PaginationField,
|
||||
params?: Record<string, unknown>,
|
||||
isUserInitiated?: boolean,
|
||||
) {
|
||||
let pluginAction;
|
||||
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;
|
||||
}
|
||||
const actionId = pluginAction.id;
|
||||
|
||||
if (pluginAction.confirmBeforeExecute) {
|
||||
const modalPayload = {
|
||||
|
|
@ -1392,6 +1384,7 @@ function* executePluginActionSaga(
|
|||
executePluginActionSuccess({
|
||||
id: actionId,
|
||||
response: payload,
|
||||
isActionCreatedInApp: getIsActionCreatedInApp(pluginAction),
|
||||
}),
|
||||
);
|
||||
|
||||
|
|
@ -1454,6 +1447,7 @@ function* executePluginActionSaga(
|
|||
executePluginActionSuccess({
|
||||
id: actionId,
|
||||
response: EMPTY_RESPONSE,
|
||||
isActionCreatedInApp: getIsActionCreatedInApp(pluginAction),
|
||||
}),
|
||||
);
|
||||
yield put(
|
||||
|
|
|
|||
|
|
@ -15,3 +15,6 @@ export const getApiPaneConfigSelectedTabIndex = (state: AppState) =>
|
|||
|
||||
export const getApiRightPaneSelectedTab = (state: AppState) =>
|
||||
state.ui.apiPane.selectedRightPaneTab;
|
||||
|
||||
export const getIsRunning = (state: AppState, apiId: string) =>
|
||||
state.ui.apiPane.isRunning[apiId];
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user