fix: API Body format focus retention (#37150)
## Description - Use Focus retention to store user context of the POST body format of APIs - Update component to use hooks instead of redux connect hoc - remove need of passing Action ID in form data as it is redundant for focus retention states Fixes #36984 ## Automation /ok-to-test tags="@tag.Datasource, @tag.IDE" ### 🔍 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/11609483418> > Commit: a3a1a59101773372a65ded41ea1cce3cd83ffd5f > <a href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=11609483418&attempt=1" target="_blank">Cypress dashboard</a>. > Tags: `@tag.Datasource, @tag.IDE` > Spec: > <hr>Thu, 31 Oct 2024 10:31:09 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 ## Release Notes - **New Features** - Introduced a streamlined method for managing API body content types and form data. - Added new action creator `setExtraFormData` for enhanced form data handling. - **Improvements** - Simplified the `PostBodyData` component by utilizing React hooks for state management. - Updated selectors and reducers to support a flatter structure for form data, improving clarity and maintainability. - **Updates** - Enhanced focus elements configuration to better manage plugin action form data within the application. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
parent
951be4a34e
commit
9a9a7c4b17
|
|
@ -1,15 +1,12 @@
|
||||||
import React from "react";
|
import React, { useCallback } from "react";
|
||||||
import { connect } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { formValueSelector } from "redux-form";
|
|
||||||
import {
|
import {
|
||||||
POST_BODY_FORMAT_OPTIONS,
|
POST_BODY_FORMAT_OPTIONS,
|
||||||
POST_BODY_FORMAT_TITLES,
|
POST_BODY_FORMAT_TITLES,
|
||||||
} from "../../../../constants/CommonApiConstants";
|
} from "../../../../constants/CommonApiConstants";
|
||||||
import { API_EDITOR_FORM_NAME } from "ee/constants/forms";
|
|
||||||
import KeyValueFieldArray from "components/editorComponents/form/fields/KeyValueFieldArray";
|
import KeyValueFieldArray from "components/editorComponents/form/fields/KeyValueFieldArray";
|
||||||
import DynamicTextField from "components/editorComponents/form/fields/DynamicTextField";
|
import DynamicTextField from "components/editorComponents/form/fields/DynamicTextField";
|
||||||
import type { AppState } from "ee/reducers";
|
|
||||||
import FIELD_VALUES from "constants/FieldExpectedValue";
|
import FIELD_VALUES from "constants/FieldExpectedValue";
|
||||||
import type { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig";
|
import type { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig";
|
||||||
import {
|
import {
|
||||||
|
|
@ -61,11 +58,8 @@ const NoBodyMessage = styled.div`
|
||||||
`;
|
`;
|
||||||
|
|
||||||
interface PostDataProps {
|
interface PostDataProps {
|
||||||
displayFormat: { label: string; value: string };
|
|
||||||
dataTreePath: string;
|
dataTreePath: string;
|
||||||
theme?: EditorTheme;
|
theme?: EditorTheme;
|
||||||
apiId: string;
|
|
||||||
updateBodyContentType: (contentType: string, apiId: string) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = PostDataProps;
|
type Props = PostDataProps;
|
||||||
|
|
@ -77,9 +71,13 @@ const expectedPostBody: CodeEditorExpected = {
|
||||||
};
|
};
|
||||||
|
|
||||||
function PostBodyData(props: Props) {
|
function PostBodyData(props: Props) {
|
||||||
const [selectedTab, setSelectedTab] = React.useState(
|
const postBodyFormat = useSelector(getPostBodyFormat);
|
||||||
props.displayFormat?.value,
|
const dispatch = useDispatch();
|
||||||
);
|
|
||||||
|
const updateBodyContentType = useCallback((tab: string) => {
|
||||||
|
dispatch(updatePostBodyContentType(tab));
|
||||||
|
}, []);
|
||||||
|
|
||||||
const { dataTreePath, theme } = props;
|
const { dataTreePath, theme } = props;
|
||||||
|
|
||||||
const tabComponentsMap = (key: string) => {
|
const tabComponentsMap = (key: string) => {
|
||||||
|
|
@ -172,18 +170,13 @@ function PostBodyData(props: Props) {
|
||||||
value: el.key,
|
value: el.key,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const postBodyDataOnChangeFn = (key: string) => {
|
|
||||||
setSelectedTab(key);
|
|
||||||
props?.updateBodyContentType(key, props.apiId);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PostBodyContainer>
|
<PostBodyContainer>
|
||||||
<Select
|
<Select
|
||||||
data-testid="t--api-body-tab-switch"
|
data-testid="t--api-body-tab-switch"
|
||||||
defaultValue={selectedTab}
|
defaultValue={postBodyFormat.value}
|
||||||
onSelect={(value) => postBodyDataOnChangeFn(value)}
|
onSelect={updateBodyContentType}
|
||||||
value={selectedTab}
|
value={postBodyFormat.value}
|
||||||
>
|
>
|
||||||
{options.map((option) => (
|
{options.map((option) => (
|
||||||
<Option key={option.value} value={option.value}>
|
<Option key={option.value} value={option.value}>
|
||||||
|
|
@ -191,31 +184,9 @@ function PostBodyData(props: Props) {
|
||||||
</Option>
|
</Option>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
{tabComponentsMap(selectedTab)}
|
{tabComponentsMap(postBodyFormat.value)}
|
||||||
</PostBodyContainer>
|
</PostBodyContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const selector = formValueSelector(API_EDITOR_FORM_NAME);
|
export default PostBodyData;
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
|
||||||
|
|
@ -28,10 +28,16 @@ export const openPluginActionSettings = (payload: boolean) => ({
|
||||||
|
|
||||||
export const updatePostBodyContentType = (
|
export const updatePostBodyContentType = (
|
||||||
title: string,
|
title: string,
|
||||||
apiId: string,
|
): ReduxAction<{ title: string }> => ({
|
||||||
): ReduxAction<{ title: string; apiId: string }> => ({
|
|
||||||
type: ReduxActionTypes.UPDATE_API_ACTION_BODY_CONTENT_TYPE,
|
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 = (
|
export const changeApi = (
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import type { AppState } from "ee/reducers";
|
||||||
import { createSelector } from "reselect";
|
import { createSelector } from "reselect";
|
||||||
|
|
||||||
import { POST_BODY_FORM_DATA_KEY } from "./constants";
|
import { POST_BODY_FORM_DATA_KEY } from "./constants";
|
||||||
|
import { POST_BODY_FORMAT_OPTIONS } from "../constants/CommonApiConstants";
|
||||||
|
|
||||||
export const getActionEditorSavingMap = (state: AppState) =>
|
export const getActionEditorSavingMap = (state: AppState) =>
|
||||||
state.ui.pluginActionEditor.isSaving;
|
state.ui.pluginActionEditor.isSaving;
|
||||||
|
|
@ -37,19 +38,25 @@ export const isActionDeleting = (id: string) =>
|
||||||
(deletingMap) => id in deletingMap && deletingMap[id],
|
(deletingMap) => id in deletingMap && deletingMap[id],
|
||||||
);
|
);
|
||||||
|
|
||||||
type GetFormData = (
|
export const getFormData = (state: AppState) =>
|
||||||
state: AppState,
|
state.ui.pluginActionEditor.formData;
|
||||||
id: string,
|
|
||||||
) => { label: string; value: string } | undefined;
|
|
||||||
|
|
||||||
export const getPostBodyFormat: GetFormData = (state, id) => {
|
type GetFormPostBodyFormat = (state: AppState) => {
|
||||||
const formData = state.ui.pluginActionEditor.formData;
|
label: string;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
|
||||||
if (id in formData) {
|
export const getPostBodyFormat: GetFormPostBodyFormat = (state) => {
|
||||||
return formData[id][POST_BODY_FORM_DATA_KEY];
|
const formData = getFormData(state);
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
export const getPluginActionConfigSelectedTab = (state: AppState) =>
|
export const getPluginActionConfigSelectedTab = (state: AppState) =>
|
||||||
state.ui.pluginActionEditor.selectedConfigTab;
|
state.ui.pluginActionEditor.selectedConfigTab;
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ export interface PluginActionEditorState {
|
||||||
isDirty: Record<string, boolean>;
|
isDirty: Record<string, boolean>;
|
||||||
runErrorMessage: Record<string, string>;
|
runErrorMessage: Record<string, string>;
|
||||||
selectedConfigTab?: string;
|
selectedConfigTab?: string;
|
||||||
formData: Record<string, Record<string, { label: string; value: string }>>;
|
formData: Record<string, { label: string; value: string }>;
|
||||||
debugger: PluginEditorDebuggerState;
|
debugger: PluginEditorDebuggerState;
|
||||||
settingsOpen?: boolean;
|
settingsOpen?: boolean;
|
||||||
}
|
}
|
||||||
|
|
@ -144,13 +144,12 @@ export const handlers = {
|
||||||
[ReduxActionTypes.SET_EXTRA_FORMDATA]: (
|
[ReduxActionTypes.SET_EXTRA_FORMDATA]: (
|
||||||
state: PluginActionEditorState,
|
state: PluginActionEditorState,
|
||||||
action: ReduxAction<{
|
action: ReduxAction<{
|
||||||
id: string;
|
|
||||||
values: Record<string, { label: string; value: 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]: (
|
[ReduxActionTypes.SET_PLUGIN_ACTION_EDITOR_FORM_SELECTED_TAB]: (
|
||||||
state: PluginActionEditorState,
|
state: PluginActionEditorState,
|
||||||
|
|
|
||||||
|
|
@ -77,11 +77,16 @@ import { ActionExecutionResizerHeight } from "PluginActionEditor/components/Plug
|
||||||
import {
|
import {
|
||||||
getPluginActionConfigSelectedTab,
|
getPluginActionConfigSelectedTab,
|
||||||
getPluginActionDebuggerState,
|
getPluginActionDebuggerState,
|
||||||
|
getFormData,
|
||||||
|
setExtraFormData,
|
||||||
setPluginActionEditorDebuggerState,
|
setPluginActionEditorDebuggerState,
|
||||||
setPluginActionEditorSelectedTab,
|
setPluginActionEditorSelectedTab,
|
||||||
} from "PluginActionEditor/store";
|
} from "PluginActionEditor/store";
|
||||||
import { EDITOR_TABS } from "constants/QueryEditorConstants";
|
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 = {
|
export const AppIDEFocusElements: FocusElementsConfigList = {
|
||||||
[FocusEntity.DATASOURCE_LIST]: [
|
[FocusEntity.DATASOURCE_LIST]: [
|
||||||
|
|
@ -152,9 +157,13 @@ export const AppIDEFocusElements: FocusElementsConfigList = {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: FocusElementConfigType.Redux,
|
type: FocusElementConfigType.Redux,
|
||||||
name: FocusElement.InputField,
|
name: FocusElement.PluginActionFormData,
|
||||||
selector: getFocusableInputField,
|
selector: getFormData,
|
||||||
setter: setFocusableInputField,
|
setter: setExtraFormData,
|
||||||
|
defaultValue: {
|
||||||
|
label: POST_BODY_FORMAT_OPTIONS.NONE,
|
||||||
|
value: POST_BODY_FORMAT_OPTIONS.NONE,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: FocusElementConfigType.Redux,
|
type: FocusElementConfigType.Redux,
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import type { AppState } from "ee/reducers";
|
||||||
|
|
||||||
export enum FocusElement {
|
export enum FocusElement {
|
||||||
PluginActionConfigTabs = "PluginActionConfigTabs",
|
PluginActionConfigTabs = "PluginActionConfigTabs",
|
||||||
|
PluginActionFormData = "PluginActionFormData",
|
||||||
CodeEditorHistory = "CodeEditorHistory",
|
CodeEditorHistory = "CodeEditorHistory",
|
||||||
EntityCollapsibleState = "EntityCollapsibleState",
|
EntityCollapsibleState = "EntityCollapsibleState",
|
||||||
EntityExplorerWidth = "EntityExplorerWidth",
|
EntityExplorerWidth = "EntityExplorerWidth",
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,10 @@ import {
|
||||||
import { updateReplayEntity } from "actions/pageActions";
|
import { updateReplayEntity } from "actions/pageActions";
|
||||||
import { ENTITY_TYPE } from "ee/entities/AppsmithConsole/utils";
|
import { ENTITY_TYPE } from "ee/entities/AppsmithConsole/utils";
|
||||||
import type { Plugin } from "api/PluginApi";
|
import type { Plugin } from "api/PluginApi";
|
||||||
import { getPostBodyFormat } from "../PluginActionEditor/store";
|
import {
|
||||||
|
getPostBodyFormat,
|
||||||
|
setExtraFormData,
|
||||||
|
} from "../PluginActionEditor/store";
|
||||||
import { apiEditorIdURL, datasourcesEditorIdURL } from "ee/RouteBuilder";
|
import { apiEditorIdURL, datasourcesEditorIdURL } from "ee/RouteBuilder";
|
||||||
import { getCurrentBasePageId } from "selectors/editorSelectors";
|
import { getCurrentBasePageId } from "selectors/editorSelectors";
|
||||||
import { validateResponse } from "./ErrorSagas";
|
import { validateResponse } from "./ErrorSagas";
|
||||||
|
|
@ -135,10 +138,8 @@ function* syncApiParamsSaga(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function* handleUpdateBodyContentType(
|
function* handleUpdateBodyContentType(action: ReduxAction<{ title: string }>) {
|
||||||
action: ReduxAction<{ title: string; apiId: string }>,
|
const { title } = action.payload;
|
||||||
) {
|
|
||||||
const { apiId, title } = action.payload;
|
|
||||||
const { values } = yield select(getFormData, API_EDITOR_FORM_NAME);
|
const { values } = yield select(getFormData, API_EDITOR_FORM_NAME);
|
||||||
|
|
||||||
const displayFormatValue = POST_BODY_FORMAT_OPTIONS_ARRAY.find(
|
const displayFormatValue = POST_BODY_FORMAT_OPTIONS_ARRAY.find(
|
||||||
|
|
@ -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
|
// 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
|
// 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.
|
// to show the appropriate content type section.
|
||||||
yield put({
|
yield put(
|
||||||
type: ReduxActionTypes.SET_EXTRA_FORMDATA,
|
setExtraFormData({
|
||||||
payload: {
|
[POST_BODY_FORM_DATA_KEY]: {
|
||||||
id: apiId,
|
label: title,
|
||||||
values: {
|
value: title,
|
||||||
displayFormat: {
|
|
||||||
label: title,
|
|
||||||
value: title,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
});
|
);
|
||||||
|
|
||||||
// help to prevent cyclic dependency error in case the bodyFormData is empty.
|
// help to prevent cyclic dependency error in case the bodyFormData is empty.
|
||||||
|
|
||||||
|
|
@ -257,7 +254,8 @@ function* updateExtraFormDataSaga() {
|
||||||
const { values } = formData;
|
const { values } = formData;
|
||||||
|
|
||||||
// when initializing, check if theres a display format present.
|
// 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 }> =
|
const headers: Array<{ key: string; value: string }> =
|
||||||
get(values, "actionConfiguration.headers") || [];
|
get(values, "actionConfiguration.headers") || [];
|
||||||
|
|
@ -363,15 +361,11 @@ function* setApiBodyTabHeaderFormat(apiId: string, apiContentType?: string) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
yield put({
|
yield put(
|
||||||
type: ReduxActionTypes.SET_EXTRA_FORMDATA,
|
setExtraFormData({
|
||||||
payload: {
|
[POST_BODY_FORM_DATA_KEY]: displayFormat,
|
||||||
id: apiId,
|
}),
|
||||||
values: {
|
);
|
||||||
[POST_BODY_FORM_DATA_KEY]: displayFormat,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function* formValueChangeSaga(
|
function* formValueChangeSaga(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user