feat: enable post run actions for plugin queries (#39325)
## Description This PR enables the response pane of DB queries in appsmith to show a form container once the action is executed and the server returns with a `postRunAction` config. The contents to be shown inside the container are controlled by the `postRunAction` config. The config has a unique identifier which should be registered in the client. Based on this registry, client can render the required form inside the container. If no form is found, the container is not shown. Fixes #39402 ## Automation /test sanity ### 🔍 Cypress test results <!-- This is an auto-generated comment: Cypress test results --> > [!TIP] > 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉 > Workflow run: <https://github.com/appsmithorg/appsmith/actions/runs/13493868532> > Commit: 75b13354c6147717360831fff06b60063d14c3ed > <a href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=13493868532&attempt=1" target="_blank">Cypress dashboard</a>. > Tags: `@tag.Sanity` > Spec: > <hr>Mon, 24 Feb 2025 09:13:40 UTC <!-- end of auto-generated comment: Cypress test results --> ## Communication Should the DevRel and Marketing teams inform users about this change? - [ ] Yes - [ ] No <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Enhanced action response experience: The application now conditionally displays an interactive post-run action interface. When additional configuration is detected after an action execution, a streamlined modal form appears to guide you through follow-up tasks, making post-action interactions more intuitive and visible. This update offers a smoother, clearer workflow where supplementary steps are seamlessly integrated into your experience. - **Tests** - Added new test cases for the `Response` component to validate the rendering logic based on post-run actions. - Introduced tests for utility functions related to post-run actions to ensure correct behavior and output. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
parent
0401607f50
commit
f3eca9b234
|
|
@ -28,6 +28,13 @@ const defaultProps = {
|
|||
responseTabHeight: 200,
|
||||
};
|
||||
|
||||
// mock the postrunactionmap
|
||||
jest.mock("ee/components/PostActionRunComponents", () => ({
|
||||
PostRunActionComponentMap: {
|
||||
test_modal: () => <div data-testid="t--post-run-action-test-modal-form" />,
|
||||
},
|
||||
}));
|
||||
|
||||
const storeData = getIDETestState({});
|
||||
|
||||
describe("Response", () => {
|
||||
|
|
@ -36,6 +43,7 @@ describe("Response", () => {
|
|||
|
||||
beforeEach(() => {
|
||||
store = mockStore(storeData);
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
/** Test use prepared statement warning **/
|
||||
|
|
@ -208,4 +216,152 @@ describe("Response", () => {
|
|||
container.querySelector("[data-testid='t--prepared-statement-warning']"),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("6. Should show post run action container when post run action exists", () => {
|
||||
const postRunAction = {
|
||||
type: "FORM",
|
||||
name: "test_modal",
|
||||
};
|
||||
const actionResponse = {
|
||||
isExecutionSuccess: true,
|
||||
body: [{ key: "value" }],
|
||||
postRunAction,
|
||||
dataTypes: [{ dataType: "JSON" }],
|
||||
responseDisplayFormat: "JSON",
|
||||
} as unknown as ActionResponse;
|
||||
|
||||
store = mockStore({
|
||||
...storeData,
|
||||
entities: {
|
||||
...storeData.entities,
|
||||
actions: [
|
||||
{
|
||||
config: {
|
||||
id: "test-action-id",
|
||||
name: "Test Action",
|
||||
},
|
||||
isLoading: false,
|
||||
data: actionResponse,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const props = {
|
||||
...defaultProps,
|
||||
actionResponse,
|
||||
currentContentType: "JSON",
|
||||
};
|
||||
|
||||
const { getByTestId } = render(
|
||||
<Provider store={store}>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<Router>
|
||||
<Response {...props} />
|
||||
</Router>
|
||||
</ThemeProvider>
|
||||
</Provider>,
|
||||
);
|
||||
|
||||
// Check if post run action container is showing
|
||||
expect(getByTestId("t--post-run-action-container")).not.toBeNull();
|
||||
expect(getByTestId("t--post-run-action-test-modal-form")).not.toBeNull();
|
||||
});
|
||||
|
||||
it("7. Should not show post run action container when post run action doesn't exist", () => {
|
||||
const actionResponse = {
|
||||
isExecutionSuccess: true,
|
||||
body: [{ key: "value" }],
|
||||
dataTypes: [{ dataType: "JSON" }],
|
||||
responseDisplayFormat: "JSON",
|
||||
} as unknown as ActionResponse;
|
||||
|
||||
store = mockStore({
|
||||
...storeData,
|
||||
entities: {
|
||||
...storeData.entities,
|
||||
actions: [
|
||||
{
|
||||
config: {
|
||||
id: "test-action-id",
|
||||
name: "Test Action",
|
||||
},
|
||||
isLoading: false,
|
||||
data: actionResponse,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const props = {
|
||||
...defaultProps,
|
||||
actionResponse,
|
||||
};
|
||||
|
||||
const { container } = render(
|
||||
<Provider store={store}>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<Router>
|
||||
<Response {...props} />
|
||||
</Router>
|
||||
</ThemeProvider>
|
||||
</Provider>,
|
||||
);
|
||||
|
||||
// Check if post run action container is not showing
|
||||
expect(
|
||||
container.querySelector("[data-testid='t--post-run-action-container']"),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("8. Should not show post run action container when correct mapping is not found", () => {
|
||||
const postRunAction = {
|
||||
type: "FORM",
|
||||
name: "invalid_modal",
|
||||
};
|
||||
const actionResponse = {
|
||||
isExecutionSuccess: true,
|
||||
body: [{ key: "value" }],
|
||||
postRunAction,
|
||||
dataTypes: [{ dataType: "JSON" }],
|
||||
responseDisplayFormat: "JSON",
|
||||
} as unknown as ActionResponse;
|
||||
|
||||
store = mockStore({
|
||||
...storeData,
|
||||
entities: {
|
||||
...storeData.entities,
|
||||
actions: [
|
||||
{
|
||||
config: {
|
||||
id: "test-action-id",
|
||||
name: "Test Action",
|
||||
},
|
||||
isLoading: false,
|
||||
data: actionResponse,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const props = {
|
||||
...defaultProps,
|
||||
actionResponse,
|
||||
};
|
||||
|
||||
const { container } = render(
|
||||
<Provider store={store}>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<Router>
|
||||
<Response {...props} />
|
||||
</Router>
|
||||
</ThemeProvider>
|
||||
</Provider>,
|
||||
);
|
||||
|
||||
// Check if post run action container is not showing
|
||||
expect(
|
||||
container.querySelector("[data-testid='t--post-run-action-container']"),
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ import { RESPONSE_TABLE_HEIGHT_OFFSET } from "./constants";
|
|||
import * as Styled from "./styles";
|
||||
import { checkForPreparedStatement, parseActionResponse } from "./utils";
|
||||
import ActionExecutionInProgressView from "./components/ActionExecutionInProgressView";
|
||||
import { checkForPostRunAction } from "./utils/postRunActionsUtil";
|
||||
import PostActionRunContainer from "./components/PostActionRunContainer";
|
||||
|
||||
interface ResponseProps {
|
||||
action: Action;
|
||||
|
|
@ -126,6 +128,9 @@ export function Response(props: ResponseProps) {
|
|||
checkForPreparedStatement(action) && errorMessage,
|
||||
);
|
||||
|
||||
const showPostRunAction =
|
||||
actionResponse && checkForPostRunAction(actionResponse?.postRunAction);
|
||||
|
||||
const actionSource: SourceEntity = useMemo(
|
||||
() => ({
|
||||
type: ENTITY_TYPE.ACTION,
|
||||
|
|
@ -267,6 +272,11 @@ export function Response(props: ResponseProps) {
|
|||
}
|
||||
/>
|
||||
</Styled.Response>
|
||||
{showPostRunAction && (
|
||||
<PostActionRunContainer
|
||||
postRunAction={actionResponse?.postRunAction}
|
||||
/>
|
||||
)}
|
||||
<ContentTypeSelector
|
||||
contentTypeOptions={contentTypeOptions}
|
||||
currentContentType={currentContentType}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
import React from "react";
|
||||
import { getPostRunActionName } from "../utils/postRunActionsUtil";
|
||||
import styled from "styled-components";
|
||||
import { PostRunActionComponentMap } from "ee/components/PostActionRunComponents";
|
||||
import type { PostRunActionNamesInterface } from "ee/components/PostActionRunComponents/types";
|
||||
import type { PostActionRunConfig } from "api/types";
|
||||
|
||||
interface Props {
|
||||
postRunAction?: PostActionRunConfig;
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
border: 1px solid var(--ads-v2-color-border);
|
||||
z-index: 100;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
padding-bottom: var(--ads-bottom-bar-height);
|
||||
background-color: var(--ads-v2-color-bg);
|
||||
`;
|
||||
|
||||
export default function PostActionRunContainer({ postRunAction }: Props) {
|
||||
if (!postRunAction) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const name: string = getPostRunActionName(postRunAction);
|
||||
const Component = PostRunActionComponentMap[
|
||||
name as PostRunActionNamesInterface
|
||||
] as React.ComponentType;
|
||||
|
||||
if (!Component) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Container data-testid="t--post-run-action-container">
|
||||
<Component />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
import {
|
||||
checkForPostRunAction,
|
||||
getPostRunActionName,
|
||||
} from "./postRunActionsUtil";
|
||||
import type { PostActionRunConfig } from "api/types";
|
||||
|
||||
describe("checkForPostRunAction", () => {
|
||||
it("should return true for valid post run action", () => {
|
||||
const validAction: PostActionRunConfig = {
|
||||
type: "FORM",
|
||||
name: "some_name",
|
||||
};
|
||||
|
||||
expect(checkForPostRunAction(validAction)).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false for undefined input", () => {
|
||||
expect(checkForPostRunAction(undefined)).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false for input without type property", () => {
|
||||
const invalidAction = {
|
||||
name: "some_name",
|
||||
};
|
||||
|
||||
expect(checkForPostRunAction(invalidAction as PostActionRunConfig)).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getPostRunActionName", () => {
|
||||
it("should return name for valid post run action", () => {
|
||||
const validAction: PostActionRunConfig = {
|
||||
type: "FORM",
|
||||
name: "test_action",
|
||||
};
|
||||
|
||||
expect(getPostRunActionName(validAction)).toBe("test_action");
|
||||
});
|
||||
|
||||
it("should return empty string for undefined input", () => {
|
||||
expect(getPostRunActionName(undefined)).toBe("");
|
||||
});
|
||||
|
||||
it("should return empty string for action without name", () => {
|
||||
const actionWithoutName: PostActionRunConfig = {
|
||||
type: "FORM",
|
||||
name: "",
|
||||
};
|
||||
|
||||
expect(getPostRunActionName(actionWithoutName)).toBe("");
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import type { PostActionRunConfig } from "api/types";
|
||||
|
||||
export function checkForPostRunAction(postRunAction?: PostActionRunConfig) {
|
||||
if (
|
||||
postRunAction &&
|
||||
typeof postRunAction === "object" &&
|
||||
"type" in postRunAction
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getPostRunActionName(postRunAction?: PostActionRunConfig) {
|
||||
if (!postRunAction) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const { name } = postRunAction;
|
||||
|
||||
if (!name) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import type { Action, ActionViewMode } from "entities/Action";
|
|||
import type { APIRequest } from "constants/AppsmithActionConstants/ActionConstants";
|
||||
import type { WidgetType } from "constants/WidgetConstants";
|
||||
import type { ActionParentEntityTypeInterface } from "ee/entities/Engine/actionHelpers";
|
||||
import type { PostActionRunConfig } from "./types";
|
||||
|
||||
export interface Property {
|
||||
key: string;
|
||||
|
|
@ -86,6 +87,7 @@ export interface ActionResponse {
|
|||
readableError?: string;
|
||||
responseDisplayFormat?: string;
|
||||
pluginErrorDetails?: PluginErrorDetails;
|
||||
postRunAction?: PostActionRunConfig;
|
||||
}
|
||||
|
||||
//This contains the error details from the plugin that is sent to the client in the response
|
||||
|
|
|
|||
|
|
@ -22,3 +22,9 @@ export type AxiosResponseData<T> = AxiosResponse<ApiResponse<T>>["data"];
|
|||
export type ErrorHandler = (
|
||||
error: AxiosError<ApiResponse>,
|
||||
) => Promise<unknown | null>;
|
||||
|
||||
export interface PostActionRunConfig {
|
||||
type: "FORM";
|
||||
name: string;
|
||||
config?: Record<string, unknown>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
import type { PostRunActionNamesInterface } from "./types";
|
||||
|
||||
export const PostRunActionComponentMap: Record<
|
||||
PostRunActionNamesInterface,
|
||||
React.ElementType
|
||||
> = {};
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
export const PostRunActionNames = {} as const;
|
||||
|
||||
export type PostRunActionNamesInterface =
|
||||
(typeof PostRunActionNames)[keyof typeof PostRunActionNames];
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from "ce/components/PostActionRunComponents";
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from "ce/components/PostActionRunComponents/types";
|
||||
|
|
@ -81,10 +81,7 @@ import type {
|
|||
LayoutOnLoadActionErrors,
|
||||
PageAction,
|
||||
} from "constants/AppsmithActionConstants/ActionConstants";
|
||||
import {
|
||||
EventType,
|
||||
RESP_HEADER_DATATYPE,
|
||||
} from "constants/AppsmithActionConstants/ActionConstants";
|
||||
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
|
||||
import {
|
||||
getCurrentApplicationId,
|
||||
getCurrentBasePageId,
|
||||
|
|
@ -172,10 +169,7 @@ import {
|
|||
selectGitConnectModalOpen,
|
||||
selectGitOpsModalOpen,
|
||||
} from "selectors/gitModSelectors";
|
||||
|
||||
enum ActionResponseDataTypes {
|
||||
BINARY = "BINARY",
|
||||
}
|
||||
import { createActionExecutionResponse } from "./PluginActionSagaUtils";
|
||||
|
||||
interface FilePickerInstumentationObject {
|
||||
numberOfFiles: number;
|
||||
|
|
@ -208,32 +202,6 @@ export const getActionTimeout = (
|
|||
return undefined;
|
||||
};
|
||||
|
||||
const createActionExecutionResponse = (
|
||||
response: ActionExecutionResponse,
|
||||
): ActionResponse => {
|
||||
const payload = response.data;
|
||||
|
||||
if (payload.statusCode === "200 OK" && payload.hasOwnProperty("headers")) {
|
||||
const respHeaders = payload.headers;
|
||||
|
||||
if (
|
||||
respHeaders.hasOwnProperty(RESP_HEADER_DATATYPE) &&
|
||||
respHeaders[RESP_HEADER_DATATYPE].length > 0 &&
|
||||
respHeaders[RESP_HEADER_DATATYPE][0] === ActionResponseDataTypes.BINARY &&
|
||||
getType(payload.body) === Types.STRING
|
||||
) {
|
||||
// Decoding from base64 to handle the binary files because direct
|
||||
// conversion of binary files to string causes corruption in the final output
|
||||
// this is to only handle the download of binary files
|
||||
payload.body = atob(payload.body as string);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...payload,
|
||||
...response.clientMeta,
|
||||
};
|
||||
};
|
||||
const isErrorResponse = (response: ActionExecutionResponse) => {
|
||||
return !response.data.isExecutionSuccess;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
import { put } from "redux-saga/effects";
|
||||
import { setDefaultActionDisplayFormat } from "./PluginActionSagaUtils";
|
||||
import {
|
||||
RESP_HEADER_DATATYPE,
|
||||
setDefaultActionDisplayFormat,
|
||||
} from "./PluginActionSagaUtils";
|
||||
import { createActionExecutionResponse } from "./PluginActionSagaUtils";
|
||||
import { ActionResponseDataTypes } from "./PluginActionSagaUtils";
|
||||
import { HTTP_METHOD } from "PluginActionEditor/constants/CommonApiConstants";
|
||||
|
||||
const actionid = "test-id";
|
||||
|
||||
|
|
@ -99,3 +105,138 @@ describe("PluginActionSagasUtils", () => {
|
|||
expect(generator.next().value).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("createActionExecutionResponse", () => {
|
||||
it("should handle regular response without binary data", () => {
|
||||
const response = {
|
||||
data: {
|
||||
body: { key: "value" },
|
||||
statusCode: "200 OK",
|
||||
headers: {},
|
||||
isExecutionSuccess: true,
|
||||
request: {
|
||||
url: "https://example.com",
|
||||
headers: {},
|
||||
body: {},
|
||||
httpMethod: HTTP_METHOD.GET,
|
||||
},
|
||||
dataTypes: [{ dataType: "JSON" }],
|
||||
},
|
||||
clientMeta: {
|
||||
duration: "100",
|
||||
size: "50",
|
||||
},
|
||||
responseMeta: {
|
||||
status: 200,
|
||||
success: true,
|
||||
},
|
||||
};
|
||||
|
||||
const result = createActionExecutionResponse(response);
|
||||
|
||||
expect(result).toEqual({
|
||||
...response.data,
|
||||
...response.clientMeta,
|
||||
});
|
||||
});
|
||||
|
||||
it("should decode base64 binary response", () => {
|
||||
const rawData = "test binary data";
|
||||
const base64String = btoa(rawData);
|
||||
const response = {
|
||||
data: {
|
||||
body: base64String,
|
||||
statusCode: "200 OK",
|
||||
headers: {
|
||||
[RESP_HEADER_DATATYPE]: [ActionResponseDataTypes.BINARY],
|
||||
},
|
||||
isExecutionSuccess: true,
|
||||
request: {
|
||||
url: "https://example.com",
|
||||
headers: {},
|
||||
body: {},
|
||||
httpMethod: HTTP_METHOD.GET,
|
||||
},
|
||||
dataTypes: [{ dataType: "JSON" }],
|
||||
},
|
||||
clientMeta: {
|
||||
duration: "100",
|
||||
size: "50",
|
||||
},
|
||||
responseMeta: {
|
||||
status: 200,
|
||||
success: true,
|
||||
},
|
||||
};
|
||||
|
||||
const result = createActionExecutionResponse(response);
|
||||
|
||||
expect(result.body).toBe(rawData);
|
||||
});
|
||||
|
||||
it("should not decode response if status code is not 200 OK", () => {
|
||||
const base64String = btoa("test binary data");
|
||||
const response = {
|
||||
data: {
|
||||
body: base64String,
|
||||
statusCode: "404 Not Found",
|
||||
headers: {
|
||||
[RESP_HEADER_DATATYPE]: [ActionResponseDataTypes.BINARY],
|
||||
},
|
||||
isExecutionSuccess: true,
|
||||
request: {
|
||||
url: "https://example.com",
|
||||
headers: {},
|
||||
body: {},
|
||||
httpMethod: HTTP_METHOD.GET,
|
||||
},
|
||||
dataTypes: [{ dataType: "JSON" }],
|
||||
},
|
||||
clientMeta: {
|
||||
duration: "100",
|
||||
size: "50",
|
||||
},
|
||||
responseMeta: {
|
||||
status: 200,
|
||||
success: true,
|
||||
},
|
||||
};
|
||||
|
||||
const result = createActionExecutionResponse(response);
|
||||
|
||||
expect(result.body).toBe(base64String);
|
||||
});
|
||||
|
||||
it("should not decode response if header type is not BINARY", () => {
|
||||
const base64String = btoa("test binary data");
|
||||
const response = {
|
||||
data: {
|
||||
body: base64String,
|
||||
statusCode: "200 OK",
|
||||
headers: {
|
||||
[RESP_HEADER_DATATYPE]: ["JSON"],
|
||||
},
|
||||
isExecutionSuccess: true,
|
||||
request: {
|
||||
url: "https://example.com",
|
||||
headers: {},
|
||||
body: {},
|
||||
httpMethod: HTTP_METHOD.GET,
|
||||
},
|
||||
dataTypes: [{ dataType: "JSON" }],
|
||||
},
|
||||
clientMeta: {
|
||||
duration: "100",
|
||||
size: "50",
|
||||
},
|
||||
responseMeta: {
|
||||
status: 200,
|
||||
success: true,
|
||||
},
|
||||
};
|
||||
|
||||
const result = createActionExecutionResponse(response);
|
||||
|
||||
expect(result.body).toBe(base64String);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,8 +1,14 @@
|
|||
import { put } from "redux-saga/effects";
|
||||
import { setActionResponseDisplayFormat } from "actions/pluginActionActions";
|
||||
import type { ActionResponse } from "api/ActionAPI";
|
||||
import type { ActionExecutionResponse, ActionResponse } from "api/ActionAPI";
|
||||
import type { Plugin } from "entities/Plugin";
|
||||
import { getType, Types } from "utils/TypeHelpers";
|
||||
|
||||
export enum ActionResponseDataTypes {
|
||||
BINARY = "BINARY",
|
||||
}
|
||||
|
||||
export const RESP_HEADER_DATATYPE = "X-APPSMITH-DATATYPE";
|
||||
export function* setDefaultActionDisplayFormat(
|
||||
actionId: string,
|
||||
plugin: Plugin | undefined,
|
||||
|
|
@ -24,3 +30,30 @@ export function* setDefaultActionDisplayFormat(
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const createActionExecutionResponse = (
|
||||
response: ActionExecutionResponse,
|
||||
): ActionResponse => {
|
||||
const payload = response.data;
|
||||
|
||||
if (payload.statusCode === "200 OK" && payload.hasOwnProperty("headers")) {
|
||||
const respHeaders = payload.headers;
|
||||
|
||||
if (
|
||||
respHeaders.hasOwnProperty(RESP_HEADER_DATATYPE) &&
|
||||
respHeaders[RESP_HEADER_DATATYPE].length > 0 &&
|
||||
respHeaders[RESP_HEADER_DATATYPE][0] === ActionResponseDataTypes.BINARY &&
|
||||
getType(payload.body) === Types.STRING
|
||||
) {
|
||||
// Decoding from base64 to handle the binary files because direct
|
||||
// conversion of binary files to string causes corruption in the final output
|
||||
// this is to only handle the download of binary files
|
||||
payload.body = atob(payload.body as string);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...payload,
|
||||
...response.clientMeta,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user