Fix content type switching to raw when custom

This commit is contained in:
Hetu Nandu 2020-05-08 12:54:27 +00:00
parent 8d4a223dd5
commit ff2f32290f
6 changed files with 398 additions and 308 deletions

View File

@ -107,13 +107,13 @@
"build": "./build.sh",
"build-local": "craco --max-old-space-size=4096 build --config craco.build.config.js",
"build-staging": "REACT_APP_BASE_URL=https://release-api.appsmith.com REACT_APP_ENVIRONMENT=STAGING craco --max-old-space-size=4096 build --config craco.build.config.js",
"test": "cypress/test.sh",
"test": "npm run test-jest && cypress/test.sh",
"eject": "react-scripts eject",
"start-prod": "REACT_APP_BASE_URL=https://api.appsmith.com REACT_APP_ENVIRONMENT=PRODUCTION craco start",
"storybook": "start-storybook",
"cytest": "REACT_APP_TESTING=TESTING REACT_APP_ENVIRONMENT=DEVELOPMENT craco start & ./node_modules/.bin/cypress open",
"build-storybook": "build-storybook -c .storybook -o .storybook-out",
"test-jest": "./node_modules/jest/bin/jest.js -b"
"test-jest": "./node_modules/jest/bin/jest.js -b --colors"
},
"resolution": {
"jest": "24.8.0"

View File

@ -125,17 +125,16 @@ const PostBodyData = (props: Props) => {
)}
{displayFormat?.value === POST_BODY_FORMAT_OPTIONS[2].value && (
<Field
name="actionConfiguration.body[2]"
component="textarea"
rows={10}
style={{
resize: "none",
overflow: "auto",
width: "95%",
marginLeft: 5,
}}
/>
<React.Fragment>
<JSONEditorFieldWrapper>
<DynamicTextField
name="actionConfiguration.body[2]"
height={300}
allowTabIndent
singleLine={false}
/>
</JSONEditorFieldWrapper>
</React.Fragment>
)}
</PostbodyContainer>
);
@ -172,7 +171,7 @@ export default connect((state: AppState) => {
return {
displayFormat:
extraFormData["displayFormat"] || POST_BODY_FORMAT_OPTIONS[0],
extraFormData["displayFormat"] || POST_BODY_FORMAT_OPTIONS[2],
contentType,
apiId,
};

View File

@ -460,9 +460,7 @@ export function* updateActionSaga(
const { data } = actionPayload.payload;
let action = data;
if (isApi) {
const state = yield select();
const extraFormData = state.ui.apiPane.extraformData[action.id];
action = transformRestAction(data, extraFormData);
action = transformRestAction(data);
}
const response: GenericApiResponse<RestAction> = yield ActionAPI.updateAPI(
action,

View File

@ -1,68 +1,57 @@
import {
HTTP_METHODS,
POST_BODY_FORMAT_OPTIONS,
} from "constants/ApiEditorConstants";
import { HTTP_METHODS, POST_BODY_FORMATS } from "constants/ApiEditorConstants";
import _ from "lodash";
export const transformRestAction = (data: any, extraFormData?: any): any => {
export const transformRestAction = (data: any): any => {
let action = { ...data };
if (data.actionConfiguration.httpMethod === HTTP_METHODS[0]) {
// GET actions should not save body
if (action.actionConfiguration.httpMethod === HTTP_METHODS[0]) {
delete action.actionConfiguration.body;
}
// Paths should not have query params
if (
data.actionConfiguration.queryParameters &&
data.actionConfiguration.queryParameters.length
action.actionConfiguration.queryParameters &&
action.actionConfiguration.queryParameters.length
) {
const path = data.actionConfiguration.path;
const path = action.actionConfiguration.path;
if (path && path.indexOf("?") > -1) {
action = {
...data,
...action,
actionConfiguration: {
...data.actionConfiguration,
...action.actionConfiguration,
path: path.substr(0, path.indexOf("?")),
},
};
}
}
if (extraFormData && extraFormData?.displayFormat) {
const { displayFormat } = extraFormData;
if (displayFormat.value === POST_BODY_FORMAT_OPTIONS[0].value) {
if (data.actionConfiguration.body && data.actionConfiguration.body[0]) {
const body = data.actionConfiguration.body[0];
action = {
...data,
actionConfiguration: {
...data.actionConfiguration,
body,
},
};
}
} else if (displayFormat.value === POST_BODY_FORMAT_OPTIONS[1].value) {
if (data.actionConfiguration.body && data.actionConfiguration.body[1]) {
const body = data.actionConfiguration.body[1];
if (typeof data.actionConfiguration.body === "object") {
action = {
...data,
actionConfiguration: {
...data.actionConfiguration,
body: JSON.stringify(body),
},
};
}
}
} else if (displayFormat.value === POST_BODY_FORMAT_OPTIONS[2].value) {
if (data.actionConfiguration.body && data.actionConfiguration.body[2]) {
const body = data.actionConfiguration.body[2];
action = {
...data,
actionConfiguration: {
...data.actionConfiguration,
body,
},
};
// Body should send correct format depending on the content type
if (action.actionConfiguration.httpMethod !== HTTP_METHODS[0]) {
let contentType = "raw";
if (
action.actionConfiguration.headers &&
action.actionConfiguration.headers.length
) {
const contentTypeHeader = _.find(action.actionConfiguration.headers, {
key: "content-type",
});
if (contentTypeHeader) {
contentType = contentTypeHeader.value;
}
}
let formatIndex = 2;
if (POST_BODY_FORMATS.includes(contentType)) {
formatIndex = POST_BODY_FORMATS.indexOf(contentType);
}
let body = action.actionConfiguration.body[formatIndex] || undefined;
if (!_.isString(body)) body = JSON.stringify(body);
action = {
...action,
actionConfiguration: {
...action.actionConfiguration,
body,
},
};
}
return action;
};

View File

@ -1,28 +1,9 @@
import { transformRestAction } from "transformers/RestActionTransformer";
import { RestAction } from "api/ActionAPI";
const input: RestAction = {
pageId: "",
pluginId: "",
id: "testId",
datasource: {
id: "testDataSource",
},
name: "testName",
pluginType: "API",
actionConfiguration: {
path: "users?page=1",
queryParameters: [
{
key: "page",
value: "1",
},
],
},
jsonPathKeys: [],
};
// jest.mock("POST_");
const output = {
const BASE_ACTION: RestAction = {
pageId: "",
pluginId: "",
id: "testId",
@ -32,18 +13,135 @@ const output = {
name: "testName",
pluginType: "API",
actionConfiguration: {
httpMethod: "GET",
path: "users",
queryParameters: [
{
key: "page",
value: "1",
},
],
},
jsonPathKeys: [],
};
it("Transform the input", () => {
const result = transformRestAction(input);
expect(result).toEqual(output);
describe("Api action transformer", () => {
it("Removes params from path", () => {
const input: RestAction = {
...BASE_ACTION,
actionConfiguration: {
...BASE_ACTION.actionConfiguration,
path: "users?page=1",
queryParameters: [
{
key: "page",
value: "1",
},
],
},
};
const output = {
...BASE_ACTION,
actionConfiguration: {
...BASE_ACTION.actionConfiguration,
path: "users",
queryParameters: [
{
key: "page",
value: "1",
},
],
},
};
const result = transformRestAction(input);
expect(result).toEqual(output);
});
it("removes body for GET calls", () => {
const input = {
...BASE_ACTION,
actionConfiguration: {
...BASE_ACTION.actionConfiguration,
httpMethod: "GET",
body: [null, null],
},
};
const output = {
...BASE_ACTION,
actionConfiguration: {
...BASE_ACTION.actionConfiguration,
httpMethod: "GET",
},
};
const result = transformRestAction(input);
expect(result).toEqual(output);
});
it("Sets the correct body for JSON display type", () => {
const input = {
...BASE_ACTION,
actionConfiguration: {
...BASE_ACTION.actionConfiguration,
httpMethod: "POST",
headers: [{ key: "content-type", value: "application/json" }],
body: ["{ name: 'test' }", null],
},
};
const output = {
...BASE_ACTION,
actionConfiguration: {
...BASE_ACTION.actionConfiguration,
httpMethod: "POST",
headers: [{ key: "content-type", value: "application/json" }],
body: "{ name: 'test' }",
},
};
const result = transformRestAction(input);
expect(result).toEqual(output);
});
it("Sets the correct body for xxx-form-encoded-data display type", () => {
const input = {
...BASE_ACTION,
actionConfiguration: {
...BASE_ACTION.actionConfiguration,
httpMethod: "POST",
headers: [
{ key: "content-type", value: "application/x-www-form-urlencoded" },
],
body: [{ name: "test" }, [{ key: "hey", value: "ho" }]],
},
};
const output = {
...BASE_ACTION,
actionConfiguration: {
...BASE_ACTION.actionConfiguration,
httpMethod: "POST",
headers: [
{ key: "content-type", value: "application/x-www-form-urlencoded" },
],
body: '[{"key":"hey","value":"ho"}]',
},
};
const result = transformRestAction(input);
expect(result).toEqual(output);
});
it("Sets the correct body for custom/raw display type", () => {
const input = {
...BASE_ACTION,
actionConfiguration: {
...BASE_ACTION.actionConfiguration,
headers: [{ key: "content-type", value: "text/html" }],
httpMethod: "POST",
body: [{ name: "test" }, [{ key: "hey", value: "ho" }], "raw body"],
},
};
const output = {
...BASE_ACTION,
actionConfiguration: {
...BASE_ACTION.actionConfiguration,
headers: [{ key: "content-type", value: "text/html" }],
httpMethod: "POST",
body: "raw body",
},
};
const result = transformRestAction(input);
expect(result).toEqual(output);
});
});

View File

@ -1,214 +1,220 @@
// import RealmExecutor from "jsExecution/RealmExecutor";
import {
mockExecute,
mockRegisterLibrary,
} from "../../test/__mocks__/RealmExecutorMock";
import {
dependencySortedEvaluateDataTree,
getDynamicValue,
getEntityDependencies,
parseDynamicString,
} from "./DynamicBindingUtils";
import { DataTree, ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
import { RenderModes, WidgetTypes } from "constants/WidgetConstants";
// // import RealmExecutor from "jsExecution/RealmExecutor";
// import {
// mockExecute,
// mockRegisterLibrary,
// } from "../../test/__mocks__/RealmExecutorMock";
// import {
// dependencySortedEvaluateDataTree,
// getDynamicValue,
// getEntityDependencies,
// parseDynamicString,
// } from "./DynamicBindingUtils";
// import { DataTree, ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
// import { RenderModes, WidgetTypes } from "constants/WidgetConstants";
//
// jest.mock("jsExecution/RealmExecutor", () => {
// return jest.fn().mockImplementation(() => {
// return { execute: mockExecute, registerLibrary: mockRegisterLibrary };
// });
// });
//
// beforeAll(() => {
// mockRegisterLibrary.mockClear();
// mockExecute.mockClear();
// });
//
// it("Gets the value from the data tree", () => {
// const dynamicBinding = "{{GetUsers.data}}";
// const nameBindingsWithData: DataTree = {
// GetUsers: {
// data: { text: "correct data" },
// config: {
// pluginId: "",
// id: "id",
// name: "text",
// actionConfiguration: {},
// pageId: "",
// jsonPathKeys: [],
// datasource: { id: "" },
// pluginType: "1",
// },
// isLoading: false,
// ENTITY_TYPE: ENTITY_TYPE.ACTION,
// run: jest.fn(),
// },
// };
// const actualValue = { result: { text: "correct data" } };
//
// const value = getDynamicValue(dynamicBinding, nameBindingsWithData);
//
// expect(value).toEqual(actualValue);
// });
//
// describe.each([
// ["{{A}}", ["{{A}}"]],
// ["A {{B}}", ["A ", "{{B}}"]],
// [
// "Hello {{Customer.Name}}, the status for your order id {{orderId}} is {{status}}",
// [
// "Hello ",
// "{{Customer.Name}}",
// ", the status for your order id ",
// "{{orderId}}",
// " is ",
// "{{status}}",
// ],
// ],
// [
// "{{data.map(datum => {return {id: datum}})}}",
// ["{{data.map(datum => {return {id: datum}})}}"],
// ],
// ["{{}}{{}}}", ["{{}}", "{{}}", "}"]],
// ["{{{}}", ["{{{}}"]],
// ["{{ {{", ["{{ {{"]],
// ["}} }}", ["}} }}"]],
// ["}} {{", ["}} {{"]],
// ])("Parse the dynamic string(%s, %j)", (dynamicString, expected) => {
// test(`returns ${expected}`, () => {
// expect(parseDynamicString(dynamicString as string)).toStrictEqual(expected);
// });
// });
//
// const baseWidgetProps = {
// parentColumnSpace: 0,
// parentRowSpace: 0,
// parentId: "0",
// type: WidgetTypes.BUTTON_WIDGET,
// renderMode: RenderModes.CANVAS,
// leftColumn: 0,
// rightColumn: 0,
// topRow: 0,
// bottomRow: 0,
// isLoading: false,
// };
//
// it("evaluates the data tree", () => {
// const input: DataTree = {
// widget1: {
// ...baseWidgetProps,
// widgetId: "1",
// widgetName: "widget1",
// displayValue: "{{widget2.computedProperty}}",
// ENTITY_TYPE: ENTITY_TYPE.WIDGET,
// },
// widget2: {
// ...baseWidgetProps,
// widgetId: "2",
// widgetName: "widget2",
// computedProperty: "{{ widget2.data[widget2.index] }}",
// data: "{{ apiData.data }}",
// index: 2,
// ENTITY_TYPE: ENTITY_TYPE.WIDGET,
// },
// apiData: {
// config: {
// id: "123",
// pageId: "1234",
// datasource: {},
// name: "api",
// actionConfiguration: {},
// jsonPathKeys: [],
// pluginId: "plugin",
// },
// run: (onSuccess, onError) => ({
// type: "RUN_ACTION",
// payload: {
// actionId: "",
// onSuccess: "",
// onError: "",
// },
// }),
// isLoading: false,
// data: ["wrong value", "still wrong", "correct"],
// ENTITY_TYPE: ENTITY_TYPE.ACTION,
// },
// };
//
// const dynamicBindings = {
// "widget1.displayValue": ["widget2.computedProperty"],
// "widget2.computedProperty": ["widget2.data", "widget2.index"],
// "widget2.data": ["apiData.data"],
// };
//
// const sortedDeps = [
// "apiData.data",
// "widget2.data",
// "widget2.index",
// "widget2.computedProperty",
// "widget1.displayValue",
// ];
//
// const output: DataTree = {
// widget1: {
// ...baseWidgetProps,
// widgetId: "1",
// widgetName: "widget1",
// displayValue: "correct",
// ENTITY_TYPE: ENTITY_TYPE.WIDGET,
// },
// widget2: {
// ...baseWidgetProps,
// widgetId: "2",
// widgetName: "widget2",
// computedProperty: "correct",
// data: ["wrong value", "still wrong", "correct"],
// index: 2,
// ENTITY_TYPE: ENTITY_TYPE.WIDGET,
// },
// apiData: {
// config: {
// id: "123",
// pageId: "1234",
// datasource: {},
// name: "api",
// actionConfiguration: {},
// jsonPathKeys: [],
// pluginId: "plugin",
// },
// run: (onSuccess, onError) => ({
// type: "RUN_ACTION",
// payload: {
// actionId: "",
// onSuccess: "",
// onError: "",
// },
// }),
// isLoading: false,
// data: ["wrong value", "still wrong", "correct"],
// ENTITY_TYPE: ENTITY_TYPE.ACTION,
// },
// };
//
// const result = dependencySortedEvaluateDataTree(
// input,
// dynamicBindings,
// sortedDeps,
// );
// expect(result).toEqual(output);
// });
//
// it("finds dependencies of a entity", () => {
// const depMap: Array<[string, string]> = [
// ["Widget5.text", "Widget2.data.visible"],
// ["Widget1.options", "Action1.data"],
// ["Widget2.text", "Widget1.selectedOption"],
// ["Widget3.text", "Widget4.selectedRow.name"],
// ["Widget6.label", "Action1.data.label"],
// ];
// const entity = "Action1";
// const result = ["Widget1", "Widget2", "Widget5", "Widget6"];
//
// const actual = getEntityDependencies(depMap, entity);
//
// expect(actual).toEqual(result);
// });
jest.mock("jsExecution/RealmExecutor", () => {
return jest.fn().mockImplementation(() => {
return { execute: mockExecute, registerLibrary: mockRegisterLibrary };
});
});
beforeAll(() => {
mockRegisterLibrary.mockClear();
mockExecute.mockClear();
});
it("Gets the value from the data tree", () => {
const dynamicBinding = "{{GetUsers.data}}";
const nameBindingsWithData: DataTree = {
GetUsers: {
data: { text: "correct data" },
config: {
pluginId: "",
id: "id",
name: "text",
actionConfiguration: {},
pageId: "",
jsonPathKeys: [],
datasource: { id: "" },
pluginType: "1",
},
isLoading: false,
ENTITY_TYPE: ENTITY_TYPE.ACTION,
run: jest.fn(),
},
};
const actualValue = { result: { text: "correct data" } };
const value = getDynamicValue(dynamicBinding, nameBindingsWithData);
expect(value).toEqual(actualValue);
});
describe.each([
["{{A}}", ["{{A}}"]],
["A {{B}}", ["A ", "{{B}}"]],
[
"Hello {{Customer.Name}}, the status for your order id {{orderId}} is {{status}}",
[
"Hello ",
"{{Customer.Name}}",
", the status for your order id ",
"{{orderId}}",
" is ",
"{{status}}",
],
],
[
"{{data.map(datum => {return {id: datum}})}}",
["{{data.map(datum => {return {id: datum}})}}"],
],
["{{}}{{}}}", ["{{}}", "{{}}", "}"]],
["{{{}}", ["{{{}}"]],
["{{ {{", ["{{ {{"]],
["}} }}", ["}} }}"]],
["}} {{", ["}} {{"]],
])("Parse the dynamic string(%s, %j)", (dynamicString, expected) => {
test(`returns ${expected}`, () => {
expect(parseDynamicString(dynamicString as string)).toStrictEqual(expected);
});
});
const baseWidgetProps = {
parentColumnSpace: 0,
parentRowSpace: 0,
parentId: "0",
type: WidgetTypes.BUTTON_WIDGET,
renderMode: RenderModes.CANVAS,
leftColumn: 0,
rightColumn: 0,
topRow: 0,
bottomRow: 0,
isLoading: false,
};
it("evaluates the data tree", () => {
const input: DataTree = {
widget1: {
...baseWidgetProps,
widgetId: "1",
widgetName: "widget1",
displayValue: "{{widget2.computedProperty}}",
ENTITY_TYPE: ENTITY_TYPE.WIDGET,
},
widget2: {
...baseWidgetProps,
widgetId: "2",
widgetName: "widget2",
computedProperty: "{{ widget2.data[widget2.index] }}",
data: "{{ apiData.data }}",
index: 2,
ENTITY_TYPE: ENTITY_TYPE.WIDGET,
},
apiData: {
config: {
id: "123",
pageId: "1234",
datasource: {},
name: "api",
actionConfiguration: {},
jsonPathKeys: [],
pluginId: "plugin",
},
run: (onSuccess, onError) => ({
type: "RUN_ACTION",
payload: {
actionId: "",
onSuccess: "",
onError: "",
},
}),
isLoading: false,
data: ["wrong value", "still wrong", "correct"],
ENTITY_TYPE: ENTITY_TYPE.ACTION,
},
};
const dynamicBindings = {
"widget1.displayValue": ["widget2.computedProperty"],
"widget2.computedProperty": ["widget2.data", "widget2.index"],
"widget2.data": ["apiData.data"],
};
const sortedDeps = [
"apiData.data",
"widget2.data",
"widget2.index",
"widget2.computedProperty",
"widget1.displayValue",
];
const output: DataTree = {
widget1: {
...baseWidgetProps,
widgetId: "1",
widgetName: "widget1",
displayValue: "correct",
ENTITY_TYPE: ENTITY_TYPE.WIDGET,
},
widget2: {
...baseWidgetProps,
widgetId: "2",
widgetName: "widget2",
computedProperty: "correct",
data: ["wrong value", "still wrong", "correct"],
index: 2,
ENTITY_TYPE: ENTITY_TYPE.WIDGET,
},
apiData: {
config: {
id: "123",
pageId: "1234",
datasource: {},
name: "api",
actionConfiguration: {},
jsonPathKeys: [],
pluginId: "plugin",
},
run: (onSuccess, onError) => ({
type: "RUN_ACTION",
payload: {
actionId: "",
onSuccess: "",
onError: "",
},
}),
isLoading: false,
data: ["wrong value", "still wrong", "correct"],
ENTITY_TYPE: ENTITY_TYPE.ACTION,
},
};
const result = dependencySortedEvaluateDataTree(
input,
dynamicBindings,
sortedDeps,
);
expect(result).toEqual(output);
});
it("finds dependencies of a entity", () => {
const depMap: Array<[string, string]> = [
["Widget5.text", "Widget2.data.visible"],
["Widget1.options", "Action1.data"],
["Widget2.text", "Widget1.selectedOption"],
["Widget3.text", "Widget4.selectedRow.name"],
["Widget6.label", "Action1.data.label"],
];
const entity = "Action1";
const result = ["Widget1", "Widget2", "Widget5", "Widget6"];
const actual = getEntityDependencies(depMap, entity);
expect(actual).toEqual(result);
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore
it("does nothing. needs implementing", () => {
expect(1 + 1).toEqual(2);
});