Streamline action save with widgets (#10)
- Remove drafts from actions - Direct update action from forms - Debounced saving of actions - Add org id in default datasource - Merge query and api run saga
This commit is contained in:
parent
10b1e40acd
commit
4a6717889c
|
|
@ -23,10 +23,6 @@ describe("API Panel Test Functionality", function() {
|
||||||
cy.EditApiName("SecondAPI");
|
cy.EditApiName("SecondAPI");
|
||||||
cy.ClearSearch();
|
cy.ClearSearch();
|
||||||
cy.SearchAPIandClick("SecondAPI");
|
cy.SearchAPIandClick("SecondAPI");
|
||||||
//invalid api end point check
|
|
||||||
cy.EditSourceDetail(testdata.baseUrl, testdata.invalidPath);
|
|
||||||
cy.RunAPI();
|
|
||||||
cy.ResponseStatusCheck("404 NOT_FOUND");
|
|
||||||
cy.DeleteAPI();
|
cy.DeleteAPI();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,7 @@ describe("Moustache test Functionality", function() {
|
||||||
cy.log("Navigation to API Panel screen successful");
|
cy.log("Navigation to API Panel screen successful");
|
||||||
cy.CreateAPI("TestAPINew");
|
cy.CreateAPI("TestAPINew");
|
||||||
cy.log("Creation of API Action successful");
|
cy.log("Creation of API Action successful");
|
||||||
cy.EnterSourceDetailsWithHeader(
|
cy.enterDatasourceAndPath(testdata.baseUrl2, testdata.moustacheMethod);
|
||||||
testdata.baseUrl2,
|
|
||||||
testdata.moustacheMethod,
|
|
||||||
testdata.headerKey,
|
|
||||||
testdata.headerValue,
|
|
||||||
);
|
|
||||||
cy.RunAPI();
|
cy.RunAPI();
|
||||||
cy.ResponseStatusCheck(testdata.successStatusCode);
|
cy.ResponseStatusCheck(testdata.successStatusCode);
|
||||||
cy.log("Response code check successful");
|
cy.log("Response code check successful");
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,8 @@ describe("Test Create Api and Bind to Table widget", function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Test_Add Paginate with Table Page No and Execute the Api", function() {
|
it("Test_Add Paginate with Table Page No and Execute the Api", function() {
|
||||||
cy.NavigateToApiEditor();
|
|
||||||
cy.testCreateApiButton();
|
|
||||||
/**Create an Api1 of Paginate with Table Page No */
|
/**Create an Api1 of Paginate with Table Page No */
|
||||||
cy.createApi(this.data.paginationUrl, this.data.paginationParam);
|
cy.createAndFillApi(this.data.paginationUrl, this.data.paginationParam);
|
||||||
cy.RunAPI();
|
cy.RunAPI();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -56,10 +54,8 @@ describe("Test Create Api and Bind to Table widget", function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Test_Add Paginate with Response URL and Execute the Api", function() {
|
it("Test_Add Paginate with Response URL and Execute the Api", function() {
|
||||||
cy.NavigateToApiEditor();
|
|
||||||
cy.testCreateApiButton();
|
|
||||||
/** Create Api2 of Paginate with Response URL*/
|
/** Create Api2 of Paginate with Response URL*/
|
||||||
cy.createApi(this.data.paginationUrl, "pokemon");
|
cy.createAndFillApi(this.data.paginationUrl, "pokemon");
|
||||||
cy.RunAPI();
|
cy.RunAPI();
|
||||||
cy.NavigateToPaginationTab();
|
cy.NavigateToPaginationTab();
|
||||||
cy.get(apiPage.apiPaginationNextText).type("{{Api2.data.next}}", {
|
cy.get(apiPage.apiPaginationNextText).type("{{Api2.data.next}}", {
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,7 @@ describe("Test Create Api and Bind to Table widget", function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Test_Add users api and execute api", function() {
|
it("Test_Add users api and execute api", function() {
|
||||||
cy.NavigateToApiEditor();
|
cy.createAndFillApi(this.data.userApi, "users");
|
||||||
cy.testCreateApiButton();
|
|
||||||
cy.createApi(this.data.userApi, "users");
|
|
||||||
cy.RunAPI();
|
cy.RunAPI();
|
||||||
cy.get(apiPage.responseBody)
|
cy.get(apiPage.responseBody)
|
||||||
.contains("name")
|
.contains("name")
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
"home": ".single-select >div:contains('Page1')",
|
"home": ".single-select >div:contains('Page1')",
|
||||||
"delete": ".single-select >div:contains('Delete')",
|
"delete": ".single-select >div:contains('Delete')",
|
||||||
"path": ".t--path >div textarea",
|
"path": ".t--path >div textarea",
|
||||||
"editResourceUrl": ".t--dataSourceField input",
|
"editResourceUrl": ".t--dataSourceField",
|
||||||
"autoSuggest": "//div[contains(@id,'react-select')]",
|
"autoSuggest": "//div[contains(@id,'react-select')]",
|
||||||
"headerKey": "(//div[contains(@class,'t--actionConfiguration.headers[0].key.0')]//textarea)[2]",
|
"headerKey": "(//div[contains(@class,'t--actionConfiguration.headers[0].key.0')]//textarea)[2]",
|
||||||
"headerValue": "(//div[contains(@class,'t--actionConfiguration.headers[0].value.0')]//textarea)[2]",
|
"headerValue": "(//div[contains(@class,'t--actionConfiguration.headers[0].value.0')]//textarea)[2]",
|
||||||
|
|
@ -35,4 +35,4 @@
|
||||||
"TestPreUrl": ".t--apiFormPaginationPrevTest",
|
"TestPreUrl": ".t--apiFormPaginationPrevTest",
|
||||||
"EditApiName": "img[alt='Edit pen']",
|
"EditApiName": "img[alt='Edit pen']",
|
||||||
"ApiName": ".t--action-name-edit-field span"
|
"ApiName": ".t--action-name-edit-field span"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -177,15 +177,14 @@ Cypress.Commands.add("CreateAPI", apiname => {
|
||||||
.click({ force: true });
|
.click({ force: true });
|
||||||
cy.get(apiwidget.createapi).click({ force: true });
|
cy.get(apiwidget.createapi).click({ force: true });
|
||||||
cy.wait("@createNewApi");
|
cy.wait("@createNewApi");
|
||||||
cy.wait("@postSave");
|
|
||||||
cy.get(apiwidget.resourceUrl).should("be.visible");
|
cy.get(apiwidget.resourceUrl).should("be.visible");
|
||||||
cy.wait("@postexe");
|
|
||||||
cy.get(apiwidget.EditApiName).should("be.visible");
|
cy.get(apiwidget.EditApiName).should("be.visible");
|
||||||
cy.get(apiwidget.EditApiName).click();
|
cy.get(apiwidget.EditApiName).click();
|
||||||
cy.get(apiwidget.apiTxt)
|
cy.get(apiwidget.apiTxt)
|
||||||
.clear()
|
.clear()
|
||||||
.type(apiname)
|
.type(apiname)
|
||||||
.should("have.value", apiname);
|
.should("have.value", apiname)
|
||||||
|
.blur();
|
||||||
//cy.WaitAutoSave();
|
//cy.WaitAutoSave();
|
||||||
// Added because api name edit takes some time to
|
// Added because api name edit takes some time to
|
||||||
// reflect in api sidebar after the call passes.
|
// reflect in api sidebar after the call passes.
|
||||||
|
|
@ -216,13 +215,14 @@ Cypress.Commands.add("EditApiName", apiname => {
|
||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add("WaitAutoSave", () => {
|
Cypress.Commands.add("WaitAutoSave", () => {
|
||||||
|
// wait for save query to trigger
|
||||||
|
cy.wait(200);
|
||||||
cy.wait("@saveQuery");
|
cy.wait("@saveQuery");
|
||||||
//cy.wait("@postExecute");
|
//cy.wait("@postExecute");
|
||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add("RunAPI", () => {
|
Cypress.Commands.add("RunAPI", () => {
|
||||||
cy.get(ApiEditor.ApiRunBtn).click({ force: true });
|
cy.get(ApiEditor.ApiRunBtn).click({ force: true });
|
||||||
// cy.wait('@postTrack');
|
|
||||||
cy.wait("@postExecute");
|
cy.wait("@postExecute");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -258,7 +258,7 @@ Cypress.Commands.add("enterDatasourceAndPath", (datasource, path) => {
|
||||||
.first()
|
.first()
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
.type(datasource);
|
.type(datasource);
|
||||||
/*
|
/*
|
||||||
cy.xpath(apiwidget.autoSuggest)
|
cy.xpath(apiwidget.autoSuggest)
|
||||||
.first()
|
.first()
|
||||||
.click({ force: true });
|
.click({ force: true });
|
||||||
|
|
@ -1017,7 +1017,9 @@ Cypress.Commands.add("closePropertyPane", () => {
|
||||||
cy.get(commonlocators.editPropCrossButton).click();
|
cy.get(commonlocators.editPropCrossButton).click();
|
||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add("createApi", (url, parameters) => {
|
Cypress.Commands.add("createAndFillApi", (url, parameters) => {
|
||||||
|
cy.NavigateToApiEditor();
|
||||||
|
cy.testCreateApiButton();
|
||||||
cy.get("@createNewApi").then(response => {
|
cy.get("@createNewApi").then(response => {
|
||||||
cy.get(ApiEditor.ApiNameField).should("be.visible");
|
cy.get(ApiEditor.ApiNameField).should("be.visible");
|
||||||
cy.expect(response.response.body.responseMeta.success).to.eq(true);
|
cy.expect(response.response.body.responseMeta.success).to.eq(true);
|
||||||
|
|
@ -1131,10 +1133,8 @@ Cypress.Commands.add("startServerAndRoutes", () => {
|
||||||
cy.route("POST", "/api/v1/applications/publish/*").as("publishApp");
|
cy.route("POST", "/api/v1/applications/publish/*").as("publishApp");
|
||||||
cy.route("PUT", "/api/v1/layouts/*/pages/*").as("updateLayout");
|
cy.route("PUT", "/api/v1/layouts/*/pages/*").as("updateLayout");
|
||||||
|
|
||||||
cy.route("POST", "/v1/t").as("postSave");
|
|
||||||
cy.route("PUT", "/api/v1/actions/*").as("putActions");
|
cy.route("PUT", "/api/v1/actions/*").as("putActions");
|
||||||
cy.route("POST", "/track/*").as("postTrack");
|
cy.route("POST", "/track/*").as("postTrack");
|
||||||
cy.route("POST", "/v1/m").as("postexe");
|
|
||||||
cy.route("POST", "/api/v1/actions/execute").as("postExecute");
|
cy.route("POST", "/api/v1/actions/execute").as("postExecute");
|
||||||
cy.route("POST", "/api/v1/actions").as("postaction");
|
cy.route("POST", "/api/v1/actions").as("postaction");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -182,4 +182,4 @@
|
||||||
"pre-commit": "lint-staged"
|
"pre-commit": "lint-staged"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import {
|
||||||
ReduxActionErrorTypes,
|
ReduxActionErrorTypes,
|
||||||
} from "constants/ReduxActionConstants";
|
} from "constants/ReduxActionConstants";
|
||||||
import { Action, RestAction } from "entities/Action";
|
import { Action, RestAction } from "entities/Action";
|
||||||
|
import { batchAction } from "actions/batchActions";
|
||||||
|
|
||||||
export const createActionRequest = (payload: Partial<Action>) => {
|
export const createActionRequest = (payload: Partial<Action>) => {
|
||||||
return {
|
return {
|
||||||
|
|
@ -47,12 +48,12 @@ export const fetchActionsForPageSuccess = (actions: RestAction[]) => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const runApiAction = (id: string, paginationField?: PaginationField) => {
|
export const runAction = (id: string, paginationField?: PaginationField) => {
|
||||||
return {
|
return {
|
||||||
type: ReduxActionTypes.RUN_API_REQUEST,
|
type: ReduxActionTypes.RUN_ACTION_REQUEST,
|
||||||
payload: {
|
payload: {
|
||||||
id: id,
|
id,
|
||||||
paginationField: paginationField,
|
paginationField,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -163,24 +164,35 @@ export const saveApiName = (payload: { id: string; name: string }) => ({
|
||||||
payload: payload,
|
payload: payload,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const updateApiNameDraft = (payload: {
|
export type SetActionPropertyPayload = {
|
||||||
id: string;
|
actionId: string;
|
||||||
draft?: {
|
propertyName: string;
|
||||||
value: string;
|
value: string;
|
||||||
validation: {
|
};
|
||||||
isValid: boolean;
|
|
||||||
validationMessage: string;
|
export const setActionProperty = (payload: SetActionPropertyPayload) => ({
|
||||||
};
|
type: ReduxActionTypes.SET_ACTION_PROPERTY,
|
||||||
};
|
payload,
|
||||||
}) => ({
|
|
||||||
type: ReduxActionTypes.UPDATE_API_NAME_DRAFT,
|
|
||||||
payload: payload,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export type UpdateActionPropertyActionPayload = {
|
||||||
|
id: string;
|
||||||
|
field: string;
|
||||||
|
value: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateActionProperty = (
|
||||||
|
payload: UpdateActionPropertyActionPayload,
|
||||||
|
) =>
|
||||||
|
batchAction({
|
||||||
|
type: ReduxActionTypes.UPDATE_ACTION_PROPERTY,
|
||||||
|
payload,
|
||||||
|
});
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
createAction: createActionRequest,
|
createAction: createActionRequest,
|
||||||
fetchActions,
|
fetchActions,
|
||||||
runAction: runApiAction,
|
runAction: runAction,
|
||||||
deleteAction,
|
deleteAction,
|
||||||
deleteActionSuccess,
|
deleteActionSuccess,
|
||||||
updateAction,
|
updateAction,
|
||||||
|
|
|
||||||
|
|
@ -22,16 +22,6 @@ export const deleteQuerySuccess = (payload: { id: string }) => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const executeQuery = (payload: {
|
|
||||||
action: RestAction;
|
|
||||||
actionId: string;
|
|
||||||
}) => {
|
|
||||||
return {
|
|
||||||
type: ReduxActionTypes.EXECUTE_QUERY_REQUEST,
|
|
||||||
payload,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const initQueryPane = (
|
export const initQueryPane = (
|
||||||
pluginType: string,
|
pluginType: string,
|
||||||
urlId?: string,
|
urlId?: string,
|
||||||
|
|
|
||||||
|
|
@ -155,7 +155,7 @@ class CodeEditor extends Component<Props, State> {
|
||||||
componentDidUpdate(prevProps: Props): void {
|
componentDidUpdate(prevProps: Props): void {
|
||||||
this.editor.refresh();
|
this.editor.refresh();
|
||||||
if (!this.state.isFocused) {
|
if (!this.state.isFocused) {
|
||||||
const currentMode = this.editor.getOption("mode");
|
// const currentMode = this.editor.getOption("mode");
|
||||||
const editorValue = this.editor.getValue();
|
const editorValue = this.editor.getValue();
|
||||||
let inputValue = this.props.input.value;
|
let inputValue = this.props.input.value;
|
||||||
// Safe update of value of the editor when value updated outside the editor
|
// Safe update of value of the editor when value updated outside the editor
|
||||||
|
|
@ -170,10 +170,11 @@ class CodeEditor extends Component<Props, State> {
|
||||||
if ((!!inputValue || inputValue === "") && inputValue !== editorValue) {
|
if ((!!inputValue || inputValue === "") && inputValue !== editorValue) {
|
||||||
this.editor.setValue(inputValue);
|
this.editor.setValue(inputValue);
|
||||||
}
|
}
|
||||||
|
this.updateMarkings();
|
||||||
|
|
||||||
if (currentMode !== this.props.mode) {
|
// if (currentMode !== this.props.mode) {
|
||||||
this.editor.setOption("mode", this.props?.mode);
|
// this.editor.setOption("mode", this.props?.mode);
|
||||||
}
|
// }
|
||||||
} else {
|
} else {
|
||||||
// Update the dynamic bindings for autocomplete
|
// Update the dynamic bindings for autocomplete
|
||||||
if (prevProps.dynamicData !== this.props.dynamicData) {
|
if (prevProps.dynamicData !== this.props.dynamicData) {
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import { bindingHint } from "components/editorComponents/CodeEditor/hintHelpers"
|
||||||
import StoreAsDatasource from "components/editorComponents/StoreAsDatasource";
|
import StoreAsDatasource from "components/editorComponents/StoreAsDatasource";
|
||||||
|
|
||||||
type ReduxStateProps = {
|
type ReduxStateProps = {
|
||||||
|
orgId: string;
|
||||||
datasource: Datasource | EmbeddedDatasource;
|
datasource: Datasource | EmbeddedDatasource;
|
||||||
datasourceList: Datasource[];
|
datasourceList: Datasource[];
|
||||||
apiName: string;
|
apiName: string;
|
||||||
|
|
@ -47,13 +48,13 @@ const fullPathRegexExp = /(https?:\/{2}\S+)(\/\S*?)$/;
|
||||||
|
|
||||||
class EmbeddedDatasourcePathComponent extends React.Component<Props> {
|
class EmbeddedDatasourcePathComponent extends React.Component<Props> {
|
||||||
handleDatasourceUrlUpdate = (datasourceUrl: string) => {
|
handleDatasourceUrlUpdate = (datasourceUrl: string) => {
|
||||||
const { datasource, pluginId, datasourceList } = this.props;
|
const { datasource, pluginId, orgId, datasourceList } = this.props;
|
||||||
const urlHasUpdated =
|
const urlHasUpdated =
|
||||||
datasourceUrl !== datasource.datasourceConfiguration?.url;
|
datasourceUrl !== datasource.datasourceConfiguration?.url;
|
||||||
if (urlHasUpdated) {
|
if (urlHasUpdated) {
|
||||||
if ("id" in datasource && datasource.id) {
|
if ("id" in datasource && datasource.id) {
|
||||||
this.props.updateDatasource({
|
this.props.updateDatasource({
|
||||||
...DEFAULT_DATASOURCE(pluginId),
|
...DEFAULT_DATASOURCE(pluginId, orgId),
|
||||||
datasourceConfiguration: {
|
datasourceConfiguration: {
|
||||||
...datasource.datasourceConfiguration,
|
...datasource.datasourceConfiguration,
|
||||||
url: datasourceUrl,
|
url: datasourceUrl,
|
||||||
|
|
@ -68,7 +69,7 @@ class EmbeddedDatasourcePathComponent extends React.Component<Props> {
|
||||||
this.props.updateDatasource(matchesExistingDatasource);
|
this.props.updateDatasource(matchesExistingDatasource);
|
||||||
} else {
|
} else {
|
||||||
this.props.updateDatasource({
|
this.props.updateDatasource({
|
||||||
...DEFAULT_DATASOURCE(pluginId),
|
...DEFAULT_DATASOURCE(pluginId, orgId),
|
||||||
datasourceConfiguration: {
|
datasourceConfiguration: {
|
||||||
...datasource.datasourceConfiguration,
|
...datasource.datasourceConfiguration,
|
||||||
url: datasourceUrl,
|
url: datasourceUrl,
|
||||||
|
|
@ -249,6 +250,7 @@ const mapStateToProps = (
|
||||||
ownProps: { pluginId: string },
|
ownProps: { pluginId: string },
|
||||||
): ReduxStateProps => {
|
): ReduxStateProps => {
|
||||||
return {
|
return {
|
||||||
|
orgId: state.ui.orgs.currentOrgId,
|
||||||
apiName: apiFormValueSelector(state, "name"),
|
apiName: apiFormValueSelector(state, "name"),
|
||||||
datasource: apiFormValueSelector(state, "datasource"),
|
datasource: apiFormValueSelector(state, "datasource"),
|
||||||
datasourceList: state.entities.datasources.list.filter(
|
datasourceList: state.entities.datasources.list.filter(
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ const KeyValueRow = (props: Props & WrappedFieldArrayProps) => {
|
||||||
}, [props.fields, props.pushFields]);
|
}, [props.fields, props.pushFields]);
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{typeof props.fields.getAll() === "object" && (
|
{props.fields.length && (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{props.fields.map((field: any, index: number) => {
|
{props.fields.map((field: any, index: number) => {
|
||||||
const otherProps: Record<string, any> = {};
|
const otherProps: Record<string, any> = {};
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ export const DEFAULT_API_ACTION: Partial<RestAction> = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const API_CONSTANT = "API";
|
export const PLUGIN_TYPE_API = "API";
|
||||||
export const DEFAULT_PROVIDER_OPTION = "Business Software";
|
export const DEFAULT_PROVIDER_OPTION = "Business Software";
|
||||||
export const CONTENT_TYPE = "content-type";
|
export const CONTENT_TYPE = "content-type";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,10 +27,10 @@ export const ReduxActionTypes: { [key: string]: string } = {
|
||||||
REMOVE_PAGE_WIDGET: "REMOVE_PAGE_WIDGET",
|
REMOVE_PAGE_WIDGET: "REMOVE_PAGE_WIDGET",
|
||||||
LOAD_API_RESPONSE: "LOAD_API_RESPONSE",
|
LOAD_API_RESPONSE: "LOAD_API_RESPONSE",
|
||||||
LOAD_QUERY_RESPONSE: "LOAD_QUERY_RESPONSE",
|
LOAD_QUERY_RESPONSE: "LOAD_QUERY_RESPONSE",
|
||||||
RUN_API_REQUEST: "RUN_API_REQUEST",
|
RUN_ACTION_REQUEST: "RUN_ACTION_REQUEST",
|
||||||
|
RUN_ACTION_SUCCESS: "RUN_ACTION_SUCCESS",
|
||||||
INIT_API_PANE: "INIT_API_PANE",
|
INIT_API_PANE: "INIT_API_PANE",
|
||||||
API_PANE_CHANGE_API: "API_PANE_CHANGE_API",
|
API_PANE_CHANGE_API: "API_PANE_CHANGE_API",
|
||||||
RUN_API_SUCCESS: "RUN_API_SUCCESS",
|
|
||||||
EXECUTE_ACTION: "EXECUTE_ACTION",
|
EXECUTE_ACTION: "EXECUTE_ACTION",
|
||||||
EXECUTE_ACTION_SUCCESS: "EXECUTE_ACTION_SUCCESS",
|
EXECUTE_ACTION_SUCCESS: "EXECUTE_ACTION_SUCCESS",
|
||||||
LOAD_CANVAS_ACTIONS: "LOAD_CANVAS_ACTIONS",
|
LOAD_CANVAS_ACTIONS: "LOAD_CANVAS_ACTIONS",
|
||||||
|
|
@ -99,8 +99,6 @@ export const ReduxActionTypes: { [key: string]: string } = {
|
||||||
HIDE_PROPERTY_PANE: "HIDE_PROPERTY_PANE",
|
HIDE_PROPERTY_PANE: "HIDE_PROPERTY_PANE",
|
||||||
INIT_DATASOURCE_PANE: "INIT_DATASOURCE_PANE",
|
INIT_DATASOURCE_PANE: "INIT_DATASOURCE_PANE",
|
||||||
INIT_QUERY_PANE: "INIT_QUERY_PANE",
|
INIT_QUERY_PANE: "INIT_QUERY_PANE",
|
||||||
UPDATE_API_DRAFT: "UPDATE_API_DRAFT",
|
|
||||||
DELETE_API_DRAFT: "DELETE_API_DRAFT",
|
|
||||||
QUERY_PANE_CHANGE: "QUERY_PANE_CHANGE",
|
QUERY_PANE_CHANGE: "QUERY_PANE_CHANGE",
|
||||||
UPDATE_ROUTES_PARAMS: "UPDATE_ROUTES_PARAMS",
|
UPDATE_ROUTES_PARAMS: "UPDATE_ROUTES_PARAMS",
|
||||||
SET_EXTRA_FORMDATA: "SET_EXTRA_FORMDATA",
|
SET_EXTRA_FORMDATA: "SET_EXTRA_FORMDATA",
|
||||||
|
|
@ -190,8 +188,6 @@ export const ReduxActionTypes: { [key: string]: string } = {
|
||||||
ADD_API_TO_PAGE_SUCCESS: "ADD_API_TO_PAGE_SUCCESS",
|
ADD_API_TO_PAGE_SUCCESS: "ADD_API_TO_PAGE_SUCCESS",
|
||||||
DELETE_QUERY_INIT: "DELETE_QUERY_INIT",
|
DELETE_QUERY_INIT: "DELETE_QUERY_INIT",
|
||||||
DELETE_QUERY_SUCCESS: "DELETE_QUERY_SUCCESS",
|
DELETE_QUERY_SUCCESS: "DELETE_QUERY_SUCCESS",
|
||||||
EXECUTE_QUERY_REQUEST: "EXECUTE_QUERY_REQUEST",
|
|
||||||
RUN_QUERY_SUCCESS: "RUN_QUERY_SUCCESS",
|
|
||||||
CLEAR_PREVIOUSLY_EXECUTED_QUERY: "CLEAR_PREVIOUSLY_EXECUTED_QUERY",
|
CLEAR_PREVIOUSLY_EXECUTED_QUERY: "CLEAR_PREVIOUSLY_EXECUTED_QUERY",
|
||||||
FETCH_PROVIDERS_CATEGORIES_INIT: "FETCH_PROVIDERS_CATEGORIES_INIT",
|
FETCH_PROVIDERS_CATEGORIES_INIT: "FETCH_PROVIDERS_CATEGORIES_INIT",
|
||||||
FETCH_PROVIDERS_CATEGORIES_SUCCESS: "FETCH_PROVIDERS_CATEGORIES_SUCCESS",
|
FETCH_PROVIDERS_CATEGORIES_SUCCESS: "FETCH_PROVIDERS_CATEGORIES_SUCCESS",
|
||||||
|
|
@ -237,6 +233,8 @@ export const ReduxActionTypes: { [key: string]: string } = {
|
||||||
SAVE_API_NAME: "SAVE_API_NAME",
|
SAVE_API_NAME: "SAVE_API_NAME",
|
||||||
SAVE_API_NAME_SUCCESS: "SAVE_API_NAME_SUCCESS",
|
SAVE_API_NAME_SUCCESS: "SAVE_API_NAME_SUCCESS",
|
||||||
UPDATE_API_NAME_DRAFT: "UPDATE_API_NAME_DRAFT",
|
UPDATE_API_NAME_DRAFT: "UPDATE_API_NAME_DRAFT",
|
||||||
|
SET_ACTION_PROPERTY: "SET_ACTION_PROPERTY",
|
||||||
|
UPDATE_ACTION_PROPERTY: "UPDATE_ACTION_PROPERTY",
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ReduxActionType = typeof ReduxActionTypes[keyof typeof ReduxActionTypes];
|
export type ReduxActionType = typeof ReduxActionTypes[keyof typeof ReduxActionTypes];
|
||||||
|
|
@ -261,7 +259,7 @@ export const ReduxActionErrorTypes: { [key: string]: string } = {
|
||||||
CREATE_ACTION_ERROR: "CREATE_ACTION_ERROR",
|
CREATE_ACTION_ERROR: "CREATE_ACTION_ERROR",
|
||||||
UPDATE_ACTION_ERROR: "UPDATE_ACTION_ERROR",
|
UPDATE_ACTION_ERROR: "UPDATE_ACTION_ERROR",
|
||||||
DELETE_ACTION_ERROR: "DELETE_ACTION_ERROR",
|
DELETE_ACTION_ERROR: "DELETE_ACTION_ERROR",
|
||||||
RUN_API_ERROR: "RUN_API_ERROR",
|
RUN_ACTION_ERROR: "RUN_ACTION_ERROR",
|
||||||
EXECUTE_ACTION_ERROR: "EXECUTE_ACTION_ERROR",
|
EXECUTE_ACTION_ERROR: "EXECUTE_ACTION_ERROR",
|
||||||
FETCH_DATASOURCES_ERROR: "FETCH_DATASOURCES_ERROR",
|
FETCH_DATASOURCES_ERROR: "FETCH_DATASOURCES_ERROR",
|
||||||
SEARCH_APIORPROVIDERS_ERROR: "SEARCH_APIORPROVIDERS_ERROR",
|
SEARCH_APIORPROVIDERS_ERROR: "SEARCH_APIORPROVIDERS_ERROR",
|
||||||
|
|
@ -299,7 +297,6 @@ export const ReduxActionErrorTypes: { [key: string]: string } = {
|
||||||
MOVE_ACTION_ERROR: "MOVE_ACTION_ERROR",
|
MOVE_ACTION_ERROR: "MOVE_ACTION_ERROR",
|
||||||
COPY_ACTION_ERROR: "COPY_ACTION_ERROR",
|
COPY_ACTION_ERROR: "COPY_ACTION_ERROR",
|
||||||
DELETE_PAGE_ERROR: "DELETE_PAGE_ERROR",
|
DELETE_PAGE_ERROR: "DELETE_PAGE_ERROR",
|
||||||
RUN_QUERY_ERROR: "RUN_QUERY_ERROR",
|
|
||||||
DELETE_APPLICATION_ERROR: "DELETE_APPLICATION_ERROR",
|
DELETE_APPLICATION_ERROR: "DELETE_APPLICATION_ERROR",
|
||||||
SET_DEFAULT_APPLICATION_PAGE_ERROR: "SET_DEFAULT_APPLICATION_PAGE_ERROR",
|
SET_DEFAULT_APPLICATION_PAGE_ERROR: "SET_DEFAULT_APPLICATION_PAGE_ERROR",
|
||||||
CREATE_ORGANIZATION_ERROR: "CREATE_ORGANIZATION_ERROR",
|
CREATE_ORGANIZATION_ERROR: "CREATE_ORGANIZATION_ERROR",
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,7 @@ import { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsRe
|
||||||
import { MetaState } from "reducers/entityReducers/metaReducer";
|
import { MetaState } from "reducers/entityReducers/metaReducer";
|
||||||
import { PageListPayload } from "constants/ReduxActionConstants";
|
import { PageListPayload } from "constants/ReduxActionConstants";
|
||||||
import WidgetFactory from "utils/WidgetFactory";
|
import WidgetFactory from "utils/WidgetFactory";
|
||||||
import { ActionDraftsState } from "reducers/entityReducers/actionDraftsReducer";
|
import { ActionConfig, Property } from "entities/Action";
|
||||||
import { Property, ActionConfig } from "entities/Action";
|
|
||||||
|
|
||||||
export type ActionDescription<T> = {
|
export type ActionDescription<T> = {
|
||||||
type: string;
|
type: string;
|
||||||
|
|
@ -68,7 +67,6 @@ export type DataTree = {
|
||||||
|
|
||||||
type DataTreeSeed = {
|
type DataTreeSeed = {
|
||||||
actions: ActionDataState;
|
actions: ActionDataState;
|
||||||
actionDrafts: ActionDraftsState;
|
|
||||||
widgets: CanvasWidgetsReduxState;
|
widgets: CanvasWidgetsReduxState;
|
||||||
widgetsMeta: MetaState;
|
widgetsMeta: MetaState;
|
||||||
pageList: PageListPayload;
|
pageList: PageListPayload;
|
||||||
|
|
@ -77,7 +75,7 @@ type DataTreeSeed = {
|
||||||
|
|
||||||
export class DataTreeFactory {
|
export class DataTreeFactory {
|
||||||
static create(
|
static create(
|
||||||
{ actions, actionDrafts, widgets, widgetsMeta, pageList }: DataTreeSeed,
|
{ actions, widgets, widgetsMeta, pageList }: DataTreeSeed,
|
||||||
// TODO(hetu)
|
// TODO(hetu)
|
||||||
// temporary fix for not getting functions while normal evals which crashes the app
|
// temporary fix for not getting functions while normal evals which crashes the app
|
||||||
// need to remove this after we get a proper solve
|
// need to remove this after we get a proper solve
|
||||||
|
|
@ -86,8 +84,7 @@ export class DataTreeFactory {
|
||||||
const dataTree: DataTree = {};
|
const dataTree: DataTree = {};
|
||||||
const actionPaths = [];
|
const actionPaths = [];
|
||||||
actions.forEach(a => {
|
actions.forEach(a => {
|
||||||
const config =
|
const config = a.config;
|
||||||
a.config.id in actionDrafts ? actionDrafts[a.config.id] : a.config;
|
|
||||||
let dynamicBindingPathList: Property[] = [];
|
let dynamicBindingPathList: Property[] = [];
|
||||||
// update paths
|
// update paths
|
||||||
if (
|
if (
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,10 @@ import { Datasource } from "api/DatasourcesApi";
|
||||||
|
|
||||||
export type EmbeddedDatasource = Omit<Datasource, "id">;
|
export type EmbeddedDatasource = Omit<Datasource, "id">;
|
||||||
|
|
||||||
export const DEFAULT_DATASOURCE = (pluginId: string): EmbeddedDatasource => ({
|
export const DEFAULT_DATASOURCE = (
|
||||||
|
pluginId: string,
|
||||||
|
organizationId: string,
|
||||||
|
): EmbeddedDatasource => ({
|
||||||
name: "DEFAULT_REST_DATASOURCE",
|
name: "DEFAULT_REST_DATASOURCE",
|
||||||
datasourceConfiguration: {
|
datasourceConfiguration: {
|
||||||
url: "",
|
url: "",
|
||||||
|
|
@ -10,4 +13,5 @@ export const DEFAULT_DATASOURCE = (pluginId: string): EmbeddedDatasource => ({
|
||||||
invalids: [],
|
invalids: [],
|
||||||
isValid: true,
|
isValid: true,
|
||||||
pluginId,
|
pluginId,
|
||||||
|
organizationId,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,9 @@ export default class RealmExecutor implements JSExecutor {
|
||||||
safeObject.actionPaths.forEach(path => {
|
safeObject.actionPaths.forEach(path => {
|
||||||
const action = _.get(safeObject, path);
|
const action = _.get(safeObject, path);
|
||||||
const entity = _.get(safeObject, path.split(".")[0])
|
const entity = _.get(safeObject, path.split(".")[0])
|
||||||
_.set(safeObject, path, pusher.bind(safeObject, action.bind(entity)))
|
if(action) {
|
||||||
|
_.set(safeObject, path, pusher.bind(safeObject, action.bind(entity)))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return safeObject
|
return safeObject
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,7 @@ import { getFormValues, submit } from "redux-form";
|
||||||
import ApiEditorForm from "./Form";
|
import ApiEditorForm from "./Form";
|
||||||
import RapidApiEditorForm from "./RapidApiEditorForm";
|
import RapidApiEditorForm from "./RapidApiEditorForm";
|
||||||
import ApiHomeScreen from "./ApiHomeScreen";
|
import ApiHomeScreen from "./ApiHomeScreen";
|
||||||
import {
|
import { runAction, deleteAction, updateAction } from "actions/actionActions";
|
||||||
runApiAction,
|
|
||||||
deleteAction,
|
|
||||||
updateAction,
|
|
||||||
} from "actions/actionActions";
|
|
||||||
import { PaginationField } from "api/ActionAPI";
|
import { PaginationField } from "api/ActionAPI";
|
||||||
import { AppState } from "reducers";
|
import { AppState } from "reducers";
|
||||||
import { RouteComponentProps } from "react-router";
|
import { RouteComponentProps } from "react-router";
|
||||||
|
|
@ -235,8 +231,7 @@ const mapStateToProps = (state: AppState, props: any): ReduxStateProps => {
|
||||||
const apiName = getApiName(state, props.match.params.apiId);
|
const apiName = getApiName(state, props.match.params.apiId);
|
||||||
|
|
||||||
const { isDeleting, isRunning, isCreating } = state.ui.apiPane;
|
const { isDeleting, isRunning, isCreating } = state.ui.apiPane;
|
||||||
const actionDrafts = state.entities.actionDrafts;
|
const allowSave = true;
|
||||||
const allowSave = !!(apiAction && apiAction.id in actionDrafts);
|
|
||||||
const datasourceFieldText =
|
const datasourceFieldText =
|
||||||
state.ui.apiPane.datasourceFieldText[formData?.id ?? ""] || "";
|
state.ui.apiPane.datasourceFieldText[formData?.id ?? ""] || "";
|
||||||
|
|
||||||
|
|
@ -261,7 +256,7 @@ const mapStateToProps = (state: AppState, props: any): ReduxStateProps => {
|
||||||
const mapDispatchToProps = (dispatch: any): ReduxActionProps => ({
|
const mapDispatchToProps = (dispatch: any): ReduxActionProps => ({
|
||||||
submitForm: (name: string) => dispatch(submit(name)),
|
submitForm: (name: string) => dispatch(submit(name)),
|
||||||
runAction: (id: string, paginationField?: PaginationField) =>
|
runAction: (id: string, paginationField?: PaginationField) =>
|
||||||
dispatch(runApiAction(id, paginationField)),
|
dispatch(runAction(id, paginationField)),
|
||||||
deleteAction: (id: string, name: string) =>
|
deleteAction: (id: string, name: string) =>
|
||||||
dispatch(deleteAction({ id, name })),
|
dispatch(deleteAction({ id, name })),
|
||||||
updateAction: (data: RestAction) => dispatch(updateAction({ data })),
|
updateAction: (data: RestAction) => dispatch(updateAction({ data })),
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ import { getNextEntityName } from "utils/AppsmithUtils";
|
||||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||||
import { Page } from "constants/ReduxActionConstants";
|
import { Page } from "constants/ReduxActionConstants";
|
||||||
import { RestAction } from "entities/Action";
|
import { RestAction } from "entities/Action";
|
||||||
import { ActionDraftsState } from "reducers/entityReducers/actionDraftsReducer";
|
|
||||||
|
|
||||||
const HTTPMethod = styled.span<{ method?: string }>`
|
const HTTPMethod = styled.span<{ method?: string }>`
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
@ -51,6 +50,7 @@ const ActionItem = styled.div`
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
max-width: 90%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ActionName = styled.span`
|
const ActionName = styled.span`
|
||||||
|
|
@ -59,12 +59,10 @@ const ActionName = styled.span`
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
max-width: 100px;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
interface ReduxStateProps {
|
interface ReduxStateProps {
|
||||||
actions: ActionDataState;
|
actions: ActionDataState;
|
||||||
actionDrafts: ActionDraftsState;
|
|
||||||
apiPane: ApiPaneReduxState;
|
apiPane: ApiPaneReduxState;
|
||||||
pages: Page[];
|
pages: Page[];
|
||||||
}
|
}
|
||||||
|
|
@ -92,16 +90,6 @@ class ApiSidebar extends React.Component<Props> {
|
||||||
this.props.initApiPane(this.props.match.params.apiId);
|
this.props.initApiPane(this.props.match.params.apiId);
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps: Readonly<Props>): boolean {
|
|
||||||
if (
|
|
||||||
Object.keys(nextProps.actionDrafts) !==
|
|
||||||
Object.keys(this.props.actionDrafts)
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return nextProps.actions !== this.props.actions;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleApiChange = (actionId: string) => {
|
handleApiChange = (actionId: string) => {
|
||||||
this.props.onApiChange(actionId);
|
this.props.onApiChange(actionId);
|
||||||
};
|
};
|
||||||
|
|
@ -197,20 +185,20 @@ class ApiSidebar extends React.Component<Props> {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
actionDrafts,
|
|
||||||
apiPane: { isFetching },
|
apiPane: { isFetching },
|
||||||
match: {
|
match: {
|
||||||
params: { apiId },
|
params: { apiId },
|
||||||
},
|
},
|
||||||
actions,
|
actions,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const data = actions.map(a => a.config).filter(a => a.pluginType === "API");
|
const data = actions
|
||||||
|
.filter(a => a.config?.pluginType === "API")
|
||||||
|
.map(a => a.config);
|
||||||
return (
|
return (
|
||||||
<EditorSidebar
|
<EditorSidebar
|
||||||
isLoading={isFetching}
|
isLoading={isFetching}
|
||||||
list={data}
|
list={data}
|
||||||
selectedItemId={apiId}
|
selectedItemId={apiId}
|
||||||
draftIds={Object.keys(actionDrafts)}
|
|
||||||
itemRender={this.renderItem}
|
itemRender={this.renderItem}
|
||||||
onItemCreateClick={this.handleCreateNewApiClick}
|
onItemCreateClick={this.handleCreateNewApiClick}
|
||||||
onItemSelected={this.handleApiChange}
|
onItemSelected={this.handleApiChange}
|
||||||
|
|
@ -225,7 +213,6 @@ class ApiSidebar extends React.Component<Props> {
|
||||||
|
|
||||||
const mapStateToProps = (state: AppState): ReduxStateProps => ({
|
const mapStateToProps = (state: AppState): ReduxStateProps => ({
|
||||||
actions: state.entities.actions,
|
actions: state.entities.actions,
|
||||||
actionDrafts: state.entities.actionDrafts,
|
|
||||||
apiPane: state.ui.apiPane,
|
apiPane: state.ui.apiPane,
|
||||||
pages: state.entities.pageList.pages,
|
pages: state.entities.pageList.pages,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import TreeDropdown from "components/editorComponents/actioncreator/TreeDropdown
|
||||||
import { theme } from "constants/DefaultTheme";
|
import { theme } from "constants/DefaultTheme";
|
||||||
import { Colors } from "constants/Colors";
|
import { Colors } from "constants/Colors";
|
||||||
import { ControlIcons } from "icons/ControlIcons";
|
import { ControlIcons } from "icons/ControlIcons";
|
||||||
import NotificationIcon from "components/designSystems/appsmith/NotificationIcon";
|
|
||||||
|
|
||||||
const LoadingContainer = styled(CenteredWrapper)`
|
const LoadingContainer = styled(CenteredWrapper)`
|
||||||
height: 50%;
|
height: 50%;
|
||||||
|
|
@ -165,11 +164,6 @@ const ItemContainer = styled.div<{
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const DraftIconIndicator = styled(NotificationIcon)<{ isHidden: boolean }>`
|
|
||||||
margin: 0 5px;
|
|
||||||
opacity: ${({ isHidden }) => (isHidden ? 0 : 1)};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledAddButton = styled(Button)<IIconProps>`
|
const StyledAddButton = styled(Button)<IIconProps>`
|
||||||
&&& {
|
&&& {
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
@ -192,7 +186,6 @@ type EditorSidebarComponentProps = {
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
list: Array<Item>;
|
list: Array<Item>;
|
||||||
selectedItemId?: string;
|
selectedItemId?: string;
|
||||||
draftIds: string[];
|
|
||||||
itemRender: (item: any) => JSX.Element;
|
itemRender: (item: any) => JSX.Element;
|
||||||
onItemCreateClick: (pageId: string) => void;
|
onItemCreateClick: (pageId: string) => void;
|
||||||
onItemSelected: (itemId: string, itemPageId: string) => void;
|
onItemSelected: (itemId: string, itemPageId: string) => void;
|
||||||
|
|
@ -306,7 +299,6 @@ class EditorSidebar extends React.Component<Props, State> {
|
||||||
isLoading,
|
isLoading,
|
||||||
itemRender,
|
itemRender,
|
||||||
selectedItemId,
|
selectedItemId,
|
||||||
draftIds,
|
|
||||||
location,
|
location,
|
||||||
createButtonTitle,
|
createButtonTitle,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
@ -404,11 +396,6 @@ class EditorSidebar extends React.Component<Props, State> {
|
||||||
{this.state.itemDragging !==
|
{this.state.itemDragging !==
|
||||||
item.id && (
|
item.id && (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<DraftIconIndicator
|
|
||||||
isHidden={
|
|
||||||
draftIds.indexOf(item.id) === -1
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<TreeDropdown
|
<TreeDropdown
|
||||||
defaultText=""
|
defaultText=""
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,10 @@ class JSONOutput extends React.Component<Props> {
|
||||||
collapsed: 1,
|
collapsed: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (typeof src !== "object") {
|
||||||
|
return <OutputContainer>{src}</OutputContainer>;
|
||||||
|
}
|
||||||
|
|
||||||
if (!src.length) {
|
if (!src.length) {
|
||||||
return (
|
return (
|
||||||
<OutputContainer>
|
<OutputContainer>
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ import styled from "styled-components";
|
||||||
import { QueryEditorRouteParams } from "constants/routes";
|
import { QueryEditorRouteParams } from "constants/routes";
|
||||||
import QueryEditorForm from "./Form";
|
import QueryEditorForm from "./Form";
|
||||||
import QueryHomeScreen from "./QueryHomeScreen";
|
import QueryHomeScreen from "./QueryHomeScreen";
|
||||||
import { updateAction } from "actions/actionActions";
|
import { runAction, updateAction } from "actions/actionActions";
|
||||||
import { deleteQuery, executeQuery } from "actions/queryPaneActions";
|
import { deleteQuery } from "actions/queryPaneActions";
|
||||||
import { AppState } from "reducers";
|
import { AppState } from "reducers";
|
||||||
import { getDataSources } from "selectors/editorSelectors";
|
import { getDataSources } from "selectors/editorSelectors";
|
||||||
import { QUERY_EDITOR_FORM_NAME } from "constants/forms";
|
import { QUERY_EDITOR_FORM_NAME } from "constants/forms";
|
||||||
|
|
@ -32,7 +32,6 @@ import {
|
||||||
import { getCurrentApplication } from "selectors/applicationSelectors";
|
import { getCurrentApplication } from "selectors/applicationSelectors";
|
||||||
import { QueryAction, RestAction } from "entities/Action";
|
import { QueryAction, RestAction } from "entities/Action";
|
||||||
import { getPluginImage } from "pages/Editor/QueryEditor/helpers";
|
import { getPluginImage } from "pages/Editor/QueryEditor/helpers";
|
||||||
import { ActionDraftsState } from "reducers/entityReducers/actionDraftsReducer";
|
|
||||||
|
|
||||||
const EmptyStateContainer = styled.div`
|
const EmptyStateContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -46,7 +45,6 @@ type QueryPageProps = {
|
||||||
queryPane: QueryPaneReduxState;
|
queryPane: QueryPaneReduxState;
|
||||||
formData: RestAction;
|
formData: RestAction;
|
||||||
isCreating: boolean;
|
isCreating: boolean;
|
||||||
actionDrafts: ActionDraftsState;
|
|
||||||
initialValues: RestAction;
|
initialValues: RestAction;
|
||||||
pluginIds: Array<string> | undefined;
|
pluginIds: Array<string> | undefined;
|
||||||
submitForm: (name: string) => void;
|
submitForm: (name: string) => void;
|
||||||
|
|
@ -97,7 +95,6 @@ class QueryEditor extends React.Component<Props> {
|
||||||
pluginIds,
|
pluginIds,
|
||||||
executedQueryData,
|
executedQueryData,
|
||||||
selectedPluginPackage,
|
selectedPluginPackage,
|
||||||
actionDrafts,
|
|
||||||
isCreating,
|
isCreating,
|
||||||
runErrorMessage,
|
runErrorMessage,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
@ -130,7 +127,7 @@ class QueryEditor extends React.Component<Props> {
|
||||||
location={this.props.location}
|
location={this.props.location}
|
||||||
applicationId={applicationId}
|
applicationId={applicationId}
|
||||||
pageId={pageId}
|
pageId={pageId}
|
||||||
allowSave={queryId in actionDrafts}
|
allowSave={true}
|
||||||
isSaving={isSaving[queryId]}
|
isSaving={isSaving[queryId]}
|
||||||
isRunning={isRunning[queryId]}
|
isRunning={isRunning[queryId]}
|
||||||
isDeleting={isDeleting[queryId]}
|
isDeleting={isDeleting[queryId]}
|
||||||
|
|
@ -175,7 +172,6 @@ const mapStateToProps = (state: AppState): any => {
|
||||||
return {
|
return {
|
||||||
plugins: getPlugins(state),
|
plugins: getPlugins(state),
|
||||||
runErrorMessage,
|
runErrorMessage,
|
||||||
actionDrafts: state.entities.actionDrafts,
|
|
||||||
pluginIds: getPluginIdsOfPackageNames(state, PLUGIN_PACKAGE_DBS),
|
pluginIds: getPluginIdsOfPackageNames(state, PLUGIN_PACKAGE_DBS),
|
||||||
dataSources: getDataSources(state),
|
dataSources: getDataSources(state),
|
||||||
executedQueryData: state.ui.queryPane.runQuerySuccessData,
|
executedQueryData: state.ui.queryPane.runQuerySuccessData,
|
||||||
|
|
@ -193,7 +189,7 @@ const mapDispatchToProps = (dispatch: any): any => ({
|
||||||
updateAction: (data: RestAction) => dispatch(updateAction({ data })),
|
updateAction: (data: RestAction) => dispatch(updateAction({ data })),
|
||||||
deleteAction: (id: string) => dispatch(deleteQuery({ id })),
|
deleteAction: (id: string) => dispatch(deleteQuery({ id })),
|
||||||
runAction: (action: RestAction, actionId: string) =>
|
runAction: (action: RestAction, actionId: string) =>
|
||||||
dispatch(executeQuery({ action, actionId })),
|
dispatch(runAction(actionId)),
|
||||||
createTemplate: (template: any) => {
|
createTemplate: (template: any) => {
|
||||||
dispatch(change(QUERY_EDITOR_FORM_NAME, QUERY_BODY_FIELD, template));
|
dispatch(change(QUERY_EDITOR_FORM_NAME, QUERY_BODY_FIELD, template));
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ import { getDataSources } from "selectors/editorSelectors";
|
||||||
import { QUERY_EDITOR_URL_WITH_SELECTED_PAGE_ID } from "constants/routes";
|
import { QUERY_EDITOR_URL_WITH_SELECTED_PAGE_ID } from "constants/routes";
|
||||||
import { RestAction } from "entities/Action";
|
import { RestAction } from "entities/Action";
|
||||||
import { Colors } from "constants/Colors";
|
import { Colors } from "constants/Colors";
|
||||||
import { ActionDraftsState } from "reducers/entityReducers/actionDraftsReducer";
|
|
||||||
|
|
||||||
const ActionItem = styled.div`
|
const ActionItem = styled.div`
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
@ -60,7 +59,6 @@ interface ReduxStateProps {
|
||||||
plugins: Plugin[];
|
plugins: Plugin[];
|
||||||
queries: ActionDataState;
|
queries: ActionDataState;
|
||||||
apiPane: ApiPaneReduxState;
|
apiPane: ApiPaneReduxState;
|
||||||
actionDrafts: ActionDraftsState;
|
|
||||||
actions: ActionDataState;
|
actions: ActionDataState;
|
||||||
dataSources: Datasource[];
|
dataSources: Datasource[];
|
||||||
}
|
}
|
||||||
|
|
@ -88,16 +86,6 @@ class QuerySidebar extends React.Component<Props> {
|
||||||
this.props.initQueryPane(QUERY_CONSTANT, this.props.match.params.queryId);
|
this.props.initQueryPane(QUERY_CONSTANT, this.props.match.params.queryId);
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps: Readonly<Props>): boolean {
|
|
||||||
if (
|
|
||||||
Object.keys(nextProps.actionDrafts) !==
|
|
||||||
Object.keys(this.props.actionDrafts)
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return nextProps.actions !== this.props.actions;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleCreateNew = () => {
|
handleCreateNew = () => {
|
||||||
const { actions } = this.props;
|
const { actions } = this.props;
|
||||||
const { pageId } = this.props.match.params;
|
const { pageId } = this.props.match.params;
|
||||||
|
|
@ -182,7 +170,6 @@ class QuerySidebar extends React.Component<Props> {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
actionDrafts,
|
|
||||||
apiPane: { isFetching },
|
apiPane: { isFetching },
|
||||||
match: {
|
match: {
|
||||||
params: { queryId },
|
params: { queryId },
|
||||||
|
|
@ -196,7 +183,6 @@ class QuerySidebar extends React.Component<Props> {
|
||||||
isLoading={isFetching}
|
isLoading={isFetching}
|
||||||
list={data}
|
list={data}
|
||||||
selectedItemId={queryId}
|
selectedItemId={queryId}
|
||||||
draftIds={Object.keys(actionDrafts)}
|
|
||||||
itemRender={this.renderItem}
|
itemRender={this.renderItem}
|
||||||
onItemCreateClick={this.handleCreateNewQueryClick}
|
onItemCreateClick={this.handleCreateNewQueryClick}
|
||||||
onItemSelected={this.handleQueryChange}
|
onItemSelected={this.handleQueryChange}
|
||||||
|
|
@ -212,7 +198,6 @@ class QuerySidebar extends React.Component<Props> {
|
||||||
const mapStateToProps = (state: AppState): ReduxStateProps => ({
|
const mapStateToProps = (state: AppState): ReduxStateProps => ({
|
||||||
plugins: getPlugins(state),
|
plugins: getPlugins(state),
|
||||||
queries: getQueryActions(state),
|
queries: getQueryActions(state),
|
||||||
actionDrafts: state.entities.actionDrafts,
|
|
||||||
apiPane: state.ui.apiPane,
|
apiPane: state.ui.apiPane,
|
||||||
actions: state.entities.actions,
|
actions: state.entities.actions,
|
||||||
dataSources: getDataSources(state),
|
dataSources: getDataSources(state),
|
||||||
|
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
import { createReducer } from "utils/AppsmithUtils";
|
|
||||||
import { ReduxAction, ReduxActionTypes } from "constants/ReduxActionConstants";
|
|
||||||
import { Action, RestAction } from "entities/Action";
|
|
||||||
import _ from "lodash";
|
|
||||||
import { ApiPaneReduxState } from "reducers/uiReducers/apiPaneReducer";
|
|
||||||
|
|
||||||
export type ActionDraftsState = Record<string, Action>;
|
|
||||||
|
|
||||||
const initialState: ActionDraftsState = {};
|
|
||||||
|
|
||||||
const actionDraftsReducer = createReducer(initialState, {
|
|
||||||
[ReduxActionTypes.UPDATE_API_DRAFT]: (
|
|
||||||
state: ApiPaneReduxState,
|
|
||||||
action: ReduxAction<{ id: string; draft: Partial<RestAction> }>,
|
|
||||||
) => ({
|
|
||||||
...state,
|
|
||||||
[action.payload.id]: action.payload.draft,
|
|
||||||
}),
|
|
||||||
[ReduxActionTypes.DELETE_API_DRAFT]: (
|
|
||||||
state: ApiPaneReduxState,
|
|
||||||
action: ReduxAction<{ id: string }>,
|
|
||||||
) => _.omit(state, action.payload.id),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default actionDraftsReducer;
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { ActionResponse } from "api/ActionAPI";
|
||||||
import { ExecuteErrorPayload } from "constants/ActionConstants";
|
import { ExecuteErrorPayload } from "constants/ActionConstants";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { RapidApiAction, RestAction } from "entities/Action";
|
import { RapidApiAction, RestAction } from "entities/Action";
|
||||||
|
import { UpdateActionPropertyActionPayload } from "actions/actionActions";
|
||||||
export interface ActionData {
|
export interface ActionData {
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
config: RestAction | RapidApiAction;
|
config: RestAction | RapidApiAction;
|
||||||
|
|
@ -87,6 +88,16 @@ const actionsReducer = createReducer(initialState, {
|
||||||
return { ...a, config: action.payload.data };
|
return { ...a, config: action.payload.data };
|
||||||
return a;
|
return a;
|
||||||
}),
|
}),
|
||||||
|
[ReduxActionTypes.UPDATE_ACTION_PROPERTY]: (
|
||||||
|
state: ActionDataState,
|
||||||
|
action: ReduxAction<UpdateActionPropertyActionPayload>,
|
||||||
|
) =>
|
||||||
|
state.map(a => {
|
||||||
|
if (a.config.id === action.payload.id) {
|
||||||
|
return _.set(a, `config.${action.payload.field}`, action.payload.value);
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}),
|
||||||
[ReduxActionTypes.DELETE_ACTION_SUCCESS]: (
|
[ReduxActionTypes.DELETE_ACTION_SUCCESS]: (
|
||||||
state: ActionDataState,
|
state: ActionDataState,
|
||||||
action: ReduxAction<{ id: string }>,
|
action: ReduxAction<{ id: string }>,
|
||||||
|
|
@ -126,17 +137,17 @@ const actionsReducer = createReducer(initialState, {
|
||||||
): ActionDataState =>
|
): ActionDataState =>
|
||||||
state.map(a => {
|
state.map(a => {
|
||||||
if (a.config.id === action.payload.actionId) {
|
if (a.config.id === action.payload.actionId) {
|
||||||
return { ...a, isLoading: false };
|
return { ...a, isLoading: false, data: action.payload.error };
|
||||||
}
|
}
|
||||||
|
|
||||||
return a;
|
return a;
|
||||||
}),
|
}),
|
||||||
[ReduxActionTypes.RUN_API_REQUEST]: (
|
[ReduxActionTypes.RUN_ACTION_REQUEST]: (
|
||||||
state: ActionDataState,
|
state: ActionDataState,
|
||||||
action: ReduxAction<string>,
|
action: ReduxAction<{ id: string }>,
|
||||||
): ActionDataState =>
|
): ActionDataState =>
|
||||||
state.map(a => {
|
state.map(a => {
|
||||||
if (action.payload === a.config.id) {
|
if (action.payload.id === a.config.id) {
|
||||||
return {
|
return {
|
||||||
...a,
|
...a,
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
|
|
@ -145,7 +156,7 @@ const actionsReducer = createReducer(initialState, {
|
||||||
|
|
||||||
return a;
|
return a;
|
||||||
}),
|
}),
|
||||||
[ReduxActionTypes.RUN_API_SUCCESS]: (
|
[ReduxActionTypes.RUN_ACTION_SUCCESS]: (
|
||||||
state: ActionDataState,
|
state: ActionDataState,
|
||||||
action: ReduxAction<{ [id: string]: ActionResponse }>,
|
action: ReduxAction<{ [id: string]: ActionResponse }>,
|
||||||
): ActionDataState => {
|
): ActionDataState => {
|
||||||
|
|
@ -157,20 +168,7 @@ const actionsReducer = createReducer(initialState, {
|
||||||
return a;
|
return a;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[ReduxActionTypes.RUN_QUERY_SUCCESS]: (
|
[ReduxActionErrorTypes.RUN_ACTION_ERROR]: (
|
||||||
state: ActionDataState,
|
|
||||||
action: ReduxAction<{ actionId: string; data: ActionResponse }>,
|
|
||||||
): ActionDataState => {
|
|
||||||
const actionId: string = action.payload.actionId;
|
|
||||||
|
|
||||||
return state.map(a => {
|
|
||||||
if (a.config.id === actionId) {
|
|
||||||
return { ...a, isLoading: false, data: action.payload.data };
|
|
||||||
}
|
|
||||||
return a;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[ReduxActionErrorTypes.RUN_API_ERROR]: (
|
|
||||||
state: ActionDataState,
|
state: ActionDataState,
|
||||||
action: ReduxAction<{ id: string }>,
|
action: ReduxAction<{ id: string }>,
|
||||||
): ActionDataState =>
|
): ActionDataState =>
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,12 @@ import pageListReducer from "./pageListReducer";
|
||||||
import jsExecutionsReducer from "./jsExecutionsReducer";
|
import jsExecutionsReducer from "./jsExecutionsReducer";
|
||||||
import pluginsReducer from "reducers/entityReducers/pluginsReducer";
|
import pluginsReducer from "reducers/entityReducers/pluginsReducer";
|
||||||
import metaReducer from "./metaReducer";
|
import metaReducer from "./metaReducer";
|
||||||
import actionDraftsReducer from "reducers/entityReducers/actionDraftsReducer";
|
|
||||||
|
|
||||||
const entityReducer = combineReducers({
|
const entityReducer = combineReducers({
|
||||||
canvasWidgets: canvasWidgetsReducer,
|
canvasWidgets: canvasWidgetsReducer,
|
||||||
queryData: queryDataReducer,
|
queryData: queryDataReducer,
|
||||||
widgetConfig: widgetConfigReducer,
|
widgetConfig: widgetConfigReducer,
|
||||||
actions: actionsReducer,
|
actions: actionsReducer,
|
||||||
actionDrafts: actionDraftsReducer,
|
|
||||||
propertyConfig: propertyPaneConfigReducer,
|
propertyConfig: propertyPaneConfigReducer,
|
||||||
datasources: datasourceReducer,
|
datasources: datasourceReducer,
|
||||||
pageList: pageListReducer,
|
pageList: pageListReducer,
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ import { ImportedCollectionsReduxState } from "reducers/uiReducers/importedColle
|
||||||
import { ProvidersReduxState } from "reducers/uiReducers/providerReducer";
|
import { ProvidersReduxState } from "reducers/uiReducers/providerReducer";
|
||||||
import { MetaState } from "./entityReducers/metaReducer";
|
import { MetaState } from "./entityReducers/metaReducer";
|
||||||
import { ImportReduxState } from "reducers/uiReducers/importReducer";
|
import { ImportReduxState } from "reducers/uiReducers/importReducer";
|
||||||
import { ActionDraftsState } from "reducers/entityReducers/actionDraftsReducer";
|
|
||||||
import { HelpReduxState } from "./uiReducers/helpReducer";
|
import { HelpReduxState } from "./uiReducers/helpReducer";
|
||||||
import { ApiNameReduxState } from "./uiReducers/apiNameReducer";
|
import { ApiNameReduxState } from "./uiReducers/apiNameReducer";
|
||||||
|
|
||||||
|
|
@ -64,7 +63,6 @@ export interface AppState {
|
||||||
canvasWidgets: CanvasWidgetsReduxState;
|
canvasWidgets: CanvasWidgetsReduxState;
|
||||||
queryData: QueryDataState;
|
queryData: QueryDataState;
|
||||||
actions: ActionDataState;
|
actions: ActionDataState;
|
||||||
actionDrafts: ActionDraftsState;
|
|
||||||
propertyConfig: PropertyPaneConfigState;
|
propertyConfig: PropertyPaneConfigState;
|
||||||
widgetConfig: WidgetConfigReducerState;
|
widgetConfig: WidgetConfigReducerState;
|
||||||
datasources: DatasourceDataState;
|
datasources: DatasourceDataState;
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import {
|
||||||
ReduxAction,
|
ReduxAction,
|
||||||
} from "constants/ReduxActionConstants";
|
} from "constants/ReduxActionConstants";
|
||||||
import { RestAction } from "entities/Action";
|
import { RestAction } from "entities/Action";
|
||||||
|
import { UpdateActionPropertyActionPayload } from "actions/actionActions";
|
||||||
|
|
||||||
const initialState: ApiPaneReduxState = {
|
const initialState: ApiPaneReduxState = {
|
||||||
lastUsed: "",
|
lastUsed: "",
|
||||||
|
|
@ -13,6 +14,7 @@ const initialState: ApiPaneReduxState = {
|
||||||
isRunning: {},
|
isRunning: {},
|
||||||
isSaving: {},
|
isSaving: {},
|
||||||
isDeleting: {},
|
isDeleting: {},
|
||||||
|
isDirty: {},
|
||||||
currentCategory: "",
|
currentCategory: "",
|
||||||
lastUsedEditorPage: "",
|
lastUsedEditorPage: "",
|
||||||
lastSelectedPage: "",
|
lastSelectedPage: "",
|
||||||
|
|
@ -27,6 +29,7 @@ export interface ApiPaneReduxState {
|
||||||
isRunning: Record<string, boolean>;
|
isRunning: Record<string, boolean>;
|
||||||
isSaving: Record<string, boolean>;
|
isSaving: Record<string, boolean>;
|
||||||
isDeleting: Record<string, boolean>;
|
isDeleting: Record<string, boolean>;
|
||||||
|
isDirty: Record<string, boolean>;
|
||||||
currentCategory: string;
|
currentCategory: string;
|
||||||
lastUsedEditorPage: string;
|
lastUsedEditorPage: string;
|
||||||
datasourceFieldText: Record<string, string>;
|
datasourceFieldText: Record<string, string>;
|
||||||
|
|
@ -65,7 +68,7 @@ const apiPaneReducer = createReducer(initialState, {
|
||||||
...state,
|
...state,
|
||||||
isCreating: false,
|
isCreating: false,
|
||||||
}),
|
}),
|
||||||
[ReduxActionTypes.RUN_API_REQUEST]: (
|
[ReduxActionTypes.RUN_ACTION_REQUEST]: (
|
||||||
state: ApiPaneReduxState,
|
state: ApiPaneReduxState,
|
||||||
action: ReduxAction<{ id: string }>,
|
action: ReduxAction<{ id: string }>,
|
||||||
) => ({
|
) => ({
|
||||||
|
|
@ -75,7 +78,7 @@ const apiPaneReducer = createReducer(initialState, {
|
||||||
[action.payload.id]: true,
|
[action.payload.id]: true,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
[ReduxActionTypes.RUN_API_SUCCESS]: (
|
[ReduxActionTypes.RUN_ACTION_SUCCESS]: (
|
||||||
state: ApiPaneReduxState,
|
state: ApiPaneReduxState,
|
||||||
action: ReduxAction<{ [id: string]: any }>,
|
action: ReduxAction<{ [id: string]: any }>,
|
||||||
) => {
|
) => {
|
||||||
|
|
@ -88,7 +91,7 @@ const apiPaneReducer = createReducer(initialState, {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[ReduxActionErrorTypes.RUN_API_ERROR]: (
|
[ReduxActionErrorTypes.RUN_ACTION_ERROR]: (
|
||||||
state: ApiPaneReduxState,
|
state: ApiPaneReduxState,
|
||||||
action: ReduxAction<{ id: string }>,
|
action: ReduxAction<{ id: string }>,
|
||||||
) => ({
|
) => ({
|
||||||
|
|
@ -98,6 +101,16 @@ const apiPaneReducer = createReducer(initialState, {
|
||||||
[action.payload.id]: false,
|
[action.payload.id]: false,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
[ReduxActionTypes.UPDATE_ACTION_PROPERTY]: (
|
||||||
|
state: ApiPaneReduxState,
|
||||||
|
action: ReduxAction<UpdateActionPropertyActionPayload>,
|
||||||
|
) => ({
|
||||||
|
...state,
|
||||||
|
isDirty: {
|
||||||
|
...state.isDirty,
|
||||||
|
[action.payload.id]: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
[ReduxActionTypes.UPDATE_ACTION_INIT]: (
|
[ReduxActionTypes.UPDATE_ACTION_INIT]: (
|
||||||
state: ApiPaneReduxState,
|
state: ApiPaneReduxState,
|
||||||
action: ReduxAction<{ data: RestAction }>,
|
action: ReduxAction<{ data: RestAction }>,
|
||||||
|
|
@ -117,6 +130,10 @@ const apiPaneReducer = createReducer(initialState, {
|
||||||
...state.isSaving,
|
...state.isSaving,
|
||||||
[action.payload.data.id]: false,
|
[action.payload.data.id]: false,
|
||||||
},
|
},
|
||||||
|
isDirty: {
|
||||||
|
...state.isDirty,
|
||||||
|
[action.payload.data.id]: false,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
[ReduxActionErrorTypes.UPDATE_ACTION_ERROR]: (
|
[ReduxActionErrorTypes.UPDATE_ACTION_ERROR]: (
|
||||||
state: ApiPaneReduxState,
|
state: ApiPaneReduxState,
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import {
|
||||||
} from "constants/ReduxActionConstants";
|
} from "constants/ReduxActionConstants";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { RestAction } from "entities/Action";
|
import { RestAction } from "entities/Action";
|
||||||
|
import { ActionResponse } from "api/ActionAPI";
|
||||||
|
|
||||||
const initialState: QueryPaneReduxState = {
|
const initialState: QueryPaneReduxState = {
|
||||||
isFetching: false,
|
isFetching: false,
|
||||||
|
|
@ -115,15 +116,15 @@ const queryPaneReducer = createReducer(initialState, {
|
||||||
[action.payload.id]: false,
|
[action.payload.id]: false,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
[ReduxActionTypes.EXECUTE_QUERY_REQUEST]: (
|
[ReduxActionTypes.RUN_ACTION_REQUEST]: (
|
||||||
state: any,
|
state: any,
|
||||||
action: ReduxAction<{ action: RestAction; actionId: string }>,
|
action: ReduxAction<{ id: string }>,
|
||||||
) => {
|
) => {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
isRunning: {
|
isRunning: {
|
||||||
...state.isRunning,
|
...state.isRunning,
|
||||||
[action.payload.actionId]: true,
|
[action.payload.id]: true,
|
||||||
},
|
},
|
||||||
runQuerySuccessData: [],
|
runQuerySuccessData: [],
|
||||||
};
|
};
|
||||||
|
|
@ -133,40 +134,39 @@ const queryPaneReducer = createReducer(initialState, {
|
||||||
runQuerySuccessData: [],
|
runQuerySuccessData: [],
|
||||||
}),
|
}),
|
||||||
|
|
||||||
[ReduxActionTypes.RUN_QUERY_SUCCESS]: (
|
[ReduxActionTypes.RUN_ACTION_SUCCESS]: (
|
||||||
state: any,
|
state: any,
|
||||||
action: ReduxAction<{ actionId: string; data: object }>,
|
action: ReduxAction<{ [id: string]: ActionResponse }>,
|
||||||
) => {
|
) => {
|
||||||
const { actionId } = action.payload;
|
const actionId = Object.keys(action.payload)[0];
|
||||||
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
isRunning: {
|
|
||||||
...state.isRunning,
|
|
||||||
[action.payload.actionId]: false,
|
|
||||||
},
|
|
||||||
runQuerySuccessData: {
|
|
||||||
...state.runQuerySuccessData,
|
|
||||||
[action.payload.actionId]: action.payload.data,
|
|
||||||
},
|
|
||||||
runErrorMessage: _.omit(state.runErrorMessage, [actionId]),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
[ReduxActionErrorTypes.RUN_QUERY_ERROR]: (
|
|
||||||
state: any,
|
|
||||||
action: ReduxAction<{ actionId: string; message: string }>,
|
|
||||||
) => {
|
|
||||||
const { actionId, message } = action.payload;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
isRunning: {
|
isRunning: {
|
||||||
...state.isRunning,
|
...state.isRunning,
|
||||||
[actionId]: false,
|
[actionId]: false,
|
||||||
},
|
},
|
||||||
|
runQuerySuccessData: {
|
||||||
|
...state.runQuerySuccessData,
|
||||||
|
...action.payload,
|
||||||
|
},
|
||||||
|
runErrorMessage: _.omit(state.runErrorMessage, [actionId]),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
[ReduxActionErrorTypes.RUN_ACTION_ERROR]: (
|
||||||
|
state: any,
|
||||||
|
action: ReduxAction<{ id: string; error: Error }>,
|
||||||
|
) => {
|
||||||
|
const { id, error } = action.payload;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
isRunning: {
|
||||||
|
...state.isRunning,
|
||||||
|
[id]: false,
|
||||||
|
},
|
||||||
runErrorMessage: {
|
runErrorMessage: {
|
||||||
...state.runError,
|
...state.runError,
|
||||||
[actionId]: message,
|
[id]: error.message,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
|
||||||
454
app/client/src/sagas/ActionExecutionSagas.ts
Normal file
454
app/client/src/sagas/ActionExecutionSagas.ts
Normal file
|
|
@ -0,0 +1,454 @@
|
||||||
|
import {
|
||||||
|
ReduxAction,
|
||||||
|
ReduxActionErrorTypes,
|
||||||
|
ReduxActionTypes,
|
||||||
|
} from "constants/ReduxActionConstants";
|
||||||
|
import {
|
||||||
|
EventType,
|
||||||
|
ExecuteActionPayload,
|
||||||
|
ExecuteActionPayloadEvent,
|
||||||
|
PageAction,
|
||||||
|
} from "constants/ActionConstants";
|
||||||
|
import * as log from "loglevel";
|
||||||
|
import {
|
||||||
|
all,
|
||||||
|
call,
|
||||||
|
put,
|
||||||
|
select,
|
||||||
|
take,
|
||||||
|
takeEvery,
|
||||||
|
takeLatest,
|
||||||
|
} from "redux-saga/effects";
|
||||||
|
import { evaluateDataTree } from "selectors/dataTreeSelectors";
|
||||||
|
import {
|
||||||
|
getDynamicBindings,
|
||||||
|
getDynamicValue,
|
||||||
|
isDynamicValue,
|
||||||
|
} from "utils/DynamicBindingUtils";
|
||||||
|
import {
|
||||||
|
ActionDescription,
|
||||||
|
RunActionPayload,
|
||||||
|
} from "entities/DataTree/dataTreeFactory";
|
||||||
|
import { AppToaster } from "components/editorComponents/ToastComponent";
|
||||||
|
import { executeAction, executeActionError } from "actions/widgetActions";
|
||||||
|
import {
|
||||||
|
getCurrentApplicationId,
|
||||||
|
getPageList,
|
||||||
|
} from "selectors/editorSelectors";
|
||||||
|
import _ from "lodash";
|
||||||
|
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||||
|
import history from "utils/history";
|
||||||
|
import {
|
||||||
|
BUILDER_PAGE_URL,
|
||||||
|
getApplicationViewerPageURL,
|
||||||
|
} from "constants/routes";
|
||||||
|
import {
|
||||||
|
executeApiActionRequest,
|
||||||
|
executeApiActionSuccess,
|
||||||
|
updateAction,
|
||||||
|
} from "actions/actionActions";
|
||||||
|
import { Action, RestAction } from "entities/Action";
|
||||||
|
import ActionAPI, {
|
||||||
|
ActionApiResponse,
|
||||||
|
ActionResponse,
|
||||||
|
ExecuteActionRequest,
|
||||||
|
PaginationField,
|
||||||
|
Property,
|
||||||
|
} from "api/ActionAPI";
|
||||||
|
import {
|
||||||
|
getAction,
|
||||||
|
getCurrentPageNameByActionId,
|
||||||
|
isActionDirty,
|
||||||
|
isActionSaving,
|
||||||
|
} from "selectors/entitiesSelector";
|
||||||
|
import { AppState } from "reducers";
|
||||||
|
import { mapToPropList } from "utils/AppsmithUtils";
|
||||||
|
import { validateResponse } from "sagas/ErrorSagas";
|
||||||
|
import { ToastType } from "react-toastify";
|
||||||
|
import { PLUGIN_TYPE_API } from "constants/ApiEditorConstants";
|
||||||
|
|
||||||
|
function* navigateActionSaga(
|
||||||
|
action: { pageNameOrUrl: string; params: Record<string, string> },
|
||||||
|
event: ExecuteActionPayloadEvent,
|
||||||
|
) {
|
||||||
|
const pageList = yield select(getPageList);
|
||||||
|
const applicationId = yield select(getCurrentApplicationId);
|
||||||
|
const page = _.find(pageList, { pageName: action.pageNameOrUrl });
|
||||||
|
if (page) {
|
||||||
|
AnalyticsUtil.logEvent("NAVIGATE", {
|
||||||
|
pageName: action.pageNameOrUrl,
|
||||||
|
pageParams: action.params,
|
||||||
|
});
|
||||||
|
// TODO need to make this check via RENDER_MODE;
|
||||||
|
const path =
|
||||||
|
history.location.pathname.indexOf("/edit") !== -1
|
||||||
|
? BUILDER_PAGE_URL(applicationId, page.pageId, action.params)
|
||||||
|
: getApplicationViewerPageURL(
|
||||||
|
applicationId,
|
||||||
|
page.pageId,
|
||||||
|
action.params,
|
||||||
|
);
|
||||||
|
history.push(path);
|
||||||
|
if (event.callback) event.callback({ success: true });
|
||||||
|
} else {
|
||||||
|
AnalyticsUtil.logEvent("NAVIGATE", {
|
||||||
|
navUrl: action.pageNameOrUrl,
|
||||||
|
});
|
||||||
|
// Add a default protocol if it doesn't exist.
|
||||||
|
let url = action.pageNameOrUrl;
|
||||||
|
if (url.indexOf("://") === -1) {
|
||||||
|
url = "https://" + url;
|
||||||
|
}
|
||||||
|
window.location.assign(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getActionTimeout = (
|
||||||
|
state: AppState,
|
||||||
|
actionId: string,
|
||||||
|
): number | undefined => {
|
||||||
|
const action = _.find(state.entities.actions, a => a.config.id === actionId);
|
||||||
|
if (action) {
|
||||||
|
const timeout = action.config.actionConfiguration.timeoutInMillisecond;
|
||||||
|
if (timeout) {
|
||||||
|
// Extra timeout padding to account for network calls
|
||||||
|
return timeout + 5000;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
const createActionExecutionResponse = (
|
||||||
|
response: ActionApiResponse,
|
||||||
|
): ActionResponse => ({
|
||||||
|
...response.data,
|
||||||
|
...response.clientMeta,
|
||||||
|
});
|
||||||
|
const isErrorResponse = (response: ActionApiResponse) => {
|
||||||
|
return !response.data.isExecutionSuccess;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function* evaluateDynamicBoundValueSaga(path: string): any {
|
||||||
|
log.debug("Evaluating data tree to get action binding value");
|
||||||
|
const tree = yield select(evaluateDataTree(false));
|
||||||
|
const dynamicResult = getDynamicValue(`{{${path}}}`, tree);
|
||||||
|
return dynamicResult.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* getActionParams(jsonPathKeys: string[] | undefined) {
|
||||||
|
if (_.isNil(jsonPathKeys)) return [];
|
||||||
|
const values: any = yield all(
|
||||||
|
jsonPathKeys.map((jsonPath: string) => {
|
||||||
|
return call(evaluateDynamicBoundValueSaga, jsonPath);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const dynamicBindings: Record<string, string> = {};
|
||||||
|
jsonPathKeys.forEach((key, i) => {
|
||||||
|
let value = values[i];
|
||||||
|
if (typeof value === "object") value = JSON.stringify(value);
|
||||||
|
dynamicBindings[key] = value;
|
||||||
|
});
|
||||||
|
return mapToPropList(dynamicBindings);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function extractBindingsFromAction(action: Action) {
|
||||||
|
const bindings: string[] = [];
|
||||||
|
action.dynamicBindingPathList.forEach(a => {
|
||||||
|
const value = _.get(action, a.key);
|
||||||
|
if (isDynamicValue(value)) {
|
||||||
|
const { jsSnippets } = getDynamicBindings(value);
|
||||||
|
bindings.push(...jsSnippets.filter(jsSnippet => !!jsSnippet));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return bindings;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* executeActionSaga(
|
||||||
|
apiAction: RunActionPayload,
|
||||||
|
event: ExecuteActionPayloadEvent,
|
||||||
|
) {
|
||||||
|
const { actionId, onSuccess, onError } = apiAction;
|
||||||
|
try {
|
||||||
|
yield put(executeApiActionRequest({ id: apiAction.actionId }));
|
||||||
|
const api: RestAction = yield select(getAction, actionId);
|
||||||
|
const params: Property[] = yield call(getActionParams, api.jsonPathKeys);
|
||||||
|
const pagination =
|
||||||
|
event.type === EventType.ON_NEXT_PAGE
|
||||||
|
? "NEXT"
|
||||||
|
: event.type === EventType.ON_PREV_PAGE
|
||||||
|
? "PREV"
|
||||||
|
: undefined;
|
||||||
|
const executeActionRequest: ExecuteActionRequest = {
|
||||||
|
action: { id: actionId },
|
||||||
|
params,
|
||||||
|
paginationField: pagination,
|
||||||
|
};
|
||||||
|
const timeout = yield select(getActionTimeout, actionId);
|
||||||
|
const response: ActionApiResponse = yield ActionAPI.executeAction(
|
||||||
|
executeActionRequest,
|
||||||
|
timeout,
|
||||||
|
);
|
||||||
|
const payload = createActionExecutionResponse(response);
|
||||||
|
yield put(
|
||||||
|
executeApiActionSuccess({
|
||||||
|
id: actionId,
|
||||||
|
response: payload,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
if (isErrorResponse(response)) {
|
||||||
|
if (onError) {
|
||||||
|
yield put(
|
||||||
|
executeAction({
|
||||||
|
dynamicString: onError,
|
||||||
|
event: {
|
||||||
|
...event,
|
||||||
|
type: EventType.ON_ERROR,
|
||||||
|
},
|
||||||
|
responseData: payload,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (event.callback) {
|
||||||
|
event.callback({ success: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (onSuccess) {
|
||||||
|
yield put(
|
||||||
|
executeAction({
|
||||||
|
dynamicString: onSuccess,
|
||||||
|
event: {
|
||||||
|
...event,
|
||||||
|
type: EventType.ON_SUCCESS,
|
||||||
|
},
|
||||||
|
responseData: payload,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (event.callback) {
|
||||||
|
event.callback({ success: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
yield put(
|
||||||
|
executeActionError({
|
||||||
|
actionId: actionId,
|
||||||
|
error,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
AppToaster.show({
|
||||||
|
message: "Action execution failed",
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
|
if (onError) {
|
||||||
|
yield put(
|
||||||
|
executeAction({
|
||||||
|
dynamicString: `{{${onError}}}`,
|
||||||
|
event: {
|
||||||
|
...event,
|
||||||
|
type: EventType.ON_ERROR,
|
||||||
|
},
|
||||||
|
responseData: {},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (event.callback) {
|
||||||
|
event.callback({ success: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function* executeActionTriggers(
|
||||||
|
trigger: ActionDescription<any>,
|
||||||
|
event: ExecuteActionPayloadEvent,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
switch (trigger.type) {
|
||||||
|
case "RUN_ACTION":
|
||||||
|
yield call(executeActionSaga, trigger.payload, event);
|
||||||
|
break;
|
||||||
|
case "NAVIGATE_TO":
|
||||||
|
yield call(navigateActionSaga, trigger.payload, event);
|
||||||
|
break;
|
||||||
|
case "SHOW_ALERT":
|
||||||
|
AppToaster.show({
|
||||||
|
message: trigger.payload.message,
|
||||||
|
type: trigger.payload.style,
|
||||||
|
});
|
||||||
|
if (event.callback) event.callback({ success: true });
|
||||||
|
break;
|
||||||
|
case "SHOW_MODAL_BY_NAME":
|
||||||
|
yield put(trigger);
|
||||||
|
if (event.callback) event.callback({ success: true });
|
||||||
|
break;
|
||||||
|
case "CLOSE_MODAL":
|
||||||
|
yield put(trigger);
|
||||||
|
if (event.callback) event.callback({ success: true });
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
yield put(
|
||||||
|
executeActionError({
|
||||||
|
error: "Trigger type unknown",
|
||||||
|
actionId: "",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
yield put(
|
||||||
|
executeActionError({
|
||||||
|
error: "Failed to execute action",
|
||||||
|
actionId: "",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
if (event.callback) event.callback({ success: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function* executeAppAction(action: ReduxAction<ExecuteActionPayload>) {
|
||||||
|
const { dynamicString, event, responseData } = action.payload;
|
||||||
|
log.debug("Evaluating data tree to get action trigger");
|
||||||
|
log.debug({ dynamicString });
|
||||||
|
const tree = yield select(evaluateDataTree(true));
|
||||||
|
log.debug({ tree });
|
||||||
|
const { triggers } = getDynamicValue(dynamicString, tree, responseData, true);
|
||||||
|
log.debug({ triggers });
|
||||||
|
if (triggers && triggers.length) {
|
||||||
|
yield all(
|
||||||
|
triggers.map(trigger => call(executeActionTriggers, trigger, event)),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (event.callback) event.callback({ success: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function* runActionSaga(
|
||||||
|
reduxAction: ReduxAction<{
|
||||||
|
id: string;
|
||||||
|
paginationField: PaginationField;
|
||||||
|
}>,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const actionId = reduxAction.payload.id;
|
||||||
|
const isSaving = yield select(isActionSaving(actionId));
|
||||||
|
const isDirty = yield select(isActionDirty(actionId));
|
||||||
|
if (isSaving || isDirty) {
|
||||||
|
if (isDirty && !isSaving) {
|
||||||
|
const actionObject = yield select(getAction, actionId);
|
||||||
|
yield put(updateAction({ data: actionObject }));
|
||||||
|
}
|
||||||
|
yield take(ReduxActionTypes.UPDATE_ACTION_SUCCESS);
|
||||||
|
}
|
||||||
|
const actionObject = yield select(getAction, actionId);
|
||||||
|
const action: ExecuteActionRequest["action"] = { id: actionId };
|
||||||
|
const jsonPathKeys = actionObject.jsonPathKeys;
|
||||||
|
|
||||||
|
const { paginationField } = reduxAction.payload;
|
||||||
|
|
||||||
|
const params = yield call(getActionParams, jsonPathKeys);
|
||||||
|
const timeout = yield select(getActionTimeout, actionId);
|
||||||
|
const response: ActionApiResponse = yield ActionAPI.executeAction(
|
||||||
|
{
|
||||||
|
action,
|
||||||
|
params,
|
||||||
|
paginationField,
|
||||||
|
},
|
||||||
|
timeout,
|
||||||
|
);
|
||||||
|
const isValidResponse = yield validateResponse(response);
|
||||||
|
|
||||||
|
if (isValidResponse) {
|
||||||
|
const payload = createActionExecutionResponse(response);
|
||||||
|
|
||||||
|
const pageName = yield select(getCurrentPageNameByActionId, actionId);
|
||||||
|
const eventName =
|
||||||
|
actionObject.pluginType === PLUGIN_TYPE_API ? "RUN_API" : "RUN_QUERY";
|
||||||
|
|
||||||
|
AnalyticsUtil.logEvent(eventName, {
|
||||||
|
actionId,
|
||||||
|
actionName: actionObject.name,
|
||||||
|
pageName: pageName,
|
||||||
|
responseTime: response.clientMeta.duration,
|
||||||
|
apiType: "INTERNAL",
|
||||||
|
});
|
||||||
|
|
||||||
|
yield put({
|
||||||
|
type: ReduxActionTypes.RUN_ACTION_SUCCESS,
|
||||||
|
payload: { [actionId]: payload },
|
||||||
|
});
|
||||||
|
AppToaster.show({
|
||||||
|
message: "Action ran successfully",
|
||||||
|
type: ToastType.SUCCESS,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let error = "An unexpected error occurred";
|
||||||
|
if (response.data.body) {
|
||||||
|
error = response.data.body.toString();
|
||||||
|
}
|
||||||
|
yield put({
|
||||||
|
type: ReduxActionErrorTypes.RUN_ACTION_ERROR,
|
||||||
|
payload: { error, id: reduxAction.payload.id },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
yield put({
|
||||||
|
type: ReduxActionErrorTypes.RUN_ACTION_ERROR,
|
||||||
|
payload: { error, id: reduxAction.payload.id },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function* executePageLoadAction(pageAction: PageAction) {
|
||||||
|
yield put(executeApiActionRequest({ id: pageAction.id }));
|
||||||
|
const params: Property[] = yield call(
|
||||||
|
getActionParams,
|
||||||
|
pageAction.jsonPathKeys,
|
||||||
|
);
|
||||||
|
const executeActionRequest: ExecuteActionRequest = {
|
||||||
|
action: { id: pageAction.id },
|
||||||
|
params,
|
||||||
|
};
|
||||||
|
const response: ActionApiResponse = yield ActionAPI.executeAction(
|
||||||
|
executeActionRequest,
|
||||||
|
pageAction.timeoutInMillisecond,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isErrorResponse(response)) {
|
||||||
|
yield put(
|
||||||
|
executeActionError({
|
||||||
|
actionId: pageAction.id,
|
||||||
|
error: response.responseMeta.error,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const payload = createActionExecutionResponse(response);
|
||||||
|
yield put(
|
||||||
|
executeApiActionSuccess({
|
||||||
|
id: pageAction.id,
|
||||||
|
response: payload,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function* executePageLoadActionsSaga(action: ReduxAction<PageAction[][]>) {
|
||||||
|
const pageActions = action.payload;
|
||||||
|
for (const actionSet of pageActions) {
|
||||||
|
// Load all sets in parallel
|
||||||
|
yield* yield all(actionSet.map(a => call(executePageLoadAction, a)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* watchActionExecutionSagas() {
|
||||||
|
yield all([
|
||||||
|
takeEvery(ReduxActionTypes.EXECUTE_ACTION, executeAppAction),
|
||||||
|
takeLatest(ReduxActionTypes.RUN_ACTION_REQUEST, runActionSaga),
|
||||||
|
takeLatest(
|
||||||
|
ReduxActionTypes.EXECUTE_PAGE_LOAD_ACTIONS,
|
||||||
|
executePageLoadActionsSaga,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
@ -11,23 +11,8 @@ import {
|
||||||
takeEvery,
|
takeEvery,
|
||||||
takeLatest,
|
takeLatest,
|
||||||
} from "redux-saga/effects";
|
} from "redux-saga/effects";
|
||||||
import {
|
import ActionAPI, { ActionCreateUpdateResponse, Property } from "api/ActionAPI";
|
||||||
EventType,
|
|
||||||
ExecuteActionPayload,
|
|
||||||
ExecuteActionPayloadEvent,
|
|
||||||
PageAction,
|
|
||||||
} from "constants/ActionConstants";
|
|
||||||
import ActionAPI, {
|
|
||||||
ActionApiResponse,
|
|
||||||
ActionCreateUpdateResponse,
|
|
||||||
ActionResponse,
|
|
||||||
ExecuteActionRequest,
|
|
||||||
PaginationField,
|
|
||||||
Property,
|
|
||||||
} from "api/ActionAPI";
|
|
||||||
import { AppState } from "reducers";
|
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { mapToPropList } from "utils/AppsmithUtils";
|
|
||||||
import { AppToaster } from "components/editorComponents/ToastComponent";
|
import { AppToaster } from "components/editorComponents/ToastComponent";
|
||||||
import { GenericApiResponse } from "api/ApiResponses";
|
import { GenericApiResponse } from "api/ApiResponses";
|
||||||
import PageApi from "api/PageApi";
|
import PageApi from "api/PageApi";
|
||||||
|
|
@ -37,366 +22,32 @@ import {
|
||||||
copyActionSuccess,
|
copyActionSuccess,
|
||||||
createActionSuccess,
|
createActionSuccess,
|
||||||
deleteActionSuccess,
|
deleteActionSuccess,
|
||||||
executeApiActionRequest,
|
fetchActionsForPage,
|
||||||
executeApiActionSuccess,
|
|
||||||
fetchActionsForPageSuccess,
|
fetchActionsForPageSuccess,
|
||||||
FetchActionsPayload,
|
FetchActionsPayload,
|
||||||
moveActionError,
|
moveActionError,
|
||||||
moveActionSuccess,
|
moveActionSuccess,
|
||||||
|
SetActionPropertyPayload,
|
||||||
|
updateActionProperty,
|
||||||
updateActionSuccess,
|
updateActionSuccess,
|
||||||
fetchActionsForPage,
|
|
||||||
} from "actions/actionActions";
|
} from "actions/actionActions";
|
||||||
import {
|
import {
|
||||||
getDynamicBindings,
|
|
||||||
getDynamicValue,
|
|
||||||
isDynamicValue,
|
isDynamicValue,
|
||||||
removeBindingsFromObject,
|
removeBindingsFromActionObject,
|
||||||
} from "utils/DynamicBindingUtils";
|
} from "utils/DynamicBindingUtils";
|
||||||
import { validateResponse } from "./ErrorSagas";
|
import { validateResponse } from "./ErrorSagas";
|
||||||
import { getFormData } from "selectors/formSelectors";
|
|
||||||
import { API_EDITOR_FORM_NAME } from "constants/forms";
|
|
||||||
import { executeAction, executeActionError } from "actions/widgetActions";
|
|
||||||
import { evaluateDataTree } from "selectors/dataTreeSelectors";
|
|
||||||
import { transformRestAction } from "transformers/RestActionTransformer";
|
import { transformRestAction } from "transformers/RestActionTransformer";
|
||||||
import {
|
import { getCurrentPageId } from "selectors/editorSelectors";
|
||||||
ActionDescription,
|
|
||||||
RunActionPayload,
|
|
||||||
} from "entities/DataTree/dataTreeFactory";
|
|
||||||
import {
|
|
||||||
getCurrentApplicationId,
|
|
||||||
getPageList,
|
|
||||||
getCurrentPageId,
|
|
||||||
} from "selectors/editorSelectors";
|
|
||||||
import history from "utils/history";
|
|
||||||
import {
|
|
||||||
BUILDER_PAGE_URL,
|
|
||||||
getApplicationViewerPageURL,
|
|
||||||
} from "constants/routes";
|
|
||||||
import { ToastType } from "react-toastify";
|
import { ToastType } from "react-toastify";
|
||||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||||
import * as log from "loglevel";
|
|
||||||
import { QUERY_CONSTANT } from "constants/QueryEditorConstants";
|
import { QUERY_CONSTANT } from "constants/QueryEditorConstants";
|
||||||
import { Action, RestAction } from "entities/Action";
|
import { Action, RestAction } from "entities/Action";
|
||||||
import { ActionData } from "reducers/entityReducers/actionsReducer";
|
import { ActionData } from "reducers/entityReducers/actionsReducer";
|
||||||
import { getActions } from "selectors/entitiesSelector";
|
import {
|
||||||
|
getAction,
|
||||||
export const getAction = (
|
getCurrentPageNameByActionId,
|
||||||
state: AppState,
|
getPageNameByPageId,
|
||||||
actionId: string,
|
} from "selectors/entitiesSelector";
|
||||||
): RestAction | undefined => {
|
|
||||||
const action = _.find(state.entities.actions, a => a.config.id === actionId);
|
|
||||||
return action ? action.config : undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getActionTimeout = (
|
|
||||||
state: AppState,
|
|
||||||
actionId: string,
|
|
||||||
): number | undefined => {
|
|
||||||
const action = _.find(state.entities.actions, a => a.config.id === actionId);
|
|
||||||
if (action) {
|
|
||||||
const timeout = action.config.actionConfiguration.timeoutInMillisecond;
|
|
||||||
if (timeout) {
|
|
||||||
// Extra timeout padding to account for network calls
|
|
||||||
return timeout + 5000;
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
const createActionSuccessResponse = (
|
|
||||||
response: ActionApiResponse,
|
|
||||||
): ActionResponse => ({
|
|
||||||
...response.data,
|
|
||||||
...response.clientMeta,
|
|
||||||
});
|
|
||||||
|
|
||||||
const isErrorResponse = (response: ActionApiResponse) => {
|
|
||||||
return (
|
|
||||||
(response.responseMeta && response.responseMeta.error) ||
|
|
||||||
!response.data.isExecutionSuccess
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
function getCurrentPageNameByActionId(
|
|
||||||
state: AppState,
|
|
||||||
actionId: string,
|
|
||||||
): string {
|
|
||||||
const action = state.entities.actions.find(action => {
|
|
||||||
return action.config.id === actionId;
|
|
||||||
});
|
|
||||||
const pageId = action ? action.config.pageId : "";
|
|
||||||
return getPageNameByPageId(state, pageId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPageNameByPageId(state: AppState, pageId: string): string {
|
|
||||||
const page = state.entities.pageList.pages.find(
|
|
||||||
page => page.pageId === pageId,
|
|
||||||
);
|
|
||||||
return page ? page.pageName : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
const createActionErrorResponse = (
|
|
||||||
response: ActionApiResponse,
|
|
||||||
): ActionResponse => ({
|
|
||||||
body: response.responseMeta.error || { error: "Error" },
|
|
||||||
statusCode: response.responseMeta.error
|
|
||||||
? response.responseMeta.error.code.toString()
|
|
||||||
: "Error",
|
|
||||||
headers: {},
|
|
||||||
request: {
|
|
||||||
headers: {},
|
|
||||||
body: {},
|
|
||||||
httpMethod: "",
|
|
||||||
url: "",
|
|
||||||
},
|
|
||||||
duration: "0",
|
|
||||||
size: "0",
|
|
||||||
});
|
|
||||||
|
|
||||||
export function* evaluateDynamicBoundValueSaga(path: string): any {
|
|
||||||
log.debug("Evaluating data tree to get action binding value");
|
|
||||||
const tree = yield select(evaluateDataTree(true));
|
|
||||||
const dynamicResult = getDynamicValue(`{{${path}}}`, tree);
|
|
||||||
return dynamicResult.result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function* getActionParams(jsonPathKeys: string[] | undefined) {
|
|
||||||
if (_.isNil(jsonPathKeys)) return [];
|
|
||||||
const values: any = yield all(
|
|
||||||
jsonPathKeys.map((jsonPath: string) => {
|
|
||||||
return call(evaluateDynamicBoundValueSaga, jsonPath);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
const dynamicBindings: Record<string, string> = {};
|
|
||||||
jsonPathKeys.forEach((key, i) => {
|
|
||||||
let value = values[i];
|
|
||||||
if (typeof value === "object") value = JSON.stringify(value);
|
|
||||||
dynamicBindings[key] = value;
|
|
||||||
});
|
|
||||||
return mapToPropList(dynamicBindings);
|
|
||||||
}
|
|
||||||
|
|
||||||
// function* executeJSActionSaga(jsAction: ExecuteJSActionPayload) {
|
|
||||||
// const tree = yield select(getParsedDataTree);
|
|
||||||
// const result = JSExecutionManagerSingleton.evaluateSync(
|
|
||||||
// jsAction.jsFunction,
|
|
||||||
// tree,
|
|
||||||
// );
|
|
||||||
//
|
|
||||||
// yield put({
|
|
||||||
// type: ReduxActionTypes.SAVE_JS_EXECUTION_RECORD,
|
|
||||||
// payload: {
|
|
||||||
// [jsAction.jsFunctionId]: result,
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
export function* executeActionSaga(
|
|
||||||
apiAction: RunActionPayload,
|
|
||||||
event: ExecuteActionPayloadEvent,
|
|
||||||
) {
|
|
||||||
const { actionId, onSuccess, onError } = apiAction;
|
|
||||||
try {
|
|
||||||
yield put(executeApiActionRequest({ id: apiAction.actionId }));
|
|
||||||
const api: RestAction = yield select(getAction, actionId);
|
|
||||||
const params: Property[] = yield call(getActionParams, api.jsonPathKeys);
|
|
||||||
const pagination =
|
|
||||||
event.type === EventType.ON_NEXT_PAGE
|
|
||||||
? "NEXT"
|
|
||||||
: event.type === EventType.ON_PREV_PAGE
|
|
||||||
? "PREV"
|
|
||||||
: undefined;
|
|
||||||
const executeActionRequest: ExecuteActionRequest = {
|
|
||||||
action: { id: actionId },
|
|
||||||
params,
|
|
||||||
paginationField: pagination,
|
|
||||||
};
|
|
||||||
const timeout = yield select(getActionTimeout, actionId);
|
|
||||||
const response: ActionApiResponse = yield ActionAPI.executeAction(
|
|
||||||
executeActionRequest,
|
|
||||||
timeout,
|
|
||||||
);
|
|
||||||
if (isErrorResponse(response)) {
|
|
||||||
const payload = createActionErrorResponse(response);
|
|
||||||
if (_.isNil(response.responseMeta.error)) {
|
|
||||||
AppToaster.show({
|
|
||||||
message: api.name + " execution failed",
|
|
||||||
type: "error",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (onError) {
|
|
||||||
yield put(
|
|
||||||
executeAction({
|
|
||||||
dynamicString: onError,
|
|
||||||
event: {
|
|
||||||
...event,
|
|
||||||
type: EventType.ON_ERROR,
|
|
||||||
},
|
|
||||||
responseData: payload,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
if (event.callback) {
|
|
||||||
event.callback({ success: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
yield put(
|
|
||||||
executeActionError({
|
|
||||||
actionId,
|
|
||||||
error: response.responseMeta.error,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const payload = createActionSuccessResponse(response);
|
|
||||||
yield put(
|
|
||||||
executeApiActionSuccess({
|
|
||||||
id: apiAction.actionId,
|
|
||||||
response: payload,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
if (onSuccess) {
|
|
||||||
yield put(
|
|
||||||
executeAction({
|
|
||||||
dynamicString: onSuccess,
|
|
||||||
event: {
|
|
||||||
...event,
|
|
||||||
type: EventType.ON_SUCCESS,
|
|
||||||
},
|
|
||||||
responseData: payload,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
if (event.callback) {
|
|
||||||
event.callback({ success: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
} catch (error) {
|
|
||||||
yield put(
|
|
||||||
executeActionError({
|
|
||||||
actionId: actionId,
|
|
||||||
error,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
if (onError) {
|
|
||||||
yield put(
|
|
||||||
executeAction({
|
|
||||||
dynamicString: `{{${onError}}}`,
|
|
||||||
event: {
|
|
||||||
...event,
|
|
||||||
type: EventType.ON_ERROR,
|
|
||||||
},
|
|
||||||
responseData: {},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
if (event.callback) {
|
|
||||||
event.callback({ success: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function* navigateActionSaga(
|
|
||||||
action: { pageNameOrUrl: string; params: Record<string, string> },
|
|
||||||
event: ExecuteActionPayloadEvent,
|
|
||||||
) {
|
|
||||||
const pageList = yield select(getPageList);
|
|
||||||
const applicationId = yield select(getCurrentApplicationId);
|
|
||||||
const page = _.find(pageList, { pageName: action.pageNameOrUrl });
|
|
||||||
if (page) {
|
|
||||||
AnalyticsUtil.logEvent("NAVIGATE", {
|
|
||||||
pageName: action.pageNameOrUrl,
|
|
||||||
pageParams: action.params,
|
|
||||||
});
|
|
||||||
// TODO need to make this check via RENDER_MODE;
|
|
||||||
const path =
|
|
||||||
history.location.pathname.indexOf("/edit") !== -1
|
|
||||||
? BUILDER_PAGE_URL(applicationId, page.pageId, action.params)
|
|
||||||
: getApplicationViewerPageURL(
|
|
||||||
applicationId,
|
|
||||||
page.pageId,
|
|
||||||
action.params,
|
|
||||||
);
|
|
||||||
history.push(path);
|
|
||||||
if (event.callback) event.callback({ success: true });
|
|
||||||
} else {
|
|
||||||
AnalyticsUtil.logEvent("NAVIGATE", {
|
|
||||||
navUrl: action.pageNameOrUrl,
|
|
||||||
});
|
|
||||||
// Add a default protocol if it doesn't exist.
|
|
||||||
let url = action.pageNameOrUrl;
|
|
||||||
if (url.indexOf("://") === -1) {
|
|
||||||
url = "https://" + url;
|
|
||||||
}
|
|
||||||
window.location.assign(url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function* executeActionTriggers(
|
|
||||||
trigger: ActionDescription<any>,
|
|
||||||
event: ExecuteActionPayloadEvent,
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
switch (trigger.type) {
|
|
||||||
case "RUN_ACTION":
|
|
||||||
yield call(executeActionSaga, trigger.payload, event);
|
|
||||||
break;
|
|
||||||
case "NAVIGATE_TO":
|
|
||||||
yield call(navigateActionSaga, trigger.payload, event);
|
|
||||||
break;
|
|
||||||
case "SHOW_ALERT":
|
|
||||||
AppToaster.show({
|
|
||||||
message: trigger.payload.message,
|
|
||||||
type: trigger.payload.style,
|
|
||||||
});
|
|
||||||
if (event.callback) event.callback({ success: true });
|
|
||||||
break;
|
|
||||||
case "SHOW_MODAL_BY_NAME":
|
|
||||||
yield put(trigger);
|
|
||||||
if (event.callback) event.callback({ success: true });
|
|
||||||
break;
|
|
||||||
case "CLOSE_MODAL":
|
|
||||||
yield put(trigger);
|
|
||||||
if (event.callback) event.callback({ success: true });
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
yield put(
|
|
||||||
executeActionError({
|
|
||||||
error: "Trigger type unknown",
|
|
||||||
actionId: "",
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
yield put(
|
|
||||||
executeActionError({
|
|
||||||
error: "Failed to execute action",
|
|
||||||
actionId: "",
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
if (event.callback) event.callback({ success: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function* executeAppAction(action: ReduxAction<ExecuteActionPayload>) {
|
|
||||||
const { dynamicString, event, responseData } = action.payload;
|
|
||||||
log.debug("Evaluating data tree to get action trigger");
|
|
||||||
log.debug({ dynamicString });
|
|
||||||
const tree = yield select(evaluateDataTree(true));
|
|
||||||
log.debug({ tree });
|
|
||||||
const { triggers } = getDynamicValue(dynamicString, tree, responseData, true);
|
|
||||||
log.debug({ triggers });
|
|
||||||
if (triggers && triggers.length) {
|
|
||||||
yield all(
|
|
||||||
triggers.map(trigger => call(executeActionTriggers, trigger, event)),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
if (event.callback) event.callback({ success: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function* createActionSaga(actionPayload: ReduxAction<RestAction>) {
|
export function* createActionSaga(actionPayload: ReduxAction<RestAction>) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -555,126 +206,6 @@ export function* deleteActionSaga(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function extractBindingsFromAction(action: Action) {
|
|
||||||
const bindings: string[] = [];
|
|
||||||
action.dynamicBindingPathList.forEach(a => {
|
|
||||||
const value = _.get(action, a.key);
|
|
||||||
if (isDynamicValue(value)) {
|
|
||||||
const { jsSnippets } = getDynamicBindings(value);
|
|
||||||
bindings.push(...jsSnippets.filter(jsSnippet => !!jsSnippet));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return bindings;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function* runApiActionSaga(
|
|
||||||
reduxAction: ReduxAction<{
|
|
||||||
id: string;
|
|
||||||
paginationField: PaginationField;
|
|
||||||
}>,
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
const {
|
|
||||||
values,
|
|
||||||
dirty,
|
|
||||||
valid,
|
|
||||||
}: {
|
|
||||||
values: RestAction;
|
|
||||||
dirty: boolean;
|
|
||||||
valid: boolean;
|
|
||||||
} = yield select(getFormData, API_EDITOR_FORM_NAME);
|
|
||||||
const actionObject: PageAction = yield select(getAction, values.id);
|
|
||||||
let action: ExecuteActionRequest["action"] = { id: values.id };
|
|
||||||
let jsonPathKeys = actionObject.jsonPathKeys;
|
|
||||||
if (!valid) {
|
|
||||||
console.error("Form error");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (dirty) {
|
|
||||||
action = _.omit(transformRestAction(values), "id") as RestAction;
|
|
||||||
jsonPathKeys = extractBindingsFromAction(action as RestAction);
|
|
||||||
}
|
|
||||||
const { paginationField } = reduxAction.payload;
|
|
||||||
|
|
||||||
const params = yield call(getActionParams, jsonPathKeys);
|
|
||||||
const timeout = yield select(getActionTimeout, values.id);
|
|
||||||
const response: ActionApiResponse = yield ActionAPI.executeAction(
|
|
||||||
{
|
|
||||||
action,
|
|
||||||
params,
|
|
||||||
paginationField,
|
|
||||||
},
|
|
||||||
timeout,
|
|
||||||
);
|
|
||||||
let payload = createActionSuccessResponse(response);
|
|
||||||
if (response.responseMeta && response.responseMeta.error) {
|
|
||||||
payload = createActionErrorResponse(response);
|
|
||||||
}
|
|
||||||
const id = values.id || "DRY_RUN";
|
|
||||||
|
|
||||||
const pageName = yield select(getCurrentPageNameByActionId, values.id);
|
|
||||||
|
|
||||||
AnalyticsUtil.logEvent("RUN_API", {
|
|
||||||
apiId: values.id,
|
|
||||||
apiName: values.name,
|
|
||||||
pageName: pageName,
|
|
||||||
responseTime: response.clientMeta.duration,
|
|
||||||
apiType: "INTERNAL",
|
|
||||||
});
|
|
||||||
|
|
||||||
yield put({
|
|
||||||
type: ReduxActionTypes.RUN_API_SUCCESS,
|
|
||||||
payload: { [id]: payload },
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
yield put({
|
|
||||||
type: ReduxActionErrorTypes.RUN_API_ERROR,
|
|
||||||
payload: { error, id: reduxAction.payload.id },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function* executePageLoadAction(pageAction: PageAction) {
|
|
||||||
yield put(executeApiActionRequest({ id: pageAction.id }));
|
|
||||||
const params: Property[] = yield call(
|
|
||||||
getActionParams,
|
|
||||||
pageAction.jsonPathKeys,
|
|
||||||
);
|
|
||||||
const executeActionRequest: ExecuteActionRequest = {
|
|
||||||
action: { id: pageAction.id },
|
|
||||||
params,
|
|
||||||
};
|
|
||||||
const response: ActionApiResponse = yield ActionAPI.executeAction(
|
|
||||||
executeActionRequest,
|
|
||||||
pageAction.timeoutInMillisecond,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isErrorResponse(response)) {
|
|
||||||
yield put(
|
|
||||||
executeActionError({
|
|
||||||
actionId: pageAction.id,
|
|
||||||
error: response.responseMeta.error,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const payload = createActionSuccessResponse(response);
|
|
||||||
yield put(
|
|
||||||
executeApiActionSuccess({
|
|
||||||
id: pageAction.id,
|
|
||||||
response: payload,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function* executePageLoadActionsSaga(action: ReduxAction<PageAction[][]>) {
|
|
||||||
const pageActions = action.payload;
|
|
||||||
for (const actionSet of pageActions) {
|
|
||||||
// Load all sets in parallel
|
|
||||||
yield* yield all(actionSet.map(a => call(executePageLoadAction, a)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function* moveActionSaga(
|
function* moveActionSaga(
|
||||||
action: ReduxAction<{
|
action: ReduxAction<{
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -683,12 +214,8 @@ function* moveActionSaga(
|
||||||
name: string;
|
name: string;
|
||||||
}>,
|
}>,
|
||||||
) {
|
) {
|
||||||
const drafts = yield select(state => state.ui.apiPane.drafts);
|
const actionObject: RestAction = yield select(getAction, action.payload.id);
|
||||||
const dirty = action.payload.id in drafts;
|
const withoutBindings = removeBindingsFromActionObject(actionObject);
|
||||||
const actionObject: RestAction = dirty
|
|
||||||
? drafts[action.payload.id]
|
|
||||||
: yield select(getAction, action.payload.id);
|
|
||||||
const withoutBindings = removeBindingsFromObject(actionObject);
|
|
||||||
try {
|
try {
|
||||||
const response = yield ActionAPI.moveAction({
|
const response = yield ActionAPI.moveAction({
|
||||||
action: {
|
action: {
|
||||||
|
|
@ -729,13 +256,9 @@ function* moveActionSaga(
|
||||||
function* copyActionSaga(
|
function* copyActionSaga(
|
||||||
action: ReduxAction<{ id: string; destinationPageId: string; name: string }>,
|
action: ReduxAction<{ id: string; destinationPageId: string; name: string }>,
|
||||||
) {
|
) {
|
||||||
const drafts = yield select(state => state.ui.apiPane.drafts);
|
let actionObject: RestAction = yield select(getAction, action.payload.id);
|
||||||
const dirty = action.payload.id in drafts;
|
|
||||||
let actionObject = dirty
|
|
||||||
? drafts[action.payload.id]
|
|
||||||
: yield select(getAction, action.payload.id);
|
|
||||||
if (action.payload.destinationPageId !== actionObject.pageId) {
|
if (action.payload.destinationPageId !== actionObject.pageId) {
|
||||||
actionObject = removeBindingsFromObject(actionObject);
|
actionObject = removeBindingsFromActionObject(actionObject);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const copyAction = {
|
const copyAction = {
|
||||||
|
|
@ -842,19 +365,56 @@ function* saveApiNameSaga(action: ReduxAction<{ id: string; name: string }>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDynamicBindingsChangesSaga(
|
||||||
|
action: Action,
|
||||||
|
value: string,
|
||||||
|
field: string,
|
||||||
|
) {
|
||||||
|
const bindingField = field.replace("actionConfiguration.", "");
|
||||||
|
const isDynamic = isDynamicValue(value);
|
||||||
|
let dynamicBindings: Property[] = action.dynamicBindingPathList || [];
|
||||||
|
const fieldExists = _.some(dynamicBindings, { key: bindingField });
|
||||||
|
|
||||||
|
if (!isDynamic && fieldExists) {
|
||||||
|
dynamicBindings = dynamicBindings.filter(d => d.key !== bindingField);
|
||||||
|
}
|
||||||
|
if (isDynamic && !fieldExists) {
|
||||||
|
dynamicBindings.push({ key: bindingField });
|
||||||
|
}
|
||||||
|
if (dynamicBindings !== action.dynamicBindingPathList) {
|
||||||
|
return dynamicBindings;
|
||||||
|
}
|
||||||
|
return action.dynamicBindingPathList;
|
||||||
|
}
|
||||||
|
|
||||||
|
function* setActionPropertySaga(action: ReduxAction<SetActionPropertyPayload>) {
|
||||||
|
const { actionId, value, propertyName } = action.payload;
|
||||||
|
if (!actionId) return;
|
||||||
|
const actionObj = yield select(getAction, actionId);
|
||||||
|
const effects: Record<string, any> = {};
|
||||||
|
// Value change effect
|
||||||
|
effects[propertyName] = value;
|
||||||
|
// Bindings change effect
|
||||||
|
effects.dynamicBindingPathList = getDynamicBindingsChangesSaga(
|
||||||
|
actionObj,
|
||||||
|
value,
|
||||||
|
propertyName,
|
||||||
|
);
|
||||||
|
yield all(
|
||||||
|
Object.keys(effects).map(field =>
|
||||||
|
put(updateActionProperty({ id: actionId, field, value: effects[field] })),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function* watchActionSagas() {
|
export function* watchActionSagas() {
|
||||||
yield all([
|
yield all([
|
||||||
|
takeEvery(ReduxActionTypes.SET_ACTION_PROPERTY, setActionPropertySaga),
|
||||||
takeEvery(ReduxActionTypes.FETCH_ACTIONS_INIT, fetchActionsSaga),
|
takeEvery(ReduxActionTypes.FETCH_ACTIONS_INIT, fetchActionsSaga),
|
||||||
takeEvery(ReduxActionTypes.EXECUTE_ACTION, executeAppAction),
|
|
||||||
takeLatest(ReduxActionTypes.RUN_API_REQUEST, runApiActionSaga),
|
|
||||||
takeEvery(ReduxActionTypes.CREATE_ACTION_INIT, createActionSaga),
|
takeEvery(ReduxActionTypes.CREATE_ACTION_INIT, createActionSaga),
|
||||||
takeLatest(ReduxActionTypes.UPDATE_ACTION_INIT, updateActionSaga),
|
takeLatest(ReduxActionTypes.UPDATE_ACTION_INIT, updateActionSaga),
|
||||||
takeLatest(ReduxActionTypes.DELETE_ACTION_INIT, deleteActionSaga),
|
takeLatest(ReduxActionTypes.DELETE_ACTION_INIT, deleteActionSaga),
|
||||||
takeLatest(ReduxActionTypes.SAVE_API_NAME, saveApiNameSaga),
|
takeLatest(ReduxActionTypes.SAVE_API_NAME, saveApiNameSaga),
|
||||||
takeLatest(
|
|
||||||
ReduxActionTypes.EXECUTE_PAGE_LOAD_ACTIONS,
|
|
||||||
executePageLoadActionsSaga,
|
|
||||||
),
|
|
||||||
takeLatest(ReduxActionTypes.MOVE_ACTION_INIT, moveActionSaga),
|
takeLatest(ReduxActionTypes.MOVE_ACTION_INIT, moveActionSaga),
|
||||||
takeLatest(ReduxActionTypes.COPY_ACTION_INIT, copyActionSaga),
|
takeLatest(ReduxActionTypes.COPY_ACTION_INIT, copyActionSaga),
|
||||||
takeLatest(
|
takeLatest(
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,7 @@
|
||||||
* Handles the Api pane ui state. It looks into the routing based on actions too
|
* Handles the Api pane ui state. It looks into the routing based on actions too
|
||||||
* */
|
* */
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import {
|
import { all, select, put, takeEvery, take, call } from "redux-saga/effects";
|
||||||
all,
|
|
||||||
select,
|
|
||||||
put,
|
|
||||||
takeEvery,
|
|
||||||
take,
|
|
||||||
call,
|
|
||||||
race,
|
|
||||||
delay,
|
|
||||||
} from "redux-saga/effects";
|
|
||||||
import { getFormSyncErrors } from "redux-form";
|
|
||||||
import {
|
import {
|
||||||
ReduxAction,
|
ReduxAction,
|
||||||
ReduxActionErrorTypes,
|
ReduxActionErrorTypes,
|
||||||
|
|
@ -46,36 +36,20 @@ import {
|
||||||
getDataSources,
|
getDataSources,
|
||||||
} from "selectors/editorSelectors";
|
} from "selectors/editorSelectors";
|
||||||
import { initialize, autofill, change } from "redux-form";
|
import { initialize, autofill, change } from "redux-form";
|
||||||
import { getAction } from "./ActionSagas";
|
|
||||||
import { AppState } from "reducers";
|
import { AppState } from "reducers";
|
||||||
import { Property } from "api/ActionAPI";
|
import { Property } from "api/ActionAPI";
|
||||||
import { changeApi, setDatasourceFieldText } from "actions/apiPaneActions";
|
import { changeApi, setDatasourceFieldText } from "actions/apiPaneActions";
|
||||||
import {
|
|
||||||
FIELD_REQUIRED_ERROR,
|
|
||||||
UNIQUE_NAME_ERROR,
|
|
||||||
VALID_FUNCTION_NAME_ERROR,
|
|
||||||
} from "constants/messages";
|
|
||||||
import { createNewApiName, getNextEntityName } from "utils/AppsmithUtils";
|
import { createNewApiName, getNextEntityName } from "utils/AppsmithUtils";
|
||||||
import { getPluginIdOfPackageName } from "sagas/selectors";
|
import { getPluginIdOfPackageName } from "sagas/selectors";
|
||||||
import { getActions, getPlugins } from "selectors/entitiesSelector";
|
import { getAction, getActions, getPlugins } from "selectors/entitiesSelector";
|
||||||
import { ActionData } from "reducers/entityReducers/actionsReducer";
|
import { ActionData } from "reducers/entityReducers/actionsReducer";
|
||||||
import { createActionRequest } from "actions/actionActions";
|
import { createActionRequest, setActionProperty } from "actions/actionActions";
|
||||||
import { Datasource } from "api/DatasourcesApi";
|
import { Datasource } from "api/DatasourcesApi";
|
||||||
import { Plugin } from "api/PluginApi";
|
import { Plugin } from "api/PluginApi";
|
||||||
import { PLUGIN_PACKAGE_DBS } from "constants/QueryEditorConstants";
|
import { PLUGIN_PACKAGE_DBS } from "constants/QueryEditorConstants";
|
||||||
import { RestAction } from "entities/Action";
|
import { RestAction } from "entities/Action";
|
||||||
import { isDynamicValue } from "utils/DynamicBindingUtils";
|
|
||||||
import { getCurrentOrgId } from "selectors/organizationSelectors";
|
import { getCurrentOrgId } from "selectors/organizationSelectors";
|
||||||
|
|
||||||
const getApiDraft = (state: AppState, id: string) => {
|
|
||||||
const drafts = state.entities.actionDrafts;
|
|
||||||
if (id in drafts) return drafts[id];
|
|
||||||
return {};
|
|
||||||
};
|
|
||||||
|
|
||||||
const getActionConfigs = (state: AppState): ActionData["config"][] =>
|
|
||||||
state.entities.actions.map(a => a.config);
|
|
||||||
|
|
||||||
const getLastUsedAction = (state: AppState) => state.ui.apiPane.lastUsed;
|
const getLastUsedAction = (state: AppState) => state.ui.apiPane.lastUsed;
|
||||||
const getLastUsedEditorPage = (state: AppState) =>
|
const getLastUsedEditorPage = (state: AppState) =>
|
||||||
state.ui.apiPane.lastUsedEditorPage;
|
state.ui.apiPane.lastUsedEditorPage;
|
||||||
|
|
@ -229,25 +203,20 @@ function* changeApiSaga(actionPayload: ReduxAction<{ id: string }>) {
|
||||||
}
|
}
|
||||||
const action = yield select(getAction, id);
|
const action = yield select(getAction, id);
|
||||||
if (!action) return;
|
if (!action) return;
|
||||||
const draft = yield select(getApiDraft, id);
|
|
||||||
let data;
|
|
||||||
|
|
||||||
if (_.isEmpty(draft)) {
|
yield put(initialize(API_EDITOR_FORM_NAME, action));
|
||||||
data = action;
|
|
||||||
} else {
|
|
||||||
data = draft;
|
|
||||||
}
|
|
||||||
|
|
||||||
yield put(initialize(API_EDITOR_FORM_NAME, data));
|
|
||||||
history.push(API_EDITOR_ID_URL(applicationId, pageId, id));
|
history.push(API_EDITOR_ID_URL(applicationId, pageId, id));
|
||||||
|
|
||||||
yield call(initializeExtraFormDataSaga);
|
yield call(initializeExtraFormDataSaga);
|
||||||
|
|
||||||
if (data.actionConfiguration && data.actionConfiguration.queryParameters) {
|
if (
|
||||||
|
action.actionConfiguration &&
|
||||||
|
action.actionConfiguration.queryParameters?.length
|
||||||
|
) {
|
||||||
// Sync the api params my mocking a change action
|
// Sync the api params my mocking a change action
|
||||||
yield call(syncApiParamsSaga, {
|
yield call(syncApiParamsSaga, {
|
||||||
type: ReduxFormActionTypes.ARRAY_REMOVE,
|
type: ReduxFormActionTypes.ARRAY_REMOVE,
|
||||||
payload: data.actionConfiguration.queryParameters,
|
payload: action.actionConfiguration.queryParameters,
|
||||||
meta: {
|
meta: {
|
||||||
field: "actionConfiguration.queryParameters",
|
field: "actionConfiguration.queryParameters",
|
||||||
},
|
},
|
||||||
|
|
@ -255,82 +224,15 @@ function* changeApiSaga(actionPayload: ReduxAction<{ id: string }>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function* updateDraftsSaga() {
|
|
||||||
// debounce
|
|
||||||
// TODO check for save
|
|
||||||
const result = yield race({
|
|
||||||
change: take(ReduxFormActionTypes.VALUE_CHANGE),
|
|
||||||
timeout: delay(300),
|
|
||||||
});
|
|
||||||
if (result.timeout) {
|
|
||||||
const { values } = yield select(getFormData, API_EDITOR_FORM_NAME);
|
|
||||||
if (!values.id) return;
|
|
||||||
const action = yield select(getAction, values.id);
|
|
||||||
|
|
||||||
if (_.isEqual(values, action)) {
|
|
||||||
yield put({
|
|
||||||
type: ReduxActionTypes.DELETE_API_DRAFT,
|
|
||||||
payload: { id: values.id },
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
yield put({
|
|
||||||
type: ReduxActionTypes.UPDATE_API_DRAFT,
|
|
||||||
payload: { id: values.id, draft: values },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function* validateInputSaga() {
|
|
||||||
const errors = {};
|
|
||||||
const existingErrors = yield select(getFormSyncErrors);
|
|
||||||
const actions: RestAction[] = yield select(getActionConfigs);
|
|
||||||
const { values } = yield select(getFormData, API_EDITOR_FORM_NAME);
|
|
||||||
|
|
||||||
// Name field validation
|
|
||||||
let hasSameName = false;
|
|
||||||
const sameNames = actions.filter(
|
|
||||||
(action: RestAction) => action.name === values.name && action.id,
|
|
||||||
);
|
|
||||||
if (
|
|
||||||
sameNames.length > 1 ||
|
|
||||||
(sameNames.length === 1 && sameNames[0].id !== values.id)
|
|
||||||
) {
|
|
||||||
hasSameName = true;
|
|
||||||
}
|
|
||||||
if (!_.trim(values.name)) {
|
|
||||||
_.set(errors, "name", FIELD_REQUIRED_ERROR);
|
|
||||||
} else if (values.name.indexOf(" ") !== -1) {
|
|
||||||
_.set(errors, "name", VALID_FUNCTION_NAME_ERROR);
|
|
||||||
} else if (hasSameName) {
|
|
||||||
_.set(errors, "name", UNIQUE_NAME_ERROR);
|
|
||||||
} else {
|
|
||||||
_.unset(errors, "name");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existingErrors !== errors) {
|
|
||||||
yield put({
|
|
||||||
type: ReduxFormActionTypes.UPDATE_FIELD_ERROR,
|
|
||||||
meta: {
|
|
||||||
form: API_EDITOR_FORM_NAME,
|
|
||||||
},
|
|
||||||
payload: {
|
|
||||||
syncErrors: errors,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function* updateFormFields(
|
function* updateFormFields(
|
||||||
actionPayload: ReduxActionWithMeta<string, { field: string }>,
|
actionPayload: ReduxActionWithMeta<string, { field: string }>,
|
||||||
) {
|
) {
|
||||||
const field = actionPayload.meta.field;
|
const field = actionPayload.meta.field;
|
||||||
const value = actionPayload.payload;
|
const value = actionPayload.payload;
|
||||||
const formData = yield select(getFormData, API_EDITOR_FORM_NAME);
|
const { values } = yield select(getFormData, API_EDITOR_FORM_NAME);
|
||||||
|
|
||||||
if (field === "actionConfiguration.httpMethod") {
|
if (field === "actionConfiguration.httpMethod") {
|
||||||
if (value !== "GET") {
|
if (value !== "GET") {
|
||||||
const { values } = yield select(getFormData, API_EDITOR_FORM_NAME);
|
|
||||||
const { actionConfiguration } = values;
|
const { actionConfiguration } = values;
|
||||||
const actionConfigurationHeaders = actionConfiguration.headers;
|
const actionConfigurationHeaders = actionConfiguration.headers;
|
||||||
let contentType;
|
let contentType;
|
||||||
|
|
@ -354,12 +256,11 @@ function* updateFormFields(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (field.includes("actionConfiguration.headers")) {
|
} else if (field.includes("actionConfiguration.headers")) {
|
||||||
const formValues = formData.values;
|
|
||||||
const actionConfigurationHeaders = _.get(
|
const actionConfigurationHeaders = _.get(
|
||||||
formValues,
|
values,
|
||||||
"actionConfiguration.headers",
|
"actionConfiguration.headers",
|
||||||
);
|
);
|
||||||
const apiId = _.get(formValues, "id");
|
const apiId = _.get(values, "id");
|
||||||
let displayFormat;
|
let displayFormat;
|
||||||
|
|
||||||
if (actionConfigurationHeaders) {
|
if (actionConfigurationHeaders) {
|
||||||
|
|
@ -389,41 +290,35 @@ function* updateFormFields(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function* updateDynamicBindingsSaga(
|
|
||||||
actionPayload: ReduxActionWithMeta<string, { field: string }>,
|
|
||||||
) {
|
|
||||||
const field = actionPayload.meta.field.replace("actionConfiguration.", "");
|
|
||||||
const value = actionPayload.payload;
|
|
||||||
const { values } = yield select(getFormData, API_EDITOR_FORM_NAME);
|
|
||||||
if (!values.id) return;
|
|
||||||
|
|
||||||
const isDynamic = isDynamicValue(value);
|
|
||||||
let dynamicBindings: Property[] = values.dynamicBindingPathList || [];
|
|
||||||
const fieldExists = _.some(dynamicBindings, { key: field });
|
|
||||||
|
|
||||||
if (!isDynamic && fieldExists) {
|
|
||||||
dynamicBindings = dynamicBindings.filter(d => d.key !== field);
|
|
||||||
}
|
|
||||||
if (isDynamic && !fieldExists) {
|
|
||||||
dynamicBindings.push({ key: field });
|
|
||||||
}
|
|
||||||
if (dynamicBindings !== values.dynamicBindingPathList) {
|
|
||||||
yield put(
|
|
||||||
change(API_EDITOR_FORM_NAME, "dynamicBindingPathList", dynamicBindings),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function* formValueChangeSaga(
|
function* formValueChangeSaga(
|
||||||
actionPayload: ReduxActionWithMeta<string, { field: string; form: string }>,
|
actionPayload: ReduxActionWithMeta<string, { field: string; form: string }>,
|
||||||
) {
|
) {
|
||||||
const { form, field } = actionPayload.meta;
|
const { form, field } = actionPayload.meta;
|
||||||
if (form !== API_EDITOR_FORM_NAME) return;
|
if (form !== API_EDITOR_FORM_NAME) return;
|
||||||
if (field === "dynamicBindingPathList") return;
|
if (field === "dynamicBindingPathList") return;
|
||||||
|
const { values } = yield select(getFormData, API_EDITOR_FORM_NAME);
|
||||||
|
if (!values.id) return;
|
||||||
|
if (
|
||||||
|
actionPayload.type === ReduxFormActionTypes.ARRAY_REMOVE ||
|
||||||
|
actionPayload.type === ReduxFormActionTypes.ARRAY_PUSH
|
||||||
|
) {
|
||||||
|
const value = _.get(values, field);
|
||||||
|
setActionProperty({
|
||||||
|
actionId: values.id,
|
||||||
|
propertyName: field,
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
yield put(
|
||||||
|
setActionProperty({
|
||||||
|
actionId: values.id,
|
||||||
|
propertyName: field,
|
||||||
|
value: actionPayload.payload,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
yield all([
|
yield all([
|
||||||
call(updateDynamicBindingsSaga, actionPayload),
|
|
||||||
call(validateInputSaga),
|
|
||||||
call(updateDraftsSaga),
|
|
||||||
call(syncApiParamsSaga, actionPayload),
|
call(syncApiParamsSaga, actionPayload),
|
||||||
call(updateFormFields, actionPayload),
|
call(updateFormFields, actionPayload),
|
||||||
]);
|
]);
|
||||||
|
|
@ -442,25 +337,10 @@ function* handleActionCreatedSaga(actionPayload: ReduxAction<RestAction>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function* handleActionUpdatedSaga(
|
function* handleActionDeletedSaga() {
|
||||||
actionPayload: ReduxAction<{ data: RestAction }>,
|
|
||||||
) {
|
|
||||||
const { id } = actionPayload.payload.data;
|
|
||||||
yield put({
|
|
||||||
type: ReduxActionTypes.DELETE_API_DRAFT,
|
|
||||||
payload: { id },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function* handleActionDeletedSaga(actionPayload: ReduxAction<{ id: string }>) {
|
|
||||||
const { id } = actionPayload.payload;
|
|
||||||
const applicationId = yield select(getCurrentApplicationId);
|
const applicationId = yield select(getCurrentApplicationId);
|
||||||
const pageId = yield select(getCurrentPageId);
|
const pageId = yield select(getCurrentPageId);
|
||||||
history.push(API_EDITOR_URL(applicationId, pageId));
|
history.push(API_EDITOR_URL(applicationId, pageId));
|
||||||
yield put({
|
|
||||||
type: ReduxActionTypes.DELETE_API_DRAFT,
|
|
||||||
payload: { id },
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function* handleMoveOrCopySaga(actionPayload: ReduxAction<{ id: string }>) {
|
function* handleMoveOrCopySaga(actionPayload: ReduxAction<{ id: string }>) {
|
||||||
|
|
@ -571,7 +451,6 @@ export default function* root() {
|
||||||
takeEvery(ReduxActionTypes.INIT_API_PANE, initApiPaneSaga),
|
takeEvery(ReduxActionTypes.INIT_API_PANE, initApiPaneSaga),
|
||||||
takeEvery(ReduxActionTypes.API_PANE_CHANGE_API, changeApiSaga),
|
takeEvery(ReduxActionTypes.API_PANE_CHANGE_API, changeApiSaga),
|
||||||
takeEvery(ReduxActionTypes.CREATE_ACTION_SUCCESS, handleActionCreatedSaga),
|
takeEvery(ReduxActionTypes.CREATE_ACTION_SUCCESS, handleActionCreatedSaga),
|
||||||
takeEvery(ReduxActionTypes.UPDATE_ACTION_SUCCESS, handleActionUpdatedSaga),
|
|
||||||
takeEvery(ReduxActionTypes.DELETE_ACTION_SUCCESS, handleActionDeletedSaga),
|
takeEvery(ReduxActionTypes.DELETE_ACTION_SUCCESS, handleActionDeletedSaga),
|
||||||
takeEvery(ReduxActionTypes.MOVE_ACTION_SUCCESS, handleMoveOrCopySaga),
|
takeEvery(ReduxActionTypes.MOVE_ACTION_SUCCESS, handleMoveOrCopySaga),
|
||||||
takeEvery(ReduxActionTypes.COPY_ACTION_SUCCESS, handleMoveOrCopySaga),
|
takeEvery(ReduxActionTypes.COPY_ACTION_SUCCESS, handleMoveOrCopySaga),
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,10 @@ const BATCH_PRIORITY = {
|
||||||
priority: 1,
|
priority: 1,
|
||||||
needsSaga: true,
|
needsSaga: true,
|
||||||
},
|
},
|
||||||
|
[ReduxActionTypes.UPDATE_ACTION_PROPERTY]: {
|
||||||
|
priority: 1,
|
||||||
|
needsSaga: false,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const batches: ReduxAction<any>[][] = [];
|
const batches: ReduxAction<any>[][] = [];
|
||||||
|
|
|
||||||
|
|
@ -27,38 +27,20 @@ import {
|
||||||
getCurrentPageId,
|
getCurrentPageId,
|
||||||
} from "selectors/editorSelectors";
|
} from "selectors/editorSelectors";
|
||||||
import { change, initialize } from "redux-form";
|
import { change, initialize } from "redux-form";
|
||||||
import {
|
|
||||||
extractBindingsFromAction,
|
|
||||||
getAction,
|
|
||||||
getActionParams,
|
|
||||||
getActionTimeout,
|
|
||||||
} from "./ActionSagas";
|
|
||||||
import { AppState } from "reducers";
|
import { AppState } from "reducers";
|
||||||
import ActionAPI, {
|
import ActionAPI, { Property } from "api/ActionAPI";
|
||||||
PaginationField,
|
|
||||||
ExecuteActionRequest,
|
|
||||||
ActionApiResponse,
|
|
||||||
Property,
|
|
||||||
} from "api/ActionAPI";
|
|
||||||
import { QUERY_CONSTANT } from "constants/QueryEditorConstants";
|
import { QUERY_CONSTANT } from "constants/QueryEditorConstants";
|
||||||
import { changeQuery, deleteQuerySuccess } from "actions/queryPaneActions";
|
import { changeQuery, deleteQuerySuccess } from "actions/queryPaneActions";
|
||||||
import { AppToaster } from "components/editorComponents/ToastComponent";
|
import { AppToaster } from "components/editorComponents/ToastComponent";
|
||||||
import { ToastType } from "react-toastify";
|
import { ToastType } from "react-toastify";
|
||||||
import { PageAction } from "constants/ActionConstants";
|
|
||||||
import { isDynamicValue } from "utils/DynamicBindingUtils";
|
import { isDynamicValue } from "utils/DynamicBindingUtils";
|
||||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||||
import { GenericApiResponse } from "api/ApiResponses";
|
import { GenericApiResponse } from "api/ApiResponses";
|
||||||
import { validateResponse } from "./ErrorSagas";
|
import { validateResponse } from "./ErrorSagas";
|
||||||
import { getQueryName } from "selectors/entitiesSelector";
|
import { getAction, getQueryName } from "selectors/entitiesSelector";
|
||||||
import { QueryAction, RestAction } from "entities/Action";
|
import { RestAction } from "entities/Action";
|
||||||
import { updateAction } from "actions/actionActions";
|
import { updateAction } from "actions/actionActions";
|
||||||
|
|
||||||
const getQueryDraft = (state: AppState, id: string) => {
|
|
||||||
const drafts = state.entities.actionDrafts;
|
|
||||||
if (id in drafts) return drafts[id];
|
|
||||||
return {};
|
|
||||||
};
|
|
||||||
|
|
||||||
const getActions = (state: AppState) =>
|
const getActions = (state: AppState) =>
|
||||||
state.entities.actions.map(a => a.config);
|
state.entities.actions.map(a => a.config);
|
||||||
|
|
||||||
|
|
@ -117,34 +99,19 @@ function* changeQuerySaga(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const draft = yield select(getQueryDraft, id);
|
|
||||||
const data = _.isEmpty(draft) ? action : draft;
|
|
||||||
const URL = QUERIES_EDITOR_ID_URL(applicationId, pageId, id);
|
const URL = QUERIES_EDITOR_ID_URL(applicationId, pageId, id);
|
||||||
yield put(initialize(QUERY_EDITOR_FORM_NAME, data));
|
yield put(initialize(QUERY_EDITOR_FORM_NAME, action));
|
||||||
history.push(URL);
|
history.push(URL);
|
||||||
}
|
}
|
||||||
|
|
||||||
function* saveQueryAction() {
|
function* saveQueryAction() {
|
||||||
const { values } = yield select(getFormData, QUERY_EDITOR_FORM_NAME);
|
const { values } = yield select(getFormData, QUERY_EDITOR_FORM_NAME);
|
||||||
if (!values.id) return;
|
if (!values.id) return;
|
||||||
const action = yield select(getAction, values.id);
|
yield put(
|
||||||
if (_.isEqual(values, action)) {
|
updateAction({
|
||||||
yield put({
|
data: values,
|
||||||
type: ReduxActionTypes.DELETE_API_DRAFT,
|
}),
|
||||||
payload: { id: values.id },
|
);
|
||||||
});
|
|
||||||
} else {
|
|
||||||
yield put({
|
|
||||||
type: ReduxActionTypes.UPDATE_API_DRAFT,
|
|
||||||
payload: { id: values.id, draft: values },
|
|
||||||
});
|
|
||||||
|
|
||||||
yield put(
|
|
||||||
updateAction({
|
|
||||||
data: values,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function* updateDynamicBindingsSaga(
|
function* updateDynamicBindingsSaga(
|
||||||
|
|
@ -223,88 +190,7 @@ function* handleMoveOrCopySaga(actionPayload: ReduxAction<{ id: string }>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function* executeQuerySaga(
|
function* deleteQuerySaga(actionPayload: ReduxAction<{ id: string }>) {
|
||||||
actionPayload: ReduxAction<{
|
|
||||||
action: QueryAction;
|
|
||||||
actionId: string;
|
|
||||||
paginationField: PaginationField;
|
|
||||||
}>,
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
const {
|
|
||||||
values,
|
|
||||||
dirty,
|
|
||||||
}: {
|
|
||||||
values: QueryAction;
|
|
||||||
dirty: boolean;
|
|
||||||
valid: boolean;
|
|
||||||
} = yield select(getFormData, QUERY_EDITOR_FORM_NAME);
|
|
||||||
const actionObject: PageAction = yield select(getAction, values.id);
|
|
||||||
let action: ExecuteActionRequest["action"] = { id: values.id };
|
|
||||||
let jsonPathKeys = actionObject.jsonPathKeys;
|
|
||||||
|
|
||||||
if (dirty) {
|
|
||||||
action = _.omit(values, "id") as QueryAction;
|
|
||||||
jsonPathKeys = extractBindingsFromAction(action as QueryAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { paginationField } = actionPayload.payload;
|
|
||||||
|
|
||||||
const params = yield call(getActionParams, jsonPathKeys);
|
|
||||||
const timeout = yield select(getActionTimeout, values.id);
|
|
||||||
const response: ActionApiResponse = yield ActionAPI.executeAction(
|
|
||||||
{
|
|
||||||
action,
|
|
||||||
params,
|
|
||||||
paginationField,
|
|
||||||
},
|
|
||||||
timeout,
|
|
||||||
);
|
|
||||||
const isValidResponse = yield validateResponse(response);
|
|
||||||
const isExecutionSuccess = response.data.isExecutionSuccess;
|
|
||||||
|
|
||||||
if (!isExecutionSuccess) {
|
|
||||||
throw Error(response.data.body.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.data.body) {
|
|
||||||
throw Error("An unexpected error occurred.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isValidResponse) {
|
|
||||||
yield put({
|
|
||||||
type: ReduxActionTypes.RUN_QUERY_SUCCESS,
|
|
||||||
payload: {
|
|
||||||
data: response.data,
|
|
||||||
actionId: actionPayload.payload.actionId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
AppToaster.show({
|
|
||||||
message: "Query ran successfully",
|
|
||||||
type: ToastType.SUCCESS,
|
|
||||||
});
|
|
||||||
AnalyticsUtil.logEvent("RUN_QUERY", {
|
|
||||||
queryName: actionPayload.payload.action.name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
yield put({
|
|
||||||
type: ReduxActionErrorTypes.RUN_QUERY_ERROR,
|
|
||||||
payload: {
|
|
||||||
actionId: actionPayload.payload.actionId,
|
|
||||||
message: error.message,
|
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
AppToaster.show({
|
|
||||||
message: error.message,
|
|
||||||
type: ToastType.ERROR,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function* deleteQuerySaga(actionPayload: ReduxAction<{ id: string }>) {
|
|
||||||
try {
|
try {
|
||||||
const id = actionPayload.payload.id;
|
const id = actionPayload.payload.id;
|
||||||
const response: GenericApiResponse<RestAction> = yield ActionAPI.deleteAction(
|
const response: GenericApiResponse<RestAction> = yield ActionAPI.deleteAction(
|
||||||
|
|
@ -337,7 +223,6 @@ export default function* root() {
|
||||||
takeEvery(ReduxActionTypes.DELETE_QUERY_SUCCESS, handleQueryDeletedSaga),
|
takeEvery(ReduxActionTypes.DELETE_QUERY_SUCCESS, handleQueryDeletedSaga),
|
||||||
takeEvery(ReduxActionTypes.MOVE_ACTION_SUCCESS, handleMoveOrCopySaga),
|
takeEvery(ReduxActionTypes.MOVE_ACTION_SUCCESS, handleMoveOrCopySaga),
|
||||||
takeEvery(ReduxActionTypes.COPY_ACTION_SUCCESS, handleMoveOrCopySaga),
|
takeEvery(ReduxActionTypes.COPY_ACTION_SUCCESS, handleMoveOrCopySaga),
|
||||||
takeLatest(ReduxActionTypes.EXECUTE_QUERY_REQUEST, executeQuerySaga),
|
|
||||||
takeEvery(ReduxActionTypes.QUERY_PANE_CHANGE, changeQuerySaga),
|
takeEvery(ReduxActionTypes.QUERY_PANE_CHANGE, changeQuerySaga),
|
||||||
takeEvery(ReduxActionTypes.INIT_QUERY_PANE, initQueryPaneSaga),
|
takeEvery(ReduxActionTypes.INIT_QUERY_PANE, initQueryPaneSaga),
|
||||||
// Intercepting the redux-form change actionType
|
// Intercepting the redux-form change actionType
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { all, spawn } from "redux-saga/effects";
|
||||||
import pageSagas from "sagas/PageSagas";
|
import pageSagas from "sagas/PageSagas";
|
||||||
import { fetchWidgetCardsSaga } from "./WidgetSidebarSagas";
|
import { fetchWidgetCardsSaga } from "./WidgetSidebarSagas";
|
||||||
import { watchActionSagas } from "./ActionSagas";
|
import { watchActionSagas } from "./ActionSagas";
|
||||||
|
import { watchActionExecutionSagas } from "sagas/ActionExecutionSagas";
|
||||||
import widgetOperationSagas from "./WidgetOperationSagas";
|
import widgetOperationSagas from "./WidgetOperationSagas";
|
||||||
import errorSagas from "./ErrorSagas";
|
import errorSagas from "./ErrorSagas";
|
||||||
import configsSagas from "./ConfigsSagas";
|
import configsSagas from "./ConfigsSagas";
|
||||||
|
|
@ -25,6 +26,7 @@ export function* rootSaga() {
|
||||||
spawn(pageSagas),
|
spawn(pageSagas),
|
||||||
spawn(fetchWidgetCardsSaga),
|
spawn(fetchWidgetCardsSaga),
|
||||||
spawn(watchActionSagas),
|
spawn(watchActionSagas),
|
||||||
|
spawn(watchActionExecutionSagas),
|
||||||
spawn(widgetOperationSagas),
|
spawn(widgetOperationSagas),
|
||||||
spawn(errorSagas),
|
spawn(errorSagas),
|
||||||
spawn(configsSagas),
|
spawn(configsSagas),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { createSelector } from "reselect";
|
import { createSelector } from "reselect";
|
||||||
import { getActionDrafts, getActionsForCurrentPage } from "./entitiesSelector";
|
import { getActionsForCurrentPage } from "./entitiesSelector";
|
||||||
import { ActionDataState } from "reducers/entityReducers/actionsReducer";
|
import { ActionDataState } from "reducers/entityReducers/actionsReducer";
|
||||||
import { getEvaluatedDataTree } from "utils/DynamicBindingUtils";
|
import { getEvaluatedDataTree } from "utils/DynamicBindingUtils";
|
||||||
import { DataTree, DataTreeFactory } from "entities/DataTree/dataTreeFactory";
|
import { DataTree, DataTreeFactory } from "entities/DataTree/dataTreeFactory";
|
||||||
|
|
@ -40,16 +40,14 @@ import { getPageList } from "./appViewSelectors";
|
||||||
export const getUnevaluatedDataTree = (withFunctions?: boolean) =>
|
export const getUnevaluatedDataTree = (withFunctions?: boolean) =>
|
||||||
createSelector(
|
createSelector(
|
||||||
getActionsForCurrentPage,
|
getActionsForCurrentPage,
|
||||||
getActionDrafts,
|
|
||||||
getWidgets,
|
getWidgets,
|
||||||
getWidgetsMeta,
|
getWidgetsMeta,
|
||||||
getPageList,
|
getPageList,
|
||||||
(actions, actionDrafts, widgets, widgetsMeta, pageListPayload) => {
|
(actions, widgets, widgetsMeta, pageListPayload) => {
|
||||||
const pageList = pageListPayload || [];
|
const pageList = pageListPayload || [];
|
||||||
return DataTreeFactory.create(
|
return DataTreeFactory.create(
|
||||||
{
|
{
|
||||||
actions,
|
actions,
|
||||||
actionDrafts,
|
|
||||||
widgets,
|
widgets,
|
||||||
widgetsMeta,
|
widgetsMeta,
|
||||||
pageList,
|
pageList,
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,16 @@
|
||||||
import { AppState } from "reducers";
|
import { AppState } from "reducers";
|
||||||
import {
|
import {
|
||||||
ActionDataState,
|
|
||||||
ActionData,
|
ActionData,
|
||||||
|
ActionDataState,
|
||||||
} from "reducers/entityReducers/actionsReducer";
|
} from "reducers/entityReducers/actionsReducer";
|
||||||
import { ActionResponse } from "api/ActionAPI";
|
import { ActionResponse } from "api/ActionAPI";
|
||||||
import { QUERY_CONSTANT } from "constants/QueryEditorConstants";
|
import { QUERY_CONSTANT } from "constants/QueryEditorConstants";
|
||||||
import { API_CONSTANT } from "constants/ApiEditorConstants";
|
import { PLUGIN_TYPE_API } from "constants/ApiEditorConstants";
|
||||||
import { createSelector } from "reselect";
|
import { createSelector } from "reselect";
|
||||||
import { Page } from "constants/ReduxActionConstants";
|
import { Page } from "constants/ReduxActionConstants";
|
||||||
import { Datasource } from "api/DatasourcesApi";
|
import { Datasource } from "api/DatasourcesApi";
|
||||||
|
import { Action } from "entities/Action";
|
||||||
|
import { find } from "lodash";
|
||||||
|
|
||||||
export const getEntities = (state: AppState): AppState["entities"] =>
|
export const getEntities = (state: AppState): AppState["entities"] =>
|
||||||
state.entities;
|
state.entities;
|
||||||
|
|
@ -124,12 +126,6 @@ export const getDatasourceDraft = (state: AppState, id: string) => {
|
||||||
|
|
||||||
export const getPlugins = (state: AppState) => state.entities.plugins.list;
|
export const getPlugins = (state: AppState) => state.entities.plugins.list;
|
||||||
|
|
||||||
export const getApiActions = (state: AppState): ActionDataState => {
|
|
||||||
return state.entities.actions.filter((action: ActionData) => {
|
|
||||||
return action.config.pluginType === API_CONSTANT;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getQueryName = (state: AppState, actionId: string): string => {
|
export const getQueryName = (state: AppState, actionId: string): string => {
|
||||||
const action = state.entities.actions.find((action: ActionData) => {
|
const action = state.entities.actions.find((action: ActionData) => {
|
||||||
return action.config.id === actionId;
|
return action.config.id === actionId;
|
||||||
|
|
@ -138,14 +134,6 @@ export const getQueryName = (state: AppState, actionId: string): string => {
|
||||||
return action?.config.name ?? "";
|
return action?.config.name ?? "";
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getPageName = (state: AppState, pageId: string): string => {
|
|
||||||
const page = state.entities.pageList.pages.find((page: Page) => {
|
|
||||||
return page.pageId === pageId;
|
|
||||||
});
|
|
||||||
|
|
||||||
return page?.pageName ?? "";
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getQueryActions = (state: AppState): ActionDataState => {
|
export const getQueryActions = (state: AppState): ActionDataState => {
|
||||||
return state.entities.actions.filter((action: ActionData) => {
|
return state.entities.actions.filter((action: ActionData) => {
|
||||||
return action.config.pluginType === QUERY_CONSTANT;
|
return action.config.pluginType === QUERY_CONSTANT;
|
||||||
|
|
@ -167,8 +155,6 @@ export const getActionsForCurrentPage = createSelector(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getActionDrafts = (state: AppState) => state.entities.actionDrafts;
|
|
||||||
|
|
||||||
export const getActionResponses = createSelector(getActions, actions => {
|
export const getActionResponses = createSelector(getActions, actions => {
|
||||||
const responses: Record<string, ActionResponse | undefined> = {};
|
const responses: Record<string, ActionResponse | undefined> = {};
|
||||||
|
|
||||||
|
|
@ -178,3 +164,48 @@ export const getActionResponses = createSelector(getActions, actions => {
|
||||||
|
|
||||||
return responses;
|
return responses;
|
||||||
});
|
});
|
||||||
|
export const getAction = (
|
||||||
|
state: AppState,
|
||||||
|
actionId: string,
|
||||||
|
): Action | undefined => {
|
||||||
|
const action = find(state.entities.actions, a => a.config.id === actionId);
|
||||||
|
return action ? action.config : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getCurrentPageNameByActionId(
|
||||||
|
state: AppState,
|
||||||
|
actionId: string,
|
||||||
|
): string {
|
||||||
|
const action = state.entities.actions.find(action => {
|
||||||
|
return action.config.id === actionId;
|
||||||
|
});
|
||||||
|
const pageId = action ? action.config.pageId : "";
|
||||||
|
return getPageNameByPageId(state, pageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPageNameByPageId(state: AppState, pageId: string): string {
|
||||||
|
const page = state.entities.pageList.pages.find(
|
||||||
|
page => page.pageId === pageId,
|
||||||
|
);
|
||||||
|
return page ? page.pageName : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const getQueryPaneSavingMap = (state: AppState) => state.ui.queryPane.isSaving;
|
||||||
|
const getApiPaneSavingMap = (state: AppState) => state.ui.apiPane.isSaving;
|
||||||
|
const getActionDirtyState = (state: AppState) => state.ui.apiPane.isDirty;
|
||||||
|
|
||||||
|
export const isActionSaving = (id: string) =>
|
||||||
|
createSelector(
|
||||||
|
[getQueryPaneSavingMap, getApiPaneSavingMap],
|
||||||
|
(querySavingMap, apiSavingsMap) => {
|
||||||
|
return (
|
||||||
|
(id in querySavingMap && querySavingMap[id]) ||
|
||||||
|
(id in apiSavingsMap && apiSavingsMap[id])
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const isActionDirty = (id: string) =>
|
||||||
|
createSelector([getActionDirtyState], actionDirtyMap => {
|
||||||
|
return id in actionDirtyMap && actionDirtyMap[id];
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -6,15 +6,13 @@ import { ActionData } from "reducers/entityReducers/actionsReducer";
|
||||||
type GetFormData = (
|
type GetFormData = (
|
||||||
state: AppState,
|
state: AppState,
|
||||||
formName: string,
|
formName: string,
|
||||||
) => { values: object; dirty: boolean; valid: boolean };
|
) => { values: object; valid: boolean };
|
||||||
|
|
||||||
export const getFormData: GetFormData = (state, formName) => {
|
export const getFormData: GetFormData = (state, formName) => {
|
||||||
const initialValues = getFormInitialValues(formName)(state) as RestAction;
|
const initialValues = getFormInitialValues(formName)(state) as RestAction;
|
||||||
const values = getFormValues(formName)(state) as RestAction;
|
const values = getFormValues(formName)(state) as RestAction;
|
||||||
const drafts = state.entities.actionDrafts;
|
|
||||||
const dirty = values.id in drafts;
|
|
||||||
const valid = isValid(formName)(state);
|
const valid = isValid(formName)(state);
|
||||||
return { initialValues, values, dirty, valid };
|
return { initialValues, values, valid };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getApiName = (state: AppState, id: string) => {
|
export const getApiName = (state: AppState, id: string) => {
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,9 @@ import equal from "fast-deep-equal/es6";
|
||||||
import WidgetFactory from "utils/WidgetFactory";
|
import WidgetFactory from "utils/WidgetFactory";
|
||||||
import { AppToaster } from "components/editorComponents/ToastComponent";
|
import { AppToaster } from "components/editorComponents/ToastComponent";
|
||||||
import { ToastType } from "react-toastify";
|
import { ToastType } from "react-toastify";
|
||||||
|
import { Action } from "entities/Action";
|
||||||
|
|
||||||
export const removeBindingsFromObject = (obj: object) => {
|
export const removeBindingsFromActionObject = (obj: Action) => {
|
||||||
const string = JSON.stringify(obj);
|
const string = JSON.stringify(obj);
|
||||||
const withBindings = string.replace(DATA_BIND_REGEX_GLOBAL, "{{ }}");
|
const withBindings = string.replace(DATA_BIND_REGEX_GLOBAL, "{{ }}");
|
||||||
return JSON.parse(withBindings);
|
return JSON.parse(withBindings);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user