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.ClearSearch();
|
||||
cy.SearchAPIandClick("SecondAPI");
|
||||
//invalid api end point check
|
||||
cy.EditSourceDetail(testdata.baseUrl, testdata.invalidPath);
|
||||
cy.RunAPI();
|
||||
cy.ResponseStatusCheck("404 NOT_FOUND");
|
||||
cy.DeleteAPI();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -15,12 +15,7 @@ describe("Moustache test Functionality", function() {
|
|||
cy.log("Navigation to API Panel screen successful");
|
||||
cy.CreateAPI("TestAPINew");
|
||||
cy.log("Creation of API Action successful");
|
||||
cy.EnterSourceDetailsWithHeader(
|
||||
testdata.baseUrl2,
|
||||
testdata.moustacheMethod,
|
||||
testdata.headerKey,
|
||||
testdata.headerValue,
|
||||
);
|
||||
cy.enterDatasourceAndPath(testdata.baseUrl2, testdata.moustacheMethod);
|
||||
cy.RunAPI();
|
||||
cy.ResponseStatusCheck(testdata.successStatusCode);
|
||||
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() {
|
||||
cy.NavigateToApiEditor();
|
||||
cy.testCreateApiButton();
|
||||
/**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();
|
||||
});
|
||||
|
||||
|
|
@ -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() {
|
||||
cy.NavigateToApiEditor();
|
||||
cy.testCreateApiButton();
|
||||
/** Create Api2 of Paginate with Response URL*/
|
||||
cy.createApi(this.data.paginationUrl, "pokemon");
|
||||
cy.createAndFillApi(this.data.paginationUrl, "pokemon");
|
||||
cy.RunAPI();
|
||||
cy.NavigateToPaginationTab();
|
||||
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() {
|
||||
cy.NavigateToApiEditor();
|
||||
cy.testCreateApiButton();
|
||||
cy.createApi(this.data.userApi, "users");
|
||||
cy.createAndFillApi(this.data.userApi, "users");
|
||||
cy.RunAPI();
|
||||
cy.get(apiPage.responseBody)
|
||||
.contains("name")
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
"home": ".single-select >div:contains('Page1')",
|
||||
"delete": ".single-select >div:contains('Delete')",
|
||||
"path": ".t--path >div textarea",
|
||||
"editResourceUrl": ".t--dataSourceField input",
|
||||
"editResourceUrl": ".t--dataSourceField",
|
||||
"autoSuggest": "//div[contains(@id,'react-select')]",
|
||||
"headerKey": "(//div[contains(@class,'t--actionConfiguration.headers[0].key.0')]//textarea)[2]",
|
||||
"headerValue": "(//div[contains(@class,'t--actionConfiguration.headers[0].value.0')]//textarea)[2]",
|
||||
|
|
@ -35,4 +35,4 @@
|
|||
"TestPreUrl": ".t--apiFormPaginationPrevTest",
|
||||
"EditApiName": "img[alt='Edit pen']",
|
||||
"ApiName": ".t--action-name-edit-field span"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -177,15 +177,14 @@ Cypress.Commands.add("CreateAPI", apiname => {
|
|||
.click({ force: true });
|
||||
cy.get(apiwidget.createapi).click({ force: true });
|
||||
cy.wait("@createNewApi");
|
||||
cy.wait("@postSave");
|
||||
cy.get(apiwidget.resourceUrl).should("be.visible");
|
||||
cy.wait("@postexe");
|
||||
cy.get(apiwidget.EditApiName).should("be.visible");
|
||||
cy.get(apiwidget.EditApiName).click();
|
||||
cy.get(apiwidget.apiTxt)
|
||||
.clear()
|
||||
.type(apiname)
|
||||
.should("have.value", apiname);
|
||||
.should("have.value", apiname)
|
||||
.blur();
|
||||
//cy.WaitAutoSave();
|
||||
// Added because api name edit takes some time to
|
||||
// reflect in api sidebar after the call passes.
|
||||
|
|
@ -216,13 +215,14 @@ Cypress.Commands.add("EditApiName", apiname => {
|
|||
});
|
||||
|
||||
Cypress.Commands.add("WaitAutoSave", () => {
|
||||
// wait for save query to trigger
|
||||
cy.wait(200);
|
||||
cy.wait("@saveQuery");
|
||||
//cy.wait("@postExecute");
|
||||
});
|
||||
|
||||
Cypress.Commands.add("RunAPI", () => {
|
||||
cy.get(ApiEditor.ApiRunBtn).click({ force: true });
|
||||
// cy.wait('@postTrack');
|
||||
cy.wait("@postExecute");
|
||||
});
|
||||
|
||||
|
|
@ -258,7 +258,7 @@ Cypress.Commands.add("enterDatasourceAndPath", (datasource, path) => {
|
|||
.first()
|
||||
.click({ force: true })
|
||||
.type(datasource);
|
||||
/*
|
||||
/*
|
||||
cy.xpath(apiwidget.autoSuggest)
|
||||
.first()
|
||||
.click({ force: true });
|
||||
|
|
@ -1017,7 +1017,9 @@ Cypress.Commands.add("closePropertyPane", () => {
|
|||
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(ApiEditor.ApiNameField).should("be.visible");
|
||||
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("PUT", "/api/v1/layouts/*/pages/*").as("updateLayout");
|
||||
|
||||
cy.route("POST", "/v1/t").as("postSave");
|
||||
cy.route("PUT", "/api/v1/actions/*").as("putActions");
|
||||
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").as("postaction");
|
||||
|
||||
|
|
|
|||
|
|
@ -182,4 +182,4 @@
|
|||
"pre-commit": "lint-staged"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
ReduxActionErrorTypes,
|
||||
} from "constants/ReduxActionConstants";
|
||||
import { Action, RestAction } from "entities/Action";
|
||||
import { batchAction } from "actions/batchActions";
|
||||
|
||||
export const createActionRequest = (payload: Partial<Action>) => {
|
||||
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 {
|
||||
type: ReduxActionTypes.RUN_API_REQUEST,
|
||||
type: ReduxActionTypes.RUN_ACTION_REQUEST,
|
||||
payload: {
|
||||
id: id,
|
||||
paginationField: paginationField,
|
||||
id,
|
||||
paginationField,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
@ -163,24 +164,35 @@ export const saveApiName = (payload: { id: string; name: string }) => ({
|
|||
payload: payload,
|
||||
});
|
||||
|
||||
export const updateApiNameDraft = (payload: {
|
||||
id: string;
|
||||
draft?: {
|
||||
value: string;
|
||||
validation: {
|
||||
isValid: boolean;
|
||||
validationMessage: string;
|
||||
};
|
||||
};
|
||||
}) => ({
|
||||
type: ReduxActionTypes.UPDATE_API_NAME_DRAFT,
|
||||
payload: payload,
|
||||
export type SetActionPropertyPayload = {
|
||||
actionId: string;
|
||||
propertyName: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export const setActionProperty = (payload: SetActionPropertyPayload) => ({
|
||||
type: ReduxActionTypes.SET_ACTION_PROPERTY,
|
||||
payload,
|
||||
});
|
||||
|
||||
export type UpdateActionPropertyActionPayload = {
|
||||
id: string;
|
||||
field: string;
|
||||
value: any;
|
||||
};
|
||||
|
||||
export const updateActionProperty = (
|
||||
payload: UpdateActionPropertyActionPayload,
|
||||
) =>
|
||||
batchAction({
|
||||
type: ReduxActionTypes.UPDATE_ACTION_PROPERTY,
|
||||
payload,
|
||||
});
|
||||
|
||||
export default {
|
||||
createAction: createActionRequest,
|
||||
fetchActions,
|
||||
runAction: runApiAction,
|
||||
runAction: runAction,
|
||||
deleteAction,
|
||||
deleteActionSuccess,
|
||||
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 = (
|
||||
pluginType: string,
|
||||
urlId?: string,
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ class CodeEditor extends Component<Props, State> {
|
|||
componentDidUpdate(prevProps: Props): void {
|
||||
this.editor.refresh();
|
||||
if (!this.state.isFocused) {
|
||||
const currentMode = this.editor.getOption("mode");
|
||||
// const currentMode = this.editor.getOption("mode");
|
||||
const editorValue = this.editor.getValue();
|
||||
let inputValue = this.props.input.value;
|
||||
// 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) {
|
||||
this.editor.setValue(inputValue);
|
||||
}
|
||||
this.updateMarkings();
|
||||
|
||||
if (currentMode !== this.props.mode) {
|
||||
this.editor.setOption("mode", this.props?.mode);
|
||||
}
|
||||
// if (currentMode !== this.props.mode) {
|
||||
// this.editor.setOption("mode", this.props?.mode);
|
||||
// }
|
||||
} else {
|
||||
// Update the dynamic bindings for autocomplete
|
||||
if (prevProps.dynamicData !== this.props.dynamicData) {
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import { bindingHint } from "components/editorComponents/CodeEditor/hintHelpers"
|
|||
import StoreAsDatasource from "components/editorComponents/StoreAsDatasource";
|
||||
|
||||
type ReduxStateProps = {
|
||||
orgId: string;
|
||||
datasource: Datasource | EmbeddedDatasource;
|
||||
datasourceList: Datasource[];
|
||||
apiName: string;
|
||||
|
|
@ -47,13 +48,13 @@ const fullPathRegexExp = /(https?:\/{2}\S+)(\/\S*?)$/;
|
|||
|
||||
class EmbeddedDatasourcePathComponent extends React.Component<Props> {
|
||||
handleDatasourceUrlUpdate = (datasourceUrl: string) => {
|
||||
const { datasource, pluginId, datasourceList } = this.props;
|
||||
const { datasource, pluginId, orgId, datasourceList } = this.props;
|
||||
const urlHasUpdated =
|
||||
datasourceUrl !== datasource.datasourceConfiguration?.url;
|
||||
if (urlHasUpdated) {
|
||||
if ("id" in datasource && datasource.id) {
|
||||
this.props.updateDatasource({
|
||||
...DEFAULT_DATASOURCE(pluginId),
|
||||
...DEFAULT_DATASOURCE(pluginId, orgId),
|
||||
datasourceConfiguration: {
|
||||
...datasource.datasourceConfiguration,
|
||||
url: datasourceUrl,
|
||||
|
|
@ -68,7 +69,7 @@ class EmbeddedDatasourcePathComponent extends React.Component<Props> {
|
|||
this.props.updateDatasource(matchesExistingDatasource);
|
||||
} else {
|
||||
this.props.updateDatasource({
|
||||
...DEFAULT_DATASOURCE(pluginId),
|
||||
...DEFAULT_DATASOURCE(pluginId, orgId),
|
||||
datasourceConfiguration: {
|
||||
...datasource.datasourceConfiguration,
|
||||
url: datasourceUrl,
|
||||
|
|
@ -249,6 +250,7 @@ const mapStateToProps = (
|
|||
ownProps: { pluginId: string },
|
||||
): ReduxStateProps => {
|
||||
return {
|
||||
orgId: state.ui.orgs.currentOrgId,
|
||||
apiName: apiFormValueSelector(state, "name"),
|
||||
datasource: apiFormValueSelector(state, "datasource"),
|
||||
datasourceList: state.entities.datasources.list.filter(
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ const KeyValueRow = (props: Props & WrappedFieldArrayProps) => {
|
|||
}, [props.fields, props.pushFields]);
|
||||
return (
|
||||
<React.Fragment>
|
||||
{typeof props.fields.getAll() === "object" && (
|
||||
{props.fields.length && (
|
||||
<React.Fragment>
|
||||
{props.fields.map((field: any, index: number) => {
|
||||
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 CONTENT_TYPE = "content-type";
|
||||
|
||||
|
|
|
|||
|
|
@ -27,10 +27,10 @@ export const ReduxActionTypes: { [key: string]: string } = {
|
|||
REMOVE_PAGE_WIDGET: "REMOVE_PAGE_WIDGET",
|
||||
LOAD_API_RESPONSE: "LOAD_API_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",
|
||||
API_PANE_CHANGE_API: "API_PANE_CHANGE_API",
|
||||
RUN_API_SUCCESS: "RUN_API_SUCCESS",
|
||||
EXECUTE_ACTION: "EXECUTE_ACTION",
|
||||
EXECUTE_ACTION_SUCCESS: "EXECUTE_ACTION_SUCCESS",
|
||||
LOAD_CANVAS_ACTIONS: "LOAD_CANVAS_ACTIONS",
|
||||
|
|
@ -99,8 +99,6 @@ export const ReduxActionTypes: { [key: string]: string } = {
|
|||
HIDE_PROPERTY_PANE: "HIDE_PROPERTY_PANE",
|
||||
INIT_DATASOURCE_PANE: "INIT_DATASOURCE_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",
|
||||
UPDATE_ROUTES_PARAMS: "UPDATE_ROUTES_PARAMS",
|
||||
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",
|
||||
DELETE_QUERY_INIT: "DELETE_QUERY_INIT",
|
||||
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",
|
||||
FETCH_PROVIDERS_CATEGORIES_INIT: "FETCH_PROVIDERS_CATEGORIES_INIT",
|
||||
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_SUCCESS: "SAVE_API_NAME_SUCCESS",
|
||||
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];
|
||||
|
|
@ -261,7 +259,7 @@ export const ReduxActionErrorTypes: { [key: string]: string } = {
|
|||
CREATE_ACTION_ERROR: "CREATE_ACTION_ERROR",
|
||||
UPDATE_ACTION_ERROR: "UPDATE_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",
|
||||
FETCH_DATASOURCES_ERROR: "FETCH_DATASOURCES_ERROR",
|
||||
SEARCH_APIORPROVIDERS_ERROR: "SEARCH_APIORPROVIDERS_ERROR",
|
||||
|
|
@ -299,7 +297,6 @@ export const ReduxActionErrorTypes: { [key: string]: string } = {
|
|||
MOVE_ACTION_ERROR: "MOVE_ACTION_ERROR",
|
||||
COPY_ACTION_ERROR: "COPY_ACTION_ERROR",
|
||||
DELETE_PAGE_ERROR: "DELETE_PAGE_ERROR",
|
||||
RUN_QUERY_ERROR: "RUN_QUERY_ERROR",
|
||||
DELETE_APPLICATION_ERROR: "DELETE_APPLICATION_ERROR",
|
||||
SET_DEFAULT_APPLICATION_PAGE_ERROR: "SET_DEFAULT_APPLICATION_PAGE_ERROR",
|
||||
CREATE_ORGANIZATION_ERROR: "CREATE_ORGANIZATION_ERROR",
|
||||
|
|
|
|||
|
|
@ -8,8 +8,7 @@ import { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsRe
|
|||
import { MetaState } from "reducers/entityReducers/metaReducer";
|
||||
import { PageListPayload } from "constants/ReduxActionConstants";
|
||||
import WidgetFactory from "utils/WidgetFactory";
|
||||
import { ActionDraftsState } from "reducers/entityReducers/actionDraftsReducer";
|
||||
import { Property, ActionConfig } from "entities/Action";
|
||||
import { ActionConfig, Property } from "entities/Action";
|
||||
|
||||
export type ActionDescription<T> = {
|
||||
type: string;
|
||||
|
|
@ -68,7 +67,6 @@ export type DataTree = {
|
|||
|
||||
type DataTreeSeed = {
|
||||
actions: ActionDataState;
|
||||
actionDrafts: ActionDraftsState;
|
||||
widgets: CanvasWidgetsReduxState;
|
||||
widgetsMeta: MetaState;
|
||||
pageList: PageListPayload;
|
||||
|
|
@ -77,7 +75,7 @@ type DataTreeSeed = {
|
|||
|
||||
export class DataTreeFactory {
|
||||
static create(
|
||||
{ actions, actionDrafts, widgets, widgetsMeta, pageList }: DataTreeSeed,
|
||||
{ actions, widgets, widgetsMeta, pageList }: DataTreeSeed,
|
||||
// TODO(hetu)
|
||||
// temporary fix for not getting functions while normal evals which crashes the app
|
||||
// need to remove this after we get a proper solve
|
||||
|
|
@ -86,8 +84,7 @@ export class DataTreeFactory {
|
|||
const dataTree: DataTree = {};
|
||||
const actionPaths = [];
|
||||
actions.forEach(a => {
|
||||
const config =
|
||||
a.config.id in actionDrafts ? actionDrafts[a.config.id] : a.config;
|
||||
const config = a.config;
|
||||
let dynamicBindingPathList: Property[] = [];
|
||||
// update paths
|
||||
if (
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@ import { Datasource } from "api/DatasourcesApi";
|
|||
|
||||
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",
|
||||
datasourceConfiguration: {
|
||||
url: "",
|
||||
|
|
@ -10,4 +13,5 @@ export const DEFAULT_DATASOURCE = (pluginId: string): EmbeddedDatasource => ({
|
|||
invalids: [],
|
||||
isValid: true,
|
||||
pluginId,
|
||||
organizationId,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -40,7 +40,9 @@ export default class RealmExecutor implements JSExecutor {
|
|||
safeObject.actionPaths.forEach(path => {
|
||||
const action = _.get(safeObject, path);
|
||||
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
|
||||
|
|
|
|||
|
|
@ -4,11 +4,7 @@ import { getFormValues, submit } from "redux-form";
|
|||
import ApiEditorForm from "./Form";
|
||||
import RapidApiEditorForm from "./RapidApiEditorForm";
|
||||
import ApiHomeScreen from "./ApiHomeScreen";
|
||||
import {
|
||||
runApiAction,
|
||||
deleteAction,
|
||||
updateAction,
|
||||
} from "actions/actionActions";
|
||||
import { runAction, deleteAction, updateAction } from "actions/actionActions";
|
||||
import { PaginationField } from "api/ActionAPI";
|
||||
import { AppState } from "reducers";
|
||||
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 { isDeleting, isRunning, isCreating } = state.ui.apiPane;
|
||||
const actionDrafts = state.entities.actionDrafts;
|
||||
const allowSave = !!(apiAction && apiAction.id in actionDrafts);
|
||||
const allowSave = true;
|
||||
const datasourceFieldText =
|
||||
state.ui.apiPane.datasourceFieldText[formData?.id ?? ""] || "";
|
||||
|
||||
|
|
@ -261,7 +256,7 @@ const mapStateToProps = (state: AppState, props: any): ReduxStateProps => {
|
|||
const mapDispatchToProps = (dispatch: any): ReduxActionProps => ({
|
||||
submitForm: (name: string) => dispatch(submit(name)),
|
||||
runAction: (id: string, paginationField?: PaginationField) =>
|
||||
dispatch(runApiAction(id, paginationField)),
|
||||
dispatch(runAction(id, paginationField)),
|
||||
deleteAction: (id: string, name: string) =>
|
||||
dispatch(deleteAction({ id, name })),
|
||||
updateAction: (data: RestAction) => dispatch(updateAction({ data })),
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import { getNextEntityName } from "utils/AppsmithUtils";
|
|||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||
import { Page } from "constants/ReduxActionConstants";
|
||||
import { RestAction } from "entities/Action";
|
||||
import { ActionDraftsState } from "reducers/entityReducers/actionDraftsReducer";
|
||||
|
||||
const HTTPMethod = styled.span<{ method?: string }>`
|
||||
flex: 1;
|
||||
|
|
@ -51,6 +50,7 @@ const ActionItem = styled.div`
|
|||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
max-width: 90%;
|
||||
`;
|
||||
|
||||
const ActionName = styled.span`
|
||||
|
|
@ -59,12 +59,10 @@ const ActionName = styled.span`
|
|||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100px;
|
||||
`;
|
||||
|
||||
interface ReduxStateProps {
|
||||
actions: ActionDataState;
|
||||
actionDrafts: ActionDraftsState;
|
||||
apiPane: ApiPaneReduxState;
|
||||
pages: Page[];
|
||||
}
|
||||
|
|
@ -92,16 +90,6 @@ class ApiSidebar extends React.Component<Props> {
|
|||
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) => {
|
||||
this.props.onApiChange(actionId);
|
||||
};
|
||||
|
|
@ -197,20 +185,20 @@ class ApiSidebar extends React.Component<Props> {
|
|||
|
||||
render() {
|
||||
const {
|
||||
actionDrafts,
|
||||
apiPane: { isFetching },
|
||||
match: {
|
||||
params: { apiId },
|
||||
},
|
||||
actions,
|
||||
} = 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 (
|
||||
<EditorSidebar
|
||||
isLoading={isFetching}
|
||||
list={data}
|
||||
selectedItemId={apiId}
|
||||
draftIds={Object.keys(actionDrafts)}
|
||||
itemRender={this.renderItem}
|
||||
onItemCreateClick={this.handleCreateNewApiClick}
|
||||
onItemSelected={this.handleApiChange}
|
||||
|
|
@ -225,7 +213,6 @@ class ApiSidebar extends React.Component<Props> {
|
|||
|
||||
const mapStateToProps = (state: AppState): ReduxStateProps => ({
|
||||
actions: state.entities.actions,
|
||||
actionDrafts: state.entities.actionDrafts,
|
||||
apiPane: state.ui.apiPane,
|
||||
pages: state.entities.pageList.pages,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import TreeDropdown from "components/editorComponents/actioncreator/TreeDropdown
|
|||
import { theme } from "constants/DefaultTheme";
|
||||
import { Colors } from "constants/Colors";
|
||||
import { ControlIcons } from "icons/ControlIcons";
|
||||
import NotificationIcon from "components/designSystems/appsmith/NotificationIcon";
|
||||
|
||||
const LoadingContainer = styled(CenteredWrapper)`
|
||||
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>`
|
||||
&&& {
|
||||
outline: none;
|
||||
|
|
@ -192,7 +186,6 @@ type EditorSidebarComponentProps = {
|
|||
isLoading: boolean;
|
||||
list: Array<Item>;
|
||||
selectedItemId?: string;
|
||||
draftIds: string[];
|
||||
itemRender: (item: any) => JSX.Element;
|
||||
onItemCreateClick: (pageId: string) => void;
|
||||
onItemSelected: (itemId: string, itemPageId: string) => void;
|
||||
|
|
@ -306,7 +299,6 @@ class EditorSidebar extends React.Component<Props, State> {
|
|||
isLoading,
|
||||
itemRender,
|
||||
selectedItemId,
|
||||
draftIds,
|
||||
location,
|
||||
createButtonTitle,
|
||||
} = this.props;
|
||||
|
|
@ -404,11 +396,6 @@ class EditorSidebar extends React.Component<Props, State> {
|
|||
{this.state.itemDragging !==
|
||||
item.id && (
|
||||
<React.Fragment>
|
||||
<DraftIconIndicator
|
||||
isHidden={
|
||||
draftIds.indexOf(item.id) === -1
|
||||
}
|
||||
/>
|
||||
<TreeDropdown
|
||||
defaultText=""
|
||||
onSelect={() => {
|
||||
|
|
|
|||
|
|
@ -35,6 +35,10 @@ class JSONOutput extends React.Component<Props> {
|
|||
collapsed: 1,
|
||||
};
|
||||
|
||||
if (typeof src !== "object") {
|
||||
return <OutputContainer>{src}</OutputContainer>;
|
||||
}
|
||||
|
||||
if (!src.length) {
|
||||
return (
|
||||
<OutputContainer>
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ import styled from "styled-components";
|
|||
import { QueryEditorRouteParams } from "constants/routes";
|
||||
import QueryEditorForm from "./Form";
|
||||
import QueryHomeScreen from "./QueryHomeScreen";
|
||||
import { updateAction } from "actions/actionActions";
|
||||
import { deleteQuery, executeQuery } from "actions/queryPaneActions";
|
||||
import { runAction, updateAction } from "actions/actionActions";
|
||||
import { deleteQuery } from "actions/queryPaneActions";
|
||||
import { AppState } from "reducers";
|
||||
import { getDataSources } from "selectors/editorSelectors";
|
||||
import { QUERY_EDITOR_FORM_NAME } from "constants/forms";
|
||||
|
|
@ -32,7 +32,6 @@ import {
|
|||
import { getCurrentApplication } from "selectors/applicationSelectors";
|
||||
import { QueryAction, RestAction } from "entities/Action";
|
||||
import { getPluginImage } from "pages/Editor/QueryEditor/helpers";
|
||||
import { ActionDraftsState } from "reducers/entityReducers/actionDraftsReducer";
|
||||
|
||||
const EmptyStateContainer = styled.div`
|
||||
display: flex;
|
||||
|
|
@ -46,7 +45,6 @@ type QueryPageProps = {
|
|||
queryPane: QueryPaneReduxState;
|
||||
formData: RestAction;
|
||||
isCreating: boolean;
|
||||
actionDrafts: ActionDraftsState;
|
||||
initialValues: RestAction;
|
||||
pluginIds: Array<string> | undefined;
|
||||
submitForm: (name: string) => void;
|
||||
|
|
@ -97,7 +95,6 @@ class QueryEditor extends React.Component<Props> {
|
|||
pluginIds,
|
||||
executedQueryData,
|
||||
selectedPluginPackage,
|
||||
actionDrafts,
|
||||
isCreating,
|
||||
runErrorMessage,
|
||||
} = this.props;
|
||||
|
|
@ -130,7 +127,7 @@ class QueryEditor extends React.Component<Props> {
|
|||
location={this.props.location}
|
||||
applicationId={applicationId}
|
||||
pageId={pageId}
|
||||
allowSave={queryId in actionDrafts}
|
||||
allowSave={true}
|
||||
isSaving={isSaving[queryId]}
|
||||
isRunning={isRunning[queryId]}
|
||||
isDeleting={isDeleting[queryId]}
|
||||
|
|
@ -175,7 +172,6 @@ const mapStateToProps = (state: AppState): any => {
|
|||
return {
|
||||
plugins: getPlugins(state),
|
||||
runErrorMessage,
|
||||
actionDrafts: state.entities.actionDrafts,
|
||||
pluginIds: getPluginIdsOfPackageNames(state, PLUGIN_PACKAGE_DBS),
|
||||
dataSources: getDataSources(state),
|
||||
executedQueryData: state.ui.queryPane.runQuerySuccessData,
|
||||
|
|
@ -193,7 +189,7 @@ const mapDispatchToProps = (dispatch: any): any => ({
|
|||
updateAction: (data: RestAction) => dispatch(updateAction({ data })),
|
||||
deleteAction: (id: string) => dispatch(deleteQuery({ id })),
|
||||
runAction: (action: RestAction, actionId: string) =>
|
||||
dispatch(executeQuery({ action, actionId })),
|
||||
dispatch(runAction(actionId)),
|
||||
createTemplate: (template: any) => {
|
||||
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 { RestAction } from "entities/Action";
|
||||
import { Colors } from "constants/Colors";
|
||||
import { ActionDraftsState } from "reducers/entityReducers/actionDraftsReducer";
|
||||
|
||||
const ActionItem = styled.div`
|
||||
flex: 1;
|
||||
|
|
@ -60,7 +59,6 @@ interface ReduxStateProps {
|
|||
plugins: Plugin[];
|
||||
queries: ActionDataState;
|
||||
apiPane: ApiPaneReduxState;
|
||||
actionDrafts: ActionDraftsState;
|
||||
actions: ActionDataState;
|
||||
dataSources: Datasource[];
|
||||
}
|
||||
|
|
@ -88,16 +86,6 @@ class QuerySidebar extends React.Component<Props> {
|
|||
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 = () => {
|
||||
const { actions } = this.props;
|
||||
const { pageId } = this.props.match.params;
|
||||
|
|
@ -182,7 +170,6 @@ class QuerySidebar extends React.Component<Props> {
|
|||
|
||||
render() {
|
||||
const {
|
||||
actionDrafts,
|
||||
apiPane: { isFetching },
|
||||
match: {
|
||||
params: { queryId },
|
||||
|
|
@ -196,7 +183,6 @@ class QuerySidebar extends React.Component<Props> {
|
|||
isLoading={isFetching}
|
||||
list={data}
|
||||
selectedItemId={queryId}
|
||||
draftIds={Object.keys(actionDrafts)}
|
||||
itemRender={this.renderItem}
|
||||
onItemCreateClick={this.handleCreateNewQueryClick}
|
||||
onItemSelected={this.handleQueryChange}
|
||||
|
|
@ -212,7 +198,6 @@ class QuerySidebar extends React.Component<Props> {
|
|||
const mapStateToProps = (state: AppState): ReduxStateProps => ({
|
||||
plugins: getPlugins(state),
|
||||
queries: getQueryActions(state),
|
||||
actionDrafts: state.entities.actionDrafts,
|
||||
apiPane: state.ui.apiPane,
|
||||
actions: state.entities.actions,
|
||||
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 _ from "lodash";
|
||||
import { RapidApiAction, RestAction } from "entities/Action";
|
||||
import { UpdateActionPropertyActionPayload } from "actions/actionActions";
|
||||
export interface ActionData {
|
||||
isLoading: boolean;
|
||||
config: RestAction | RapidApiAction;
|
||||
|
|
@ -87,6 +88,16 @@ const actionsReducer = createReducer(initialState, {
|
|||
return { ...a, config: action.payload.data };
|
||||
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]: (
|
||||
state: ActionDataState,
|
||||
action: ReduxAction<{ id: string }>,
|
||||
|
|
@ -126,17 +137,17 @@ const actionsReducer = createReducer(initialState, {
|
|||
): ActionDataState =>
|
||||
state.map(a => {
|
||||
if (a.config.id === action.payload.actionId) {
|
||||
return { ...a, isLoading: false };
|
||||
return { ...a, isLoading: false, data: action.payload.error };
|
||||
}
|
||||
|
||||
return a;
|
||||
}),
|
||||
[ReduxActionTypes.RUN_API_REQUEST]: (
|
||||
[ReduxActionTypes.RUN_ACTION_REQUEST]: (
|
||||
state: ActionDataState,
|
||||
action: ReduxAction<string>,
|
||||
action: ReduxAction<{ id: string }>,
|
||||
): ActionDataState =>
|
||||
state.map(a => {
|
||||
if (action.payload === a.config.id) {
|
||||
if (action.payload.id === a.config.id) {
|
||||
return {
|
||||
...a,
|
||||
isLoading: true,
|
||||
|
|
@ -145,7 +156,7 @@ const actionsReducer = createReducer(initialState, {
|
|||
|
||||
return a;
|
||||
}),
|
||||
[ReduxActionTypes.RUN_API_SUCCESS]: (
|
||||
[ReduxActionTypes.RUN_ACTION_SUCCESS]: (
|
||||
state: ActionDataState,
|
||||
action: ReduxAction<{ [id: string]: ActionResponse }>,
|
||||
): ActionDataState => {
|
||||
|
|
@ -157,20 +168,7 @@ const actionsReducer = createReducer(initialState, {
|
|||
return a;
|
||||
});
|
||||
},
|
||||
[ReduxActionTypes.RUN_QUERY_SUCCESS]: (
|
||||
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]: (
|
||||
[ReduxActionErrorTypes.RUN_ACTION_ERROR]: (
|
||||
state: ActionDataState,
|
||||
action: ReduxAction<{ id: string }>,
|
||||
): ActionDataState =>
|
||||
|
|
|
|||
|
|
@ -9,14 +9,12 @@ import pageListReducer from "./pageListReducer";
|
|||
import jsExecutionsReducer from "./jsExecutionsReducer";
|
||||
import pluginsReducer from "reducers/entityReducers/pluginsReducer";
|
||||
import metaReducer from "./metaReducer";
|
||||
import actionDraftsReducer from "reducers/entityReducers/actionDraftsReducer";
|
||||
|
||||
const entityReducer = combineReducers({
|
||||
canvasWidgets: canvasWidgetsReducer,
|
||||
queryData: queryDataReducer,
|
||||
widgetConfig: widgetConfigReducer,
|
||||
actions: actionsReducer,
|
||||
actionDrafts: actionDraftsReducer,
|
||||
propertyConfig: propertyPaneConfigReducer,
|
||||
datasources: datasourceReducer,
|
||||
pageList: pageListReducer,
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ import { ImportedCollectionsReduxState } from "reducers/uiReducers/importedColle
|
|||
import { ProvidersReduxState } from "reducers/uiReducers/providerReducer";
|
||||
import { MetaState } from "./entityReducers/metaReducer";
|
||||
import { ImportReduxState } from "reducers/uiReducers/importReducer";
|
||||
import { ActionDraftsState } from "reducers/entityReducers/actionDraftsReducer";
|
||||
import { HelpReduxState } from "./uiReducers/helpReducer";
|
||||
import { ApiNameReduxState } from "./uiReducers/apiNameReducer";
|
||||
|
||||
|
|
@ -64,7 +63,6 @@ export interface AppState {
|
|||
canvasWidgets: CanvasWidgetsReduxState;
|
||||
queryData: QueryDataState;
|
||||
actions: ActionDataState;
|
||||
actionDrafts: ActionDraftsState;
|
||||
propertyConfig: PropertyPaneConfigState;
|
||||
widgetConfig: WidgetConfigReducerState;
|
||||
datasources: DatasourceDataState;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
ReduxAction,
|
||||
} from "constants/ReduxActionConstants";
|
||||
import { RestAction } from "entities/Action";
|
||||
import { UpdateActionPropertyActionPayload } from "actions/actionActions";
|
||||
|
||||
const initialState: ApiPaneReduxState = {
|
||||
lastUsed: "",
|
||||
|
|
@ -13,6 +14,7 @@ const initialState: ApiPaneReduxState = {
|
|||
isRunning: {},
|
||||
isSaving: {},
|
||||
isDeleting: {},
|
||||
isDirty: {},
|
||||
currentCategory: "",
|
||||
lastUsedEditorPage: "",
|
||||
lastSelectedPage: "",
|
||||
|
|
@ -27,6 +29,7 @@ export interface ApiPaneReduxState {
|
|||
isRunning: Record<string, boolean>;
|
||||
isSaving: Record<string, boolean>;
|
||||
isDeleting: Record<string, boolean>;
|
||||
isDirty: Record<string, boolean>;
|
||||
currentCategory: string;
|
||||
lastUsedEditorPage: string;
|
||||
datasourceFieldText: Record<string, string>;
|
||||
|
|
@ -65,7 +68,7 @@ const apiPaneReducer = createReducer(initialState, {
|
|||
...state,
|
||||
isCreating: false,
|
||||
}),
|
||||
[ReduxActionTypes.RUN_API_REQUEST]: (
|
||||
[ReduxActionTypes.RUN_ACTION_REQUEST]: (
|
||||
state: ApiPaneReduxState,
|
||||
action: ReduxAction<{ id: string }>,
|
||||
) => ({
|
||||
|
|
@ -75,7 +78,7 @@ const apiPaneReducer = createReducer(initialState, {
|
|||
[action.payload.id]: true,
|
||||
},
|
||||
}),
|
||||
[ReduxActionTypes.RUN_API_SUCCESS]: (
|
||||
[ReduxActionTypes.RUN_ACTION_SUCCESS]: (
|
||||
state: ApiPaneReduxState,
|
||||
action: ReduxAction<{ [id: string]: any }>,
|
||||
) => {
|
||||
|
|
@ -88,7 +91,7 @@ const apiPaneReducer = createReducer(initialState, {
|
|||
},
|
||||
};
|
||||
},
|
||||
[ReduxActionErrorTypes.RUN_API_ERROR]: (
|
||||
[ReduxActionErrorTypes.RUN_ACTION_ERROR]: (
|
||||
state: ApiPaneReduxState,
|
||||
action: ReduxAction<{ id: string }>,
|
||||
) => ({
|
||||
|
|
@ -98,6 +101,16 @@ const apiPaneReducer = createReducer(initialState, {
|
|||
[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]: (
|
||||
state: ApiPaneReduxState,
|
||||
action: ReduxAction<{ data: RestAction }>,
|
||||
|
|
@ -117,6 +130,10 @@ const apiPaneReducer = createReducer(initialState, {
|
|||
...state.isSaving,
|
||||
[action.payload.data.id]: false,
|
||||
},
|
||||
isDirty: {
|
||||
...state.isDirty,
|
||||
[action.payload.data.id]: false,
|
||||
},
|
||||
}),
|
||||
[ReduxActionErrorTypes.UPDATE_ACTION_ERROR]: (
|
||||
state: ApiPaneReduxState,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {
|
|||
} from "constants/ReduxActionConstants";
|
||||
import _ from "lodash";
|
||||
import { RestAction } from "entities/Action";
|
||||
import { ActionResponse } from "api/ActionAPI";
|
||||
|
||||
const initialState: QueryPaneReduxState = {
|
||||
isFetching: false,
|
||||
|
|
@ -115,15 +116,15 @@ const queryPaneReducer = createReducer(initialState, {
|
|||
[action.payload.id]: false,
|
||||
},
|
||||
}),
|
||||
[ReduxActionTypes.EXECUTE_QUERY_REQUEST]: (
|
||||
[ReduxActionTypes.RUN_ACTION_REQUEST]: (
|
||||
state: any,
|
||||
action: ReduxAction<{ action: RestAction; actionId: string }>,
|
||||
action: ReduxAction<{ id: string }>,
|
||||
) => {
|
||||
return {
|
||||
...state,
|
||||
isRunning: {
|
||||
...state.isRunning,
|
||||
[action.payload.actionId]: true,
|
||||
[action.payload.id]: true,
|
||||
},
|
||||
runQuerySuccessData: [],
|
||||
};
|
||||
|
|
@ -133,40 +134,39 @@ const queryPaneReducer = createReducer(initialState, {
|
|||
runQuerySuccessData: [],
|
||||
}),
|
||||
|
||||
[ReduxActionTypes.RUN_QUERY_SUCCESS]: (
|
||||
[ReduxActionTypes.RUN_ACTION_SUCCESS]: (
|
||||
state: any,
|
||||
action: ReduxAction<{ actionId: string; data: object }>,
|
||||
action: ReduxAction<{ [id: string]: ActionResponse }>,
|
||||
) => {
|
||||
const { actionId } = action.payload;
|
||||
|
||||
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;
|
||||
|
||||
const actionId = Object.keys(action.payload)[0];
|
||||
return {
|
||||
...state,
|
||||
isRunning: {
|
||||
...state.isRunning,
|
||||
[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: {
|
||||
...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,
|
||||
takeLatest,
|
||||
} from "redux-saga/effects";
|
||||
import {
|
||||
EventType,
|
||||
ExecuteActionPayload,
|
||||
ExecuteActionPayloadEvent,
|
||||
PageAction,
|
||||
} from "constants/ActionConstants";
|
||||
import ActionAPI, {
|
||||
ActionApiResponse,
|
||||
ActionCreateUpdateResponse,
|
||||
ActionResponse,
|
||||
ExecuteActionRequest,
|
||||
PaginationField,
|
||||
Property,
|
||||
} from "api/ActionAPI";
|
||||
import { AppState } from "reducers";
|
||||
import ActionAPI, { ActionCreateUpdateResponse, Property } from "api/ActionAPI";
|
||||
import _ from "lodash";
|
||||
import { mapToPropList } from "utils/AppsmithUtils";
|
||||
import { AppToaster } from "components/editorComponents/ToastComponent";
|
||||
import { GenericApiResponse } from "api/ApiResponses";
|
||||
import PageApi from "api/PageApi";
|
||||
|
|
@ -37,366 +22,32 @@ import {
|
|||
copyActionSuccess,
|
||||
createActionSuccess,
|
||||
deleteActionSuccess,
|
||||
executeApiActionRequest,
|
||||
executeApiActionSuccess,
|
||||
fetchActionsForPage,
|
||||
fetchActionsForPageSuccess,
|
||||
FetchActionsPayload,
|
||||
moveActionError,
|
||||
moveActionSuccess,
|
||||
SetActionPropertyPayload,
|
||||
updateActionProperty,
|
||||
updateActionSuccess,
|
||||
fetchActionsForPage,
|
||||
} from "actions/actionActions";
|
||||
import {
|
||||
getDynamicBindings,
|
||||
getDynamicValue,
|
||||
isDynamicValue,
|
||||
removeBindingsFromObject,
|
||||
removeBindingsFromActionObject,
|
||||
} from "utils/DynamicBindingUtils";
|
||||
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 {
|
||||
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 { getCurrentPageId } from "selectors/editorSelectors";
|
||||
import { ToastType } from "react-toastify";
|
||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||
import * as log from "loglevel";
|
||||
import { QUERY_CONSTANT } from "constants/QueryEditorConstants";
|
||||
import { Action, RestAction } from "entities/Action";
|
||||
import { ActionData } from "reducers/entityReducers/actionsReducer";
|
||||
import { getActions } from "selectors/entitiesSelector";
|
||||
|
||||
export const getAction = (
|
||||
state: AppState,
|
||||
actionId: string,
|
||||
): 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 });
|
||||
}
|
||||
}
|
||||
import {
|
||||
getAction,
|
||||
getCurrentPageNameByActionId,
|
||||
getPageNameByPageId,
|
||||
} from "selectors/entitiesSelector";
|
||||
|
||||
export function* createActionSaga(actionPayload: ReduxAction<RestAction>) {
|
||||
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(
|
||||
action: ReduxAction<{
|
||||
id: string;
|
||||
|
|
@ -683,12 +214,8 @@ function* moveActionSaga(
|
|||
name: string;
|
||||
}>,
|
||||
) {
|
||||
const drafts = yield select(state => state.ui.apiPane.drafts);
|
||||
const dirty = action.payload.id in drafts;
|
||||
const actionObject: RestAction = dirty
|
||||
? drafts[action.payload.id]
|
||||
: yield select(getAction, action.payload.id);
|
||||
const withoutBindings = removeBindingsFromObject(actionObject);
|
||||
const actionObject: RestAction = yield select(getAction, action.payload.id);
|
||||
const withoutBindings = removeBindingsFromActionObject(actionObject);
|
||||
try {
|
||||
const response = yield ActionAPI.moveAction({
|
||||
action: {
|
||||
|
|
@ -729,13 +256,9 @@ function* moveActionSaga(
|
|||
function* copyActionSaga(
|
||||
action: ReduxAction<{ id: string; destinationPageId: string; name: string }>,
|
||||
) {
|
||||
const drafts = yield select(state => state.ui.apiPane.drafts);
|
||||
const dirty = action.payload.id in drafts;
|
||||
let actionObject = dirty
|
||||
? drafts[action.payload.id]
|
||||
: yield select(getAction, action.payload.id);
|
||||
let actionObject: RestAction = yield select(getAction, action.payload.id);
|
||||
if (action.payload.destinationPageId !== actionObject.pageId) {
|
||||
actionObject = removeBindingsFromObject(actionObject);
|
||||
actionObject = removeBindingsFromActionObject(actionObject);
|
||||
}
|
||||
try {
|
||||
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() {
|
||||
yield all([
|
||||
takeEvery(ReduxActionTypes.SET_ACTION_PROPERTY, setActionPropertySaga),
|
||||
takeEvery(ReduxActionTypes.FETCH_ACTIONS_INIT, fetchActionsSaga),
|
||||
takeEvery(ReduxActionTypes.EXECUTE_ACTION, executeAppAction),
|
||||
takeLatest(ReduxActionTypes.RUN_API_REQUEST, runApiActionSaga),
|
||||
takeEvery(ReduxActionTypes.CREATE_ACTION_INIT, createActionSaga),
|
||||
takeLatest(ReduxActionTypes.UPDATE_ACTION_INIT, updateActionSaga),
|
||||
takeLatest(ReduxActionTypes.DELETE_ACTION_INIT, deleteActionSaga),
|
||||
takeLatest(ReduxActionTypes.SAVE_API_NAME, saveApiNameSaga),
|
||||
takeLatest(
|
||||
ReduxActionTypes.EXECUTE_PAGE_LOAD_ACTIONS,
|
||||
executePageLoadActionsSaga,
|
||||
),
|
||||
takeLatest(ReduxActionTypes.MOVE_ACTION_INIT, moveActionSaga),
|
||||
takeLatest(ReduxActionTypes.COPY_ACTION_INIT, copyActionSaga),
|
||||
takeLatest(
|
||||
|
|
|
|||
|
|
@ -2,17 +2,7 @@
|
|||
* Handles the Api pane ui state. It looks into the routing based on actions too
|
||||
* */
|
||||
import _ from "lodash";
|
||||
import {
|
||||
all,
|
||||
select,
|
||||
put,
|
||||
takeEvery,
|
||||
take,
|
||||
call,
|
||||
race,
|
||||
delay,
|
||||
} from "redux-saga/effects";
|
||||
import { getFormSyncErrors } from "redux-form";
|
||||
import { all, select, put, takeEvery, take, call } from "redux-saga/effects";
|
||||
import {
|
||||
ReduxAction,
|
||||
ReduxActionErrorTypes,
|
||||
|
|
@ -46,36 +36,20 @@ import {
|
|||
getDataSources,
|
||||
} from "selectors/editorSelectors";
|
||||
import { initialize, autofill, change } from "redux-form";
|
||||
import { getAction } from "./ActionSagas";
|
||||
import { AppState } from "reducers";
|
||||
import { Property } from "api/ActionAPI";
|
||||
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 { getPluginIdOfPackageName } from "sagas/selectors";
|
||||
import { getActions, getPlugins } from "selectors/entitiesSelector";
|
||||
import { getAction, getActions, getPlugins } from "selectors/entitiesSelector";
|
||||
import { ActionData } from "reducers/entityReducers/actionsReducer";
|
||||
import { createActionRequest } from "actions/actionActions";
|
||||
import { createActionRequest, setActionProperty } from "actions/actionActions";
|
||||
import { Datasource } from "api/DatasourcesApi";
|
||||
import { Plugin } from "api/PluginApi";
|
||||
import { PLUGIN_PACKAGE_DBS } from "constants/QueryEditorConstants";
|
||||
import { RestAction } from "entities/Action";
|
||||
import { isDynamicValue } from "utils/DynamicBindingUtils";
|
||||
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 getLastUsedEditorPage = (state: AppState) =>
|
||||
state.ui.apiPane.lastUsedEditorPage;
|
||||
|
|
@ -229,25 +203,20 @@ function* changeApiSaga(actionPayload: ReduxAction<{ id: string }>) {
|
|||
}
|
||||
const action = yield select(getAction, id);
|
||||
if (!action) return;
|
||||
const draft = yield select(getApiDraft, id);
|
||||
let data;
|
||||
|
||||
if (_.isEmpty(draft)) {
|
||||
data = action;
|
||||
} else {
|
||||
data = draft;
|
||||
}
|
||||
|
||||
yield put(initialize(API_EDITOR_FORM_NAME, data));
|
||||
yield put(initialize(API_EDITOR_FORM_NAME, action));
|
||||
history.push(API_EDITOR_ID_URL(applicationId, pageId, id));
|
||||
|
||||
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
|
||||
yield call(syncApiParamsSaga, {
|
||||
type: ReduxFormActionTypes.ARRAY_REMOVE,
|
||||
payload: data.actionConfiguration.queryParameters,
|
||||
payload: action.actionConfiguration.queryParameters,
|
||||
meta: {
|
||||
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(
|
||||
actionPayload: ReduxActionWithMeta<string, { field: string }>,
|
||||
) {
|
||||
const field = actionPayload.meta.field;
|
||||
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 (value !== "GET") {
|
||||
const { values } = yield select(getFormData, API_EDITOR_FORM_NAME);
|
||||
const { actionConfiguration } = values;
|
||||
const actionConfigurationHeaders = actionConfiguration.headers;
|
||||
let contentType;
|
||||
|
|
@ -354,12 +256,11 @@ function* updateFormFields(
|
|||
}
|
||||
}
|
||||
} else if (field.includes("actionConfiguration.headers")) {
|
||||
const formValues = formData.values;
|
||||
const actionConfigurationHeaders = _.get(
|
||||
formValues,
|
||||
values,
|
||||
"actionConfiguration.headers",
|
||||
);
|
||||
const apiId = _.get(formValues, "id");
|
||||
const apiId = _.get(values, "id");
|
||||
let displayFormat;
|
||||
|
||||
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(
|
||||
actionPayload: ReduxActionWithMeta<string, { field: string; form: string }>,
|
||||
) {
|
||||
const { form, field } = actionPayload.meta;
|
||||
if (form !== API_EDITOR_FORM_NAME) 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([
|
||||
call(updateDynamicBindingsSaga, actionPayload),
|
||||
call(validateInputSaga),
|
||||
call(updateDraftsSaga),
|
||||
call(syncApiParamsSaga, actionPayload),
|
||||
call(updateFormFields, actionPayload),
|
||||
]);
|
||||
|
|
@ -442,25 +337,10 @@ function* handleActionCreatedSaga(actionPayload: ReduxAction<RestAction>) {
|
|||
}
|
||||
}
|
||||
|
||||
function* handleActionUpdatedSaga(
|
||||
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;
|
||||
function* handleActionDeletedSaga() {
|
||||
const applicationId = yield select(getCurrentApplicationId);
|
||||
const pageId = yield select(getCurrentPageId);
|
||||
history.push(API_EDITOR_URL(applicationId, pageId));
|
||||
yield put({
|
||||
type: ReduxActionTypes.DELETE_API_DRAFT,
|
||||
payload: { id },
|
||||
});
|
||||
}
|
||||
|
||||
function* handleMoveOrCopySaga(actionPayload: ReduxAction<{ id: string }>) {
|
||||
|
|
@ -571,7 +451,6 @@ export default function* root() {
|
|||
takeEvery(ReduxActionTypes.INIT_API_PANE, initApiPaneSaga),
|
||||
takeEvery(ReduxActionTypes.API_PANE_CHANGE_API, changeApiSaga),
|
||||
takeEvery(ReduxActionTypes.CREATE_ACTION_SUCCESS, handleActionCreatedSaga),
|
||||
takeEvery(ReduxActionTypes.UPDATE_ACTION_SUCCESS, handleActionUpdatedSaga),
|
||||
takeEvery(ReduxActionTypes.DELETE_ACTION_SUCCESS, handleActionDeletedSaga),
|
||||
takeEvery(ReduxActionTypes.MOVE_ACTION_SUCCESS, handleMoveOrCopySaga),
|
||||
takeEvery(ReduxActionTypes.COPY_ACTION_SUCCESS, handleMoveOrCopySaga),
|
||||
|
|
|
|||
|
|
@ -24,6 +24,10 @@ const BATCH_PRIORITY = {
|
|||
priority: 1,
|
||||
needsSaga: true,
|
||||
},
|
||||
[ReduxActionTypes.UPDATE_ACTION_PROPERTY]: {
|
||||
priority: 1,
|
||||
needsSaga: false,
|
||||
},
|
||||
};
|
||||
|
||||
const batches: ReduxAction<any>[][] = [];
|
||||
|
|
|
|||
|
|
@ -27,38 +27,20 @@ import {
|
|||
getCurrentPageId,
|
||||
} from "selectors/editorSelectors";
|
||||
import { change, initialize } from "redux-form";
|
||||
import {
|
||||
extractBindingsFromAction,
|
||||
getAction,
|
||||
getActionParams,
|
||||
getActionTimeout,
|
||||
} from "./ActionSagas";
|
||||
import { AppState } from "reducers";
|
||||
import ActionAPI, {
|
||||
PaginationField,
|
||||
ExecuteActionRequest,
|
||||
ActionApiResponse,
|
||||
Property,
|
||||
} from "api/ActionAPI";
|
||||
import ActionAPI, { Property } from "api/ActionAPI";
|
||||
import { QUERY_CONSTANT } from "constants/QueryEditorConstants";
|
||||
import { changeQuery, deleteQuerySuccess } from "actions/queryPaneActions";
|
||||
import { AppToaster } from "components/editorComponents/ToastComponent";
|
||||
import { ToastType } from "react-toastify";
|
||||
import { PageAction } from "constants/ActionConstants";
|
||||
import { isDynamicValue } from "utils/DynamicBindingUtils";
|
||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||
import { GenericApiResponse } from "api/ApiResponses";
|
||||
import { validateResponse } from "./ErrorSagas";
|
||||
import { getQueryName } from "selectors/entitiesSelector";
|
||||
import { QueryAction, RestAction } from "entities/Action";
|
||||
import { getAction, getQueryName } from "selectors/entitiesSelector";
|
||||
import { RestAction } from "entities/Action";
|
||||
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) =>
|
||||
state.entities.actions.map(a => a.config);
|
||||
|
||||
|
|
@ -117,34 +99,19 @@ function* changeQuerySaga(
|
|||
return;
|
||||
}
|
||||
|
||||
const draft = yield select(getQueryDraft, id);
|
||||
const data = _.isEmpty(draft) ? action : draft;
|
||||
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);
|
||||
}
|
||||
|
||||
function* saveQueryAction() {
|
||||
const { values } = yield select(getFormData, QUERY_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 },
|
||||
});
|
||||
|
||||
yield put(
|
||||
updateAction({
|
||||
data: values,
|
||||
}),
|
||||
);
|
||||
}
|
||||
yield put(
|
||||
updateAction({
|
||||
data: values,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
function* updateDynamicBindingsSaga(
|
||||
|
|
@ -223,88 +190,7 @@ function* handleMoveOrCopySaga(actionPayload: ReduxAction<{ id: string }>) {
|
|||
}
|
||||
}
|
||||
|
||||
export function* executeQuerySaga(
|
||||
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 }>) {
|
||||
function* deleteQuerySaga(actionPayload: ReduxAction<{ id: string }>) {
|
||||
try {
|
||||
const id = actionPayload.payload.id;
|
||||
const response: GenericApiResponse<RestAction> = yield ActionAPI.deleteAction(
|
||||
|
|
@ -337,7 +223,6 @@ export default function* root() {
|
|||
takeEvery(ReduxActionTypes.DELETE_QUERY_SUCCESS, handleQueryDeletedSaga),
|
||||
takeEvery(ReduxActionTypes.MOVE_ACTION_SUCCESS, handleMoveOrCopySaga),
|
||||
takeEvery(ReduxActionTypes.COPY_ACTION_SUCCESS, handleMoveOrCopySaga),
|
||||
takeLatest(ReduxActionTypes.EXECUTE_QUERY_REQUEST, executeQuerySaga),
|
||||
takeEvery(ReduxActionTypes.QUERY_PANE_CHANGE, changeQuerySaga),
|
||||
takeEvery(ReduxActionTypes.INIT_QUERY_PANE, initQueryPaneSaga),
|
||||
// Intercepting the redux-form change actionType
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { all, spawn } from "redux-saga/effects";
|
|||
import pageSagas from "sagas/PageSagas";
|
||||
import { fetchWidgetCardsSaga } from "./WidgetSidebarSagas";
|
||||
import { watchActionSagas } from "./ActionSagas";
|
||||
import { watchActionExecutionSagas } from "sagas/ActionExecutionSagas";
|
||||
import widgetOperationSagas from "./WidgetOperationSagas";
|
||||
import errorSagas from "./ErrorSagas";
|
||||
import configsSagas from "./ConfigsSagas";
|
||||
|
|
@ -25,6 +26,7 @@ export function* rootSaga() {
|
|||
spawn(pageSagas),
|
||||
spawn(fetchWidgetCardsSaga),
|
||||
spawn(watchActionSagas),
|
||||
spawn(watchActionExecutionSagas),
|
||||
spawn(widgetOperationSagas),
|
||||
spawn(errorSagas),
|
||||
spawn(configsSagas),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { createSelector } from "reselect";
|
||||
import { getActionDrafts, getActionsForCurrentPage } from "./entitiesSelector";
|
||||
import { getActionsForCurrentPage } from "./entitiesSelector";
|
||||
import { ActionDataState } from "reducers/entityReducers/actionsReducer";
|
||||
import { getEvaluatedDataTree } from "utils/DynamicBindingUtils";
|
||||
import { DataTree, DataTreeFactory } from "entities/DataTree/dataTreeFactory";
|
||||
|
|
@ -40,16 +40,14 @@ import { getPageList } from "./appViewSelectors";
|
|||
export const getUnevaluatedDataTree = (withFunctions?: boolean) =>
|
||||
createSelector(
|
||||
getActionsForCurrentPage,
|
||||
getActionDrafts,
|
||||
getWidgets,
|
||||
getWidgetsMeta,
|
||||
getPageList,
|
||||
(actions, actionDrafts, widgets, widgetsMeta, pageListPayload) => {
|
||||
(actions, widgets, widgetsMeta, pageListPayload) => {
|
||||
const pageList = pageListPayload || [];
|
||||
return DataTreeFactory.create(
|
||||
{
|
||||
actions,
|
||||
actionDrafts,
|
||||
widgets,
|
||||
widgetsMeta,
|
||||
pageList,
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
import { AppState } from "reducers";
|
||||
import {
|
||||
ActionDataState,
|
||||
ActionData,
|
||||
ActionDataState,
|
||||
} from "reducers/entityReducers/actionsReducer";
|
||||
import { ActionResponse } from "api/ActionAPI";
|
||||
import { QUERY_CONSTANT } from "constants/QueryEditorConstants";
|
||||
import { API_CONSTANT } from "constants/ApiEditorConstants";
|
||||
import { PLUGIN_TYPE_API } from "constants/ApiEditorConstants";
|
||||
import { createSelector } from "reselect";
|
||||
import { Page } from "constants/ReduxActionConstants";
|
||||
import { Datasource } from "api/DatasourcesApi";
|
||||
import { Action } from "entities/Action";
|
||||
import { find } from "lodash";
|
||||
|
||||
export const getEntities = (state: AppState): AppState["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 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 => {
|
||||
const action = state.entities.actions.find((action: ActionData) => {
|
||||
return action.config.id === actionId;
|
||||
|
|
@ -138,14 +134,6 @@ export const getQueryName = (state: AppState, actionId: string): string => {
|
|||
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 => {
|
||||
return state.entities.actions.filter((action: ActionData) => {
|
||||
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 => {
|
||||
const responses: Record<string, ActionResponse | undefined> = {};
|
||||
|
||||
|
|
@ -178,3 +164,48 @@ export const getActionResponses = createSelector(getActions, actions => {
|
|||
|
||||
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 = (
|
||||
state: AppState,
|
||||
formName: string,
|
||||
) => { values: object; dirty: boolean; valid: boolean };
|
||||
) => { values: object; valid: boolean };
|
||||
|
||||
export const getFormData: GetFormData = (state, formName) => {
|
||||
const initialValues = getFormInitialValues(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);
|
||||
return { initialValues, values, dirty, valid };
|
||||
return { initialValues, values, valid };
|
||||
};
|
||||
|
||||
export const getApiName = (state: AppState, id: string) => {
|
||||
|
|
|
|||
|
|
@ -20,8 +20,9 @@ import equal from "fast-deep-equal/es6";
|
|||
import WidgetFactory from "utils/WidgetFactory";
|
||||
import { AppToaster } from "components/editorComponents/ToastComponent";
|
||||
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 withBindings = string.replace(DATA_BIND_REGEX_GLOBAL, "{{ }}");
|
||||
return JSON.parse(withBindings);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user