Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import React from "react";
import { connect } from "react-redux";
import React, { useCallback } from "react";
import { useDispatch, useSelector } from "react-redux";
import styled from "styled-components";
import { formValueSelector } from "redux-form";
import {
POST_BODY_FORMAT_OPTIONS,
POST_BODY_FORMAT_TITLES,
} from "../../../../constants/CommonApiConstants";
import { API_EDITOR_FORM_NAME } from "ee/constants/forms";
import KeyValueFieldArray from "components/editorComponents/form/fields/KeyValueFieldArray";
import DynamicTextField from "components/editorComponents/form/fields/DynamicTextField";
import type { AppState } from "ee/reducers";
import FIELD_VALUES from "constants/FieldExpectedValue";
import type { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig";
import {
Expand Down Expand Up @@ -61,11 +58,8 @@ const NoBodyMessage = styled.div`
`;

interface PostDataProps {
displayFormat: { label: string; value: string };
dataTreePath: string;
theme?: EditorTheme;
apiId: string;
updateBodyContentType: (contentType: string, apiId: string) => void;
}

type Props = PostDataProps;
Expand All @@ -77,9 +71,13 @@ const expectedPostBody: CodeEditorExpected = {
};

function PostBodyData(props: Props) {
const [selectedTab, setSelectedTab] = React.useState(
props.displayFormat?.value,
);
const postBodyFormat = useSelector(getPostBodyFormat);
const dispatch = useDispatch();

const updateBodyContentType = useCallback((tab: string) => {
dispatch(updatePostBodyContentType(tab));
}, []);

const { dataTreePath, theme } = props;

const tabComponentsMap = (key: string) => {
Expand Down Expand Up @@ -172,50 +170,23 @@ function PostBodyData(props: Props) {
value: el.key,
}));

const postBodyDataOnChangeFn = (key: string) => {
setSelectedTab(key);
props?.updateBodyContentType(key, props.apiId);
};

return (
<PostBodyContainer>
<Select
data-testid="t--api-body-tab-switch"
defaultValue={selectedTab}
onSelect={(value) => postBodyDataOnChangeFn(value)}
value={selectedTab}
defaultValue={postBodyFormat.value}
onSelect={updateBodyContentType}
value={postBodyFormat.value}
Comment on lines +177 to +179
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove redundant defaultValue prop in Select component

Using both defaultValue and value in a controlled component can cause confusion and potential bugs. Since value is already managing the selected option, defaultValue is unnecessary and should be removed.

Apply this diff to remove the redundant prop:

  <Select
    data-testid="t--api-body-tab-switch"
-   defaultValue={postBodyFormat.value}
    onSelect={updateBodyContentType}
    value={postBodyFormat.value}
  >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
defaultValue={postBodyFormat.value}
onSelect={updateBodyContentType}
value={postBodyFormat.value}
value={postBodyFormat.value}
onSelect={updateBodyContentType}
value={postBodyFormat.value}

>
{options.map((option) => (
<Option key={option.value} value={option.value}>
{option.label}
</Option>
))}
</Select>
{tabComponentsMap(selectedTab)}
{tabComponentsMap(postBodyFormat.value)}
</PostBodyContainer>
);
}

const selector = formValueSelector(API_EDITOR_FORM_NAME);

// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const mapDispatchToProps = (dispatch: any) => ({
updateBodyContentType: (contentType: string, apiId: string) =>
dispatch(updatePostBodyContentType(contentType, apiId)),
});

export default connect((state: AppState) => {
const apiId = selector(state, "id");
const postBodyFormat = getPostBodyFormat(state, apiId);
// Defaults to NONE when format is not set
const displayFormat = postBodyFormat || {
label: POST_BODY_FORMAT_OPTIONS.NONE,
value: POST_BODY_FORMAT_OPTIONS.NONE,
};

return {
displayFormat,
apiId,
};
}, mapDispatchToProps)(PostBodyData);
export default PostBodyData;
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,16 @@ export const openPluginActionSettings = (payload: boolean) => ({

export const updatePostBodyContentType = (
title: string,
apiId: string,
): ReduxAction<{ title: string; apiId: string }> => ({
): ReduxAction<{ title: string }> => ({
type: ReduxActionTypes.UPDATE_API_ACTION_BODY_CONTENT_TYPE,
payload: { title, apiId },
payload: { title },
});

export const setExtraFormData = (
values: Record<string, { label: string; value: string }>,
) => ({
type: ReduxActionTypes.SET_EXTRA_FORMDATA,
payload: { values },
});

export const changeApi = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { AppState } from "ee/reducers";
import { createSelector } from "reselect";

import { POST_BODY_FORM_DATA_KEY } from "./constants";
import { POST_BODY_FORMAT_OPTIONS } from "../constants/CommonApiConstants";

export const getActionEditorSavingMap = (state: AppState) =>
state.ui.pluginActionEditor.isSaving;
Expand Down Expand Up @@ -37,19 +38,25 @@ export const isActionDeleting = (id: string) =>
(deletingMap) => id in deletingMap && deletingMap[id],
);

type GetFormData = (
state: AppState,
id: string,
) => { label: string; value: string } | undefined;
export const getFormData = (state: AppState) =>
state.ui.pluginActionEditor.formData;
Comment on lines +41 to +42
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider creating memoized selectors for specific form data fields.

Direct state access might cause unnecessary re-renders in components that only need specific parts of formData.

Consider creating specific selectors using createSelector:

export const getSpecificFormData = (key: string) =>
  createSelector([getFormData], (formData) => formData[key]);


export const getPostBodyFormat: GetFormData = (state, id) => {
const formData = state.ui.pluginActionEditor.formData;
type GetFormPostBodyFormat = (state: AppState) => {
label: string;
value: string;
};

export const getPostBodyFormat: GetFormPostBodyFormat = (state) => {
const formData = getFormData(state);

if (id in formData) {
return formData[id][POST_BODY_FORM_DATA_KEY];
if (POST_BODY_FORM_DATA_KEY in formData) {
return formData[POST_BODY_FORM_DATA_KEY];
}

return undefined;
return {
label: POST_BODY_FORMAT_OPTIONS.NONE,
value: POST_BODY_FORMAT_OPTIONS.NONE,
};
Comment on lines +49 to +59
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Memoize the selector to prevent unnecessary recalculations.

While the implementation is correct, it should be memoized to prevent unnecessary recalculations.

Consider this implementation:

export const getPostBodyFormat: GetFormPostBodyFormat = createSelector(
  [getFormData],
  (formData) => {
    if (POST_BODY_FORM_DATA_KEY in formData) {
      return formData[POST_BODY_FORM_DATA_KEY];
    }
    return {
      label: POST_BODY_FORMAT_OPTIONS.NONE,
      value: POST_BODY_FORMAT_OPTIONS.NONE,
    };
  }
);

};
export const getPluginActionConfigSelectedTab = (state: AppState) =>
state.ui.pluginActionEditor.selectedConfigTab;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export interface PluginActionEditorState {
isDirty: Record<string, boolean>;
runErrorMessage: Record<string, string>;
selectedConfigTab?: string;
formData: Record<string, Record<string, { label: string; value: string }>>;
formData: Record<string, { label: string; value: string }>;
debugger: PluginEditorDebuggerState;
settingsOpen?: boolean;
}
Expand Down Expand Up @@ -144,13 +144,12 @@ export const handlers = {
[ReduxActionTypes.SET_EXTRA_FORMDATA]: (
state: PluginActionEditorState,
action: ReduxAction<{
id: string;
values: Record<string, { label: string; value: string }>;
}>,
) => {
const { id, values } = action.payload;
const { values } = action.payload;

set(state, ["formData", id], values);
set(state, ["formData"], values);
},
[ReduxActionTypes.SET_PLUGIN_ACTION_EDITOR_FORM_SELECTED_TAB]: (
state: PluginActionEditorState,
Expand Down
17 changes: 13 additions & 4 deletions app/client/src/ce/navigation/FocusElements/AppIDE.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,16 @@ import { ActionExecutionResizerHeight } from "PluginActionEditor/components/Plug
import {
getPluginActionConfigSelectedTab,
getPluginActionDebuggerState,
getFormData,
setExtraFormData,
setPluginActionEditorDebuggerState,
setPluginActionEditorSelectedTab,
} from "PluginActionEditor/store";
import { EDITOR_TABS } from "constants/QueryEditorConstants";
import { API_EDITOR_TABS } from "PluginActionEditor/constants/CommonApiConstants";
import {
API_EDITOR_TABS,
POST_BODY_FORMAT_OPTIONS,
} from "PluginActionEditor/constants/CommonApiConstants";

export const AppIDEFocusElements: FocusElementsConfigList = {
[FocusEntity.DATASOURCE_LIST]: [
Expand Down Expand Up @@ -152,9 +157,13 @@ export const AppIDEFocusElements: FocusElementsConfigList = {
},
{
type: FocusElementConfigType.Redux,
name: FocusElement.InputField,
selector: getFocusableInputField,
setter: setFocusableInputField,
name: FocusElement.PluginActionFormData,
selector: getFormData,
setter: setExtraFormData,
defaultValue: {
label: POST_BODY_FORMAT_OPTIONS.NONE,
value: POST_BODY_FORMAT_OPTIONS.NONE,
},
},
{
type: FocusElementConfigType.Redux,
Expand Down
1 change: 1 addition & 0 deletions app/client/src/navigation/FocusElements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { AppState } from "ee/reducers";

export enum FocusElement {
PluginActionConfigTabs = "PluginActionConfigTabs",
PluginActionFormData = "PluginActionFormData",
CodeEditorHistory = "CodeEditorHistory",
EntityCollapsibleState = "EntityCollapsibleState",
EntityExplorerWidth = "EntityExplorerWidth",
Expand Down
46 changes: 20 additions & 26 deletions app/client/src/sagas/ApiPaneSagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@ import {
import { updateReplayEntity } from "actions/pageActions";
import { ENTITY_TYPE } from "ee/entities/AppsmithConsole/utils";
import type { Plugin } from "api/PluginApi";
import { getPostBodyFormat } from "../PluginActionEditor/store";
import {
getPostBodyFormat,
setExtraFormData,
} from "../PluginActionEditor/store";
import { apiEditorIdURL, datasourcesEditorIdURL } from "ee/RouteBuilder";
import { getCurrentBasePageId } from "selectors/editorSelectors";
import { validateResponse } from "./ErrorSagas";
Expand Down Expand Up @@ -135,10 +138,8 @@ function* syncApiParamsSaga(
}
}

function* handleUpdateBodyContentType(
action: ReduxAction<{ title: string; apiId: string }>,
) {
const { apiId, title } = action.payload;
function* handleUpdateBodyContentType(action: ReduxAction<{ title: string }>) {
const { title } = action.payload;
const { values } = yield select(getFormData, API_EDITOR_FORM_NAME);

const displayFormatValue = POST_BODY_FORMAT_OPTIONS_ARRAY.find(
Expand Down Expand Up @@ -216,18 +217,14 @@ function* handleUpdateBodyContentType(
// Quick Context: The extra formadata action is responsible for updating the current multi switch mode you see on api editor body tab
// whenever a user selects a new content type through the tab e.g application/json, this action is dispatched to update that value, which is then read in the PostDataBody file
// to show the appropriate content type section.
yield put({
type: ReduxActionTypes.SET_EXTRA_FORMDATA,
payload: {
id: apiId,
values: {
displayFormat: {
label: title,
value: title,
},
yield put(
setExtraFormData({
[POST_BODY_FORM_DATA_KEY]: {
label: title,
value: title,
},
},
});
}),
);

// help to prevent cyclic dependency error in case the bodyFormData is empty.

Expand Down Expand Up @@ -257,7 +254,8 @@ function* updateExtraFormDataSaga() {
const { values } = formData;

// when initializing, check if theres a display format present.
const extraFormData: GetFormData = yield select(getPostBodyFormat, values.id);
const extraFormData: { label: string; value: string } =
yield select(getPostBodyFormat);

const headers: Array<{ key: string; value: string }> =
get(values, "actionConfiguration.headers") || [];
Expand Down Expand Up @@ -363,15 +361,11 @@ function* setApiBodyTabHeaderFormat(apiId: string, apiContentType?: string) {
};
}

yield put({
type: ReduxActionTypes.SET_EXTRA_FORMDATA,
payload: {
id: apiId,
values: {
[POST_BODY_FORM_DATA_KEY]: displayFormat,
},
},
});
yield put(
setExtraFormData({
[POST_BODY_FORM_DATA_KEY]: displayFormat,
}),
);
}

function* formValueChangeSaga(
Expand Down