Embedded Datasource
- Show path along with url in the api pane for embedded datasource - Add store as datasource menu option for embedded datasource - Allow users to type the path for global datasources - Show the base url as a tag along with the path for global datasource.
This commit is contained in:
parent
2eb6d6cbe7
commit
62f003df6e
|
|
@ -17,6 +17,7 @@
|
||||||
"responsetext2": "qui est esse",
|
"responsetext2": "qui est esse",
|
||||||
"baseUrl3": "https://reqres.in",
|
"baseUrl3": "https://reqres.in",
|
||||||
"methods2": "api/users/2",
|
"methods2": "api/users/2",
|
||||||
|
"invalidPath": "api/users/a",
|
||||||
"responsetext3": "Josh M Krantz",
|
"responsetext3": "Josh M Krantz",
|
||||||
"postUrl": "https://reqres.in",
|
"postUrl": "https://reqres.in",
|
||||||
"deleteUrl": "",
|
"deleteUrl": "",
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ describe("API Panel Test Functionality", function() {
|
||||||
cy.ClearSearch();
|
cy.ClearSearch();
|
||||||
cy.SearchAPIandClick("SecondAPI");
|
cy.SearchAPIandClick("SecondAPI");
|
||||||
//invalid api end point check
|
//invalid api end point check
|
||||||
cy.EditSourceDetail(testdata.baseUrl3, testdata.methods2);
|
cy.EditSourceDetail(testdata.baseUrl3, testdata.invalidPath);
|
||||||
cy.ResponseStatusCheck("404 NOT_FOUND");
|
cy.ResponseStatusCheck("404 NOT_FOUND");
|
||||||
cy.DeleteAPI();
|
cy.DeleteAPI();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -202,7 +202,8 @@ Cypress.Commands.add("enterDatasourceAndPath", (datasource, path) => {
|
||||||
cy.xpath(apiwidget.autoSuggest)
|
cy.xpath(apiwidget.autoSuggest)
|
||||||
.first()
|
.first()
|
||||||
.click({ force: true });
|
.click({ force: true });
|
||||||
cy.get(apiwidget.path)
|
cy.get(apiwidget.editResourceUrl)
|
||||||
|
.first()
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
.type(path, { parseSpecialCharSequences: false });
|
.type(path, { parseSpecialCharSequences: false });
|
||||||
});
|
});
|
||||||
|
|
@ -228,16 +229,17 @@ Cypress.Commands.add(
|
||||||
Cypress.Commands.add("EditSourceDetail", (baseUrl, v1method) => {
|
Cypress.Commands.add("EditSourceDetail", (baseUrl, v1method) => {
|
||||||
cy.get(apiwidget.editResourceUrl)
|
cy.get(apiwidget.editResourceUrl)
|
||||||
.first()
|
.first()
|
||||||
.clear()
|
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
.type(baseUrl);
|
.clear()
|
||||||
|
.type(`{backspace}${baseUrl}`);
|
||||||
cy.xpath(apiwidget.autoSuggest)
|
cy.xpath(apiwidget.autoSuggest)
|
||||||
.first()
|
.first()
|
||||||
.click({ force: true });
|
.click({ force: true });
|
||||||
cy.get(ApiEditor.ApiRunBtn).scrollIntoView();
|
cy.get(ApiEditor.ApiRunBtn).scrollIntoView();
|
||||||
cy.get(apiwidget.path)
|
cy.get(apiwidget.editResourceUrl)
|
||||||
|
.first()
|
||||||
.focus()
|
.focus()
|
||||||
.type("{selectall}{backspace}api/users/2")
|
.type(v1method)
|
||||||
.should("have.value", v1method);
|
.should("have.value", v1method);
|
||||||
cy.SaveAPI();
|
cy.SaveAPI();
|
||||||
});
|
});
|
||||||
|
|
@ -905,7 +907,7 @@ Cypress.Commands.add("createApi", (url, parameters) => {
|
||||||
cy.contains(url).click({
|
cy.contains(url).click({
|
||||||
force: true,
|
force: true,
|
||||||
});
|
});
|
||||||
cy.get(".CodeMirror.CodeMirror-empty textarea")
|
cy.get(apiwidget.editResourceUrl)
|
||||||
.first()
|
.first()
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
.type(parameters, { force: true });
|
.type(parameters, { force: true });
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,16 @@ export const createNewApiAction = (
|
||||||
payload: { pageId },
|
payload: { pageId },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const setDatasourceFieldText = (
|
||||||
|
apiId: string,
|
||||||
|
value: string,
|
||||||
|
): ReduxAction<{ apiId: string; value: string }> => {
|
||||||
|
return {
|
||||||
|
type: ReduxActionTypes.SET_DATASOURCE_FIELD_TEXT,
|
||||||
|
payload: { apiId, value },
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const setExtraFormData = (
|
export const setExtraFormData = (
|
||||||
apiId: string,
|
apiId: string,
|
||||||
extraformData: {},
|
extraformData: {},
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,12 @@ export const initDatasourcePane = (
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const storeAsDatasource = () => {
|
||||||
|
return {
|
||||||
|
type: ReduxActionTypes.STORE_AS_DATASOURCE_INIT,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
createDatasource,
|
createDatasource,
|
||||||
fetchDatasources,
|
fetchDatasources,
|
||||||
|
|
|
||||||
3
app/client/src/assets/icons/menu/storage.svg
Normal file
3
app/client/src/assets/icons/menu/storage.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="20" height="16" viewBox="0 0 20 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M0 16H20V12H0V16ZM2 13H4V15H2V13ZM0 0V4H20V0H0ZM4 3H2V1H4V3ZM0 10H20V6H0V10ZM2 7H4V9H2V7Z" fill="#F4F4F4"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 220 B |
|
|
@ -1,7 +1,9 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import Creatable from "react-select/creatable";
|
import Select, { InputActionMeta } from "react-select";
|
||||||
import { WrappedFieldInputProps, WrappedFieldMetaProps } from "redux-form";
|
import { WrappedFieldInputProps, WrappedFieldMetaProps } from "redux-form";
|
||||||
|
|
||||||
import { theme } from "constants/DefaultTheme";
|
import { theme } from "constants/DefaultTheme";
|
||||||
|
import { SelectComponents } from "react-select/src/components";
|
||||||
|
|
||||||
type DropdownProps = {
|
type DropdownProps = {
|
||||||
options: Array<{
|
options: Array<{
|
||||||
|
|
@ -12,9 +14,12 @@ type DropdownProps = {
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
input: WrappedFieldInputProps;
|
input: WrappedFieldInputProps;
|
||||||
meta: WrappedFieldMetaProps;
|
meta: WrappedFieldMetaProps;
|
||||||
|
components: SelectComponents<any>;
|
||||||
onCreateOption: (inputValue: string) => void;
|
onCreateOption: (inputValue: string) => void;
|
||||||
formatCreateLabel?: (value: string) => React.ReactNode;
|
formatCreateLabel?: (value: string) => React.ReactNode;
|
||||||
noOptionsMessage?: (obj: { inputValue: string }) => string;
|
noOptionsMessage?: (obj: { inputValue: string }) => string;
|
||||||
|
inputValue?: string;
|
||||||
|
onInputChange: (value: string, actionMeta: InputActionMeta) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectStyles = {
|
const selectStyles = {
|
||||||
|
|
@ -22,7 +27,7 @@ const selectStyles = {
|
||||||
...provided,
|
...provided,
|
||||||
color: "#a3b3bf",
|
color: "#a3b3bf",
|
||||||
}),
|
}),
|
||||||
singleValue: (provided: any) => ({
|
multiValue: (provided: any) => ({
|
||||||
...provided,
|
...provided,
|
||||||
backgroundColor: "rgba(104,113,239,0.1)",
|
backgroundColor: "rgba(104,113,239,0.1)",
|
||||||
border: "1px solid rgba(104, 113, 239, 0.5)",
|
border: "1px solid rgba(104, 113, 239, 0.5)",
|
||||||
|
|
@ -34,9 +39,15 @@ const selectStyles = {
|
||||||
display: "inline-block",
|
display: "inline-block",
|
||||||
transform: "none",
|
transform: "none",
|
||||||
}),
|
}),
|
||||||
|
multiValueRemove: () => {
|
||||||
|
return {
|
||||||
|
display: "none",
|
||||||
|
};
|
||||||
|
},
|
||||||
container: (styles: any) => ({
|
container: (styles: any) => ({
|
||||||
...styles,
|
...styles,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
zIndex: "5",
|
||||||
}),
|
}),
|
||||||
control: (styles: any, state: any) => ({
|
control: (styles: any, state: any) => ({
|
||||||
...styles,
|
...styles,
|
||||||
|
|
@ -69,23 +80,34 @@ class CreatableDropdown extends React.Component<DropdownProps> {
|
||||||
placeholder,
|
placeholder,
|
||||||
options,
|
options,
|
||||||
isLoading,
|
isLoading,
|
||||||
onCreateOption,
|
|
||||||
input,
|
input,
|
||||||
formatCreateLabel,
|
|
||||||
noOptionsMessage,
|
noOptionsMessage,
|
||||||
|
components,
|
||||||
|
inputValue,
|
||||||
|
onInputChange,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const optionalProps: Partial<DropdownProps> = {};
|
const optionalProps: Partial<DropdownProps> = {};
|
||||||
if (formatCreateLabel) optionalProps.formatCreateLabel = formatCreateLabel;
|
|
||||||
if (noOptionsMessage) optionalProps.noOptionsMessage = noOptionsMessage;
|
if (noOptionsMessage) optionalProps.noOptionsMessage = noOptionsMessage;
|
||||||
|
if (components) optionalProps.components = components;
|
||||||
|
if (inputValue) optionalProps.inputValue = inputValue;
|
||||||
|
if (onInputChange) optionalProps.onInputChange = onInputChange;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Creatable
|
<Select
|
||||||
|
isMulti
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
options={options}
|
options={options}
|
||||||
styles={selectStyles}
|
styles={selectStyles}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
onCreateOption={onCreateOption}
|
|
||||||
{...input}
|
{...input}
|
||||||
onChange={value => input.onChange(value)}
|
onChange={value => {
|
||||||
|
const formattedValue = value;
|
||||||
|
if (formattedValue && formattedValue.length > 1) {
|
||||||
|
formattedValue.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
input.onChange(formattedValue);
|
||||||
|
}}
|
||||||
onBlur={() => input.value}
|
onBlur={() => input.value}
|
||||||
isClearable
|
isClearable
|
||||||
{...optionalProps}
|
{...optionalProps}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import { AppState } from "reducers";
|
||||||
import { ActionResponse } from "api/ActionAPI";
|
import { ActionResponse } from "api/ActionAPI";
|
||||||
import { formatBytes } from "utils/helpers";
|
import { formatBytes } from "utils/helpers";
|
||||||
import { APIEditorRouteParams } from "constants/routes";
|
import { APIEditorRouteParams } from "constants/routes";
|
||||||
import { ApiPaneReduxState } from "reducers/uiReducers/apiPaneReducer";
|
|
||||||
import LoadingOverlayScreen from "components/editorComponents/LoadingOverlayScreen";
|
import LoadingOverlayScreen from "components/editorComponents/LoadingOverlayScreen";
|
||||||
import CodeEditor from "components/editorComponents/CodeEditor";
|
import CodeEditor from "components/editorComponents/CodeEditor";
|
||||||
import { getActionResponses } from "selectors/entitiesSelector";
|
import { getActionResponses } from "selectors/entitiesSelector";
|
||||||
|
|
@ -53,7 +52,7 @@ const TableWrapper = styled.div`
|
||||||
|
|
||||||
interface ReduxStateProps {
|
interface ReduxStateProps {
|
||||||
responses: Record<string, ActionResponse | undefined>;
|
responses: Record<string, ActionResponse | undefined>;
|
||||||
apiPane: ApiPaneReduxState;
|
isRunning: Record<string, boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ResponseHeadersView = (props: { data: Record<string, string[]> }) => {
|
const ResponseHeadersView = (props: { data: Record<string, string[]> }) => {
|
||||||
|
|
@ -109,14 +108,13 @@ const ApiResponseView = (props: Props) => {
|
||||||
params: { apiId },
|
params: { apiId },
|
||||||
},
|
},
|
||||||
responses,
|
responses,
|
||||||
apiPane,
|
|
||||||
} = props;
|
} = props;
|
||||||
let response: ActionResponse = EMPTY_RESPONSE;
|
let response: ActionResponse = EMPTY_RESPONSE;
|
||||||
let isRunning = false;
|
let isRunning = false;
|
||||||
let hasFailed = false;
|
let hasFailed = false;
|
||||||
if (apiId && apiId in responses) {
|
if (apiId && apiId in responses) {
|
||||||
response = responses[apiId] || EMPTY_RESPONSE;
|
response = responses[apiId] || EMPTY_RESPONSE;
|
||||||
isRunning = apiPane.isRunning[apiId];
|
isRunning = props.isRunning[apiId];
|
||||||
hasFailed = response.statusCode ? response.statusCode[0] !== "2" : false;
|
hasFailed = response.statusCode ? response.statusCode[0] !== "2" : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -217,7 +215,7 @@ const ApiResponseView = (props: Props) => {
|
||||||
const mapStateToProps = (state: AppState): ReduxStateProps => {
|
const mapStateToProps = (state: AppState): ReduxStateProps => {
|
||||||
return {
|
return {
|
||||||
responses: getActionResponses(state),
|
responses: getActionResponses(state),
|
||||||
apiPane: state.ui.apiPane,
|
isRunning: state.ui.apiPane.isRunning,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,65 +1,266 @@
|
||||||
import React from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import CreatableDropdown from "components/designSystems/appsmith/CreatableDropdown";
|
import CreatableDropdown from "components/designSystems/appsmith/CreatableDropdown";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { Field } from "redux-form";
|
import { Field, formValueSelector, change } from "redux-form";
|
||||||
import { AppState } from "reducers";
|
import { AppState } from "reducers";
|
||||||
|
import { ReactComponent as StorageIcon } from "assets/icons/menu/storage.svg";
|
||||||
import { DatasourceDataState } from "reducers/entityReducers/datasourceReducer";
|
import { DatasourceDataState } from "reducers/entityReducers/datasourceReducer";
|
||||||
import { Plugin } from "api/PluginApi";
|
import { Plugin } from "api/PluginApi";
|
||||||
import { getDatasourcePlugins } from "selectors/entitiesSelector";
|
import { getDatasourcePlugins } from "selectors/entitiesSelector";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { createDatasource } from "actions/datasourceActions";
|
import { createDatasource, storeAsDatasource } from "actions/datasourceActions";
|
||||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||||
|
import { Datasource, CreateDatasourceConfig } from "api/DatasourcesApi";
|
||||||
|
import styled, { createGlobalStyle } from "styled-components";
|
||||||
|
import { MenuItem, Menu, Popover, Position } from "@blueprintjs/core";
|
||||||
|
import { IconWrapper } from "constants/IconConstants";
|
||||||
|
import { theme } from "constants/DefaultTheme";
|
||||||
|
import { ControlIcons } from "icons/ControlIcons";
|
||||||
|
import { API_EDITOR_FORM_NAME } from "constants/forms";
|
||||||
|
import { InputActionMeta } from "react-select";
|
||||||
|
import { setDatasourceFieldText } from "actions/apiPaneActions";
|
||||||
|
|
||||||
interface ReduxStateProps {
|
interface ReduxStateProps {
|
||||||
datasources: DatasourceDataState;
|
datasources: DatasourceDataState;
|
||||||
validDatasourcePlugins: Plugin[];
|
validDatasourcePlugins: Plugin[];
|
||||||
|
apiId: string;
|
||||||
|
value: Datasource;
|
||||||
}
|
}
|
||||||
interface ReduxActionProps {
|
interface ReduxActionProps {
|
||||||
createDatasource: (value: string) => void;
|
createDatasource: (value: string) => void;
|
||||||
|
storeAsDatasource: () => void;
|
||||||
|
changeDatasource: (value: Datasource | CreateDatasourceConfig) => void;
|
||||||
|
changePath: (value: string) => void;
|
||||||
|
setDatasourceFieldText: (apiId: string, value: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ComponentProps {
|
interface ComponentProps {
|
||||||
name: string;
|
name: string;
|
||||||
pluginId: string;
|
pluginId: string;
|
||||||
appName: string;
|
appName: string;
|
||||||
|
datasourceFieldText: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const StyledMenuItem = styled(MenuItem)`
|
||||||
|
&&&&.bp3-menu-item {
|
||||||
|
align-items: center;
|
||||||
|
width: 202px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledMenu = styled(Menu)`
|
||||||
|
&&&&.bp3-menu {
|
||||||
|
padding: 8px;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid #ebeff2;
|
||||||
|
box-sizing: border-box;
|
||||||
|
box-shadow: 0px 2px 4px rgba(67, 70, 74, 0.14);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TooltipStyles = createGlobalStyle`
|
||||||
|
.helper-tooltip{
|
||||||
|
.bp3-popover {
|
||||||
|
margin-right: 10px;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
const DatasourcesField = (
|
const DatasourcesField = (
|
||||||
props: ReduxActionProps & ReduxStateProps & ComponentProps,
|
props: ReduxActionProps & ReduxStateProps & ComponentProps,
|
||||||
) => {
|
) => {
|
||||||
const options = props.datasources.list
|
const [inputValue, setValue] = useState(props.datasourceFieldText);
|
||||||
.filter(r => r.pluginId === props.pluginId)
|
|
||||||
.filter(r =>
|
useEffect(() => {
|
||||||
props.validDatasourcePlugins.some(plugin => plugin.id === r.pluginId),
|
setValue(props.datasourceFieldText);
|
||||||
)
|
}, [props.datasourceFieldText]);
|
||||||
.filter(r => r.datasourceConfiguration)
|
|
||||||
.filter(r => r.datasourceConfiguration.url)
|
const options = React.useMemo(() => {
|
||||||
.map(r => ({
|
return props.datasources.list
|
||||||
label: r.datasourceConfiguration?.url.endsWith("/")
|
.filter(r => r.pluginId === props.pluginId)
|
||||||
? r.datasourceConfiguration?.url.slice(0, -1)
|
.filter(r => {
|
||||||
: r.datasourceConfiguration.url,
|
return props.validDatasourcePlugins.some(
|
||||||
value: r.id,
|
plugin => plugin.id === r.pluginId,
|
||||||
}));
|
);
|
||||||
|
})
|
||||||
|
.filter(r => r.datasourceConfiguration)
|
||||||
|
.filter(r => r.datasourceConfiguration.url)
|
||||||
|
.map(r => ({
|
||||||
|
label: r.datasourceConfiguration?.url,
|
||||||
|
value: r.id,
|
||||||
|
}));
|
||||||
|
}, [props.datasources.list, props.validDatasourcePlugins, props.pluginId]);
|
||||||
|
|
||||||
|
const { storeAsDatasource } = props;
|
||||||
|
let isEmbeddedDatasource = true;
|
||||||
|
|
||||||
|
if (props.value && props.value.id) {
|
||||||
|
isEmbeddedDatasource = false;
|
||||||
|
} else if (props.value && props.value.datasourceConfiguration) {
|
||||||
|
isEmbeddedDatasource = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DropdownIndicator = (props: any) => {
|
||||||
|
if (props.hasValue) return null;
|
||||||
|
|
||||||
|
const MenuContainer = (
|
||||||
|
<StyledMenu>
|
||||||
|
<StyledMenuItem
|
||||||
|
icon={
|
||||||
|
<IconWrapper
|
||||||
|
width={theme.fontSizes[4]}
|
||||||
|
height={theme.fontSizes[4]}
|
||||||
|
color={"#535B62"}
|
||||||
|
>
|
||||||
|
<StorageIcon />
|
||||||
|
</IconWrapper>
|
||||||
|
}
|
||||||
|
text="Store as datasource"
|
||||||
|
onClick={storeAsDatasource}
|
||||||
|
/>
|
||||||
|
</StyledMenu>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<TooltipStyles />
|
||||||
|
<Popover
|
||||||
|
content={MenuContainer}
|
||||||
|
position={Position.BOTTOM_LEFT}
|
||||||
|
usePortal
|
||||||
|
portalClassName="helper-tooltip"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: "8px 13px 3px 13px",
|
||||||
|
}}
|
||||||
|
onMouseDown={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ControlIcons.MORE_HORIZONTAL_CONTROL
|
||||||
|
width={theme.fontSizes[4]}
|
||||||
|
height={theme.fontSizes[4]}
|
||||||
|
color="#C4C4C4"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<Field
|
<Field
|
||||||
name={props.name}
|
name={props.name}
|
||||||
component={CreatableDropdown}
|
component={CreatableDropdown}
|
||||||
isLoading={props.datasources.loading}
|
isLoading={props.datasources.loading}
|
||||||
options={options}
|
options={options}
|
||||||
|
components={{
|
||||||
|
ClearIndicator: () => null,
|
||||||
|
IndicatorSeparator: () => null,
|
||||||
|
DropdownIndicator,
|
||||||
|
}}
|
||||||
placeholder="https://<base-url>.com"
|
placeholder="https://<base-url>.com"
|
||||||
onCreateOption={props.createDatasource}
|
onInputChange={(value: string, actionMeta: InputActionMeta) => {
|
||||||
format={(value: string) => _.find(options, { value }) || ""}
|
const { action } = actionMeta;
|
||||||
parse={(option: { value: string }) => (option ? option.value : null)}
|
if (action === "input-blur") {
|
||||||
formatCreateLabel={(value: string) => `Create data source "${value}"`}
|
props.setDatasourceFieldText(props.apiId, inputValue);
|
||||||
noOptionsMessage={() => "No data sources created"}
|
|
||||||
|
return value;
|
||||||
|
} else if (action === "set-value") {
|
||||||
|
setValue("");
|
||||||
|
|
||||||
|
return "";
|
||||||
|
} else if (action === "menu-close") {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
setValue(value);
|
||||||
|
if (isEmbeddedDatasource) {
|
||||||
|
let datasourcePayload: Datasource | CreateDatasourceConfig;
|
||||||
|
let pathPayload: string;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = new URL(value);
|
||||||
|
const path = url.pathname === "/" ? "" : url.pathname;
|
||||||
|
const params = url.search;
|
||||||
|
const baseUrl = url.origin;
|
||||||
|
|
||||||
|
datasourcePayload = {
|
||||||
|
name: baseUrl,
|
||||||
|
datasourceConfiguration: {
|
||||||
|
url: baseUrl,
|
||||||
|
},
|
||||||
|
pluginId: props.pluginId,
|
||||||
|
appName: props.appName,
|
||||||
|
};
|
||||||
|
pathPayload = path + params;
|
||||||
|
} catch (e) {
|
||||||
|
datasourcePayload = {
|
||||||
|
name: value,
|
||||||
|
datasourceConfiguration: {
|
||||||
|
url: value,
|
||||||
|
},
|
||||||
|
pluginId: props.pluginId,
|
||||||
|
appName: props.appName,
|
||||||
|
};
|
||||||
|
pathPayload = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateValues = _.debounce(() => {
|
||||||
|
props.changeDatasource(datasourcePayload);
|
||||||
|
props.changePath(pathPayload);
|
||||||
|
}, 50);
|
||||||
|
|
||||||
|
updateValues();
|
||||||
|
} else {
|
||||||
|
const updatePath = _.debounce(() => {
|
||||||
|
props.changePath(value);
|
||||||
|
}, 50);
|
||||||
|
|
||||||
|
updatePath();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
format={(value: Datasource) => {
|
||||||
|
if (!value || !value.datasourceConfiguration) return "";
|
||||||
|
|
||||||
|
if (!value.id) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const option = _.find(options, { value: value.id });
|
||||||
|
|
||||||
|
return option ? [option] : "";
|
||||||
|
}}
|
||||||
|
parse={(option: { value: string }[]) => {
|
||||||
|
if (!option) return null;
|
||||||
|
|
||||||
|
if (option.length) {
|
||||||
|
const datasources = props.datasources.list;
|
||||||
|
|
||||||
|
return datasources.find(
|
||||||
|
datasource => datasource.id === option[0].value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
inputValue={inputValue}
|
||||||
|
noOptionsMessage={() => null}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state: AppState): ReduxStateProps => ({
|
const mapStateToProps = (state: AppState): ReduxStateProps => {
|
||||||
datasources: state.entities.datasources,
|
const selector = formValueSelector(API_EDITOR_FORM_NAME);
|
||||||
validDatasourcePlugins: getDatasourcePlugins(state),
|
const apiId = selector(state, "id");
|
||||||
});
|
const datasource = selector(state, "datasource");
|
||||||
|
return {
|
||||||
|
datasources: state.entities.datasources,
|
||||||
|
validDatasourcePlugins: getDatasourcePlugins(state),
|
||||||
|
apiId,
|
||||||
|
value: datasource,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const mapDispatchToProps = (
|
const mapDispatchToProps = (
|
||||||
dispatch: any,
|
dispatch: any,
|
||||||
|
|
@ -88,6 +289,16 @@ const mapDispatchToProps = (
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
storeAsDatasource: () => dispatch(storeAsDatasource()),
|
||||||
|
changeDatasource: value => {
|
||||||
|
dispatch(change(API_EDITOR_FORM_NAME, "datasource", value));
|
||||||
|
},
|
||||||
|
changePath: (value: string) => {
|
||||||
|
dispatch(change(API_EDITOR_FORM_NAME, "actionConfiguration.path", value));
|
||||||
|
},
|
||||||
|
setDatasourceFieldText: (apiId, value) => {
|
||||||
|
dispatch(setDatasourceFieldText(apiId, value));
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(DatasourcesField);
|
export default connect(mapStateToProps, mapDispatchToProps)(DatasourcesField);
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,10 @@ export const ReduxActionTypes: { [key: string]: string } = {
|
||||||
FETCH_PUBLISHED_PAGE_SUCCESS: "FETCH_PUBLISHED_PAGE_SUCCESS",
|
FETCH_PUBLISHED_PAGE_SUCCESS: "FETCH_PUBLISHED_PAGE_SUCCESS",
|
||||||
DELETE_DATASOURCE_INIT: "DELETE_DATASOURCE_INIT",
|
DELETE_DATASOURCE_INIT: "DELETE_DATASOURCE_INIT",
|
||||||
DELETE_DATASOURCE_SUCCESS: "DELETE_DATASOURCE_SUCCESS",
|
DELETE_DATASOURCE_SUCCESS: "DELETE_DATASOURCE_SUCCESS",
|
||||||
|
STORE_AS_DATASOURCE_INIT: "STORE_AS_DATASOURCE_INIT",
|
||||||
|
STORE_AS_DATASOURCE_UPDATE: "STORE_AS_DATASOURCE_UPDATE",
|
||||||
|
STORE_AS_DATASOURCE_COMPLETE: "STORE_AS_DATASOURCE_COMPLETE",
|
||||||
|
SET_DATASOURCE_FIELD_TEXT: "SET_DATASOURCE_FIELD_TEXT",
|
||||||
PUBLISH_APPLICATION_INIT: "PUBLISH_APPLICATION_INIT",
|
PUBLISH_APPLICATION_INIT: "PUBLISH_APPLICATION_INIT",
|
||||||
PUBLISH_APPLICATION_SUCCESS: "PUBLISH_APPLICATION_SUCCESS",
|
PUBLISH_APPLICATION_SUCCESS: "PUBLISH_APPLICATION_SUCCESS",
|
||||||
CREATE_PAGE_INIT: "CREATE_PAGE_INIT",
|
CREATE_PAGE_INIT: "CREATE_PAGE_INIT",
|
||||||
|
|
|
||||||
|
|
@ -9,29 +9,26 @@ import {
|
||||||
import {
|
import {
|
||||||
HTTP_METHOD_OPTIONS,
|
HTTP_METHOD_OPTIONS,
|
||||||
HTTP_METHODS,
|
HTTP_METHODS,
|
||||||
CONTENT_TYPE,
|
|
||||||
} from "constants/ApiEditorConstants";
|
} from "constants/ApiEditorConstants";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import PostBodyData from "./PostBodyData";
|
|
||||||
import FormLabel from "components/editorComponents/FormLabel";
|
import FormLabel from "components/editorComponents/FormLabel";
|
||||||
import FormRow from "components/editorComponents/FormRow";
|
import FormRow from "components/editorComponents/FormRow";
|
||||||
import { BaseButton } from "components/designSystems/blueprint/ButtonComponent";
|
import { BaseButton } from "components/designSystems/blueprint/ButtonComponent";
|
||||||
import { RestAction, PaginationField } from "api/ActionAPI";
|
import { RestAction, PaginationField } from "api/ActionAPI";
|
||||||
import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
||||||
import TextField from "components/editorComponents/form/fields/TextField";
|
import TextField from "components/editorComponents/form/fields/TextField";
|
||||||
import DynamicTextField from "components/editorComponents/form/fields/DynamicTextField";
|
|
||||||
import DropdownField from "components/editorComponents/form/fields/DropdownField";
|
import DropdownField from "components/editorComponents/form/fields/DropdownField";
|
||||||
import DatasourcesField from "components/editorComponents/form/fields/DatasourcesField";
|
import DatasourcesField from "components/editorComponents/form/fields/DatasourcesField";
|
||||||
import KeyValueFieldArray from "components/editorComponents/form/fields/KeyValueFieldArray";
|
|
||||||
import ApiResponseView from "components/editorComponents/ApiResponseView";
|
|
||||||
import { API_EDITOR_FORM_NAME } from "constants/forms";
|
import { API_EDITOR_FORM_NAME } from "constants/forms";
|
||||||
import LoadingOverlayScreen from "components/editorComponents/LoadingOverlayScreen";
|
import LoadingOverlayScreen from "components/editorComponents/LoadingOverlayScreen";
|
||||||
import { FormIcons } from "icons/FormIcons";
|
|
||||||
import { BaseTabbedView } from "components/designSystems/appsmith/TabbedView";
|
|
||||||
import Pagination, { PaginationType } from "./Pagination";
|
import Pagination, { PaginationType } from "./Pagination";
|
||||||
import { Icon } from "@blueprintjs/core";
|
import { Icon } from "@blueprintjs/core";
|
||||||
import { HelpMap, HelpBaseURL } from "constants/HelpConstants";
|
import { HelpMap, HelpBaseURL } from "constants/HelpConstants";
|
||||||
import CollapsibleHelp from "components/designSystems/appsmith/help/CollapsibleHelp";
|
import CollapsibleHelp from "components/designSystems/appsmith/help/CollapsibleHelp";
|
||||||
|
import { BaseTabbedView } from "components/designSystems/appsmith/TabbedView";
|
||||||
|
import KeyValueFieldArray from "components/editorComponents/form/fields/KeyValueFieldArray";
|
||||||
|
import PostBodyData from "./PostBodyData";
|
||||||
|
import ApiResponseView from "components/editorComponents/ApiResponseView";
|
||||||
|
|
||||||
const Form = styled.form`
|
const Form = styled.form`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -59,23 +56,6 @@ const MainConfiguration = styled.div`
|
||||||
padding-left: 17px;
|
padding-left: 17px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const SecondaryWrapper = styled.div`
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
border-top: 1px solid #d0d7dd;
|
|
||||||
margin-top: 15px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const RequestParamsWrapper = styled.div`
|
|
||||||
flex: 4;
|
|
||||||
border-right: 1px solid #d0d7dd;
|
|
||||||
height: 100%;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding-top: 6px;
|
|
||||||
padding-left: 17px;
|
|
||||||
padding-right: 10px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const ActionButtons = styled.div`
|
const ActionButtons = styled.div`
|
||||||
flex: 1;
|
flex: 1;
|
||||||
`;
|
`;
|
||||||
|
|
@ -88,13 +68,15 @@ const ActionButton = styled(BaseButton)`
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const HeadersSection = styled.div`
|
|
||||||
margin-bottom: 32px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const DatasourceWrapper = styled.div`
|
const DatasourceWrapper = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 320px;
|
`;
|
||||||
|
|
||||||
|
const SecondaryWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
border-top: 1px solid #d0d7dd;
|
||||||
|
margin-top: 15px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const TabbedViewContainer = styled.div`
|
const TabbedViewContainer = styled.div`
|
||||||
|
|
@ -113,6 +95,19 @@ const StyledOpenDocsIcon = styled(Icon)`
|
||||||
height: 18px;
|
height: 18px;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
const RequestParamsWrapper = styled.div`
|
||||||
|
flex: 4;
|
||||||
|
border-right: 1px solid #d0d7dd;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding-top: 6px;
|
||||||
|
padding-left: 17px;
|
||||||
|
padding-right: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const HeadersSection = styled.div`
|
||||||
|
margin-bottom: 32px;
|
||||||
|
`;
|
||||||
|
|
||||||
interface APIFormProps {
|
interface APIFormProps {
|
||||||
pluginId: string;
|
pluginId: string;
|
||||||
|
|
@ -126,18 +121,15 @@ interface APIFormProps {
|
||||||
isDeleting: boolean;
|
isDeleting: boolean;
|
||||||
paginationType: PaginationType;
|
paginationType: PaginationType;
|
||||||
appName: string;
|
appName: string;
|
||||||
actionConfiguration?: any;
|
|
||||||
httpMethodFromForm: string;
|
httpMethodFromForm: string;
|
||||||
actionConfigurationBody: object | string;
|
actionConfigurationBody: object | string;
|
||||||
actionConfigurationHeaders?: any;
|
actionConfigurationHeaders?: any;
|
||||||
contentType: {
|
apiId: string;
|
||||||
key: string;
|
|
||||||
value: string;
|
|
||||||
};
|
|
||||||
location: {
|
location: {
|
||||||
pathname: string;
|
pathname: string;
|
||||||
};
|
};
|
||||||
dispatch: any;
|
dispatch: any;
|
||||||
|
datasourceFieldText: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = APIFormProps & InjectedFormProps<RestAction, APIFormProps>;
|
type Props = APIFormProps & InjectedFormProps<RestAction, APIFormProps>;
|
||||||
|
|
@ -153,15 +145,13 @@ const ApiEditorForm: React.FC<Props> = (props: Props) => {
|
||||||
isDeleting,
|
isDeleting,
|
||||||
isRunning,
|
isRunning,
|
||||||
isSaving,
|
isSaving,
|
||||||
actionConfiguration,
|
|
||||||
actionConfigurationHeaders,
|
actionConfigurationHeaders,
|
||||||
actionConfigurationBody,
|
actionConfigurationBody,
|
||||||
httpMethodFromForm,
|
|
||||||
location,
|
location,
|
||||||
dispatch,
|
dispatch,
|
||||||
|
apiId,
|
||||||
|
httpMethodFromForm,
|
||||||
} = props;
|
} = props;
|
||||||
const allowPostBody =
|
|
||||||
httpMethodFromForm && httpMethodFromForm !== HTTP_METHODS[0];
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ReduxActionTypes.SET_LAST_USED_EDITOR_PAGE,
|
type: ReduxActionTypes.SET_LAST_USED_EDITOR_PAGE,
|
||||||
|
|
@ -170,6 +160,8 @@ const ApiEditorForm: React.FC<Props> = (props: Props) => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
const allowPostBody =
|
||||||
|
httpMethodFromForm && httpMethodFromForm !== HTTP_METHODS[0];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form onSubmit={handleSubmit}>
|
<Form onSubmit={handleSubmit}>
|
||||||
|
|
@ -219,20 +211,13 @@ const ApiEditorForm: React.FC<Props> = (props: Props) => {
|
||||||
/>
|
/>
|
||||||
<DatasourceWrapper className="t--dataSourceField">
|
<DatasourceWrapper className="t--dataSourceField">
|
||||||
<DatasourcesField
|
<DatasourcesField
|
||||||
name="datasource.id"
|
key={apiId}
|
||||||
|
name="datasource"
|
||||||
pluginId={pluginId}
|
pluginId={pluginId}
|
||||||
|
datasourceFieldText={props.datasourceFieldText}
|
||||||
appName={props.appName}
|
appName={props.appName}
|
||||||
/>
|
/>
|
||||||
</DatasourceWrapper>
|
</DatasourceWrapper>
|
||||||
<DynamicTextField
|
|
||||||
className="t--path"
|
|
||||||
placeholder="v1/method"
|
|
||||||
name="actionConfiguration.path"
|
|
||||||
leftIcon={FormIcons.SLASH_ICON}
|
|
||||||
normalize={value => value.trim()}
|
|
||||||
singleLine
|
|
||||||
setMaxHeight
|
|
||||||
/>
|
|
||||||
</FormRow>
|
</FormRow>
|
||||||
</MainConfiguration>
|
</MainConfiguration>
|
||||||
<SecondaryWrapper>
|
<SecondaryWrapper>
|
||||||
|
|
@ -259,9 +244,7 @@ const ApiEditorForm: React.FC<Props> = (props: Props) => {
|
||||||
<KeyValueFieldArray
|
<KeyValueFieldArray
|
||||||
name="actionConfiguration.headers"
|
name="actionConfiguration.headers"
|
||||||
label="Headers"
|
label="Headers"
|
||||||
actionConfig={
|
actionConfig={actionConfigurationHeaders}
|
||||||
actionConfiguration && actionConfigurationHeaders
|
|
||||||
}
|
|
||||||
placeholder="Value"
|
placeholder="Value"
|
||||||
pushFields
|
pushFields
|
||||||
/>
|
/>
|
||||||
|
|
@ -305,24 +288,17 @@ const selector = formValueSelector(API_EDITOR_FORM_NAME);
|
||||||
|
|
||||||
export default connect(state => {
|
export default connect(state => {
|
||||||
const httpMethodFromForm = selector(state, "actionConfiguration.httpMethod");
|
const httpMethodFromForm = selector(state, "actionConfiguration.httpMethod");
|
||||||
const actionConfiguration = selector(state, "actionConfiguration");
|
|
||||||
const actionConfigurationBody = selector(state, "actionConfiguration.body");
|
const actionConfigurationBody = selector(state, "actionConfiguration.body");
|
||||||
const actionConfigurationHeaders = selector(
|
const actionConfigurationHeaders = selector(
|
||||||
state,
|
state,
|
||||||
"actionConfiguration.headers",
|
"actionConfiguration.headers",
|
||||||
);
|
);
|
||||||
let contentType;
|
const apiId = selector(state, "id");
|
||||||
if (actionConfigurationHeaders) {
|
|
||||||
contentType = actionConfigurationHeaders.find(
|
|
||||||
(header: any) => header.key.toLowerCase() === CONTENT_TYPE,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
apiId,
|
||||||
httpMethodFromForm,
|
httpMethodFromForm,
|
||||||
actionConfiguration,
|
|
||||||
actionConfigurationBody,
|
actionConfigurationBody,
|
||||||
contentType,
|
|
||||||
actionConfigurationHeaders,
|
actionConfigurationHeaders,
|
||||||
};
|
};
|
||||||
})(
|
})(
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ import {
|
||||||
ActionData,
|
ActionData,
|
||||||
ActionDataState,
|
ActionDataState,
|
||||||
} from "reducers/entityReducers/actionsReducer";
|
} from "reducers/entityReducers/actionsReducer";
|
||||||
import { ApiPaneReduxState } from "reducers/uiReducers/apiPaneReducer";
|
|
||||||
import { REST_PLUGIN_PACKAGE_NAME } from "constants/ApiEditorConstants";
|
import { REST_PLUGIN_PACKAGE_NAME } from "constants/ApiEditorConstants";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { getCurrentApplication } from "selectors/applicationSelectors";
|
import { getCurrentApplication } from "selectors/applicationSelectors";
|
||||||
|
|
@ -28,6 +27,7 @@ import { Plugin } from "api/PluginApi";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import FeatureFlag from "utils/featureFlags";
|
import FeatureFlag from "utils/featureFlags";
|
||||||
import { FeatureFlagsEnum } from "configs/types";
|
import { FeatureFlagsEnum } from "configs/types";
|
||||||
|
import { PaginationType } from "./Pagination";
|
||||||
|
|
||||||
const EmptyStateContainer = styled.div`
|
const EmptyStateContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -39,15 +39,19 @@ const EmptyStateContainer = styled.div`
|
||||||
|
|
||||||
interface ReduxStateProps {
|
interface ReduxStateProps {
|
||||||
actions: ActionDataState;
|
actions: ActionDataState;
|
||||||
apiPane: ApiPaneReduxState;
|
isRunning: Record<string, boolean>;
|
||||||
formData: RestAction;
|
isSaving: Record<string, boolean>;
|
||||||
|
isDeleting: Record<string, boolean>;
|
||||||
|
allowSave: boolean;
|
||||||
|
apiName: string;
|
||||||
currentApplication: UserApplication;
|
currentApplication: UserApplication;
|
||||||
currentPageName: string | undefined;
|
currentPageName: string | undefined;
|
||||||
pages: any;
|
pages: any;
|
||||||
plugins: Plugin[];
|
plugins: Plugin[];
|
||||||
pluginId: any;
|
pluginId: any;
|
||||||
apiAction: RestAction | ActionData | RapidApiAction | undefined;
|
apiAction: RestAction | ActionData | RapidApiAction | undefined;
|
||||||
data: RestAction | ActionData | RapidApiAction | undefined;
|
paginationType: PaginationType;
|
||||||
|
datasourceFieldText: string;
|
||||||
}
|
}
|
||||||
interface ReduxActionProps {
|
interface ReduxActionProps {
|
||||||
submitForm: (name: string) => void;
|
submitForm: (name: string) => void;
|
||||||
|
|
@ -67,35 +71,42 @@ type Props = ReduxActionProps &
|
||||||
|
|
||||||
class ApiEditor extends React.Component<Props> {
|
class ApiEditor extends React.Component<Props> {
|
||||||
handleSubmit = (values: RestAction) => {
|
handleSubmit = (values: RestAction) => {
|
||||||
const { formData } = this.props;
|
this.props.updateAction(values);
|
||||||
this.props.updateAction(formData);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
handleSaveClick = () => {
|
handleSaveClick = () => {
|
||||||
const pageName = getPageName(this.props.pages, this.props.formData.pageId);
|
const pageName = getPageName(
|
||||||
|
this.props.pages,
|
||||||
|
this.props.match.params.pageId,
|
||||||
|
);
|
||||||
AnalyticsUtil.logEvent("SAVE_API_CLICK", {
|
AnalyticsUtil.logEvent("SAVE_API_CLICK", {
|
||||||
apiName: this.props.formData.name,
|
apiName: this.props.apiName,
|
||||||
apiID: this.props.match.params.apiId,
|
apiID: this.props.match.params.apiId,
|
||||||
pageName: pageName,
|
pageName: pageName,
|
||||||
});
|
});
|
||||||
this.props.submitForm(API_EDITOR_FORM_NAME);
|
this.props.submitForm(API_EDITOR_FORM_NAME);
|
||||||
};
|
};
|
||||||
|
|
||||||
handleDeleteClick = () => {
|
handleDeleteClick = () => {
|
||||||
const pageName = getPageName(this.props.pages, this.props.formData.pageId);
|
const pageName = getPageName(
|
||||||
|
this.props.pages,
|
||||||
|
this.props.match.params.pageId,
|
||||||
|
);
|
||||||
AnalyticsUtil.logEvent("DELETE_API_CLICK", {
|
AnalyticsUtil.logEvent("DELETE_API_CLICK", {
|
||||||
apiName: this.props.formData.name,
|
apiName: this.props.apiName,
|
||||||
apiID: this.props.match.params.apiId,
|
apiID: this.props.match.params.apiId,
|
||||||
pageName: pageName,
|
pageName: pageName,
|
||||||
});
|
});
|
||||||
this.props.deleteAction(
|
this.props.deleteAction(this.props.match.params.apiId, this.props.apiName);
|
||||||
this.props.match.params.apiId,
|
|
||||||
this.props.formData.name,
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
handleRunClick = (paginationField?: PaginationField) => {
|
handleRunClick = (paginationField?: PaginationField) => {
|
||||||
const pageName = getPageName(this.props.pages, this.props.formData.pageId);
|
const pageName = getPageName(
|
||||||
|
this.props.pages,
|
||||||
|
this.props.match.params.pageId,
|
||||||
|
);
|
||||||
AnalyticsUtil.logEvent("RUN_API_CLICK", {
|
AnalyticsUtil.logEvent("RUN_API_CLICK", {
|
||||||
apiName: this.props.formData.name,
|
apiName: this.props.apiName,
|
||||||
apiID: this.props.match.params.apiId,
|
apiID: this.props.match.params.apiId,
|
||||||
pageName: pageName,
|
pageName: pageName,
|
||||||
});
|
});
|
||||||
|
|
@ -130,13 +141,16 @@ class ApiEditor extends React.Component<Props> {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
apiPane,
|
|
||||||
match: {
|
match: {
|
||||||
params: { apiId },
|
params: { apiId },
|
||||||
},
|
},
|
||||||
plugins,
|
plugins,
|
||||||
pluginId,
|
pluginId,
|
||||||
data,
|
isSaving,
|
||||||
|
isRunning,
|
||||||
|
isDeleting,
|
||||||
|
allowSave,
|
||||||
|
paginationType,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
let formUiComponent: string | undefined;
|
let formUiComponent: string | undefined;
|
||||||
|
|
@ -148,8 +162,6 @@ class ApiEditor extends React.Component<Props> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { isSaving, isRunning, isDeleting, drafts } = apiPane;
|
|
||||||
const paginationType = _.get(data, "actionConfiguration.paginationType");
|
|
||||||
const apiHomeScreen = (
|
const apiHomeScreen = (
|
||||||
<ApiHomeScreen
|
<ApiHomeScreen
|
||||||
applicationId={this.props.match.params.applicationId}
|
applicationId={this.props.match.params.applicationId}
|
||||||
|
|
@ -177,7 +189,7 @@ class ApiEditor extends React.Component<Props> {
|
||||||
{formUiComponent === "ApiEditorForm" && (
|
{formUiComponent === "ApiEditorForm" && (
|
||||||
<ApiEditorForm
|
<ApiEditorForm
|
||||||
pluginId={pluginId}
|
pluginId={pluginId}
|
||||||
allowSave={apiId in drafts}
|
allowSave={allowSave}
|
||||||
paginationType={paginationType}
|
paginationType={paginationType}
|
||||||
isSaving={isSaving[apiId]}
|
isSaving={isSaving[apiId]}
|
||||||
isRunning={isRunning[apiId]}
|
isRunning={isRunning[apiId]}
|
||||||
|
|
@ -186,6 +198,7 @@ class ApiEditor extends React.Component<Props> {
|
||||||
onSaveClick={this.handleSaveClick}
|
onSaveClick={this.handleSaveClick}
|
||||||
onDeleteClick={this.handleDeleteClick}
|
onDeleteClick={this.handleDeleteClick}
|
||||||
onRunClick={this.handleRunClick}
|
onRunClick={this.handleRunClick}
|
||||||
|
datasourceFieldText={this.props.datasourceFieldText}
|
||||||
appName={
|
appName={
|
||||||
this.props.currentApplication
|
this.props.currentApplication
|
||||||
? this.props.currentApplication.name
|
? this.props.currentApplication.name
|
||||||
|
|
@ -197,7 +210,7 @@ class ApiEditor extends React.Component<Props> {
|
||||||
|
|
||||||
{formUiComponent === "RapidApiEditorForm" && (
|
{formUiComponent === "RapidApiEditorForm" && (
|
||||||
<RapidApiEditorForm
|
<RapidApiEditorForm
|
||||||
allowSave={apiId in drafts}
|
allowSave={allowSave}
|
||||||
paginationType={paginationType}
|
paginationType={paginationType}
|
||||||
isSaving={isSaving[apiId]}
|
isSaving={isSaving[apiId]}
|
||||||
isRunning={isRunning[apiId]}
|
isRunning={isRunning[apiId]}
|
||||||
|
|
@ -227,25 +240,34 @@ const mapStateToProps = (state: AppState, props: any): ReduxStateProps => {
|
||||||
const formData = getFormValues(API_EDITOR_FORM_NAME)(state) as RestAction;
|
const formData = getFormValues(API_EDITOR_FORM_NAME)(state) as RestAction;
|
||||||
const apiAction = getActionById(state, props);
|
const apiAction = getActionById(state, props);
|
||||||
|
|
||||||
const { drafts } = state.ui.apiPane;
|
const { drafts, isSaving, isDeleting, isRunning } = state.ui.apiPane;
|
||||||
let data: RestAction | ActionData | RapidApiAction | undefined;
|
let data: RestAction | ActionData | RapidApiAction | undefined;
|
||||||
|
let allowSave;
|
||||||
if (apiAction && apiAction.id in drafts) {
|
if (apiAction && apiAction.id in drafts) {
|
||||||
data = drafts[apiAction.id];
|
data = drafts[apiAction.id];
|
||||||
|
allowSave = true;
|
||||||
} else {
|
} else {
|
||||||
data = apiAction;
|
data = apiAction;
|
||||||
|
allowSave = false;
|
||||||
}
|
}
|
||||||
|
const datasourceFieldText =
|
||||||
|
state.ui.apiPane.datasourceFieldText[formData?.id ?? ""] || "";
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
datasourceFieldText,
|
||||||
actions: state.entities.actions,
|
actions: state.entities.actions,
|
||||||
apiPane: state.ui.apiPane,
|
|
||||||
currentApplication: getCurrentApplication(state),
|
currentApplication: getCurrentApplication(state),
|
||||||
currentPageName: getCurrentPageName(state),
|
currentPageName: getCurrentPageName(state),
|
||||||
pages: state.entities.pageList.pages,
|
pages: state.entities.pageList.pages,
|
||||||
formData,
|
apiName: formData?.name || "",
|
||||||
data,
|
|
||||||
plugins: state.entities.plugins.list,
|
plugins: state.entities.plugins.list,
|
||||||
pluginId: _.get(data, "pluginId"),
|
pluginId: _.get(data, "pluginId"),
|
||||||
|
paginationType: _.get(data, "actionConfiguration.paginationType"),
|
||||||
apiAction,
|
apiAction,
|
||||||
|
isSaving,
|
||||||
|
isRunning,
|
||||||
|
isDeleting,
|
||||||
|
allowSave,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ interface DatasourceDBEditorProps {
|
||||||
isTesting: boolean;
|
isTesting: boolean;
|
||||||
loadingFormConfigs: boolean;
|
loadingFormConfigs: boolean;
|
||||||
formConfig: [];
|
formConfig: [];
|
||||||
|
isNewDatasource: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DatasourceDBEditorState {
|
interface DatasourceDBEditorState {
|
||||||
|
|
@ -327,7 +328,7 @@ class DatasourceDBEditor extends React.Component<
|
||||||
<Field
|
<Field
|
||||||
name="name"
|
name="name"
|
||||||
component={FormTitle}
|
component={FormTitle}
|
||||||
focusOnMount={this.isNewDatasource()}
|
focusOnMount={this.props.isNewDatasource}
|
||||||
/>
|
/>
|
||||||
</FormTitleContainer>
|
</FormTitleContainer>
|
||||||
{!_.isNil(sections)
|
{!_.isNil(sections)
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ interface ReduxStateProps {
|
||||||
formConfig: [];
|
formConfig: [];
|
||||||
loadingFormConfigs: boolean;
|
loadingFormConfigs: boolean;
|
||||||
isDeleting: boolean;
|
isDeleting: boolean;
|
||||||
|
newDatasource: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = ReduxStateProps &
|
type Props = ReduxStateProps &
|
||||||
|
|
@ -58,6 +59,7 @@ class DataSourceEditor extends React.Component<Props> {
|
||||||
formConfig,
|
formConfig,
|
||||||
isDeleting,
|
isDeleting,
|
||||||
deleteDatasource,
|
deleteDatasource,
|
||||||
|
newDatasource,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -69,6 +71,7 @@ class DataSourceEditor extends React.Component<Props> {
|
||||||
isSaving={isSaving}
|
isSaving={isSaving}
|
||||||
isTesting={isTesting}
|
isTesting={isTesting}
|
||||||
isDeleting={isDeleting}
|
isDeleting={isDeleting}
|
||||||
|
isNewDatasource={newDatasource === datasourceId}
|
||||||
onSubmit={this.handleSubmit}
|
onSubmit={this.handleSubmit}
|
||||||
onSave={this.handleSave}
|
onSave={this.handleSave}
|
||||||
onTest={this.props.testDatasource}
|
onTest={this.props.testDatasource}
|
||||||
|
|
@ -112,6 +115,7 @@ const mapStateToProps = (state: AppState): ReduxStateProps => {
|
||||||
isTesting: datasources.isTesting,
|
isTesting: datasources.isTesting,
|
||||||
formConfig: formConfigs[datasourcePane.selectedPlugin] || [],
|
formConfig: formConfigs[datasourcePane.selectedPlugin] || [],
|
||||||
loadingFormConfigs,
|
loadingFormConfigs,
|
||||||
|
newDatasource: datasourcePane.newDatasource,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ const initialState: ApiPaneReduxState = {
|
||||||
lastUsedEditorPage: "",
|
lastUsedEditorPage: "",
|
||||||
lastSelectedPage: "",
|
lastSelectedPage: "",
|
||||||
extraformData: {},
|
extraformData: {},
|
||||||
|
datasourceFieldText: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ApiPaneReduxState {
|
export interface ApiPaneReduxState {
|
||||||
|
|
@ -29,6 +30,7 @@ export interface ApiPaneReduxState {
|
||||||
isDeleting: Record<string, boolean>;
|
isDeleting: Record<string, boolean>;
|
||||||
currentCategory: string;
|
currentCategory: string;
|
||||||
lastUsedEditorPage: string;
|
lastUsedEditorPage: string;
|
||||||
|
datasourceFieldText: Record<string, string>;
|
||||||
lastSelectedPage: string;
|
lastSelectedPage: string;
|
||||||
extraformData: Record<string, any>;
|
extraformData: Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|
@ -201,6 +203,19 @@ const apiPaneReducer = createReducer(initialState, {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
[ReduxActionTypes.SET_DATASOURCE_FIELD_TEXT]: (
|
||||||
|
state: ApiPaneReduxState,
|
||||||
|
action: ReduxAction<{ apiId: string; value: string }>,
|
||||||
|
) => {
|
||||||
|
const { apiId } = action.payload;
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
datasourceFieldText: {
|
||||||
|
...state.datasourceFieldText,
|
||||||
|
[apiId]: action.payload.value,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default apiPaneReducer;
|
export default apiPaneReducer;
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,21 @@ const initialState: DatasourcePaneReduxState = {
|
||||||
selectedPlugin: "",
|
selectedPlugin: "",
|
||||||
datasourceRefs: {},
|
datasourceRefs: {},
|
||||||
drafts: {},
|
drafts: {},
|
||||||
|
actionRouteInfo: {},
|
||||||
|
newDatasource: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface DatasourcePaneReduxState {
|
export interface DatasourcePaneReduxState {
|
||||||
selectedPlugin: string;
|
selectedPlugin: string;
|
||||||
datasourceRefs: {};
|
datasourceRefs: {};
|
||||||
drafts: Record<string, Datasource>;
|
drafts: Record<string, Datasource>;
|
||||||
|
actionRouteInfo: Partial<{
|
||||||
|
apiId: string;
|
||||||
|
datasourceId: string;
|
||||||
|
pageId: string;
|
||||||
|
applicationId: string;
|
||||||
|
}>;
|
||||||
|
newDatasource: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const datasourcePaneReducer = createReducer(initialState, {
|
const datasourcePaneReducer = createReducer(initialState, {
|
||||||
|
|
@ -64,6 +73,43 @@ const datasourcePaneReducer = createReducer(initialState, {
|
||||||
...state,
|
...state,
|
||||||
drafts: _.omit(state.drafts, action.payload.id),
|
drafts: _.omit(state.drafts, action.payload.id),
|
||||||
}),
|
}),
|
||||||
|
[ReduxActionTypes.STORE_AS_DATASOURCE_UPDATE]: (
|
||||||
|
state: DatasourcePaneReduxState,
|
||||||
|
action: ReduxAction<{
|
||||||
|
apiId: string;
|
||||||
|
datasourceId: string;
|
||||||
|
pageId: string;
|
||||||
|
applicationId: string;
|
||||||
|
}>,
|
||||||
|
) => {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
actionRouteInfo: action.payload,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
[ReduxActionTypes.STORE_AS_DATASOURCE_COMPLETE]: (
|
||||||
|
state: DatasourcePaneReduxState,
|
||||||
|
) => ({
|
||||||
|
...state,
|
||||||
|
actionRouteInfo: {},
|
||||||
|
}),
|
||||||
|
[ReduxActionTypes.CREATE_DATASOURCE_SUCCESS]: (
|
||||||
|
state: DatasourcePaneReduxState,
|
||||||
|
action: ReduxAction<{ id: string }>,
|
||||||
|
) => {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
newDatasource: action.payload.id,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
[ReduxActionTypes.UPDATE_DATASOURCE_SUCCESS]: (
|
||||||
|
state: DatasourcePaneReduxState,
|
||||||
|
) => {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
newDatasource: "",
|
||||||
|
};
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default datasourcePaneReducer;
|
export default datasourcePaneReducer;
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ import { initialize, autofill, change } from "redux-form";
|
||||||
import { getAction } from "./ActionSagas";
|
import { getAction } from "./ActionSagas";
|
||||||
import { AppState } from "reducers";
|
import { AppState } from "reducers";
|
||||||
import { Property, RestAction } from "api/ActionAPI";
|
import { Property, RestAction } from "api/ActionAPI";
|
||||||
import { changeApi } from "actions/apiPaneActions";
|
import { changeApi, setDatasourceFieldText } from "actions/apiPaneActions";
|
||||||
import {
|
import {
|
||||||
API_PATH_START_WITH_SLASH_ERROR,
|
API_PATH_START_WITH_SLASH_ERROR,
|
||||||
FIELD_REQUIRED_ERROR,
|
FIELD_REQUIRED_ERROR,
|
||||||
|
|
@ -147,6 +147,28 @@ function* syncApiParamsSaga(
|
||||||
`${currentPath}${paramsString}`,
|
`${currentPath}${paramsString}`,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
actionPayload.type === ReduxFormActionTypes.VALUE_CHANGE ||
|
||||||
|
actionPayload.type === ReduxFormActionTypes.ARRAY_REMOVE
|
||||||
|
) {
|
||||||
|
if (values.datasource && values.datasource.id) {
|
||||||
|
yield put(
|
||||||
|
setDatasourceFieldText(values.id, `${currentPath}${paramsString}`),
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
values.datasource &&
|
||||||
|
values.datasource.datasourceConfiguration
|
||||||
|
) {
|
||||||
|
yield put(
|
||||||
|
setDatasourceFieldText(
|
||||||
|
values.id,
|
||||||
|
values.datasource.datasourceConfiguration.url +
|
||||||
|
`${currentPath}${paramsString}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -191,13 +213,6 @@ function* changeApiSaga(actionPayload: ReduxAction<{ id: string }>) {
|
||||||
data = draft;
|
data = draft;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.actionConfiguration.path) {
|
|
||||||
if (data.actionConfiguration.path.charAt(0) === "/")
|
|
||||||
data.actionConfiguration.path = data.actionConfiguration.path.substring(
|
|
||||||
1,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
data.actionConfiguration.httpMethod !== "GET" &&
|
data.actionConfiguration.httpMethod !== "GET" &&
|
||||||
!data.providerId &&
|
!data.providerId &&
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
import { takeLatest, put, all, select } from "redux-saga/effects";
|
import { takeLatest, put, all, select, take } from "redux-saga/effects";
|
||||||
import { initialize } from "redux-form";
|
|
||||||
import {
|
import {
|
||||||
ReduxActionTypes,
|
ReduxActionTypes,
|
||||||
ReduxActionErrorTypes,
|
ReduxActionErrorTypes,
|
||||||
ReduxAction,
|
ReduxAction,
|
||||||
} from "constants/ReduxActionConstants";
|
} from "constants/ReduxActionConstants";
|
||||||
import { API_EDITOR_FORM_NAME } from "constants/forms";
|
|
||||||
import { validateResponse } from "sagas/ErrorSagas";
|
import { validateResponse } from "sagas/ErrorSagas";
|
||||||
import CurlImportApi, { CurlImportRequest } from "api/ImportApi";
|
import CurlImportApi, { CurlImportRequest } from "api/ImportApi";
|
||||||
import { ApiResponse } from "api/ApiResponses";
|
import { ApiResponse } from "api/ApiResponses";
|
||||||
|
|
@ -13,14 +11,10 @@ import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||||
import { AppToaster } from "components/editorComponents/ToastComponent";
|
import { AppToaster } from "components/editorComponents/ToastComponent";
|
||||||
import { ToastType } from "react-toastify";
|
import { ToastType } from "react-toastify";
|
||||||
import { CURL_IMPORT_SUCCESS } from "constants/messages";
|
import { CURL_IMPORT_SUCCESS } from "constants/messages";
|
||||||
import { API_EDITOR_ID_URL } from "constants/routes";
|
import { getCurrentApplicationId } from "selectors/editorSelectors";
|
||||||
import history from "utils/history";
|
|
||||||
import {
|
|
||||||
getCurrentApplicationId,
|
|
||||||
getCurrentPageId,
|
|
||||||
} from "selectors/editorSelectors";
|
|
||||||
import { fetchActions } from "actions/actionActions";
|
import { fetchActions } from "actions/actionActions";
|
||||||
import { CURL } from "constants/ApiConstants";
|
import { CURL } from "constants/ApiConstants";
|
||||||
|
import { changeApi } from "actions/apiPaneActions";
|
||||||
|
|
||||||
export function* curlImportSaga(action: ReduxAction<CurlImportRequest>) {
|
export function* curlImportSaga(action: ReduxAction<CurlImportRequest>) {
|
||||||
const { type, pageId, name } = action.payload;
|
const { type, pageId, name } = action.payload;
|
||||||
|
|
@ -35,12 +29,16 @@ export function* curlImportSaga(action: ReduxAction<CurlImportRequest>) {
|
||||||
const response: ApiResponse = yield CurlImportApi.curlImport(request);
|
const response: ApiResponse = yield CurlImportApi.curlImport(request);
|
||||||
const isValidResponse = yield validateResponse(response);
|
const isValidResponse = yield validateResponse(response);
|
||||||
const applicationId = yield select(getCurrentApplicationId);
|
const applicationId = yield select(getCurrentApplicationId);
|
||||||
const currentPageId = yield select(getCurrentPageId);
|
|
||||||
|
|
||||||
if (isValidResponse) {
|
if (isValidResponse) {
|
||||||
AnalyticsUtil.logEvent("IMPORT_API", {
|
AnalyticsUtil.logEvent("IMPORT_API", {
|
||||||
importSource: CURL,
|
importSource: CURL,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
yield put(fetchActions(applicationId));
|
||||||
|
const data = { ...response.data };
|
||||||
|
yield take(ReduxActionTypes.FETCH_ACTIONS_SUCCESS);
|
||||||
|
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: CURL_IMPORT_SUCCESS,
|
message: CURL_IMPORT_SUCCESS,
|
||||||
type: ToastType.SUCCESS,
|
type: ToastType.SUCCESS,
|
||||||
|
|
@ -49,12 +47,8 @@ export function* curlImportSaga(action: ReduxAction<CurlImportRequest>) {
|
||||||
type: ReduxActionTypes.SUBMIT_CURL_FORM_SUCCESS,
|
type: ReduxActionTypes.SUBMIT_CURL_FORM_SUCCESS,
|
||||||
payload: response.data,
|
payload: response.data,
|
||||||
});
|
});
|
||||||
yield put(fetchActions(applicationId));
|
|
||||||
const data = { ...response.data };
|
yield put(changeApi(data.id));
|
||||||
yield put(initialize(API_EDITOR_FORM_NAME, data));
|
|
||||||
history.push(
|
|
||||||
API_EDITOR_ID_URL(applicationId, currentPageId, response.data.id),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
yield put({
|
yield put({
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { all, put, takeEvery, select, call } from "redux-saga/effects";
|
import { all, put, takeEvery, select, call, take } from "redux-saga/effects";
|
||||||
import { change, initialize, getFormValues } from "redux-form";
|
import { change, initialize, getFormValues } from "redux-form";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import {
|
import {
|
||||||
|
|
@ -18,7 +18,11 @@ import {
|
||||||
getDatasource,
|
getDatasource,
|
||||||
getDatasourceDraft,
|
getDatasourceDraft,
|
||||||
} from "selectors/entitiesSelector";
|
} from "selectors/entitiesSelector";
|
||||||
import { selectPlugin } from "actions/datasourceActions";
|
import {
|
||||||
|
selectPlugin,
|
||||||
|
createDatasource,
|
||||||
|
changeDatasource,
|
||||||
|
} from "actions/datasourceActions";
|
||||||
import { fetchPluginForm } from "actions/pluginActions";
|
import { fetchPluginForm } from "actions/pluginActions";
|
||||||
import { GenericApiResponse } from "api/ApiResponses";
|
import { GenericApiResponse } from "api/ApiResponses";
|
||||||
import DatasourcesApi, {
|
import DatasourcesApi, {
|
||||||
|
|
@ -26,6 +30,7 @@ import DatasourcesApi, {
|
||||||
Datasource,
|
Datasource,
|
||||||
} from "api/DatasourcesApi";
|
} from "api/DatasourcesApi";
|
||||||
import PluginApi, { DatasourceForm } from "api/PluginApi";
|
import PluginApi, { DatasourceForm } from "api/PluginApi";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DATA_SOURCES_EDITOR_ID_URL,
|
DATA_SOURCES_EDITOR_ID_URL,
|
||||||
DATA_SOURCES_EDITOR_URL,
|
DATA_SOURCES_EDITOR_URL,
|
||||||
|
|
@ -37,6 +42,7 @@ import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||||
import { AppToaster } from "components/editorComponents/ToastComponent";
|
import { AppToaster } from "components/editorComponents/ToastComponent";
|
||||||
import { ToastType } from "react-toastify";
|
import { ToastType } from "react-toastify";
|
||||||
import { getFormData } from "selectors/formSelectors";
|
import { getFormData } from "selectors/formSelectors";
|
||||||
|
import { changeApi, setDatasourceFieldText } from "actions/apiPaneActions";
|
||||||
|
|
||||||
function* fetchDatasourcesSaga() {
|
function* fetchDatasourcesSaga() {
|
||||||
try {
|
try {
|
||||||
|
|
@ -73,9 +79,7 @@ function* createDatasourceSaga(
|
||||||
type: ReduxActionTypes.CREATE_DATASOURCE_SUCCESS,
|
type: ReduxActionTypes.CREATE_DATASOURCE_SUCCESS,
|
||||||
payload: response.data,
|
payload: response.data,
|
||||||
});
|
});
|
||||||
yield put(
|
yield put(change(API_EDITOR_FORM_NAME, "datasource", response.data));
|
||||||
change(API_EDITOR_FORM_NAME, "datasource.id", response.data.id),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
yield put({
|
yield put({
|
||||||
|
|
@ -344,6 +348,53 @@ function* formValueChangeSaga(
|
||||||
yield all([call(updateDraftsSaga)]);
|
yield all([call(updateDraftsSaga)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function* storeAsDatasourceSaga() {
|
||||||
|
const { values } = yield select(getFormData, API_EDITOR_FORM_NAME);
|
||||||
|
const applicationId = yield select(getCurrentApplicationId);
|
||||||
|
const pageId = yield select(getCurrentPageId);
|
||||||
|
const datasource = _.get(values, "datasource");
|
||||||
|
|
||||||
|
history.push(DATA_SOURCES_EDITOR_URL(applicationId, pageId));
|
||||||
|
|
||||||
|
yield put(createDatasource(datasource));
|
||||||
|
const createDatasourceSuccessAction = yield take(
|
||||||
|
ReduxActionTypes.CREATE_DATASOURCE_SUCCESS,
|
||||||
|
);
|
||||||
|
const createdDatasource = createDatasourceSuccessAction.payload;
|
||||||
|
|
||||||
|
yield put({
|
||||||
|
type: ReduxActionTypes.STORE_AS_DATASOURCE_UPDATE,
|
||||||
|
payload: {
|
||||||
|
pageId,
|
||||||
|
applicationId,
|
||||||
|
apiId: values.id,
|
||||||
|
datasourceId: createdDatasource.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
yield put(changeDatasource(createdDatasource));
|
||||||
|
}
|
||||||
|
|
||||||
|
function* updateDatasourceSuccessSaga(action: ReduxAction<Datasource>) {
|
||||||
|
const state = yield select();
|
||||||
|
const actionRouteInfo = _.get(state, "ui.datasourcePane.actionRouteInfo");
|
||||||
|
const updatedDatasource = action.payload;
|
||||||
|
|
||||||
|
if (
|
||||||
|
actionRouteInfo &&
|
||||||
|
updatedDatasource.id === actionRouteInfo.datasourceId
|
||||||
|
) {
|
||||||
|
const { apiId } = actionRouteInfo;
|
||||||
|
|
||||||
|
yield put(setDatasourceFieldText(apiId, ""));
|
||||||
|
yield put(changeApi(apiId));
|
||||||
|
}
|
||||||
|
|
||||||
|
yield put({
|
||||||
|
type: ReduxActionTypes.STORE_AS_DATASOURCE_COMPLETE,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function* watchDatasourcesSagas() {
|
export function* watchDatasourcesSagas() {
|
||||||
yield all([
|
yield all([
|
||||||
takeEvery(ReduxActionTypes.FETCH_DATASOURCES_INIT, fetchDatasourcesSaga),
|
takeEvery(ReduxActionTypes.FETCH_DATASOURCES_INIT, fetchDatasourcesSaga),
|
||||||
|
|
@ -356,6 +407,11 @@ export function* watchDatasourcesSagas() {
|
||||||
takeEvery(ReduxActionTypes.TEST_DATASOURCE_INIT, testDatasourceSaga),
|
takeEvery(ReduxActionTypes.TEST_DATASOURCE_INIT, testDatasourceSaga),
|
||||||
takeEvery(ReduxActionTypes.DELETE_DATASOURCE_INIT, deleteDatasourceSaga),
|
takeEvery(ReduxActionTypes.DELETE_DATASOURCE_INIT, deleteDatasourceSaga),
|
||||||
takeEvery(ReduxActionTypes.CHANGE_DATASOURCE, changeDatasourceSaga),
|
takeEvery(ReduxActionTypes.CHANGE_DATASOURCE, changeDatasourceSaga),
|
||||||
|
takeEvery(ReduxActionTypes.STORE_AS_DATASOURCE_INIT, storeAsDatasourceSaga),
|
||||||
|
takeEvery(
|
||||||
|
ReduxActionTypes.UPDATE_DATASOURCE_SUCCESS,
|
||||||
|
updateDatasourceSuccessSaga,
|
||||||
|
),
|
||||||
// Intercepting the redux-form change actionType
|
// Intercepting the redux-form change actionType
|
||||||
takeEvery(ReduxFormActionTypes.VALUE_CHANGE, formValueChangeSaga),
|
takeEvery(ReduxFormActionTypes.VALUE_CHANGE, formValueChangeSaga),
|
||||||
]);
|
]);
|
||||||
|
|
|
||||||
|
|
@ -154,11 +154,9 @@ export const getQueryActions = (state: AppState): ActionDataState => {
|
||||||
const getCurrentPageId = (state: AppState) =>
|
const getCurrentPageId = (state: AppState) =>
|
||||||
state.entities.pageList.currentPageId;
|
state.entities.pageList.currentPageId;
|
||||||
|
|
||||||
export const getDatasourcePlugins = (state: AppState) => {
|
export const getDatasourcePlugins = createSelector(getPlugins, plugins => {
|
||||||
return state.entities.plugins.list.filter(
|
return plugins.filter(plugin => plugin?.allowUserDatasources ?? true);
|
||||||
plugin => plugin?.allowUserDatasources ?? true,
|
});
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getActionsForCurrentPage = createSelector(
|
export const getActionsForCurrentPage = createSelector(
|
||||||
getCurrentPageId,
|
getCurrentPageId,
|
||||||
|
|
@ -169,13 +167,12 @@ export const getActionsForCurrentPage = createSelector(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getActionResponses = (
|
export const getActionResponses = createSelector(getActions, actions => {
|
||||||
state: AppState,
|
|
||||||
): Record<string, ActionResponse | undefined> => {
|
|
||||||
const responses: Record<string, ActionResponse | undefined> = {};
|
const responses: Record<string, ActionResponse | undefined> = {};
|
||||||
state.entities.actions.forEach(a => {
|
|
||||||
|
actions.forEach(a => {
|
||||||
responses[a.config.id] = a.data;
|
responses[a.config.id] = a.data;
|
||||||
});
|
});
|
||||||
|
|
||||||
return responses;
|
return responses;
|
||||||
};
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user