PromucFlow_constructor/app/client/src/sagas/DebuggerSagas.ts
Rishabh Rathod bef48e8a79
chore: Config changes (#8564)
Change eslint config for console logging from error to warn
Add babel auto-import resolver to avoid using relative paths
2021-11-05 11:19:19 +05:30

495 lines
14 KiB
TypeScript

import {
addErrorLog,
debuggerLog,
debuggerLogInit,
deleteErrorLog,
LogDebuggerErrorAnalyticsPayload,
} from "actions/debuggerActions";
import { ReduxAction, ReduxActionTypes } from "constants/ReduxActionConstants";
import { ENTITY_TYPE, Log, LogActionPayload } from "entities/AppsmithConsole";
import {
all,
call,
fork,
put,
select,
take,
takeEvery,
} from "redux-saga/effects";
import { findIndex, get, isMatch, set } from "lodash";
import { getDebuggerErrors } from "selectors/debuggerSelectors";
import {
getAction,
getPlugin,
getJSCollection,
} from "selectors/entitiesSelector";
import { Action, PluginType } from "entities/Action";
import { JSCollection } from "entities/JSCollection";
import LOG_TYPE from "entities/AppsmithConsole/logtype";
import { DataTree } from "entities/DataTree/dataTreeFactory";
import {
getDataTree,
getEvaluationInverseDependencyMap,
} from "selectors/dataTreeSelectors";
import {
getEntityNameAndPropertyPath,
isAction,
isWidget,
} from "workers/evaluationUtils";
import { getDependencyChain } from "components/editorComponents/Debugger/helpers";
import {
ACTION_CONFIGURATION_UPDATED,
createMessage,
WIDGET_PROPERTIES_UPDATED,
} from "constants/messages";
import AppsmithConsole from "utils/AppsmithConsole";
import { getWidget } from "./selectors";
import AnalyticsUtil from "utils/AnalyticsUtil";
import { Plugin } from "api/PluginApi";
import { getCurrentPageId } from "selectors/editorSelectors";
import { WidgetProps } from "widgets/BaseWidget";
import * as log from "loglevel";
// Saga to format action request values to be shown in the debugger
function* formatActionRequestSaga(
payload: LogActionPayload,
requestPath?: any,
) {
// If there are no headers or body we don't format anything.
if (!payload.source || !payload.state || !requestPath) {
return payload;
}
const request = get(payload, requestPath);
const source = payload.source;
const action: Action | undefined = yield select(getAction, source.id);
// Only formatting for apis and not queries
if (action && action.pluginType === PluginType.API) {
// Formatting api headers here
if (request.headers) {
let formattedHeaders = [];
// Convert headers from Record<string, array>[] to Record<string, string>[]
// for showing in the logs
formattedHeaders = Object.keys(request.headers).map((key: string) => {
const value = request.headers[key];
return {
[key]: value[0],
};
});
set(payload, `${requestPath}.headers`, formattedHeaders);
}
// Formatting api body
if (request.body) {
let body = request.body;
try {
body = JSON.parse(body);
set(payload, `${requestPath}.body`, body);
} catch (e) {
// Nothing to do here, we show the api body as it is if it cannot be shown as
// an object
}
}
// Return the final payload to be logged
return payload;
} else {
return payload;
}
}
function* onEntityDeleteSaga(payload: Log) {
const source = payload.source;
if (!source) {
yield put(debuggerLog(payload));
return;
}
const errors: Record<string, Log> = yield select(getDebuggerErrors);
const errorIds = Object.keys(errors);
errorIds.map((e) => {
const includes = e.includes(source.id);
if (includes) {
AppsmithConsole.deleteError(e, payload.analytics);
}
});
yield put(debuggerLog(payload));
}
function* logDependentEntityProperties(payload: Log) {
const { source, state } = payload;
if (!state || !source) return;
yield take(ReduxActionTypes.SET_EVALUATED_TREE);
const dataTree: DataTree = yield select(getDataTree);
const propertyPath = `${source.name}.` + payload.source?.propertyPath;
const inverseDependencyMap = yield select(getEvaluationInverseDependencyMap);
const finalValue = getDependencyChain(propertyPath, inverseDependencyMap);
yield all(
finalValue.map((path) => {
const entityInfo = getEntityNameAndPropertyPath(path);
const entity = dataTree[entityInfo.entityName];
let log = {
...payload,
state: {
[entityInfo.propertyPath]: get(dataTree, path),
},
};
if (isAction(entity)) {
log = {
...log,
text: createMessage(ACTION_CONFIGURATION_UPDATED),
source: {
type: ENTITY_TYPE.ACTION,
name: entityInfo.entityName,
id: entity.actionId,
},
};
} else if (isWidget(entity)) {
log = {
...log,
text: createMessage(WIDGET_PROPERTIES_UPDATED),
source: {
type: ENTITY_TYPE.WIDGET,
name: entityInfo.entityName,
id: entity.widgetId,
},
};
}
return put(debuggerLog(log));
}),
);
}
function* onTriggerPropertyUpdates(payload: Log) {
const dataTree: DataTree = yield select(getDataTree);
const source = payload.source;
if (!source || !source.propertyPath) return;
const widget = dataTree[source.name];
// If property is not a trigger property we ignore
if (!isWidget(widget) || !(source.propertyPath in widget.triggerPaths))
return;
// If the value of the property is empty(or set to 'No Action')
if (widget[source.propertyPath] === "") {
AppsmithConsole.deleteError(`${source.id}-${source.propertyPath}`);
}
}
function* debuggerLogSaga(action: ReduxAction<Log>) {
const { payload } = action;
switch (payload.logType) {
case LOG_TYPE.WIDGET_UPDATE:
yield put(debuggerLog(payload));
yield call(logDependentEntityProperties, payload);
yield call(onTriggerPropertyUpdates, payload);
return;
case LOG_TYPE.ACTION_UPDATE:
yield put(debuggerLog(payload));
yield call(logDependentEntityProperties, payload);
return;
case LOG_TYPE.JS_ACTION_UPDATE:
yield put(debuggerLog(payload));
return;
case LOG_TYPE.JS_PARSE_ERROR:
yield put(addErrorLog(payload));
break;
case LOG_TYPE.JS_PARSE_SUCCESS:
AppsmithConsole.deleteError(payload.source?.id ?? "");
break;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
// Fall through intentional here
case LOG_TYPE.TRIGGER_EVAL_ERROR:
yield put(debuggerLog(payload));
case LOG_TYPE.EVAL_ERROR:
case LOG_TYPE.EVAL_WARNING:
case LOG_TYPE.WIDGET_PROPERTY_VALIDATION_ERROR:
if (payload.source && payload.source.propertyPath) {
if (payload.text) {
yield put(addErrorLog(payload));
}
}
break;
case LOG_TYPE.ACTION_EXECUTION_ERROR:
{
const formattedLog = yield call(
formatActionRequestSaga,
payload,
"state",
);
yield put(addErrorLog(formattedLog));
yield put(debuggerLog(formattedLog));
}
break;
case LOG_TYPE.ACTION_EXECUTION_SUCCESS:
{
const formattedLog = yield call(
formatActionRequestSaga,
payload,
"state.request",
);
AppsmithConsole.deleteError(payload.source?.id ?? "");
yield put(debuggerLog(formattedLog));
}
break;
case LOG_TYPE.ENTITY_DELETED:
yield fork(onEntityDeleteSaga, payload);
break;
default:
yield put(debuggerLog(payload));
}
}
// This saga is intended for analytics only
function* logDebuggerErrorAnalyticsSaga(
action: ReduxAction<LogDebuggerErrorAnalyticsPayload>,
) {
try {
const { payload } = action;
const currentPageId = yield select(getCurrentPageId);
if (payload.entityType === ENTITY_TYPE.WIDGET) {
const widget: WidgetProps | undefined = yield select(
getWidget,
payload.entityId,
);
const widgetType = widget?.type || payload?.analytics?.widgetType || "";
const propertyPath = `${widgetType}.${payload.propertyPath}`;
// Sending widget type for widgets
AnalyticsUtil.logEvent(payload.eventName, {
entityType: widgetType,
propertyPath,
errorMessages: payload.errorMessages,
pageId: currentPageId,
errorMessage: payload.errorMessage,
errorType: payload.errorType,
});
} else if (payload.entityType === ENTITY_TYPE.ACTION) {
const action: Action | undefined = yield select(
getAction,
payload.entityId,
);
const pluginId = action?.pluginId || payload?.analytics?.pluginId || "";
const plugin: Plugin = yield select(getPlugin, pluginId);
const pluginName = plugin.name.replace(/ /g, "");
let propertyPath = `${pluginName}`;
if (payload.propertyPath) {
propertyPath += `.${payload.propertyPath}`;
}
// Sending plugin name for actions
AnalyticsUtil.logEvent(payload.eventName, {
entityType: pluginName,
propertyPath,
errorMessages: payload.errorMessages,
pageId: currentPageId,
errorMessage: payload.errorMessage,
errorType: payload.errorType,
errorSubType: payload.errorSubType,
});
} else if (payload.entityType === ENTITY_TYPE.JSACTION) {
const action: JSCollection = yield select(
getJSCollection,
payload.entityId,
);
const plugin: Plugin = yield select(getPlugin, action.pluginId);
const pluginName = plugin.name.replace(/ /g, "");
// Sending plugin name for actions
AnalyticsUtil.logEvent(payload.eventName, {
entityType: pluginName,
propertyPath: payload.propertyPath,
errorMessages: payload.errorMessages,
pageId: currentPageId,
});
}
} catch (e) {
log.error(e);
}
}
function* addDebuggerErrorLogSaga(action: ReduxAction<Log>) {
const payload = action.payload;
const errors: Record<string, Log> = yield select(getDebuggerErrors);
if (!payload.source || !payload.id) return;
yield put(debuggerLogInit(payload));
const analyticsPayload = {
entityName: payload.source.name,
entityType: payload.source.type,
entityId: payload.source.id,
propertyPath: payload.source.propertyPath ?? "",
};
// If this is a new error
if (!(payload.id in errors)) {
const errorMessages = payload.messages ?? [];
yield put({
type: ReduxActionTypes.DEBUGGER_ERROR_ANALYTICS,
payload: {
...analyticsPayload,
eventName: "DEBUGGER_NEW_ERROR",
errorMessages: payload.messages,
},
});
// Log analytics for new error messages
if (errorMessages.length && payload) {
yield all(
errorMessages.map((errorMessage) =>
put({
type: ReduxActionTypes.DEBUGGER_ERROR_ANALYTICS,
payload: {
...analyticsPayload,
eventName: "DEBUGGER_NEW_ERROR_MESSAGE",
errorMessage: errorMessage.message,
errorType: errorMessage.type,
errorSubType: errorMessage.subType,
},
}),
),
);
}
} else {
const updatedErrorMessages = payload.messages ?? [];
const existingErrorMessages = errors[payload.id].messages ?? [];
// Log new error messages
yield all(
updatedErrorMessages.map((updatedErrorMessage) => {
const exists = findIndex(
existingErrorMessages,
(existingErrorMessage) => {
return isMatch(existingErrorMessage, updatedErrorMessage);
},
);
if (exists < 0) {
return put({
type: ReduxActionTypes.DEBUGGER_ERROR_ANALYTICS,
payload: {
...analyticsPayload,
eventName: "DEBUGGER_NEW_ERROR_MESSAGE",
errorMessage: updatedErrorMessage.message,
errorType: updatedErrorMessage.type,
errorSubType: updatedErrorMessage.subType,
},
});
}
}),
);
// Log resolved error messages
yield all(
existingErrorMessages.map((existingErrorMessage) => {
const exists = findIndex(
updatedErrorMessages,
(updatedErrorMessage) => {
return isMatch(updatedErrorMessage, existingErrorMessage);
},
);
if (exists < 0) {
return put({
type: ReduxActionTypes.DEBUGGER_ERROR_ANALYTICS,
payload: {
...analyticsPayload,
eventName: "DEBUGGER_RESOLVED_ERROR_MESSAGE",
errorMessage: existingErrorMessage.message,
errorType: existingErrorMessage.type,
errorSubType: existingErrorMessage.subType,
},
});
}
}),
);
}
}
function* deleteDebuggerErrorLogSaga(
action: ReduxAction<{ id: string; analytics: Log["analytics"] }>,
) {
const errors: Record<string, Log> = yield select(getDebuggerErrors);
// If no error exists with this id
if (!(action.payload.id in errors)) return;
const error = errors[action.payload.id];
if (!error || !error.source) return;
const analyticsPayload = {
entityName: error.source.name,
entityType: error.source.type,
entityId: error.source.id,
propertyPath: error.source.propertyPath ?? "",
analytics: action.payload.analytics,
};
const errorMessages = error.messages;
yield put({
type: ReduxActionTypes.DEBUGGER_ERROR_ANALYTICS,
payload: {
...analyticsPayload,
eventName: "DEBUGGER_RESOLVED_ERROR",
errorMessages,
},
});
if (errorMessages) {
yield all(
errorMessages.map((errorMessage) => {
return put({
type: ReduxActionTypes.DEBUGGER_ERROR_ANALYTICS,
payload: {
...analyticsPayload,
eventName: "DEBUGGER_RESOLVED_ERROR_MESSAGE",
errorMessage: errorMessage.message,
errorType: errorMessage.type,
errorSubType: errorMessage.subType,
},
});
}),
);
}
yield put(deleteErrorLog(action.payload.id));
}
export default function* debuggerSagasListeners() {
yield all([
takeEvery(ReduxActionTypes.DEBUGGER_LOG_INIT, debuggerLogSaga),
takeEvery(
ReduxActionTypes.DEBUGGER_ERROR_ANALYTICS,
logDebuggerErrorAnalyticsSaga,
),
takeEvery(
ReduxActionTypes.DEBUGGER_ADD_ERROR_LOG_INIT,
addDebuggerErrorLogSaga,
),
takeEvery(
ReduxActionTypes.DEBUGGER_DELETE_ERROR_LOG_INIT,
deleteDebuggerErrorLogSaga,
),
]);
}