diff --git a/app/client/src/PluginActionEditor/components/PluginActionForm/PluginActionForm.tsx b/app/client/src/PluginActionEditor/components/PluginActionForm/PluginActionForm.tsx index 4b3a2dd3a0c7..db91b29b3a27 100644 --- a/app/client/src/PluginActionEditor/components/PluginActionForm/PluginActionForm.tsx +++ b/app/client/src/PluginActionEditor/components/PluginActionForm/PluginActionForm.tsx @@ -1,10 +1,16 @@ import React from "react"; +import APIEditorForm from "./components/APIEditorForm"; +import { Flex } from "@appsmith/ads"; import { useChangeActionCall } from "./hooks/useChangeActionCall"; const PluginActionForm = () => { useChangeActionCall(); - return
; + return ( + + + + ); }; export default PluginActionForm; diff --git a/app/client/src/PluginActionEditor/components/PluginActionForm/components/APIEditorForm.tsx b/app/client/src/PluginActionEditor/components/PluginActionForm/components/APIEditorForm.tsx new file mode 100644 index 000000000000..f3a8d8ffd628 --- /dev/null +++ b/app/client/src/PluginActionEditor/components/PluginActionForm/components/APIEditorForm.tsx @@ -0,0 +1,55 @@ +import React from "react"; +import CommonEditorForm from "./CommonEditorForm"; +import { usePluginActionContext } from "PluginActionEditor"; +import { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig"; +import { API_EDITOR_FORM_NAME } from "ee/constants/forms"; +import { HTTP_METHOD_OPTIONS } from "constants/ApiEditorConstants/CommonApiConstants"; +import PostBodyData from "pages/Editor/APIEditor/PostBodyData"; +import { useFeatureFlag } from "utils/hooks/useFeatureFlag"; +import { FEATURE_FLAG } from "ee/entities/FeatureFlag"; +import { getHasManageActionPermission } from "ee/utils/BusinessFeatures/permissionPageHelpers"; +import Pagination from "pages/Editor/APIEditor/Pagination"; +import { noop } from "lodash"; +import { reduxForm } from "redux-form"; + +const FORM_NAME = API_EDITOR_FORM_NAME; + +const APIEditorForm = () => { + const { action } = usePluginActionContext(); + const theme = EditorTheme.LIGHT; + + const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled); + const isChangePermitted = getHasManageActionPermission( + isFeatureEnabled, + action.userPermissions, + ); + + return ( + + } + formName={FORM_NAME} + headersCount={0} + httpMethodOptions={HTTP_METHOD_OPTIONS} + isChangePermitted={isChangePermitted} + paginationUiComponent={ + + } + paramsCount={0} + /> + ); +}; + +export default reduxForm({ form: FORM_NAME, enableReinitialize: true })( + APIEditorForm, +); diff --git a/app/client/src/PluginActionEditor/components/PluginActionForm/components/CommonEditorForm/CommonEditorForm.tsx b/app/client/src/PluginActionEditor/components/PluginActionForm/components/CommonEditorForm/CommonEditorForm.tsx new file mode 100644 index 000000000000..881fdc56147b --- /dev/null +++ b/app/client/src/PluginActionEditor/components/PluginActionForm/components/CommonEditorForm/CommonEditorForm.tsx @@ -0,0 +1,70 @@ +import React from "react"; +import { type Action } from "entities/Action"; +import { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig"; +import type { AutoGeneratedHeader } from "pages/Editor/APIEditor/helpers"; +import { InfoFields } from "./InfoFields"; +import { RequestTabs } from "./RequestTabs"; +import { HintMessages } from "./HintMessages"; +import { Flex } from "@appsmith/ads"; + +interface Props { + httpMethodOptions: { value: string }[]; + action: Action; + formName: string; + isChangePermitted: boolean; + bodyUIComponent: React.ReactNode; + paginationUiComponent: React.ReactNode; + headersCount: number; + paramsCount: number; + // TODO: Fix this the next time the file is edited + // eslint-disable-next-line @typescript-eslint/no-explicit-any + datasourceHeaders?: any; + // TODO: Fix this the next time the file is edited + // eslint-disable-next-line @typescript-eslint/no-explicit-any + datasourceParams?: any; + // TODO: Fix this the next time the file is edited + // eslint-disable-next-line @typescript-eslint/no-explicit-any + actionConfigurationHeaders?: any; + // TODO: Fix this the next time the file is edited + // eslint-disable-next-line @typescript-eslint/no-explicit-any + actionConfigurationParams?: any; + autoGeneratedActionConfigHeaders?: AutoGeneratedHeader[]; +} + +const CommonEditorForm = (props: Props) => { + const { action } = props; + const hintMessages = action.messages || []; + const theme = EditorTheme.LIGHT; + + return ( + + + + + + ); +}; + +export default CommonEditorForm; diff --git a/app/client/src/PluginActionEditor/components/PluginActionForm/components/CommonEditorForm/HintMessages.tsx b/app/client/src/PluginActionEditor/components/PluginActionForm/components/CommonEditorForm/HintMessages.tsx new file mode 100644 index 000000000000..3909ea66ff34 --- /dev/null +++ b/app/client/src/PluginActionEditor/components/PluginActionForm/components/CommonEditorForm/HintMessages.tsx @@ -0,0 +1,18 @@ +import React from "react"; +import { Callout } from "@appsmith/ads"; + +export function HintMessages(props: { hintMessages: string[] }) { + if (props.hintMessages.length === 0) { + return null; + } + + return ( + <> + {props.hintMessages.map((msg, i) => ( + + {msg} + + ))} + + ); +} diff --git a/app/client/src/PluginActionEditor/components/PluginActionForm/components/CommonEditorForm/InfoFields.tsx b/app/client/src/PluginActionEditor/components/PluginActionForm/components/CommonEditorForm/InfoFields.tsx new file mode 100644 index 000000000000..a2f1f7b7fc13 --- /dev/null +++ b/app/client/src/PluginActionEditor/components/PluginActionForm/components/CommonEditorForm/InfoFields.tsx @@ -0,0 +1,50 @@ +import React from "react"; +import type { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig"; +import RequestDropdownField from "components/editorComponents/form/fields/RequestDropdownField"; +import { replayHighlightClass } from "globalStyles/portals"; +import EmbeddedDatasourcePathField from "components/editorComponents/form/fields/EmbeddedDatasourcePathField"; +import styled from "styled-components"; +import { Flex } from "@appsmith/ads"; + +const DatasourceWrapper = styled.div` + margin-left: 8px; + width: 100%; +`; + +export function InfoFields(props: { + changePermitted: boolean; + options: { value: string }[]; + actionName: string; + pluginId: string; + formName: string; + theme: EditorTheme.LIGHT; +}) { + return ( + +
+ +
+ +
+ + + + + ); +} diff --git a/app/client/src/PluginActionEditor/components/PluginActionForm/components/CommonEditorForm/RequestTabs.tsx b/app/client/src/PluginActionEditor/components/PluginActionForm/components/CommonEditorForm/RequestTabs.tsx new file mode 100644 index 000000000000..3783ba5f919a --- /dev/null +++ b/app/client/src/PluginActionEditor/components/PluginActionForm/components/CommonEditorForm/RequestTabs.tsx @@ -0,0 +1,137 @@ +import styled from "styled-components"; +import { Tab, TabPanel, Tabs, TabsList } from "@appsmith/ads"; +import FormLabel from "components/editorComponents/FormLabel"; +import type { AutoGeneratedHeader } from "pages/Editor/APIEditor/helpers"; +import type { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig"; +import React from "react"; +import { API_EDITOR_TABS } from "constants/ApiEditorConstants/CommonApiConstants"; +import { DatasourceConfig } from "./components/DatasourceConfig"; +import KeyValueFieldArray from "components/editorComponents/form/fields/KeyValueFieldArray"; +import ApiAuthentication from "pages/Editor/APIEditor/ApiAuthentication"; +import ActionSettings from "pages/Editor/ActionSettings"; +import { API_EDITOR_TAB_TITLES, createMessage } from "ee/constants/messages"; +import { useSelectedFormTab } from "./hooks/useSelectedFormTab"; + +const SettingsWrapper = styled.div` + padding: var(--ads-v2-spaces-4) 0; + height: 100%; + + ${FormLabel} { + padding: 0; + } +`; +const TabsListWrapper = styled.div` + padding: 0 var(--ads-v2-spaces-7); +`; +const StyledTabPanel = styled(TabPanel)` + height: calc(100% - 50px); + overflow: auto; + padding: 0 var(--ads-v2-spaces-7); +`; + +export function RequestTabs(props: { + autogeneratedHeaders: AutoGeneratedHeader[] | undefined; + // TODO: Fix this the next time the file is edited + // eslint-disable-next-line @typescript-eslint/no-explicit-any + datasourceHeaders: any; + // TODO: Fix this the next time the file is edited + // eslint-disable-next-line @typescript-eslint/no-explicit-any + actionConfigurationHeaders: any; + headersCount: number; + actionName: string; + pushFields: boolean; + theme: EditorTheme.LIGHT; + // TODO: Fix this the next time the file is edited + // eslint-disable-next-line @typescript-eslint/no-explicit-any + datasourceParams: any; + // TODO: Fix this the next time the file is edited + // eslint-disable-next-line @typescript-eslint/no-explicit-any + actionConfigurationParams: any; + paramsCount: number; + bodyUIComponent: React.ReactNode; + paginationUiComponent: React.ReactNode; + formName: string; + showSettings: boolean; + // TODO: Fix this the next time the file is edited + // eslint-disable-next-line @typescript-eslint/no-explicit-any + actionSettingsConfig?: any; +}) { + const [value, onValueChange] = useSelectedFormTab(); + + return ( + + + + {Object.values(API_EDITOR_TABS).map((tab) => ( + + {createMessage(API_EDITOR_TAB_TITLES[tab])} + + ))} + + + + + + + + + + + + {props.bodyUIComponent} + + + {props.paginationUiComponent} + + + + + {props.showSettings ? ( + + + + + + ) : null} + + ); +} diff --git a/app/client/src/PluginActionEditor/components/PluginActionForm/components/CommonEditorForm/components/DatasourceConfig.tsx b/app/client/src/PluginActionEditor/components/PluginActionForm/components/CommonEditorForm/components/DatasourceConfig.tsx new file mode 100644 index 000000000000..a6b89e291919 --- /dev/null +++ b/app/client/src/PluginActionEditor/components/PluginActionForm/components/CommonEditorForm/components/DatasourceConfig.tsx @@ -0,0 +1,284 @@ +import styled from "styled-components"; +import FormRow from "../../../../../../components/editorComponents/FormRow"; +import FormLabel from "../../../../../../components/editorComponents/FormLabel"; +import { Button, Icon, Text, Tooltip } from "@appsmith/ads"; +import { + API_PANE_AUTO_GENERATED_HEADER, + API_PANE_DUPLICATE_HEADER, + createMessage, +} from "ee/constants/messages"; +import React, { useState } from "react"; +import { Classes } from "@appsmith/ads-old"; + +const Flex = styled.div<{ + size: number; + isInvalid?: boolean; +}>` + flex: ${(props) => props.size}; + width: 100%; + position: relative; + min-height: 36px; + height: auto; + border-color: var(--ads-v2-color-border); + border-bottom: 1px solid var(--ads-v2-color-border); + border-radius: var(--ads-v2-border-radius); + color: var(--ads-v2-color-fg); + display: flex; + align-items: center; + justify-content: space-between; + + &.possible-overflow-key, + &.possible-overflow { + overflow: hidden; + text-overflow: ellipsis; + width: fit-content; + max-width: 100%; + + div { + padding: 0 6px; + } + } + + &.possible-overflow { + width: 0; + max-height: 36px; + + & > span.cs-text { + width: 100%; + } + } + + & span { + ${(props) => + props?.isInvalid + ? "text-decoration: line-through;" + : "text-decoration: none;"} + } +`; +const FlexContainer = styled.div` + display: flex; + align-items: center; + width: calc(100% - 42px); + + .key-value { + .${Classes.TEXT} { + color: var(--ads-v2-color-fg); + padding: ${(props) => props.theme.spaces[2]}px 0px + ${(props) => props.theme.spaces[2]}px + ${(props) => props.theme.spaces[5]}px; + } + + border-bottom: 0px; + } + + .key-value-header { + color: var(--ads-v2-color-fg); + border-bottom: 0px; + + &:nth-child(2) { + margin-left: 5px; + } + } + + .key-value:nth-child(2) { + margin-left: 5px; + } + + .disabled { + background: var(--ads-v2-color-bg-subtle); + border: 1px solid var(--ads-v2-color-border-muted); + margin-bottom: ${(props) => props.theme.spaces[2] - 1}px; + } +`; +const KeyValueStackContainer = styled.div` + padding: 0; +`; +const KeyValueFlexContainer = styled.div` + padding: ${(props) => props.theme.spaces[4]}px + ${(props) => props.theme.spaces[14]}px 0 0; +`; +const FormRowWithLabel = styled(FormRow)` + flex-wrap: wrap; + + ${FormLabel} { + width: 100%; + } +`; +const CenteredIcon = styled(Icon)` + align-self: center; + margin-right: 5px; +`; + +function ImportedKeyValue(props: { + datas: { key: string; value: string; isInvalid?: boolean }[]; + keyValueName: string; +}) { + return ( + <> + {/* TODO: Fix this the next time the file is edited */} + {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */} + {props.datas.map((data: any, index: number) => { + let tooltipContentValue = data?.value; + let tooltipContentKey = data?.key; + + if ("isInvalid" in data) { + if (data?.isInvalid) { + tooltipContentValue = createMessage( + API_PANE_DUPLICATE_HEADER, + data?.key, + ); + tooltipContentKey = createMessage( + API_PANE_DUPLICATE_HEADER, + data?.key, + ); + } else { + tooltipContentValue = ""; + tooltipContentKey = ""; + } + } + + return ( + + + + + +
{data.key}
+
+
+ {"isInvalid" in data && !data?.isInvalid && ( + + + + )} +
+ + + +
{data.value}
+
+
+
+
+
+ ); + })} + + ); +} + +function renderImportedDatasButton( + dataCount: number, + // TODO: Fix this the next time the file is edited + // eslint-disable-next-line @typescript-eslint/no-explicit-any + onClick: any, + showInheritedAttributes: boolean, + attributeName: string, +) { + return ( + // TODO: Maybe this should be a Toggle Button? ̦ + + ); +} + +export function DatasourceConfig(props: { + // TODO: Fix this the next time the file is edited + // eslint-disable-next-line @typescript-eslint/no-explicit-any + data: any; + // TODO: Fix this the next time the file is edited + // eslint-disable-next-line @typescript-eslint/no-explicit-any + autogeneratedHeaders?: any; + attributeName: string; +}) { + const [showDatas, toggleDatas] = useState(false); + + // commenting this out for whenever we decide to add a button to toggle auto-generated headers + // const [showAutoGeneratedHeader, toggleAutoGeneratedHeaders] = useState(true); + return ( + <> + + {props?.data && + props.data.length > 0 && + renderImportedDatasButton( + props.data.length, + toggleDatas, + showDatas, + `Inherited ${props.attributeName}${ + props.data.length > 1 ? "s" : "" + }`, + )} + + {/* commenting this out for whenever we decide to add a button to toggle auto-generated headers */} + {/* {props?.autogeneratedHeaders && + props?.autogeneratedHeaders?.length > 0 && + renderImportedDatasButton( + props?.autogeneratedHeaders?.length, + toggleAutoGeneratedHeaders, + showAutoGeneratedHeader, + `Auto Generated Header${ + props?.autogeneratedHeaders?.length > 1 ? "s" : "" + }`, + )} */} + + + + + + Key + + + Value + + + + {props?.data && props?.data?.length > 0 && showDatas && ( + + )} + {props?.autogeneratedHeaders && + props?.autogeneratedHeaders?.length > 0 && ( + + )} + + + ); +} diff --git a/app/client/src/PluginActionEditor/components/PluginActionForm/components/CommonEditorForm/hooks/useSelectedFormTab.ts b/app/client/src/PluginActionEditor/components/PluginActionForm/components/CommonEditorForm/hooks/useSelectedFormTab.ts new file mode 100644 index 000000000000..b7421a6dfc5d --- /dev/null +++ b/app/client/src/PluginActionEditor/components/PluginActionForm/components/CommonEditorForm/hooks/useSelectedFormTab.ts @@ -0,0 +1,25 @@ +import { useCallback } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { getApiPaneConfigSelectedTabIndex } from "selectors/apiPaneSelectors"; +import { API_EDITOR_TABS } from "constants/ApiEditorConstants/CommonApiConstants"; +import { setApiPaneConfigSelectedTabIndex } from "actions/apiPaneActions"; + +export function useSelectedFormTab(): [string, (id: string) => void] { + const dispatch = useDispatch(); + // the redux form has been configured with indexes, but the new ads components need strings to work. + // these functions convert them back and forth as needed. + const selectedIndex = useSelector(getApiPaneConfigSelectedTabIndex); + const selectedValue = Object.values(API_EDITOR_TABS)[selectedIndex]; + const setSelectedIndex = useCallback( + (value: string) => { + const index = Object.values(API_EDITOR_TABS).indexOf( + value as API_EDITOR_TABS, + ); + + dispatch(setApiPaneConfigSelectedTabIndex(index)); + }, + [dispatch], + ); + + return [selectedValue, setSelectedIndex]; +} diff --git a/app/client/src/PluginActionEditor/components/PluginActionForm/components/CommonEditorForm/index.ts b/app/client/src/PluginActionEditor/components/PluginActionForm/components/CommonEditorForm/index.ts new file mode 100644 index 000000000000..46cb3624cb0f --- /dev/null +++ b/app/client/src/PluginActionEditor/components/PluginActionForm/components/CommonEditorForm/index.ts @@ -0,0 +1,5 @@ +export { default } from "./CommonEditorForm"; + +export { HintMessages } from "./HintMessages"; +export { InfoFields } from "./InfoFields"; +export { RequestTabs } from "./RequestTabs"; diff --git a/app/client/src/PluginActionEditor/components/PluginActionForm/utils/getDatasourceInfo.ts b/app/client/src/PluginActionEditor/components/PluginActionForm/utils/getDatasourceInfo.ts new file mode 100644 index 000000000000..a9ceb031848d --- /dev/null +++ b/app/client/src/PluginActionEditor/components/PluginActionForm/utils/getDatasourceInfo.ts @@ -0,0 +1,31 @@ +import get from "lodash/get"; +import type { Datasource } from "entities/Datasource"; + +export const getDatasourceInfo = (datasource: Datasource): string => { + const info = []; + const headers = get(datasource, "datasourceConfiguration.headers", []); + const queryParameters = get( + datasource, + "datasourceConfiguration.queryParameters", + [], + ); + const authType = get( + datasource, + "datasourceConfiguration.authentication.authenticationType", + "", + ).toUpperCase(); + + if (headers.length) + info.push(`${headers.length} Header${headers.length > 1 ? "s" : ""}`); + + if (queryParameters.length) + info.push( + `${queryParameters.length} query parameters${ + queryParameters.length > 1 ? "s" : "" + }`, + ); + + if (authType.length) info.push(authType); + + return info.join(" | "); +}; diff --git a/app/client/src/PluginActionEditor/components/PluginActionToolbar.tsx b/app/client/src/PluginActionEditor/components/PluginActionToolbar.tsx index 2192109be83a..4a3563f9e4f2 100644 --- a/app/client/src/PluginActionEditor/components/PluginActionToolbar.tsx +++ b/app/client/src/PluginActionEditor/components/PluginActionToolbar.tsx @@ -5,7 +5,7 @@ import { modText } from "utils/helpers"; import { usePluginActionContext } from "../PluginActionContext"; import { useDispatch } from "react-redux"; import AnalyticsUtil from "ee/utils/AnalyticsUtil"; -import { runAction } from "../../actions/pluginActionActions"; +import { runAction } from "actions/pluginActionActions"; interface PluginActionToolbarProps { runOptions?: React.ReactNode; diff --git a/app/client/src/actions/apiPaneActions.ts b/app/client/src/actions/apiPaneActions.ts index a17753391eb7..30c2ee877516 100644 --- a/app/client/src/actions/apiPaneActions.ts +++ b/app/client/src/actions/apiPaneActions.ts @@ -59,13 +59,6 @@ export const setApiPaneConfigSelectedTabIndex: ( payload: { selectedTabIndex: payload }, }); -export const setApiRightPaneSelectedTab: ( - payload: string, -) => ReduxAction<{ selectedTab: string }> = (payload: string) => ({ - type: ReduxActionTypes.SET_API_RIGHT_PANE_SELECTED_TAB, - payload: { selectedTab: payload }, -}); - export const setApiPaneDebuggerState = ( payload: Partial, ) => ({ diff --git a/app/client/src/ce/constants/ReduxActionConstants.tsx b/app/client/src/ce/constants/ReduxActionConstants.tsx index 0fc6287c4c4e..133cf9a420e7 100644 --- a/app/client/src/ce/constants/ReduxActionConstants.tsx +++ b/app/client/src/ce/constants/ReduxActionConstants.tsx @@ -453,7 +453,6 @@ const IDEActionTypes = { SET_FOCUSABLE_PROPERTY_FIELD: "SET_FOCUSABLE_PROPERTY_FIELD", ROUTE_CHANGED: "ROUTE_CHANGED", SET_API_PANE_CONFIG_SELECTED_TAB: "SET_API_PANE_CONFIG_SELECTED_TAB", - SET_API_RIGHT_PANE_SELECTED_TAB: "SET_API_RIGHT_PANE_SELECTED_TAB", SET_EDITOR_FIELD_FOCUS: "SET_EDITOR_FIELD_FOCUS", SET_FOCUSABLE_INPUT_FIELD: "SET_FOCUSABLE_INPUT_FIELD", SET_CODE_EDITOR_CURSOR: "SET_CODE_EDITOR_CURSOR", diff --git a/app/client/src/ce/navigation/FocusElements/AppIDE.ts b/app/client/src/ce/navigation/FocusElements/AppIDE.ts index 9d75e82e9123..d179e44d22c7 100644 --- a/app/client/src/ce/navigation/FocusElements/AppIDE.ts +++ b/app/client/src/ce/navigation/FocusElements/AppIDE.ts @@ -1,7 +1,6 @@ import { setApiPaneConfigSelectedTabIndex, setApiPaneDebuggerState, - setApiRightPaneSelectedTab, } from "actions/apiPaneActions"; import { setAllEntityCollapsibleStates, @@ -15,7 +14,6 @@ import { import { getApiPaneConfigSelectedTabIndex, getApiPaneDebuggerState, - getApiRightPaneSelectedTab, } from "selectors/apiPaneSelectors"; import { getAllEntityCollapsibleStates, @@ -170,12 +168,6 @@ export const AppIDEFocusElements: FocusElementsConfigList = { selector: getFocusableInputField, setter: setFocusableInputField, }, - { - type: FocusElementConfigType.Redux, - name: FocusElement.ApiRightPaneTabs, - selector: getApiRightPaneSelectedTab, - setter: setApiRightPaneSelectedTab, - }, { type: FocusElementConfigType.Redux, name: FocusElement.QueryDebugger, diff --git a/app/client/src/ce/reducers/uiReducers/apiPaneReducer.ts b/app/client/src/ce/reducers/uiReducers/apiPaneReducer.ts index 228d6f340cec..61bbf458ba1e 100644 --- a/app/client/src/ce/reducers/uiReducers/apiPaneReducer.ts +++ b/app/client/src/ce/reducers/uiReducers/apiPaneReducer.ts @@ -38,7 +38,6 @@ export interface ApiPaneReduxState { // eslint-disable-next-line @typescript-eslint/no-explicit-any extraformData: Record; selectedConfigTabIndex: number; - selectedRightPaneTab?: string; debugger: ApiPaneDebuggerState; } @@ -210,7 +209,7 @@ export const handlers = { [ReduxActionTypes.SET_EXTRA_FORMDATA]: ( state: ApiPaneReduxState, action: ReduxAction<{ id: string; values: Record }>, - ) => { + ): ApiPaneReduxState => { const { id, values } = action.payload; return { @@ -232,17 +231,6 @@ export const handlers = { selectedConfigTabIndex: selectedTabIndex, }; }, - [ReduxActionTypes.SET_API_RIGHT_PANE_SELECTED_TAB]: ( - state: ApiPaneReduxState, - action: ReduxAction<{ selectedTab: number }>, - ) => { - const { selectedTab } = action.payload; - - return { - ...state, - selectedRightPaneTab: selectedTab, - }; - }, [ReduxActionTypes.SET_API_PANE_DEBUGGER_STATE]: ( state: ApiPaneReduxState, action: ReduxAction>, diff --git a/app/client/src/components/editorComponents/ActionRightPane/index.tsx b/app/client/src/components/editorComponents/ActionRightPane/index.tsx index bd8c6cfae7a3..456484c3035b 100644 --- a/app/client/src/components/editorComponents/ActionRightPane/index.tsx +++ b/app/client/src/components/editorComponents/ActionRightPane/index.tsx @@ -91,11 +91,9 @@ export function useEntityDependencies(actionName: string) { } function ActionSidebar({ - actionRightPaneBackLink, additionalSections, }: { additionalSections?: React.ReactNode; - actionRightPaneBackLink: React.ReactNode; }) { if (!additionalSections) { return null; @@ -104,7 +102,6 @@ function ActionSidebar({ return ( - {actionRightPaneBackLink} {additionalSections && ( diff --git a/app/client/src/components/editorComponents/form/fields/EmbeddedDatasourcePathField.tsx b/app/client/src/components/editorComponents/form/fields/EmbeddedDatasourcePathField.tsx index 7515d3800afe..02671a9b06ba 100644 --- a/app/client/src/components/editorComponents/form/fields/EmbeddedDatasourcePathField.tsx +++ b/app/client/src/components/editorComponents/form/fields/EmbeddedDatasourcePathField.tsx @@ -26,7 +26,6 @@ import { bindingHintHelper } from "components/editorComponents/CodeEditor/hintHe import StoreAsDatasource from "components/editorComponents/StoreAsDatasource"; import { DATASOURCE_URL_EXACT_MATCH_REGEX } from "constants/AppsmithActionConstants/ActionConstants"; import styled from "styled-components"; -import { getDatasourceInfo } from "pages/Editor/APIEditor/ApiRightPane"; import * as FontFamilies from "constants/Fonts"; import { AuthType } from "entities/Datasource/RestAPIForm"; @@ -60,6 +59,7 @@ import { } from "ee/utils/BusinessFeatures/permissionPageHelpers"; import { isGACEnabled } from "ee/utils/planHelpers"; import { selectFeatureFlags } from "ee/selectors/featureFlagsSelectors"; +import { getDatasourceInfo } from "PluginActionEditor/components/PluginActionForm/utils/getDatasourceInfo"; interface ReduxStateProps { workspaceId: string; diff --git a/app/client/src/pages/Editor/APIEditor/ApiRightPane.tsx b/app/client/src/pages/Editor/APIEditor/ApiRightPane.tsx deleted file mode 100644 index 09e651b6b551..000000000000 --- a/app/client/src/pages/Editor/APIEditor/ApiRightPane.tsx +++ /dev/null @@ -1,356 +0,0 @@ -import React, { useMemo, useCallback, useEffect } from "react"; -import styled from "styled-components"; -import { Classes, FontWeight, Text, TextType } from "@appsmith/ads-old"; -import history from "utils/history"; -import { TabbedViewContainer } from "./CommonEditorForm"; -import get from "lodash/get"; -import { getQueryParams } from "utils/URLUtils"; -import ActionRightPane from "components/editorComponents/ActionRightPane"; -import { sortedDatasourcesHandler } from "./helpers"; -import { datasourcesEditorIdURL } from "ee/RouteBuilder"; -import { setApiRightPaneSelectedTab } from "actions/apiPaneActions"; -import { useDispatch, useSelector } from "react-redux"; -import { getApiRightPaneSelectedTab } from "selectors/apiPaneSelectors"; -import isUndefined from "lodash/isUndefined"; -import { Button, Tab, TabPanel, Tabs, TabsList, Tag } from "@appsmith/ads"; -import type { Datasource } from "entities/Datasource"; -import { getCurrentEnvironmentId } from "ee/selectors/environmentSelectors"; -import type { SuggestedWidget } from "api/ActionAPI"; - -interface ApiRightPaneProps { - additionalSections?: React.ReactNode; - actionName: string; - actionRightPaneBackLink: React.ReactNode; - applicationId?: string; - currentActionDatasourceId: string; - currentBasePageId?: string; - datasourceId: string; - // TODO: Fix this the next time the file is edited - // eslint-disable-next-line @typescript-eslint/no-explicit-any - datasources: any; - hasResponse: boolean; - onClick: (datasource: Datasource) => void; - pluginId: string; - showTabbedSection: boolean; - suggestedWidgets?: SuggestedWidget[]; -} - -const EmptyDatasourceContainer = styled.div` - display: flex; - align-items: center; - justify-content: center; - padding-top: 50px; - height: 100%; - flex-direction: column; - .${Classes.TEXT} { - color: var(--ads-v2-color-fg); - width: 200px; - } -`; - -const DatasourceContainer = styled.div` - // to account for the divider - min-width: calc(${(props) => props.theme.actionSidePane.width}px - 2px); - max-width: calc(${(props) => props.theme.actionSidePane.width}px - 2px); - color: var(--ads-v2-color-fg); - - .tab-container-right-sidebar { - padding: 0 var(--ads-v2-spaces-7); - height: 100%; - border-left: 1px solid var(--ads-v2-color-border); - - .ads-v2-tabs { - display: flex; - flex-direction: column; - height: calc(100% - 70px); - - .ads-v2-tabs__panel { - flex-grow: 1; - overflow-y: scroll; - margin-top: 0px; - padding-top: var(--ads-v2-spaces-4); - } - } - } -`; - -const DataSourceListWrapper = styled.div` - display: flex; - flex-direction: column; - height: 100%; - overflow: auto; - padding: 0 var(--ads-v2-spaces-6); -`; - -const DatasourceCard = styled.div` - width: 100%; - padding: var(--ads-v2-spaces-4); - border-radius: var(--ads-v2-border-radius); - - display: flex; - flex-direction: column; - - background: var(--ads-v2-color-bg); - cursor: pointer; - transition: 0.3s all ease; - button { - opacity: 0; - visibility: hidden; - } - .ads-v2-icon { - opacity: 0; - transition: 0.3s all ease; - } - &:hover button { - opacity: 1; - visibility: visible; - } - &:hover { - background-color: var(--ads-v2-color-bg-subtle); - .ads-v2-icon { - opacity: 1; - } - } -`; - -const DatasourceURL = styled.span` - margin: 5px 0px 0px; - font-size: 14px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - color: var(--ads-v2-color-fg); - width: fit-content; - max-width: 100%; - font-weight: 500; -`; - -const DataSourceNameContainer = styled.div` - display: flex; - justify-content: space-between; - width: 100%; - .cs-text { - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - color: var(--ads-v2-color-fg); - } - .cs-text { - color: var(--ads-v2-color-fg); - } -`; - -const ActionRightPaneWrapper = styled.div` - height: 100%; - padding: 0 var(--ads-v2-spaces-4); -`; - -const NoEntityFoundWrapper = styled.div` - width: 144px; - height: 36px; - margin-bottom: 20px; - box-shadow: var(--ads-v2-shadow-popovers); - padding: 10px 9px; - border-radius: var(--ads-v2-border-radius); - .lines { - height: 4px; - border-radius: var(--ads-v2-border-radius); - background: var(--ads-v2-color-bg-muted); - &.first-line { - width: 33%; - margin-bottom: 8px; - } - &.second-line { - width: 66%; - background: var(--ads-v2-color-bg-subtle); - } - } -`; - -const TablistWithPadding = styled.div` - flex-shrink: 0; -`; - -// TODO: Fix this the next time the file is edited -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const getDatasourceInfo = (datasource: any): string => { - const info = []; - const headers = get(datasource, "datasourceConfiguration.headers", []); - const queryParameters = get( - datasource, - "datasourceConfiguration.queryParameters", - [], - ); - const authType = get( - datasource, - "datasourceConfiguration.authentication.authenticationType", - "", - ).toUpperCase(); - - if (headers.length) - info.push(`${headers.length} Header${headers.length > 1 ? "s" : ""}`); - - if (queryParameters.length) - info.push( - `${queryParameters.length} query parameters${ - queryParameters.length > 1 ? "s" : "" - }`, - ); - - if (authType.length) info.push(authType); - - return info.join(" | "); -}; - -const API_RIGHT_PANE_TABS = { - CONNECTIONS: "connections", - DATASOURCES: "datasources", -}; - -function ApiRightPane(props: ApiRightPaneProps) { - const dispatch = useDispatch(); - const selectedTab = useSelector(getApiRightPaneSelectedTab); - const currentEnvironmentId = useSelector(getCurrentEnvironmentId); - - const setSelectedTab = useCallback((selectedIndex: string) => { - dispatch(setApiRightPaneSelectedTab(selectedIndex)); - }, []); - - useEffect(() => { - // Switch to connections tab only initially after successfully run get stored value - // otherwise - if (!!props.hasResponse && isUndefined(selectedTab)) - setSelectedTab(API_RIGHT_PANE_TABS.CONNECTIONS); - }, [props.hasResponse]); - - // array of datasources with the current action's datasource first, followed by the rest. - const sortedDatasources = useMemo( - () => - sortedDatasourcesHandler( - props.datasources, - props.currentActionDatasourceId, - ), - [props.datasources, props.currentActionDatasourceId], - ); - - if (!props.additionalSections && !props.showTabbedSection) return null; - - return ( - - - {props.additionalSections} - {props.showTabbedSection && ( - - - - - Datasources - - - Connections - - - - - {props.datasources && props.datasources.length > 0 ? ( - - {(sortedDatasources || []).map( - (d: Datasource, idx: number) => { - const dataSourceInfo: string = getDatasourceInfo(d); - - return ( - props.onClick(d)} - > - - - {d.name} - - {d?.id === props.currentActionDatasourceId && ( - - In use - - )} - - ); -} - -function ImportedDatas(props: { - // TODO: Fix this the next time the file is edited - // eslint-disable-next-line @typescript-eslint/no-explicit-any - data: any; - // TODO: Fix this the next time the file is edited - // eslint-disable-next-line @typescript-eslint/no-explicit-any - autogeneratedHeaders?: any; - attributeName: string; -}) { - const [showDatas, toggleDatas] = useState(false); - - // commenting this out for whenever we decide to add a button to toggle auto-generated headers - // const [showAutoGeneratedHeader, toggleAutoGeneratedHeaders] = useState(true); - return ( - <> - - {props?.data && - props.data.length > 0 && - renderImportedDatasButton( - props.data.length, - toggleDatas, - showDatas, - `Inherited ${props.attributeName}${ - props.data.length > 1 ? "s" : "" - }`, - )} - - {/* commenting this out for whenever we decide to add a button to toggle auto-generated headers */} - {/* {props?.autogeneratedHeaders && - props?.autogeneratedHeaders?.length > 0 && - renderImportedDatasButton( - props?.autogeneratedHeaders?.length, - toggleAutoGeneratedHeaders, - showAutoGeneratedHeader, - `Auto Generated Header${ - props?.autogeneratedHeaders?.length > 1 ? "s" : "" - }`, - )} */} - - - - - - Key - - - Value - - - - {props?.data && props?.data?.length > 0 && showDatas && ( - - )} - {props?.autogeneratedHeaders && - props?.autogeneratedHeaders?.length > 0 && ( - - )} - - - ); -} - /** * Commons editor form which is being used by API and GraphQL. Since most of the things were common to both so picking out the common part was a better option. For now Body and Pagination component are being passed on by the using component. * @param props type CommonFormPropsWithExtraParams * @returns Editor with respect to which type is using it */ function CommonEditorForm(props: CommonFormPropsWithExtraParams) { - // the redux form has been configured with indexes, but the new ads components need strings to work. - // these functions convert them back and forth as needed. - const selectedIndex = useSelector(getApiPaneConfigSelectedTabIndex); - const selectedValue = Object.values(API_EDITOR_TABS)[selectedIndex]; - const setSelectedIndex = (value: API_EDITOR_TABS) => { - const index = Object.values(API_EDITOR_TABS).indexOf(value); - - dispatch(setApiPaneConfigSelectedTabIndex(index)); - }; const { actionRightPaneAdditionSections, - actionRightPaneBackLink, moreActionsMenu, notification, saveActionName, @@ -557,7 +228,6 @@ function CommonEditorForm(props: CommonFormPropsWithExtraParams) { responseDisplayFormat, settingsConfig, } = props; - const dispatch = useDispatch(); const params = useParams<{ baseApiId?: string; baseQueryId?: string }>(); @@ -603,8 +273,6 @@ function CommonEditorForm(props: CommonFormPropsWithExtraParams) { actionDatasourceName === DEFAULT_DATASOURCE_NAME) || !isExecutePermitted; - const isGraphql = isGraphqlPlugin(plugin); - const theme = EditorTheme.LIGHT; return ( @@ -644,131 +312,42 @@ function CommonEditorForm(props: CommonFormPropsWithExtraParams) { )} -
- {/* eslint-disable-next-line */} - {/* @ts-ignore*/} - -
- - - +
{hintMessages && ( - {hintMessages.map((msg, i) => ( - - {msg} - - ))} + )}
- - - - {Object.values(API_EDITOR_TABS).map((tab) => ( - - {createMessage(API_EDITOR_TAB_TITLES[tab])} - - ))} - - - - - - - - - - - - {props.bodyUIComponent} - - - {props.paginationUIComponent} - - - - - - - - - - +
diff --git a/app/client/src/pages/Editor/APIEditor/GraphQL/GraphQLEditorForm.tsx b/app/client/src/pages/Editor/APIEditor/GraphQL/GraphQLEditorForm.tsx index 639ef20f6d48..91ee9aeb7194 100644 --- a/app/client/src/pages/Editor/APIEditor/GraphQL/GraphQLEditorForm.tsx +++ b/app/client/src/pages/Editor/APIEditor/GraphQL/GraphQLEditorForm.tsx @@ -24,6 +24,7 @@ import VariableEditor from "./VariableEditor"; import Pagination from "./Pagination"; import { ApiEditorContext } from "../ApiEditorContext"; import { actionResponseDisplayDataFormats } from "pages/Editor/utils"; +import { GRAPHQL_HTTP_METHOD_OPTIONS } from "constants/ApiEditorConstants/GraphQLEditorConstants"; const ResizeableDiv = styled.div` display: flex; @@ -139,6 +140,7 @@ function GraphQLEditorForm(props: Props) { closeEditorLink={closeEditorLink} defaultTabSelected={2} formName={API_EDITOR_FORM_NAME} + httpsMethods={GRAPHQL_HTTP_METHOD_OPTIONS} paginationUIComponent={ props.theme.colors.apiPane.body.text}; - } -`; +import { HTTP_METHOD_OPTIONS } from "constants/ApiEditorConstants/CommonApiConstants"; type APIFormProps = { httpMethodFromForm: string; @@ -44,24 +32,18 @@ type Props = APIFormProps & InjectedFormProps; function ApiEditorForm(props: Props) { const { closeEditorLink } = useContext(ApiEditorContext); - const { actionName, httpMethodFromForm } = props; - const allowPostBody = httpMethodFromForm; + const { actionName } = props; const theme = EditorTheme.LIGHT; return ( - ) : ( - - {createMessage(API_PANE_NO_BODY)} - - ) + } closeEditorLink={closeEditorLink} formName={API_EDITOR_FORM_NAME} + httpsMethods={HTTP_METHOD_OPTIONS} paginationUIComponent={ (); // fetch the error count from the store. @@ -344,7 +340,6 @@ export function EditorJSONtoForm(props: Props) {
diff --git a/app/client/src/selectors/apiPaneSelectors.ts b/app/client/src/selectors/apiPaneSelectors.ts index e7bd59bc3a24..92d53733783a 100644 --- a/app/client/src/selectors/apiPaneSelectors.ts +++ b/app/client/src/selectors/apiPaneSelectors.ts @@ -16,9 +16,6 @@ export const getDisplayFormat: GetFormData = (state, apiId) => { export const getApiPaneConfigSelectedTabIndex = (state: AppState) => state.ui.apiPane.selectedConfigTabIndex; -export const getApiRightPaneSelectedTab = (state: AppState) => - state.ui.apiPane.selectedRightPaneTab; - export const getIsRunning = (state: AppState, apiId: string) => state.ui.apiPane.isRunning[apiId];