diff --git a/frontend/src/_components/ApiEndpointInput.jsx b/frontend/src/_components/ApiEndpointInput.jsx
index 063093f4a2..b58a2c9fe8 100644
--- a/frontend/src/_components/ApiEndpointInput.jsx
+++ b/frontend/src/_components/ApiEndpointInput.jsx
@@ -59,6 +59,7 @@ const ApiEndpointInput = (props) => {
const [loadingSpec, setLoadingSpec] = useState(true);
const [options, setOptions] = useState(props.options);
const [specJson, setSpecJson] = useState(null);
+ const [operationParams, setOperationParams] = useState({});
// Check if specUrl is an object (multiple specs) or string (single spec)
const isMultiSpec = typeof props.specUrl === 'object' && !Array.isArray(props.specUrl);
@@ -78,21 +79,25 @@ const ApiEndpointInput = (props) => {
setSpecJson(data);
if (isMultiSpec) {
- // Clear all parameters when switching specs
- const queryParams = {
+ // MODIFIED: Retain existing values instead of clearing them
+ const currentParams = options?.params || {
path: {},
query: {},
request: {},
};
- let newOperation = null;
- let newPath = null;
+ // Keep existing values if the operation/path still exists in the new spec
+ let newOperation = options?.operation;
+ let newPath = options?.path;
let newSelectedOperation = null;
- if (options?.path && options?.operation && data?.paths?.[options.path]?.[options.operation]) {
- newOperation = options.operation;
- newPath = options.path;
- newSelectedOperation = data.paths[options.path][options.operation];
+ // Validate if the current operation/path exists in the new spec
+ if (newPath && newOperation && data?.paths?.[newPath]?.[newOperation]) {
+ newSelectedOperation = data.paths[newPath][newOperation];
+ } else {
+ // Only clear if the operation/path doesn't exist in the new spec
+ newOperation = null;
+ newPath = null;
}
const newOptions = {
@@ -100,7 +105,8 @@ const ApiEndpointInput = (props) => {
operation: newOperation,
path: newPath,
selectedOperation: newSelectedOperation,
- params: queryParams,
+ params: currentParams, // Retain existing params
+ specType: specUrlOrType,
};
setOptions(newOptions);
@@ -112,12 +118,24 @@ const ApiEndpointInput = (props) => {
});
};
+ const getOperationKey = (operation, path) => {
+ return `${operation}_${path}`;
+ };
+
const changeOperation = (value) => {
const operation = value.split('/', 2)[0];
const path = value.substring(value.indexOf('/'));
- // Clear all params when changing operation
- const queryParams = {
+ if (options.operation && options.path) {
+ const currentOperationKey = getOperationKey(options.operation, options.path);
+ setOperationParams((prevState) => ({
+ ...prevState,
+ [currentOperationKey]: options.params,
+ }));
+ }
+
+ const newOperationKey = getOperationKey(operation, path);
+ const savedParams = operationParams[newOperationKey] || {
path: {},
query: {},
request: {},
@@ -128,7 +146,7 @@ const ApiEndpointInput = (props) => {
path,
operation,
selectedOperation: specJson.paths[path][operation],
- params: queryParams,
+ params: savedParams,
};
setOptions(newOptions);
@@ -167,7 +185,13 @@ const ApiEndpointInput = (props) => {
const removeParam = (paramType, paramName) => {
const newOptions = JSON.parse(JSON.stringify(options));
- delete newOptions['params'][paramType][paramName];
+ const newParams = { ...newOptions.params };
+ const newParamType = { ...newParams[paramType] };
+
+ delete newParamType[paramName];
+
+ newParams[paramType] = newParamType;
+ newOptions.params = newParams;
setOptions(newOptions);
props.optionsChanged(newOptions);
};
diff --git a/frontend/src/_components/ApiEndpointInputOld.jsx b/frontend/src/_components/ApiEndpointInputOld.jsx
new file mode 100644
index 0000000000..193413ee07
--- /dev/null
+++ b/frontend/src/_components/ApiEndpointInputOld.jsx
@@ -0,0 +1,361 @@
+import React, { useEffect, useState } from 'react';
+import { openapiService } from '@/_services';
+import Select from '@/_ui/Select';
+import { queryManagerSelectComponentStyle } from '@/_ui/Select/styles';
+import DOMPurify from 'dompurify';
+import { ToolTip } from '@/_components';
+import CodeHinter from '@/AppBuilder/CodeEditor';
+import { withTranslation } from 'react-i18next';
+import { isEmpty } from 'lodash';
+import PropTypes from 'prop-types';
+import SolidIcons from '@/_ui/Icon/SolidIcons';
+
+const operationColorMapping = {
+ get: 'azure',
+ post: 'green',
+ delete: 'red',
+ put: 'yellow',
+};
+
+const ApiEndpointInput = (props) => {
+ const [loadingSpec, setLoadingSpec] = useState(true);
+ const [options, setOptions] = useState(props.options);
+ const [specJson, setSpecJson] = useState(null);
+
+ const fetchOpenApiSpec = () => {
+ setLoadingSpec(true);
+ openapiService
+ .fetchSpecFromUrl(props.specUrl)
+ .then((response) => response.text())
+ .then((text) => {
+ const data = JSON.parse(text);
+ setSpecJson(data);
+ setLoadingSpec(false);
+ });
+ };
+
+ const changeOperation = (value) => {
+ const operation = value.split('/', 2)[0];
+ const path = value.substring(value.indexOf('/'));
+ const newOptions = { ...options, path, operation, selectedOperation: specJson.paths[path][operation] };
+ setOptions(newOptions);
+ props.optionsChanged(newOptions);
+ };
+
+ const changeParam = (paramType, paramName, value) => {
+ if (value === '') {
+ removeParam(paramType, paramName);
+ } else {
+ const newOptions = {
+ ...options,
+ params: {
+ ...options.params,
+ [paramType]: {
+ ...options.params[paramType],
+ [paramName]: value,
+ },
+ },
+ };
+ setOptions(newOptions);
+ props.optionsChanged(newOptions);
+ }
+ };
+
+ const removeParam = (paramType, paramName) => {
+ const newOptions = JSON.parse(JSON.stringify(options));
+ delete newOptions['params'][paramType][paramName];
+ setOptions(newOptions);
+ props.optionsChanged(newOptions);
+ };
+
+ const renderOperationOption = (data) => {
+ const path = data.value.substring(data.value.indexOf('/'));
+ const operation = data.operation;
+ if (path && operation) {
+ return (
+
+
+ {operation}
+
+
+ {path}
+
+
+ );
+ } else {
+ return 'Select an operation';
+ }
+ };
+
+ const categorizeOperations = (operation, path, acc, category) => {
+ const option = {
+ value: `${operation}${path}`,
+ label: `${path}`,
+ name: path,
+ operation: operation,
+ };
+ const existingCategory = acc.find((obj) => obj.label === category);
+ if (existingCategory) {
+ existingCategory.options.push(option);
+ } else {
+ acc.push({
+ label: category,
+ options: [option],
+ });
+ }
+ };
+
+ const computeOperationSelectionOptions = () => {
+ const paths = specJson?.paths;
+ if (isEmpty(paths)) return [];
+
+ const pathGroups = Object.keys(paths).reduce((acc, path) => {
+ const operations = Object.keys(paths[path]);
+ const category = path.split('/')[2];
+ operations.forEach((operation) => categorizeOperations(operation, path, acc, category));
+ return acc;
+ }, []);
+
+ return pathGroups;
+ };
+
+ useEffect(() => {
+ const queryParams = {
+ path: props.options?.params?.path ?? {},
+ query: props.options?.params?.query ?? {},
+ request: props.options?.params?.request ?? {},
+ };
+ setLoadingSpec(true);
+ setOptions({ ...props.options, params: queryParams });
+ fetchOpenApiSpec();
+ }, []);
+
+ return (
+
+ {loadingSpec && (
+
+
+ {props.t('stripe', 'Please wait while we load the OpenAPI specification.')}
+
+ )}
+ {options && !loadingSpec && (
+
+
+
+
+
+
+
+
+ {options?.selectedOperation && (
+
+
+
+
+
+ )}
+
+ )}
+
+ );
+};
+
+export default withTranslation()(ApiEndpointInput);
+
+ApiEndpointInput.propTypes = {
+ options: PropTypes.object,
+ specUrl: PropTypes.string,
+ optionsChanged: PropTypes.func,
+ darkMode: PropTypes.bool,
+ t: PropTypes.func,
+};
+
+const RenderParameterFields = ({ parameters, type, label, options, changeParam, removeParam, darkMode }) => {
+ let filteredParams;
+ if (type === 'request') {
+ filteredParams = Object.keys(parameters);
+ } else {
+ filteredParams = parameters?.filter((param) => param.in === type);
+ }
+
+ const paramLabelWithDescription = (param) => {
+ return (
+
+
+
+
+
+ );
+ };
+
+ const paramLabelWithoutDescription = (param) => {
+ return (
+
+ );
+ };
+
+ const paramType = (param) => {
+ return (
+
+ {type === 'query' &&
+ param?.schema?.anyOf &&
+ param?.schema?.anyOf.map((type, i) =>
+ i < param.schema?.anyOf.length - 1
+ ? type.type.substring(0, 3).toUpperCase() + '|'
+ : type.type.substring(0, 3).toUpperCase()
+ )}
+ {(type === 'path' || (type === 'query' && !param?.schema?.anyOf)) &&
+ param?.schema?.type?.substring(0, 3).toUpperCase()}
+ {type === 'request' && parameters[param].type?.substring(0, 3).toUpperCase()}
+
+ );
+ };
+
+ const paramDetails = (param) => {
+ return (
+
+ {(type === 'request' && parameters[param].description) || param?.description
+ ? paramLabelWithDescription(param)
+ : paramLabelWithoutDescription(param)}
+ {param.required && *}
+ {paramType(param)}
+
+ );
+ };
+
+ const inputField = (param) => {
+ return (
+ {
+ if (type === 'request') {
+ changeParam(type, param, value);
+ } else {
+ changeParam(type, param.name, value);
+ }
+ }}
+ height={'32px'}
+ />
+ );
+ };
+
+ const clearButton = (param) => {
+ const handleClear = () => {
+ if (type === 'request') {
+ removeParam(type, param);
+ } else {
+ removeParam(type, param.name);
+ }
+ };
+
+ return (
+ {
+ if (e.key === 'Enter') {
+ handleClear();
+ }
+ }}
+ tabIndex="0"
+ >
+
+
+ );
+ };
+
+ return (
+ filteredParams?.length > 0 && (
+
+
{label}
+
+ {filteredParams.map((param) => (
+
+
+ {paramDetails(param)}
+
{inputField(param)}
+ {((type === 'request' && options['params'][type][param]) || options['params'][type][param.name]) &&
+ clearButton(param)}
+
+
+ ))}
+
+
+ )
+ );
+};
+
+RenderParameterFields.propTypes = {
+ parameters: PropTypes.any,
+ type: PropTypes.string,
+ label: PropTypes.string,
+ options: PropTypes.object,
+ changeParam: PropTypes.func,
+ removeParam: PropTypes.func,
+ darkMode: PropTypes.bool,
+};
diff --git a/frontend/src/_components/DynamicForm.jsx b/frontend/src/_components/DynamicForm.jsx
index 5e0aa29afb..b5a7e10b1d 100644
--- a/frontend/src/_components/DynamicForm.jsx
+++ b/frontend/src/_components/DynamicForm.jsx
@@ -14,6 +14,7 @@ import GoogleSheets from '@/_components/Googlesheets';
import Slack from '@/_components/Slack';
import Zendesk from '@/_components/Zendesk';
import ApiEndpointInput from '@/_components/ApiEndpointInput';
+import ApiEndpointInputOld from './ApiEndpointInputOld';
import { ConditionFilter, CondtionSort, MultiColumn } from '@/_components/MultiConditions';
import ToolJetDbOperations from '@/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/ToolJetDbOperations';
import { orgEnvironmentVariableService, orgEnvironmentConstantService } from '../_services';
@@ -210,6 +211,8 @@ const DynamicForm = ({
return CondtionSort;
case 'react-component-api-endpoint':
return ApiEndpointInput;
+ case 'react-component-api-endpoint-old':
+ return ApiEndpointInputOld;
case 'react-component-sharepoint':
return Sharepoint;
case 'react-component-oauth':
@@ -502,6 +505,13 @@ const DynamicForm = ({
options,
darkMode,
};
+ case 'react-component-api-endpoint-old':
+ return {
+ specUrl: spec_url,
+ optionsChanged,
+ options,
+ darkMode,
+ };
default:
return {};
}
@@ -580,7 +590,11 @@ const DynamicForm = ({
{Object.keys(obj).map((key) => {
const { label, type, encrypted, className, key: propertyKey, shouldRenderTheProperty = '' } = obj[key];
const Element = getElement(type);
- const isSpecificComponent = ['tooljetdb-operations', 'react-component-api-endpoint'].includes(type);
+ const isSpecificComponent = [
+ 'tooljetdb-operations',
+ 'react-component-api-endpoint',
+ 'react-component-api-endpoint-old',
+ ].includes(type);
// shouldRenderTheProperty - key is used for Dynamic connection parameters
const enabled = shouldRenderTheProperty
? selectedDataSource?.options?.[shouldRenderTheProperty]?.value ?? false
diff --git a/plugins/packages/stripe/lib/operations.json b/plugins/packages/stripe/lib/operations.json
index 4532254d99..3fb22283d7 100644
--- a/plugins/packages/stripe/lib/operations.json
+++ b/plugins/packages/stripe/lib/operations.json
@@ -8,7 +8,7 @@
"operation": {
"label": "",
"key": "stripe_operation",
- "type": "react-component-api-endpoint",
+ "type": "react-component-api-endpoint-old",
"description": "Single select dropdown for operation",
"spec_url": "https://raw.githubusercontent.com/stripe/openapi/master/openapi/spec3.json"
}