> Pull Request Template > > Use this template to quickly create a well written pull request. Delete all quotes before creating the pull request. > ## Description There are multiple refactors and split for query module's creator flow changes which involves module input -- it's a new entity introduced as part of modules project #### PR fixes following issue(s) Fixes # (issue number) Part of https://app.zenhub.com/workspaces/modules-pod-63e0d668a7fea03850c89c6f/issues/gh/appsmithorg/appsmith/27352 #### Type of change - Chore (housekeeping or task changes that don't impact user perception) > > ## Testing > #### How Has This Been Tested? > Please describe the tests that you ran to verify your changes. Also list any relevant details for your test configuration. > Delete anything that is not relevant - [ ] Manual - [ ] JUnit - [ ] Jest - [ ] Cypress > > #### Test Plan > Add Testsmith test cases links that relate to this PR > > #### Issues raised during DP testing > Link issues raised during DP testing for better visiblity and tracking (copy link from comments dropped on this PR) > > > ## Checklist: #### Dev activity - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag #### QA activity: - [ ] [Speedbreak features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-) have been covered - [ ] Test plan covers all impacted features and [areas of interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-) - [ ] Test plan has been peer reviewed by project stakeholders and other QA members - [ ] Manually tested functionality on DP - [ ] We had an implementation alignment call with stakeholders post QA Round 2 - [ ] Cypress test cases have been added and approved by SDET/manual QA - [ ] Added `Test Plan Approved` label after Cypress tests were reviewed - [ ] Added `Test Plan Approved` label after JUnit tests were reviewed
713 lines
22 KiB
TypeScript
713 lines
22 KiB
TypeScript
import type { LogDebuggerErrorAnalyticsPayload } from "actions/debuggerActions";
|
|
import {
|
|
addErrorLogs,
|
|
debuggerLog,
|
|
debuggerLogInit,
|
|
deleteErrorLog,
|
|
} from "actions/debuggerActions";
|
|
import type { ReduxAction } from "@appsmith/constants/ReduxActionConstants";
|
|
import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants";
|
|
import type {
|
|
Log,
|
|
LogActionPayload,
|
|
LogObject,
|
|
} from "entities/AppsmithConsole";
|
|
import { ENTITY_TYPE, LOG_CATEGORY } from "entities/AppsmithConsole";
|
|
import {
|
|
all,
|
|
call,
|
|
fork,
|
|
put,
|
|
select,
|
|
take,
|
|
takeEvery,
|
|
} from "redux-saga/effects";
|
|
import { findIndex, flatten, get, isEmpty, isMatch, set } from "lodash";
|
|
import { getDebuggerErrors } from "selectors/debuggerSelectors";
|
|
import {
|
|
getAction,
|
|
getPlugin,
|
|
getJSCollection,
|
|
getAppMode,
|
|
} from "@appsmith/selectors/entitiesSelector";
|
|
import type { Action } from "entities/Action";
|
|
import { PluginType } from "entities/Action";
|
|
import type { JSCollection } from "entities/JSCollection";
|
|
import LOG_TYPE from "entities/AppsmithConsole/logtype";
|
|
import type { ConfigTree, DataTree } from "@appsmith/entities/DataTree/types";
|
|
import {
|
|
getConfigTree,
|
|
getDataTree,
|
|
getEvaluationInverseDependencyMap,
|
|
} from "selectors/dataTreeSelectors";
|
|
import {
|
|
createLogTitleString,
|
|
getDependencyChain,
|
|
} from "components/editorComponents/Debugger/helpers";
|
|
import {
|
|
ACTION_CONFIGURATION_UPDATED,
|
|
createMessage,
|
|
WIDGET_PROPERTIES_UPDATED,
|
|
} from "@appsmith/constants/messages";
|
|
import AppsmithConsole from "utils/AppsmithConsole";
|
|
import { getWidget } from "./selectors";
|
|
import AnalyticsUtil from "utils/AnalyticsUtil";
|
|
import type { Plugin } from "api/PluginApi";
|
|
import { getCurrentPageId } from "selectors/editorSelectors";
|
|
import type { WidgetProps } from "widgets/BaseWidget";
|
|
import * as log from "loglevel";
|
|
import type { DependencyMap } from "utils/DynamicBindingUtils";
|
|
import type { TriggerMeta } from "@appsmith/sagas/ActionExecution/ActionExecutionSagas";
|
|
import {
|
|
getEntityNameAndPropertyPath,
|
|
isAction,
|
|
isWidget,
|
|
} from "@appsmith/workers/Evaluation/evaluationUtils";
|
|
import { getCurrentEnvironmentDetails } from "@appsmith/selectors/environmentSelectors";
|
|
|
|
// 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 sortedLogs = payload.reduce(
|
|
(
|
|
sortedLogs: {
|
|
withSource: Log[];
|
|
withoutSource: Log[];
|
|
},
|
|
log,
|
|
) => {
|
|
return log.source
|
|
? { ...sortedLogs, withSource: [...sortedLogs.withSource, log] }
|
|
: { ...sortedLogs, withSource: [...sortedLogs.withoutSource, log] };
|
|
},
|
|
{
|
|
withSource: [],
|
|
withoutSource: [],
|
|
},
|
|
);
|
|
if (!isEmpty(sortedLogs.withoutSource)) {
|
|
yield put(debuggerLog(sortedLogs.withoutSource));
|
|
}
|
|
if (isEmpty(sortedLogs.withSource)) return;
|
|
|
|
const errors: Record<string, Log> = yield select(getDebuggerErrors);
|
|
const errorIds = Object.keys(errors);
|
|
const logSourceIds = sortedLogs.withSource.map((log) => log.source?.id);
|
|
|
|
const errorsToDelete = errorIds.reduce((errorList: Log[], currentId) => {
|
|
const isPresent = logSourceIds.some((id) => id && currentId.includes(id));
|
|
return isPresent ? [...errorList, errors[currentId]] : errorList;
|
|
}, []);
|
|
|
|
sortedLogs.withSource.filter(
|
|
(log) => log.source && errorIds.includes(log.source.id),
|
|
);
|
|
|
|
if (!isEmpty(errorsToDelete)) {
|
|
const errorPayload = errorsToDelete.map((log) => ({
|
|
id: log.id as string,
|
|
analytics: log.analytics,
|
|
}));
|
|
AppsmithConsole.deleteErrors(errorPayload);
|
|
}
|
|
yield put(debuggerLog(sortedLogs.withSource));
|
|
}
|
|
|
|
function getLogsFromDependencyChain(
|
|
dependencyChain: string[],
|
|
payload: Log,
|
|
dataTree: DataTree,
|
|
) {
|
|
return dependencyChain.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 log;
|
|
});
|
|
}
|
|
|
|
function* logDependentEntityProperties(payload: Log[]) {
|
|
const validLogs = payload.filter((log) => log.state && log.source);
|
|
if (isEmpty(validLogs)) return;
|
|
|
|
yield take(ReduxActionTypes.SET_EVALUATED_TREE);
|
|
const dataTree: DataTree = yield select(getDataTree);
|
|
const inverseDependencyMap: DependencyMap = yield select(
|
|
getEvaluationInverseDependencyMap,
|
|
);
|
|
const finalPayload: Log[][] = [];
|
|
|
|
for (const log of validLogs) {
|
|
const propertyPath = `${log.source?.name}.` + log.source?.propertyPath;
|
|
const dependencyChain = getDependencyChain(
|
|
propertyPath,
|
|
inverseDependencyMap,
|
|
);
|
|
const payloadValue = getLogsFromDependencyChain(
|
|
dependencyChain,
|
|
log,
|
|
dataTree,
|
|
);
|
|
finalPayload.push(payloadValue);
|
|
}
|
|
|
|
//logging them all at once rather than updating them individually
|
|
yield put(debuggerLog(flatten(finalPayload)));
|
|
}
|
|
|
|
function* onTriggerPropertyUpdates(payload: Log[]) {
|
|
const configTree: ConfigTree = yield select(getConfigTree);
|
|
const validLogs = payload.filter(
|
|
(log) => log.source && log.source.propertyPath,
|
|
);
|
|
if (isEmpty(validLogs)) return;
|
|
|
|
const errorsPathsToDeleteFromConsole = new Set<string>();
|
|
|
|
for (const log of validLogs) {
|
|
const { source } = log;
|
|
if (!source || !source.propertyPath) continue;
|
|
const widget = configTree[source.name];
|
|
// If property is not a trigger property we ignore
|
|
if (!isWidget(widget) || !(source.propertyPath in widget.triggerPaths))
|
|
return false;
|
|
// If the value of the property is empty(or set to 'No Action')
|
|
if (widget[source.propertyPath] === "") {
|
|
errorsPathsToDeleteFromConsole.add(`${source.id}-${source.propertyPath}`);
|
|
}
|
|
}
|
|
const errorIdsToDelete = Array.from(errorsPathsToDeleteFromConsole).map(
|
|
(path) => ({ id: path }),
|
|
);
|
|
AppsmithConsole.deleteErrors(errorIdsToDelete);
|
|
}
|
|
|
|
function* debuggerLogSaga(action: ReduxAction<Log[]>) {
|
|
const { payload: logs } = action;
|
|
// array of logs without LOG_TYPE and logs which are not handled in switch statement below.
|
|
let otherLogs: Log[] = [];
|
|
|
|
// Group logs by LOG_TYPE
|
|
const sortedLogs = logs.reduce(
|
|
(sortedLogs: Record<string, Log[]>, currentLog: Log) => {
|
|
if (currentLog.logType) {
|
|
return sortedLogs.hasOwnProperty(currentLog.logType)
|
|
? {
|
|
...sortedLogs,
|
|
[currentLog.logType]: [
|
|
...sortedLogs[currentLog.logType],
|
|
currentLog,
|
|
],
|
|
}
|
|
: {
|
|
...sortedLogs,
|
|
[currentLog.logType]: [currentLog],
|
|
};
|
|
} else {
|
|
otherLogs.push(currentLog);
|
|
return sortedLogs;
|
|
}
|
|
},
|
|
{},
|
|
);
|
|
for (const item in sortedLogs) {
|
|
const logType = Number(item);
|
|
const payload = sortedLogs[item];
|
|
switch (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(addErrorLogs(payload));
|
|
break;
|
|
case LOG_TYPE.JS_PARSE_SUCCESS: {
|
|
const errorIds = payload.map((log) => ({ id: log.source?.id ?? "" }));
|
|
AppsmithConsole.deleteErrors(errorIds);
|
|
break;
|
|
}
|
|
// @ts-expect-error: Types are not available
|
|
case LOG_TYPE.TRIGGER_EVAL_ERROR:
|
|
yield put(debuggerLog(payload));
|
|
case LOG_TYPE.EVAL_ERROR:
|
|
case LOG_TYPE.LINT_ERROR:
|
|
case LOG_TYPE.EVAL_WARNING:
|
|
case LOG_TYPE.WIDGET_PROPERTY_VALIDATION_ERROR: {
|
|
const filteredLogs = payload.filter(
|
|
(log) => log.source && log.source.propertyPath && log.text,
|
|
);
|
|
yield put(addErrorLogs(filteredLogs));
|
|
break;
|
|
}
|
|
|
|
case LOG_TYPE.ACTION_EXECUTION_ERROR:
|
|
{
|
|
const allFormatedLogs: Log[] = [];
|
|
|
|
for (const log of payload) {
|
|
const formattedLog: Log = yield call(
|
|
formatActionRequestSaga,
|
|
log,
|
|
"state",
|
|
);
|
|
allFormatedLogs.push(formattedLog);
|
|
}
|
|
yield put(addErrorLogs(allFormatedLogs));
|
|
yield put(debuggerLog(allFormatedLogs));
|
|
}
|
|
break;
|
|
case LOG_TYPE.ACTION_EXECUTION_SUCCESS:
|
|
{
|
|
const allFormatedLogs: Log[] = [];
|
|
|
|
for (const log of payload) {
|
|
const formattedLog: Log = yield call(
|
|
formatActionRequestSaga,
|
|
log,
|
|
"state.request",
|
|
);
|
|
allFormatedLogs.push(formattedLog);
|
|
}
|
|
const payloadIds = payload.map((log) => ({
|
|
id: log.source?.id ?? "",
|
|
}));
|
|
AppsmithConsole.deleteErrors(payloadIds);
|
|
|
|
yield put(debuggerLog(allFormatedLogs));
|
|
}
|
|
break;
|
|
case LOG_TYPE.ENTITY_DELETED:
|
|
yield fork(onEntityDeleteSaga, payload);
|
|
break;
|
|
default:
|
|
otherLogs = otherLogs.concat(payload);
|
|
}
|
|
}
|
|
if (!isEmpty(otherLogs)) {
|
|
yield put(debuggerLog(otherLogs));
|
|
}
|
|
}
|
|
|
|
// This saga is intended for analytics only
|
|
function* logDebuggerErrorAnalyticsSaga(
|
|
action: ReduxAction<LogDebuggerErrorAnalyticsPayload>,
|
|
) {
|
|
try {
|
|
const { payload } = action;
|
|
const currentPageId: string | undefined = 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,
|
|
errorId: payload.errorId,
|
|
errorMessages: payload.errorMessages,
|
|
pageId: currentPageId,
|
|
errorMessage: payload.errorMessage,
|
|
errorType: payload.errorType,
|
|
appMode: payload.appMode,
|
|
});
|
|
} 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,
|
|
errorId: payload.errorId,
|
|
errorMessages: payload.errorMessages,
|
|
pageId: currentPageId,
|
|
errorMessage: payload.errorMessage,
|
|
errorType: payload.errorType,
|
|
errorSubType: payload.errorSubType,
|
|
appMode: payload.appMode,
|
|
});
|
|
} else if (payload.entityType === ENTITY_TYPE.JSACTION) {
|
|
const action: JSCollection = yield select(
|
|
getJSCollection,
|
|
payload.entityId,
|
|
);
|
|
if (!action) return;
|
|
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,
|
|
errorId: payload.errorId,
|
|
propertyPath: payload.propertyPath,
|
|
errorMessages: payload.errorMessages,
|
|
pageId: currentPageId,
|
|
appMode: payload.appMode,
|
|
});
|
|
}
|
|
} catch (e) {
|
|
log.error(e);
|
|
}
|
|
}
|
|
|
|
function* addDebuggerErrorLogsSaga(action: ReduxAction<Log[]>) {
|
|
const errorLogs = action.payload;
|
|
const currentDebuggerErrors: Record<string, Log> = yield select(
|
|
getDebuggerErrors,
|
|
);
|
|
const appMode: ReturnType<typeof getAppMode> = yield select(getAppMode);
|
|
yield put(debuggerLogInit(errorLogs));
|
|
const validErrorLogs = errorLogs.filter((log) => log.source && log.id);
|
|
if (isEmpty(validErrorLogs)) return;
|
|
|
|
for (const errorLog of validErrorLogs) {
|
|
const { id, messages, source } = errorLog;
|
|
if (!source || !id) continue;
|
|
const analyticsPayload = {
|
|
entityName: source.name,
|
|
entityType: source.type,
|
|
entityId: source.id,
|
|
propertyPath: source.propertyPath ?? "",
|
|
};
|
|
|
|
// If this is a new error
|
|
if (!currentDebuggerErrors.hasOwnProperty(id)) {
|
|
const errorMessages = errorLog.messages ?? [];
|
|
|
|
yield put({
|
|
type: ReduxActionTypes.DEBUGGER_ERROR_ANALYTICS,
|
|
payload: {
|
|
...analyticsPayload,
|
|
eventName: "DEBUGGER_NEW_ERROR",
|
|
errorMessages,
|
|
appMode,
|
|
},
|
|
});
|
|
|
|
// Log analytics for new error messages
|
|
//errorID has timestamp for 1:1 mapping with new and resolved errors
|
|
if (errorMessages.length && errorLog) {
|
|
const currentEnvDetails: { id: string; name: string } = yield select(
|
|
getCurrentEnvironmentDetails,
|
|
);
|
|
yield all(
|
|
errorMessages.map((errorMessage) =>
|
|
put({
|
|
type: ReduxActionTypes.DEBUGGER_ERROR_ANALYTICS,
|
|
payload: {
|
|
...analyticsPayload,
|
|
environmentId: currentEnvDetails.id,
|
|
environmentName: currentEnvDetails.name,
|
|
eventName: "DEBUGGER_NEW_ERROR_MESSAGE",
|
|
errorId: errorLog.id + "_" + errorLog.timestamp,
|
|
errorMessage: errorMessage.message,
|
|
errorType: errorMessage.type,
|
|
errorSubType: errorMessage.subType,
|
|
appMode,
|
|
},
|
|
}),
|
|
),
|
|
);
|
|
}
|
|
} else {
|
|
const updatedErrorMessages = messages ?? [];
|
|
const existingErrorMessages = currentDebuggerErrors[id].messages ?? [];
|
|
const currentEnvDetails: { id: string; name: string } = yield select(
|
|
getCurrentEnvironmentDetails,
|
|
);
|
|
// Log new error messages
|
|
yield all(
|
|
updatedErrorMessages.map((updatedErrorMessage) => {
|
|
const exists = findIndex(
|
|
existingErrorMessages,
|
|
(existingErrorMessage) => {
|
|
return isMatch(existingErrorMessage, updatedErrorMessage);
|
|
},
|
|
);
|
|
|
|
if (exists < 0) {
|
|
//errorID has timestamp for 1:1 mapping with new and resolved errors
|
|
return put({
|
|
type: ReduxActionTypes.DEBUGGER_ERROR_ANALYTICS,
|
|
payload: {
|
|
...analyticsPayload,
|
|
environmentId: currentEnvDetails.id,
|
|
environmentName: currentEnvDetails.name,
|
|
eventName: "DEBUGGER_NEW_ERROR_MESSAGE",
|
|
errorId: errorLog.id + "_" + errorLog.timestamp,
|
|
errorMessage: updatedErrorMessage.message,
|
|
errorType: updatedErrorMessage.type,
|
|
errorSubType: updatedErrorMessage.subType,
|
|
appMode,
|
|
},
|
|
});
|
|
}
|
|
}),
|
|
);
|
|
// Log resolved error messages
|
|
yield all(
|
|
existingErrorMessages.map((existingErrorMessage) => {
|
|
const exists = findIndex(
|
|
updatedErrorMessages,
|
|
(updatedErrorMessage) => {
|
|
return isMatch(updatedErrorMessage, existingErrorMessage);
|
|
},
|
|
);
|
|
|
|
if (exists < 0) {
|
|
//errorID has timestamp for 1:1 mapping with new and resolved errors
|
|
return put({
|
|
type: ReduxActionTypes.DEBUGGER_ERROR_ANALYTICS,
|
|
payload: {
|
|
...analyticsPayload,
|
|
environmentId: currentEnvDetails.id,
|
|
environmentName: currentEnvDetails.name,
|
|
eventName: "DEBUGGER_RESOLVED_ERROR_MESSAGE",
|
|
errorId:
|
|
currentDebuggerErrors[id].id +
|
|
"_" +
|
|
currentDebuggerErrors[id].timestamp,
|
|
errorMessage: existingErrorMessage.message,
|
|
errorType: existingErrorMessage.type,
|
|
errorSubType: existingErrorMessage.subType,
|
|
appMode,
|
|
},
|
|
});
|
|
}
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
function* deleteDebuggerErrorLogsSaga(
|
|
action: ReduxAction<{ id: string; analytics: Log["analytics"] }[]>,
|
|
) {
|
|
const { payload } = action;
|
|
const currentDebuggerErrors: Record<string, Log> = yield select(
|
|
getDebuggerErrors,
|
|
);
|
|
const appMode: ReturnType<typeof getAppMode> = yield select(getAppMode);
|
|
const existingErrorPayloads = payload.filter((item) =>
|
|
currentDebuggerErrors.hasOwnProperty(item.id),
|
|
);
|
|
if (isEmpty(existingErrorPayloads)) return;
|
|
|
|
const validErrorPayloadsToDelete = existingErrorPayloads.filter((payload) => {
|
|
const existingError = currentDebuggerErrors[payload.id];
|
|
return existingError && existingError.source;
|
|
});
|
|
|
|
if (isEmpty(validErrorPayloadsToDelete)) return;
|
|
|
|
for (const validErrorPayload of validErrorPayloadsToDelete) {
|
|
const error = currentDebuggerErrors[validErrorPayload.id];
|
|
if (!error || !error.source) continue;
|
|
const analyticsPayload = {
|
|
entityName: error.source.name,
|
|
entityType: error.source.type,
|
|
entityId: error.source.id,
|
|
propertyPath: error.source.propertyPath ?? "",
|
|
analytics: validErrorPayload.analytics,
|
|
};
|
|
const errorMessages = error.messages;
|
|
|
|
yield put({
|
|
type: ReduxActionTypes.DEBUGGER_ERROR_ANALYTICS,
|
|
payload: {
|
|
...analyticsPayload,
|
|
eventName: "DEBUGGER_RESOLVED_ERROR",
|
|
errorMessages,
|
|
appMode,
|
|
},
|
|
});
|
|
|
|
if (errorMessages) {
|
|
const currentEnvDetails: { id: string; name: string } = yield select(
|
|
getCurrentEnvironmentDetails,
|
|
);
|
|
//errorID has timestamp for 1:1 mapping with new and resolved errors
|
|
yield all(
|
|
errorMessages.map((errorMessage) => {
|
|
return put({
|
|
type: ReduxActionTypes.DEBUGGER_ERROR_ANALYTICS,
|
|
payload: {
|
|
...analyticsPayload,
|
|
environmentId: currentEnvDetails.id,
|
|
environmentName: currentEnvDetails.name,
|
|
eventName: "DEBUGGER_RESOLVED_ERROR_MESSAGE",
|
|
errorId: error.id + "_" + error.timestamp,
|
|
errorMessage: errorMessage.message,
|
|
errorType: errorMessage.type,
|
|
errorSubType: errorMessage.subType,
|
|
appMode,
|
|
},
|
|
});
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
const validErrorIds = validErrorPayloadsToDelete.map((payload) => payload.id);
|
|
yield put(deleteErrorLog(validErrorIds));
|
|
}
|
|
|
|
// takes a log object array and stores it in the redux store
|
|
export function* storeLogs(logs: LogObject[]) {
|
|
AppsmithConsole.addLogs(
|
|
logs.map((log: LogObject) => {
|
|
return {
|
|
text: createLogTitleString(log.data),
|
|
logData: log.data,
|
|
source: log.source,
|
|
severity: log.severity,
|
|
timestamp: log.timestamp,
|
|
category: LOG_CATEGORY.USER_GENERATED,
|
|
isExpanded: false,
|
|
};
|
|
}),
|
|
);
|
|
}
|
|
|
|
export function* updateTriggerMeta(
|
|
triggerMeta: TriggerMeta,
|
|
dynamicTrigger: string,
|
|
) {
|
|
let name = "";
|
|
|
|
if (!!triggerMeta.source && triggerMeta.source.hasOwnProperty("name")) {
|
|
name = triggerMeta.source.name;
|
|
} else if (!!triggerMeta.triggerPropertyName) {
|
|
name = triggerMeta.triggerPropertyName;
|
|
}
|
|
|
|
if (
|
|
name.length === 0 &&
|
|
!!dynamicTrigger &&
|
|
!(dynamicTrigger.includes("{") || dynamicTrigger.includes("}"))
|
|
) {
|
|
// We use the dynamic trigger as the name if it is not a binding
|
|
name = dynamicTrigger.replace("()", "");
|
|
triggerMeta["triggerPropertyName"] = name;
|
|
}
|
|
}
|
|
|
|
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,
|
|
addDebuggerErrorLogsSaga,
|
|
),
|
|
takeEvery(
|
|
ReduxActionTypes.DEBUGGER_DELETE_ERROR_LOG_INIT,
|
|
deleteDebuggerErrorLogsSaga,
|
|
),
|
|
]);
|
|
}
|