Improve JS error reporting in the debugger (#4854)
This commit is contained in:
parent
a7a7390d08
commit
179d5ae8a9
|
|
@ -50,12 +50,19 @@ import "codemirror/addon/fold/foldgutter";
|
|||
import "codemirror/addon/fold/foldgutter.css";
|
||||
import * as Sentry from "@sentry/react";
|
||||
import { removeNewLineChars, getInputValue } from "./codeEditorUtils";
|
||||
import { getEntityNameAndPropertyPath } from "workers/evaluationUtils";
|
||||
|
||||
const LightningMenu = lazy(() =>
|
||||
retryPromise(() => import("components/editorComponents/LightningMenu")),
|
||||
);
|
||||
|
||||
const AUTOCOMPLETE_CLOSE_KEY_CODES = ["Enter", "Tab", "Escape", "Comma"];
|
||||
const AUTOCOMPLETE_CLOSE_KEY_CODES = [
|
||||
"Enter",
|
||||
"Tab",
|
||||
"Escape",
|
||||
"Comma",
|
||||
"Backspace",
|
||||
];
|
||||
|
||||
interface ReduxStateProps {
|
||||
dynamicData: DataTree;
|
||||
|
|
@ -341,16 +348,26 @@ class CodeEditor extends Component<Props, State> {
|
|||
if (!dataTreePath) {
|
||||
return { isValid: true, validationMessage: "", jsErrorMessage: "" };
|
||||
}
|
||||
const isValidPath = dataTreePath.replace("evaluatedValues", "invalidProps");
|
||||
const validationMessagePath = dataTreePath.replace(
|
||||
"evaluatedValues",
|
||||
"validationMessages",
|
||||
const { entityName, propertyPath } = getEntityNameAndPropertyPath(
|
||||
dataTreePath,
|
||||
);
|
||||
const jsErrorMessagePath = dataTreePath.replace(
|
||||
"evaluatedValues",
|
||||
"jsErrorMessages",
|
||||
);
|
||||
|
||||
let isValidPath, validationMessagePath, jsErrorMessagePath;
|
||||
if (dataTreePath && dataTreePath.match(/evaluatedValues/g)) {
|
||||
isValidPath = dataTreePath.replace("evaluatedValues", "invalidProps");
|
||||
validationMessagePath = dataTreePath.replace(
|
||||
"evaluatedValues",
|
||||
"validationMessages",
|
||||
);
|
||||
jsErrorMessagePath = dataTreePath.replace(
|
||||
"evaluatedValues",
|
||||
"jsErrorMessages",
|
||||
);
|
||||
} else {
|
||||
isValidPath = entityName + "invalidProps" + propertyPath;
|
||||
validationMessagePath =
|
||||
entityName + ".validationMessages." + propertyPath;
|
||||
jsErrorMessagePath = entityName + ".jsErrorMessages." + propertyPath;
|
||||
}
|
||||
const isValid = !_.get(dataTree, isValidPath, false);
|
||||
const validationMessage = _.get(
|
||||
dataTree,
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export function InputText(props: {
|
|||
const dataTreePath = actionPathFromName(actionName, name);
|
||||
|
||||
return (
|
||||
<div style={{ width: "50vh", height: "55px" }}>
|
||||
<div style={{ width: "50vh", minHeight: "55px" }}>
|
||||
<FormLabel>
|
||||
{label} {isRequired && "*"}
|
||||
</FormLabel>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ enum LOG_TYPE {
|
|||
ACTION_EXECUTION_SUCCESS,
|
||||
ENTITY_DELETED,
|
||||
EVAL_ERROR,
|
||||
ACTION_UPDATE,
|
||||
}
|
||||
|
||||
export default LOG_TYPE;
|
||||
|
|
|
|||
|
|
@ -45,5 +45,6 @@ export const generateDataTreeAction = (
|
|||
isLoading: action.isLoading,
|
||||
bindingPaths: getBindingPathsOfAction(action.config, editorConfig),
|
||||
dependencyMap,
|
||||
logBlackList: {},
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -56,6 +56,8 @@ export interface DataTreeAction
|
|||
bindingPaths: Record<string, EvaluationSubstitutionType>;
|
||||
ENTITY_TYPE: ENTITY_TYPE.ACTION;
|
||||
dependencyMap: DependencyMap;
|
||||
jsErrorMessages?: Record<string, string>;
|
||||
logBlackList: Record<string, true>;
|
||||
}
|
||||
|
||||
export interface DataTreeWidget extends WidgetProps {
|
||||
|
|
@ -63,6 +65,7 @@ export interface DataTreeWidget extends WidgetProps {
|
|||
triggerPaths: Record<string, boolean>;
|
||||
validationPaths: Record<string, VALIDATION_TYPES>;
|
||||
ENTITY_TYPE: ENTITY_TYPE.WIDGET;
|
||||
logBlackList: Record<string, true>;
|
||||
}
|
||||
|
||||
export interface DataTreeAppsmith extends Omit<AppDataState, "store"> {
|
||||
|
|
|
|||
|
|
@ -221,6 +221,10 @@ describe("generateDataTreeWidget", () => {
|
|||
key: "value",
|
||||
},
|
||||
],
|
||||
logBlackList: {
|
||||
isValid: true,
|
||||
value: true,
|
||||
},
|
||||
value: "{{Input1.text}}",
|
||||
isDirty: true,
|
||||
isFocused: false,
|
||||
|
|
|
|||
|
|
@ -45,6 +45,10 @@ export const generateDataTreeWidget = (
|
|||
unInitializedDefaultProps[propertyName] = undefined;
|
||||
}
|
||||
});
|
||||
const blockedDerivedProps: Record<string, true> = {};
|
||||
Object.keys(derivedProps).forEach((propertyName) => {
|
||||
blockedDerivedProps[propertyName] = true;
|
||||
});
|
||||
const {
|
||||
bindingPaths,
|
||||
triggerPaths,
|
||||
|
|
@ -62,6 +66,10 @@ export const generateDataTreeWidget = (
|
|||
...derivedProps,
|
||||
...unInitializedDefaultProps,
|
||||
dynamicBindingPathList,
|
||||
logBlackList: {
|
||||
...widget.logBlackList,
|
||||
...blockedDerivedProps,
|
||||
},
|
||||
bindingPaths,
|
||||
triggerPaths,
|
||||
validationPaths,
|
||||
|
|
|
|||
|
|
@ -886,6 +886,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
|
|||
widgets: { [widgetId: string]: FlattenedWidgetProps },
|
||||
) => {
|
||||
let template = {};
|
||||
const logBlackListMap: any = {};
|
||||
const container = get(
|
||||
widgets,
|
||||
`${get(widget, "children.0.children.0")}`,
|
||||
|
|
@ -901,6 +902,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
|
|||
canvas.children &&
|
||||
get(canvas, "children", []).forEach((child: string) => {
|
||||
const childWidget = cloneDeep(get(widgets, `${child}`));
|
||||
const logBlackList: { [key: string]: boolean } = {};
|
||||
const keys = Object.keys(childWidget);
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
|
|
@ -927,6 +929,12 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
|
|||
}
|
||||
}
|
||||
|
||||
Object.keys(childWidget).map((key) => {
|
||||
logBlackList[key] = true;
|
||||
});
|
||||
|
||||
logBlackListMap[childWidget.widgetId] = logBlackList;
|
||||
|
||||
template = {
|
||||
...template,
|
||||
[childWidget.widgetName]: childWidget,
|
||||
|
|
@ -945,6 +953,18 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
|
|||
propertyValue: template,
|
||||
},
|
||||
];
|
||||
|
||||
// add logBlackList to updateProperyMap for all children
|
||||
updatePropertyMap = updatePropertyMap.concat(
|
||||
Object.keys(logBlackListMap).map((logBlackListMapKey) => {
|
||||
return {
|
||||
widgetId: logBlackListMapKey,
|
||||
propertyName: "logBlackList",
|
||||
propertyValue: logBlackListMap[logBlackListMapKey],
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
return updatePropertyMap;
|
||||
},
|
||||
},
|
||||
|
|
@ -961,6 +981,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
|
|||
if (!parentId) return { widgets };
|
||||
const widget = { ...widgets[widgetId] };
|
||||
const parent = { ...widgets[parentId] };
|
||||
const logBlackList: { [key: string]: boolean } = {};
|
||||
|
||||
const disallowedWidgets = [WidgetTypes.FILE_PICKER_WIDGET];
|
||||
|
||||
|
|
@ -998,7 +1019,15 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
|
|||
|
||||
parent.template = template;
|
||||
|
||||
// add logBlackList for the children being added
|
||||
Object.keys(widget).map((key) => {
|
||||
logBlackList[key] = true;
|
||||
});
|
||||
|
||||
widget.logBlackList = logBlackList;
|
||||
|
||||
widgets[parentId] = parent;
|
||||
widgets[widgetId] = widget;
|
||||
|
||||
return { widgets };
|
||||
},
|
||||
|
|
|
|||
|
|
@ -624,15 +624,21 @@ function* setActionPropertySaga(action: ReduxAction<SetActionPropertyPayload>) {
|
|||
if (propertyName === "name") return;
|
||||
|
||||
const actionObj = yield select(getAction, actionId);
|
||||
const fieldToBeUpdated = propertyName.replace(
|
||||
"actionConfiguration",
|
||||
"config",
|
||||
);
|
||||
AppsmithConsole.info({
|
||||
logType: LOG_TYPE.ACTION_UPDATE,
|
||||
text: "Configuration updated",
|
||||
source: {
|
||||
type: ENTITY_TYPE.ACTION,
|
||||
name: actionObj.name,
|
||||
id: actionId,
|
||||
propertyPath: fieldToBeUpdated,
|
||||
},
|
||||
state: {
|
||||
[propertyName]: value,
|
||||
[fieldToBeUpdated]: value,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,73 +1,12 @@
|
|||
import { debuggerLog, errorLog, updateErrorLog } from "actions/debuggerActions";
|
||||
import { ReduxAction, ReduxActionTypes } from "constants/ReduxActionConstants";
|
||||
import { WidgetTypes } from "constants/WidgetConstants";
|
||||
import { LogActionPayload, Message } from "entities/AppsmithConsole";
|
||||
import {
|
||||
all,
|
||||
put,
|
||||
takeEvery,
|
||||
select,
|
||||
take,
|
||||
fork,
|
||||
call,
|
||||
} from "redux-saga/effects";
|
||||
import { getDataTree } from "selectors/dataTreeSelectors";
|
||||
import { isEmpty, set, get } from "lodash";
|
||||
import { all, call, fork, put, select, takeEvery } from "redux-saga/effects";
|
||||
import { set } from "lodash";
|
||||
import { getDebuggerErrors } from "selectors/debuggerSelectors";
|
||||
import { getAction } from "selectors/entitiesSelector";
|
||||
import { Action, PluginType } from "entities/Action";
|
||||
import LOG_TYPE from "entities/AppsmithConsole/logtype";
|
||||
import { DataTree, DataTreeWidget } from "entities/DataTree/dataTreeFactory";
|
||||
import { isWidget } from "workers/evaluationUtils";
|
||||
import { getWidget } from "./selectors";
|
||||
import { WidgetProps } from "widgets/BaseWidget";
|
||||
|
||||
function* onWidgetUpdateSaga(payload: LogActionPayload) {
|
||||
if (!payload.source) return;
|
||||
// Wait for data tree update
|
||||
yield take(ReduxActionTypes.SET_EVALUATED_TREE);
|
||||
const dataTree: DataTree = yield select(getDataTree);
|
||||
const widget = dataTree[payload.source.name];
|
||||
|
||||
if (
|
||||
!isWidget(widget) ||
|
||||
!widget.validationMessages ||
|
||||
!widget.jsErrorMessages
|
||||
)
|
||||
return;
|
||||
|
||||
// Ignore canvas widget updates
|
||||
if (widget.type === WidgetTypes.CANVAS_WIDGET) {
|
||||
return;
|
||||
}
|
||||
const source = payload.source;
|
||||
|
||||
// If widget properties no longer have validation errors update the same
|
||||
if (payload.state) {
|
||||
const propertyPath = Object.keys(payload.state)[0];
|
||||
|
||||
const validationMessages = widget.validationMessages;
|
||||
const validationMessage = validationMessages[propertyPath];
|
||||
const jsErrorMessages = widget.jsErrorMessages;
|
||||
const jsErrorMessage = jsErrorMessages[propertyPath];
|
||||
const errors = yield select(getDebuggerErrors);
|
||||
const errorId = `${source.id}-${propertyPath}`;
|
||||
const widgetErrorLog = errors[errorId];
|
||||
if (!widgetErrorLog) return;
|
||||
|
||||
const noError = isEmpty(validationMessage);
|
||||
const noJsError = isEmpty(jsErrorMessage);
|
||||
|
||||
if (noError && noJsError) {
|
||||
delete errors[errorId];
|
||||
|
||||
yield put({
|
||||
type: ReduxActionTypes.DEBUGGER_UPDATE_ERROR_LOGS,
|
||||
payload: errors,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function* formatActionRequestSaga(payload: LogActionPayload, request?: any) {
|
||||
if (!payload.source || !payload.state || !request || !request.headers) {
|
||||
|
|
@ -128,7 +67,9 @@ function* debuggerLogSaga(action: ReduxAction<Message>) {
|
|||
|
||||
switch (payload.logType) {
|
||||
case LOG_TYPE.WIDGET_UPDATE:
|
||||
yield call(onWidgetUpdateSaga, payload);
|
||||
yield put(debuggerLog(payload));
|
||||
return;
|
||||
case LOG_TYPE.ACTION_UPDATE:
|
||||
yield put(debuggerLog(payload));
|
||||
return;
|
||||
case LOG_TYPE.EVAL_ERROR:
|
||||
|
|
@ -178,42 +119,6 @@ function* debuggerLogSaga(action: ReduxAction<Message>) {
|
|||
}
|
||||
}
|
||||
|
||||
// Pass through error list once after on page load actions executions are complete
|
||||
function* onExecutePageActionsCompleteSaga() {
|
||||
yield take(ReduxActionTypes.SET_EVALUATED_TREE);
|
||||
|
||||
const dataTree: DataTree = yield select(getDataTree);
|
||||
const errors = yield select(getDebuggerErrors);
|
||||
const updatedErrors = { ...errors };
|
||||
const errorIds = Object.keys(errors);
|
||||
|
||||
for (const id of errorIds) {
|
||||
const splits = id.split("-");
|
||||
const entityId = splits[0];
|
||||
const propertyName = splits[1];
|
||||
const widget: WidgetProps | null = yield select(getWidget, entityId);
|
||||
|
||||
if (widget) {
|
||||
const dataTreeWidget = dataTree[widget.widgetName] as DataTreeWidget;
|
||||
|
||||
if (!get(dataTreeWidget.invalidProps, propertyName, null)) {
|
||||
delete updatedErrors[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
yield put({
|
||||
type: ReduxActionTypes.DEBUGGER_UPDATE_ERROR_LOGS,
|
||||
payload: updatedErrors,
|
||||
});
|
||||
}
|
||||
|
||||
export default function* debuggerSagasListeners() {
|
||||
yield all([
|
||||
takeEvery(ReduxActionTypes.DEBUGGER_LOG_INIT, debuggerLogSaga),
|
||||
takeEvery(
|
||||
ReduxActionTypes.EXECUTE_PAGE_LOAD_ACTIONS_COMPLETE,
|
||||
onExecutePageActionsCompleteSaga,
|
||||
),
|
||||
]);
|
||||
yield all([takeEvery(ReduxActionTypes.DEBUGGER_LOG_INIT, debuggerLogSaga)]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,29 +28,127 @@ import { WidgetProps } from "widgets/BaseWidget";
|
|||
import PerformanceTracker, {
|
||||
PerformanceTransactionName,
|
||||
} from "../utils/PerformanceTracker";
|
||||
import { Variant } from "components/ads/common";
|
||||
import { Toaster } from "components/ads/Toast";
|
||||
import * as Sentry from "@sentry/react";
|
||||
import { Action } from "redux";
|
||||
import _ from "lodash";
|
||||
import { ENTITY_TYPE, Message, Severity } from "entities/AppsmithConsole";
|
||||
import LOG_TYPE from "entities/AppsmithConsole/logtype";
|
||||
import { DataTree } from "entities/DataTree/dataTreeFactory";
|
||||
import { AppState } from "reducers";
|
||||
import {
|
||||
getEntityNameAndPropertyPath,
|
||||
isAction,
|
||||
isWidget,
|
||||
} from "workers/evaluationUtils";
|
||||
import moment from "moment/moment";
|
||||
import { Toaster } from "components/ads/Toast";
|
||||
import { Variant } from "components/ads/common";
|
||||
import AppsmithConsole from "utils/AppsmithConsole";
|
||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||
import {
|
||||
createMessage,
|
||||
ERROR_EVAL_ERROR_GENERIC,
|
||||
ERROR_EVAL_TRIGGER,
|
||||
} from "constants/messages";
|
||||
import AppsmithConsole from "utils/AppsmithConsole";
|
||||
import LOG_TYPE from "entities/AppsmithConsole/logtype";
|
||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||
|
||||
let widgetTypeConfigMap: WidgetTypeConfigMap;
|
||||
|
||||
const worker = new GracefulWorkerService(Worker);
|
||||
|
||||
const evalErrorHandler = (errors: EvalError[]) => {
|
||||
if (!errors) return;
|
||||
const getDebuggerErrors = (state: AppState) => state.ui.debugger.errors;
|
||||
|
||||
function getLatestEvalPropertyErrors(
|
||||
currentDebuggerErrors: Record<string, Message>,
|
||||
dataTree: DataTree,
|
||||
evaluationOrder: Array<string>,
|
||||
) {
|
||||
const updatedDebuggerErrors: Record<string, Message> = {
|
||||
...currentDebuggerErrors,
|
||||
};
|
||||
|
||||
for (const evaluatedPath of evaluationOrder) {
|
||||
const { entityName, propertyPath } = getEntityNameAndPropertyPath(
|
||||
evaluatedPath,
|
||||
);
|
||||
const entity = dataTree[entityName];
|
||||
if (isWidget(entity) || isAction(entity)) {
|
||||
if (propertyPath in entity.logBlackList) {
|
||||
continue;
|
||||
}
|
||||
const jsError = _.get(entity, `jsErrorMessages.${propertyPath}`, "");
|
||||
const validationError = _.get(
|
||||
entity,
|
||||
`validationMessages.${propertyPath}`,
|
||||
"",
|
||||
);
|
||||
const evaluatedValue = _.get(
|
||||
entity,
|
||||
`evaluatedValues.${propertyPath}`,
|
||||
"",
|
||||
);
|
||||
const error = jsError || validationError;
|
||||
const idField = isWidget(entity) ? entity.widgetId : entity.actionId;
|
||||
const nameField = isWidget(entity) ? entity.widgetName : entity.name;
|
||||
const entityType = isWidget(entity)
|
||||
? ENTITY_TYPE.WIDGET
|
||||
: ENTITY_TYPE.ACTION;
|
||||
const debuggerKey = idField + "-" + propertyPath;
|
||||
// if dataTree has error but debugger does not -> add
|
||||
// if debugger has error and data tree has error -> update error
|
||||
// if debugger has error but data tree does not -> remove
|
||||
// if debugger or data tree does not have an error -> no change
|
||||
|
||||
if (_.isString(error) && error !== "") {
|
||||
// Add or update
|
||||
updatedDebuggerErrors[debuggerKey] = {
|
||||
logType: LOG_TYPE.EVAL_ERROR,
|
||||
text: `The value at ${propertyPath} is invalid`,
|
||||
message: error,
|
||||
severity: Severity.ERROR,
|
||||
timestamp: moment().format("hh:mm:ss"),
|
||||
source: {
|
||||
id: idField,
|
||||
name: nameField,
|
||||
type: entityType,
|
||||
propertyPath: propertyPath,
|
||||
},
|
||||
state: {
|
||||
value: evaluatedValue,
|
||||
},
|
||||
};
|
||||
} else if (debuggerKey in updatedDebuggerErrors) {
|
||||
// Remove
|
||||
delete updatedDebuggerErrors[debuggerKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
return updatedDebuggerErrors;
|
||||
}
|
||||
|
||||
function* evalErrorHandler(
|
||||
errors: EvalError[],
|
||||
dataTree?: DataTree,
|
||||
evaluationOrder?: Array<string>,
|
||||
): any {
|
||||
if (dataTree && evaluationOrder) {
|
||||
const currentDebuggerErrors: Record<string, Message> = yield select(
|
||||
getDebuggerErrors,
|
||||
);
|
||||
const evalPropertyErrors = getLatestEvalPropertyErrors(
|
||||
currentDebuggerErrors,
|
||||
dataTree,
|
||||
evaluationOrder,
|
||||
);
|
||||
|
||||
yield put({
|
||||
type: ReduxActionTypes.DEBUGGER_UPDATE_ERROR_LOGS,
|
||||
payload: evalPropertyErrors,
|
||||
});
|
||||
}
|
||||
|
||||
errors.forEach((error) => {
|
||||
switch (error.type) {
|
||||
case EvalErrorTypes.DEPENDENCY_ERROR: {
|
||||
case EvalErrorTypes.CYCLICAL_DEPENDENCY_ERROR: {
|
||||
if (error.context) {
|
||||
// Add more info about node for the toast
|
||||
const { entityType, node } = error.context;
|
||||
|
|
@ -104,33 +202,13 @@ const evalErrorHandler = (errors: EvalError[]) => {
|
|||
});
|
||||
break;
|
||||
}
|
||||
case EvalErrorTypes.EVAL_ERROR: {
|
||||
log.debug(error);
|
||||
AppsmithConsole.error({
|
||||
logType: LOG_TYPE.EVAL_ERROR,
|
||||
text: `The value at ${error.context?.source.propertyPath} is invalid`,
|
||||
message: error.message,
|
||||
source: error.context?.source,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case EvalErrorTypes.WIDGET_PROPERTY_VALIDATION_ERROR: {
|
||||
AppsmithConsole.error({
|
||||
logType: LOG_TYPE.WIDGET_PROPERTY_VALIDATION_ERROR,
|
||||
text: `The value at ${error.context?.source.propertyPath} is invalid`,
|
||||
message: error.message,
|
||||
source: error.context?.source,
|
||||
state: error.context?.state,
|
||||
});
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
Sentry.captureException(error);
|
||||
log.debug(error);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function* postEvalActionDispatcher(
|
||||
actions: Array<ReduxAction<unknown> | ReduxActionWithoutPayload>,
|
||||
|
|
@ -156,10 +234,16 @@ function* evaluateTreeSaga(
|
|||
widgetTypeConfigMap,
|
||||
},
|
||||
);
|
||||
const { dataTree, dependencies, errors, logs } = workerResponse;
|
||||
const {
|
||||
dataTree,
|
||||
dependencies,
|
||||
errors,
|
||||
evaluationOrder,
|
||||
logs,
|
||||
} = workerResponse;
|
||||
log.debug({ dataTree: dataTree });
|
||||
logs.forEach((evalLog: any) => log.debug(evalLog));
|
||||
evalErrorHandler(errors);
|
||||
yield call(evalErrorHandler, errors, dataTree, evaluationOrder);
|
||||
PerformanceTracker.stopAsyncTracking(
|
||||
PerformanceTransactionName.DATA_TREE_EVALUATION,
|
||||
);
|
||||
|
|
@ -197,7 +281,7 @@ export function* evaluateActionBindings(
|
|||
|
||||
const { errors, values } = workerResponse;
|
||||
|
||||
evalErrorHandler(errors);
|
||||
yield call(evalErrorHandler, errors);
|
||||
return values;
|
||||
}
|
||||
|
||||
|
|
@ -214,7 +298,7 @@ export function* evaluateDynamicTrigger(
|
|||
);
|
||||
|
||||
const { errors, triggers } = workerResponse;
|
||||
evalErrorHandler(errors);
|
||||
yield call(evalErrorHandler, errors);
|
||||
return triggers;
|
||||
}
|
||||
|
||||
|
|
@ -302,7 +386,6 @@ const EVALUATE_REDUX_ACTIONS = [
|
|||
];
|
||||
|
||||
const shouldProcessAction = (action: ReduxAction<unknown>) => {
|
||||
// debugger;
|
||||
if (
|
||||
action.type === ReduxActionTypes.BATCH_UPDATES_SUCCESS &&
|
||||
Array.isArray(action.payload)
|
||||
|
|
|
|||
|
|
@ -264,6 +264,7 @@ const createLoadingWidget = (
|
|||
bindingPaths: {},
|
||||
triggerPaths: {},
|
||||
validationPaths: {},
|
||||
logBlackList: {},
|
||||
isLoading: true,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -84,12 +84,10 @@ export const getDynamicBindings = (
|
|||
};
|
||||
|
||||
export enum EvalErrorTypes {
|
||||
DEPENDENCY_ERROR = "DEPENDENCY_ERROR",
|
||||
CYCLICAL_DEPENDENCY_ERROR = "CYCLICAL_DEPENDENCY_ERROR",
|
||||
EVAL_PROPERTY_ERROR = "EVAL_PROPERTY_ERROR",
|
||||
WIDGET_PROPERTY_VALIDATION_ERROR = "WIDGET_PROPERTY_VALIDATION_ERROR",
|
||||
EVAL_TREE_ERROR = "EVAL_TREE_ERROR",
|
||||
UNESCAPE_STRING_ERROR = "UNESCAPE_STRING_ERROR",
|
||||
EVAL_ERROR = "EVAL_ERROR",
|
||||
UNKNOWN_ERROR = "UNKNOWN_ERROR",
|
||||
BAD_UNEVAL_TREE_ERROR = "BAD_UNEVAL_TREE_ERROR",
|
||||
EVAL_TRIGGER_ERROR = "EVAL_TRIGGER_ERROR",
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import defaultTemplate from "templates/default";
|
|||
import { generateReactKey } from "./generators";
|
||||
import { ChartDataPoint } from "widgets/ChartWidget";
|
||||
import { FlattenedWidgetProps } from "reducers/entityReducers/canvasWidgetsReducer";
|
||||
import { has, isString, omit, set } from "lodash";
|
||||
import { get, has, isString, omit, set } from "lodash";
|
||||
import log from "loglevel";
|
||||
import {
|
||||
migrateTablePrimaryColumnsBindings,
|
||||
|
|
@ -764,6 +764,11 @@ const transformDSL = (currentDSL: ContainerWidgetProps<WidgetProps>) => {
|
|||
currentDSL.version = LATEST_PAGE_VERSION;
|
||||
}
|
||||
|
||||
if (currentDSL.version === 22) {
|
||||
currentDSL = addLogBlackListToAllListWidgetChildren(currentDSL);
|
||||
currentDSL.version = 23;
|
||||
}
|
||||
|
||||
return currentDSL;
|
||||
};
|
||||
|
||||
|
|
@ -1164,3 +1169,41 @@ export const generateWidgetProps = (
|
|||
} else throw Error("Failed to create widget: Parent was not provided ");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* adds logBlackList key for all list widget children
|
||||
*
|
||||
* @param currentDSL
|
||||
* @returns
|
||||
*/
|
||||
const addLogBlackListToAllListWidgetChildren = (
|
||||
currentDSL: ContainerWidgetProps<WidgetProps>,
|
||||
) => {
|
||||
currentDSL.children = currentDSL.children?.map((children: WidgetProps) => {
|
||||
if (children.type === WidgetTypes.LIST_WIDGET) {
|
||||
const widgets = get(
|
||||
children,
|
||||
"children.0.children.0.children.0.children",
|
||||
);
|
||||
|
||||
widgets.map((widget: any, index: number) => {
|
||||
const logBlackList: { [key: string]: boolean } = {};
|
||||
|
||||
Object.keys(widget).map((key) => {
|
||||
logBlackList[key] = true;
|
||||
});
|
||||
if (!widget.logBlackList) {
|
||||
set(
|
||||
children,
|
||||
`children.0.children.0.children.0.children.${index}.logBlackList`,
|
||||
logBlackList,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return children;
|
||||
});
|
||||
|
||||
return currentDSL;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -112,6 +112,10 @@ export default class DataTreeEvaluator {
|
|||
},
|
||||
};
|
||||
this.logs.push({ timeTakenForFirstTree });
|
||||
return {
|
||||
dataTree: this.evalTree,
|
||||
evaluationOrder: this.sortedDependencies,
|
||||
};
|
||||
}
|
||||
|
||||
isDynamicLeaf(unEvalTree: DataTree, propertyPath: string) {
|
||||
|
|
@ -206,7 +210,10 @@ export default class DataTreeEvaluator {
|
|||
evaluate: (evalStop - evalStart).toFixed(2),
|
||||
};
|
||||
this.logs.push({ timeTakenForSubTreeEval });
|
||||
return this.evalTree;
|
||||
return {
|
||||
dataTree: this.evalTree,
|
||||
evaluationOrder: evaluationOrder,
|
||||
};
|
||||
}
|
||||
|
||||
getCompleteSortOrder(
|
||||
|
|
@ -502,7 +509,7 @@ export default class DataTreeEvaluator {
|
|||
entityType = entity.pluginType;
|
||||
}
|
||||
this.errors.push({
|
||||
type: EvalErrorTypes.DEPENDENCY_ERROR,
|
||||
type: EvalErrorTypes.CYCLICAL_DEPENDENCY_ERROR,
|
||||
message: "Cyclic dependency found while evaluating.",
|
||||
context: {
|
||||
node,
|
||||
|
|
@ -612,21 +619,13 @@ export default class DataTreeEvaluator {
|
|||
fullPropertyPath,
|
||||
);
|
||||
_.set(data, `${entityName}.jsErrorMessages.${propertyPath}`, e.message);
|
||||
const entity = data[entityName];
|
||||
if (isWidget(entity)) {
|
||||
this.errors.push({
|
||||
type: EvalErrorTypes.EVAL_ERROR,
|
||||
message: e.message,
|
||||
context: {
|
||||
source: {
|
||||
id: entity.widgetId,
|
||||
name: entity.widgetName,
|
||||
type: ENTITY_TYPE.WIDGET,
|
||||
propertyPath: propertyPath,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// TODO clean up
|
||||
// This is to handle situations with evaluation of triggers for execution
|
||||
this.errors.push({
|
||||
type: EvalErrorTypes.EVAL_PROPERTY_ERROR,
|
||||
message: e.message,
|
||||
});
|
||||
}
|
||||
return { result: undefined, triggers: [] };
|
||||
}
|
||||
|
|
@ -668,23 +667,7 @@ export default class DataTreeEvaluator {
|
|||
: transformed;
|
||||
const safeEvaluatedValue = removeFunctions(evaluatedValue);
|
||||
_.set(widget, `evaluatedValues.${propertyPath}`, safeEvaluatedValue);
|
||||
const jsError = _.get(widget, `jsErrorMessages.${propertyPath}`);
|
||||
if (!isValid && !jsError) {
|
||||
this.errors.push({
|
||||
type: EvalErrorTypes.WIDGET_PROPERTY_VALIDATION_ERROR,
|
||||
message: message || "",
|
||||
context: {
|
||||
source: {
|
||||
id: widget.widgetId,
|
||||
name: widget.widgetName,
|
||||
type: ENTITY_TYPE.WIDGET,
|
||||
propertyPath: propertyPath,
|
||||
},
|
||||
state: {
|
||||
value: safeEvaluatedValue,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!isValid) {
|
||||
_.set(widget, `invalidProps.${propertyPath}`, true);
|
||||
_.set(widget, `validationMessages.${propertyPath}`, message);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -384,9 +384,9 @@ describe("DataTreeEvaluator", () => {
|
|||
text: "Hey there",
|
||||
},
|
||||
};
|
||||
const updatedEvalTree = evaluator.updateDataTree(updatedUnEvalTree);
|
||||
expect(updatedEvalTree).toHaveProperty("Text2.text", "Hey there");
|
||||
expect(updatedEvalTree).toHaveProperty("Text3.text", "Hey there");
|
||||
const { dataTree } = evaluator.updateDataTree(updatedUnEvalTree);
|
||||
expect(dataTree).toHaveProperty("Text2.text", "Hey there");
|
||||
expect(dataTree).toHaveProperty("Text3.text", "Hey there");
|
||||
});
|
||||
|
||||
it("Evaluates a dependency change in update run", () => {
|
||||
|
|
@ -397,10 +397,10 @@ describe("DataTreeEvaluator", () => {
|
|||
text: "Label 3",
|
||||
},
|
||||
};
|
||||
const updatedEvalTree = evaluator.updateDataTree(updatedUnEvalTree);
|
||||
const { dataTree } = evaluator.updateDataTree(updatedUnEvalTree);
|
||||
const updatedDependencyMap = evaluator.dependencyMap;
|
||||
expect(updatedEvalTree).toHaveProperty("Text2.text", "Label");
|
||||
expect(updatedEvalTree).toHaveProperty("Text3.text", "Label 3");
|
||||
expect(dataTree).toHaveProperty("Text2.text", "Label");
|
||||
expect(dataTree).toHaveProperty("Text3.text", "Label 3");
|
||||
expect(updatedDependencyMap).toStrictEqual({
|
||||
Text1: ["Text1.text"],
|
||||
Text2: ["Text2.text"],
|
||||
|
|
@ -445,8 +445,8 @@ describe("DataTreeEvaluator", () => {
|
|||
},
|
||||
};
|
||||
|
||||
const updatedEvalTree = evaluator.updateDataTree(updatedUnEvalTree);
|
||||
expect(updatedEvalTree).toHaveProperty("Input1.text", "Default value");
|
||||
const { dataTree } = evaluator.updateDataTree(updatedUnEvalTree);
|
||||
expect(dataTree).toHaveProperty("Input1.text", "Default value");
|
||||
});
|
||||
|
||||
it("Evaluates for value changes in nested diff paths", () => {
|
||||
|
|
@ -481,11 +481,8 @@ describe("DataTreeEvaluator", () => {
|
|||
},
|
||||
},
|
||||
};
|
||||
const updatedEvalTree = evaluator.updateDataTree(updatedUnEvalTree);
|
||||
expect(updatedEvalTree).toHaveProperty(
|
||||
"Dropdown2.options.0.label",
|
||||
"newValue",
|
||||
);
|
||||
const { dataTree } = evaluator.updateDataTree(updatedUnEvalTree);
|
||||
expect(dataTree).toHaveProperty("Dropdown2.options.0.label", "newValue");
|
||||
});
|
||||
|
||||
it("Adds an entity with a complicated binding", () => {
|
||||
|
|
@ -504,9 +501,9 @@ describe("DataTreeEvaluator", () => {
|
|||
],
|
||||
},
|
||||
};
|
||||
const updatedEvalTree = evaluator.updateDataTree(updatedUnEvalTree);
|
||||
const { dataTree } = evaluator.updateDataTree(updatedUnEvalTree);
|
||||
const updatedDependencyMap = evaluator.dependencyMap;
|
||||
expect(updatedEvalTree).toHaveProperty("Table1.tableData", [
|
||||
expect(dataTree).toHaveProperty("Table1.tableData", [
|
||||
{
|
||||
test: "Hey",
|
||||
raw: "Label",
|
||||
|
|
@ -568,9 +565,9 @@ describe("DataTreeEvaluator", () => {
|
|||
],
|
||||
},
|
||||
};
|
||||
const updatedEvalTree = evaluator.updateDataTree(updatedUnEvalTree);
|
||||
const { dataTree } = evaluator.updateDataTree(updatedUnEvalTree);
|
||||
const updatedDependencyMap = evaluator.dependencyMap;
|
||||
expect(updatedEvalTree).toHaveProperty("Table1.tableData", [
|
||||
expect(dataTree).toHaveProperty("Table1.tableData", [
|
||||
{
|
||||
test: "Hey",
|
||||
raw: "Label",
|
||||
|
|
@ -580,7 +577,7 @@ describe("DataTreeEvaluator", () => {
|
|||
raw: "Label",
|
||||
},
|
||||
]);
|
||||
expect(updatedEvalTree).toHaveProperty("Text4.text", "Hey");
|
||||
expect(dataTree).toHaveProperty("Text4.text", "Hey");
|
||||
expect(updatedDependencyMap).toStrictEqual({
|
||||
Api1: ["Api1.data"],
|
||||
Text1: ["Text1.text"],
|
||||
|
|
@ -657,14 +654,14 @@ describe("DataTreeEvaluator", () => {
|
|||
},
|
||||
},
|
||||
};
|
||||
const evaluatedDataTree2 = evaluator.updateDataTree(updatedTree2);
|
||||
const { dataTree } = evaluator.updateDataTree(updatedTree2);
|
||||
expect(evaluator.dependencyMap["Api2.config.body"]).toStrictEqual([
|
||||
"Text1.text",
|
||||
"Api2.config.pluginSpecifiedTemplates[0].value",
|
||||
]);
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
expect(evaluatedDataTree2.Api2.config.body).toBe("{ 'name': Test }");
|
||||
expect(dataTree.Api2.config.body).toBe("{ 'name': Test }");
|
||||
const updatedTree3 = {
|
||||
...updatedTree2,
|
||||
Api2: {
|
||||
|
|
@ -683,13 +680,14 @@ describe("DataTreeEvaluator", () => {
|
|||
},
|
||||
},
|
||||
};
|
||||
const evaluatedDataTree3 = evaluator.updateDataTree(updatedTree3);
|
||||
const evaluatedDataTreeObject = evaluator.updateDataTree(updatedTree3);
|
||||
const dataTree3 = evaluatedDataTreeObject.dataTree;
|
||||
expect(evaluator.dependencyMap["Api2.config.body"]).toStrictEqual([
|
||||
"Text1.text",
|
||||
"Api2.config.pluginSpecifiedTemplates[0].value",
|
||||
]);
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
expect(evaluatedDataTree3.Api2.config.body).toBe("{ 'name': \"Test\" }");
|
||||
expect(dataTree3.Api2.config.body).toBe("{ 'name': \"Test\" }");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -47,18 +47,24 @@ ctx.addEventListener(
|
|||
let errors: EvalError[] = [];
|
||||
let logs: any[] = [];
|
||||
let dependencies: DependencyMap = {};
|
||||
let dataTreeObject: any = {};
|
||||
let evaluationOrder: Array<string> = [];
|
||||
try {
|
||||
if (!dataTreeEvaluator) {
|
||||
dataTreeEvaluator = new DataTreeEvaluator(widgetTypeConfigMap);
|
||||
dataTreeEvaluator.createFirstTree(unevalTree);
|
||||
dataTree = dataTreeEvaluator.evalTree;
|
||||
dataTreeObject = dataTreeEvaluator.createFirstTree(unevalTree);
|
||||
dataTree = dataTreeObject.dataTree;
|
||||
evaluationOrder = dataTreeObject.evaluationOrder;
|
||||
// dataTreeEvaluator.sortedDepedencies
|
||||
} else {
|
||||
dataTree = dataTreeEvaluator.updateDataTree(unevalTree);
|
||||
dataTreeObject = dataTreeEvaluator.updateDataTree(unevalTree);
|
||||
dataTree = dataTreeObject.dataTree;
|
||||
evaluationOrder = dataTreeObject.evaluationOrder;
|
||||
}
|
||||
|
||||
// We need to clean it to remove any possible functions inside the tree.
|
||||
// If functions exist, it will crash the web worker
|
||||
dataTree = JSON.parse(JSON.stringify(dataTree));
|
||||
dataTree = dataTree && JSON.parse(JSON.stringify(dataTree));
|
||||
dependencies = dataTreeEvaluator.inverseDependencyMap;
|
||||
errors = dataTreeEvaluator.errors;
|
||||
dataTreeEvaluator.clearErrors();
|
||||
|
|
@ -79,10 +85,12 @@ ctx.addEventListener(
|
|||
dataTree = getSafeToRenderDataTree(unevalTree, widgetTypeConfigMap);
|
||||
dataTreeEvaluator = undefined;
|
||||
}
|
||||
// step 6: eval order
|
||||
return {
|
||||
dataTree,
|
||||
dependencies,
|
||||
errors,
|
||||
evaluationOrder,
|
||||
logs,
|
||||
};
|
||||
}
|
||||
|
|
@ -108,7 +116,8 @@ ctx.addEventListener(
|
|||
if (!dataTreeEvaluator) {
|
||||
return { triggers: [], errors: [] };
|
||||
}
|
||||
const evalTree = dataTreeEvaluator.updateDataTree(dataTree);
|
||||
dataTreeEvaluator.updateDataTree(dataTree);
|
||||
const evalTree = dataTreeEvaluator.evalTree;
|
||||
const triggers = dataTreeEvaluator.getDynamicValue(
|
||||
dynamicTrigger,
|
||||
evalTree,
|
||||
|
|
@ -120,7 +129,7 @@ ctx.addEventListener(
|
|||
// Transforming eval errors into eval trigger errors. Since trigger
|
||||
// errors occur less, we want to treat it separately
|
||||
const errors = dataTreeEvaluator.errors.map((error) => {
|
||||
if (error.type === EvalErrorTypes.EVAL_ERROR) {
|
||||
if (error.type === EvalErrorTypes.EVAL_PROPERTY_ERROR) {
|
||||
return {
|
||||
...error,
|
||||
type: EvalErrorTypes.EVAL_TRIGGER_ERROR,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user