fix: ENTITY_BINDING_SUCCESS event added which is fired whenever there is a successful binding created by the user. (#21227)
## Description Adding another event called ENTITY_BINDING_SUCCESS which is fired whenever there is a successful binding created by the user. The BINDING_SUCCESS event was firing more events than actual binding and therefore we created a new event to capture the right data. Fixes #20468 ## Type of change - Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? - Manual ### Test Plan > Add Testsmith test cases links that relate to this PR ### Issues raised during DP testing When table widget 'data table' is cleared, ENTITY_BINDING_SUCCESS event is triggered https://www.loom.com/share/280ab5165b684d59948ae1bc9fe0c074 Templates automatically triggers entitybindingsuccess https://www.loom.com/share/16be5ae834b44d7bacc73a6d89a99fbd ## 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: - [ ] Test plan has been approved by relevant developers - [ ] Test plan has been peer reviewed by QA - [ ] Cypress test cases have been added and approved by either SDET or manual QA - [ ] Organized project review call with relevant stakeholders after Round 1/2 of QA - [ ] Added Test Plan Approved label after reveiwing all Cypress test
This commit is contained in:
parent
74102897a1
commit
93ab966e92
|
|
@ -33,6 +33,13 @@ export const LINT_REDUX_ACTIONS = {
|
|||
[ReduxActionTypes.META_UPDATE_DEBOUNCED_EVAL]: true,
|
||||
};
|
||||
|
||||
export const LOG_REDUX_ACTIONS = [
|
||||
ReduxActionTypes.UPDATE_LAYOUT,
|
||||
ReduxActionTypes.UPDATE_WIDGET_PROPERTY,
|
||||
ReduxActionTypes.UPDATE_WIDGET_NAME_SUCCESS,
|
||||
ReduxActionTypes.CREATE_ACTION_SUCCESS,
|
||||
];
|
||||
|
||||
export const EVALUATE_REDUX_ACTIONS = [
|
||||
...FIRST_EVAL_REDUX_ACTIONS,
|
||||
// Actions
|
||||
|
|
@ -121,6 +128,10 @@ export function shouldLint(action: ReduxAction<unknown>) {
|
|||
return LINT_REDUX_ACTIONS[action.type];
|
||||
}
|
||||
|
||||
export function shouldLog(action: ReduxAction<unknown>) {
|
||||
return LOG_REDUX_ACTIONS.includes(action.type);
|
||||
}
|
||||
|
||||
export const setEvaluatedTree = (
|
||||
updates: Diff<DataTree, DataTree>[],
|
||||
): ReduxAction<{ updates: Diff<DataTree, DataTree>[] }> => {
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ export enum DataTreeDiffEvent {
|
|||
NEW = "NEW",
|
||||
DELETE = "DELETE",
|
||||
EDIT = "EDIT",
|
||||
NOOP = "NOOP",
|
||||
NOOP = "NOOP", // No Operation (don’t do anything)
|
||||
}
|
||||
|
||||
export type DataTreeDiff = {
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ import {
|
|||
setDependencyMap,
|
||||
setEvaluatedTree,
|
||||
shouldLint,
|
||||
shouldLog,
|
||||
shouldProcessBatchedAction,
|
||||
} from "actions/evaluationActions";
|
||||
import ConfigTreeActions from "utils/configTree";
|
||||
|
|
@ -136,6 +137,7 @@ export function* evaluateTreeSaga(
|
|||
shouldReplay = true,
|
||||
requiresLinting = false,
|
||||
forceEvaluation = false,
|
||||
requiresLogging = false,
|
||||
) {
|
||||
const allActionValidationConfig: {
|
||||
[actionId: string]: ActionValidationConfigMap;
|
||||
|
|
@ -184,6 +186,7 @@ export function* evaluateTreeSaga(
|
|||
configTree,
|
||||
staleMetaIds,
|
||||
pathsToClearErrorsFor,
|
||||
isNewWidgetAdded,
|
||||
} = workerResponse;
|
||||
|
||||
PerformanceTracker.stopAsyncTracking(
|
||||
|
|
@ -230,14 +233,18 @@ export function* evaluateTreeSaga(
|
|||
if (appMode !== APP_MODE.PUBLISHED) {
|
||||
const jsData: Record<string, unknown> = yield select(getAllJSActionsData);
|
||||
yield call(makeUpdateJSCollection, jsUpdates);
|
||||
yield fork(
|
||||
logSuccessfulBindings,
|
||||
unevalTree,
|
||||
updatedDataTree,
|
||||
evaluationOrder,
|
||||
isCreateFirstTree,
|
||||
configTree,
|
||||
);
|
||||
|
||||
if (requiresLogging) {
|
||||
yield fork(
|
||||
logSuccessfulBindings,
|
||||
unevalTree,
|
||||
updatedDataTree,
|
||||
evaluationOrder,
|
||||
isCreateFirstTree,
|
||||
isNewWidgetAdded,
|
||||
configTree,
|
||||
);
|
||||
}
|
||||
|
||||
yield fork(
|
||||
updateTernDefinitions,
|
||||
|
|
@ -578,7 +585,7 @@ function* evaluationChangeListenerSaga(): any {
|
|||
type: ReduxActionType;
|
||||
postEvalActions: Array<ReduxAction<unknown>>;
|
||||
} = yield take(FIRST_EVAL_REDUX_ACTIONS);
|
||||
yield fork(evaluateTreeSaga, initAction.postEvalActions, false, true);
|
||||
yield fork(evaluateTreeSaga, initAction.postEvalActions, false, true, false);
|
||||
const evtActionChannel: ActionPattern<Action<any>> = yield actionChannel(
|
||||
EVALUATE_REDUX_ACTIONS,
|
||||
evalQueueBuffer(),
|
||||
|
|
@ -596,6 +603,8 @@ function* evaluationChangeListenerSaga(): any {
|
|||
postEvalActions,
|
||||
get(action, "payload.shouldReplay"),
|
||||
shouldLint(action),
|
||||
false,
|
||||
shouldLog(action),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,10 @@ import type FeatureFlags from "entities/FeatureFlags";
|
|||
import type { JSAction } from "entities/JSCollection";
|
||||
import { isWidgetPropertyNamePath } from "utils/widgetEvalUtils";
|
||||
import type { ActionEntityConfig } from "entities/DataTree/types";
|
||||
import SuccessfulBindingMap from "utils/SuccessfulBindingsMap";
|
||||
import type { SuccessfulBindings } from "utils/SuccessfulBindingsMap";
|
||||
|
||||
let successfulBindingsMap: SuccessfulBindingMap | undefined;
|
||||
|
||||
const getDebuggerErrors = (state: AppState) => state.ui.debugger.errors;
|
||||
|
||||
|
|
@ -317,17 +321,17 @@ export function* logSuccessfulBindings(
|
|||
dataTree: DataTree,
|
||||
evaluationOrder: string[],
|
||||
isCreateFirstTree: boolean,
|
||||
isNewWidgetAdded: boolean,
|
||||
configTree: ConfigTree,
|
||||
) {
|
||||
const appMode: APP_MODE | undefined = yield select(getAppMode);
|
||||
if (appMode === APP_MODE.PUBLISHED) return;
|
||||
if (!evaluationOrder) return;
|
||||
|
||||
if (isCreateFirstTree) {
|
||||
// we only aim to log binding success which were added by user
|
||||
// for first evaluation, bindings are not added by user hence skipping it.
|
||||
return;
|
||||
}
|
||||
const successfulBindingPaths: SuccessfulBindings = !successfulBindingsMap
|
||||
? {}
|
||||
: { ...successfulBindingsMap.get() };
|
||||
|
||||
evaluationOrder.forEach((evaluatedPath) => {
|
||||
const { entityName, propertyPath } =
|
||||
getEntityNameAndPropertyPath(evaluatedPath);
|
||||
|
|
@ -345,6 +349,23 @@ export function* logSuccessfulBindings(
|
|||
});
|
||||
|
||||
const logBlackList = entityConfig.logBlackList;
|
||||
|
||||
if (!isABinding || propertyPath in logBlackList) {
|
||||
/**Remove the binding from the map so that in case it is added again, we log it*/
|
||||
if (successfulBindingPaths[evaluatedPath]) {
|
||||
delete successfulBindingPaths[evaluatedPath];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/** All the paths that are added when a new widget is added needs to be added to the map so that
|
||||
* we don't log them again unless they are changed by the user.
|
||||
*/
|
||||
if (isNewWidgetAdded) {
|
||||
successfulBindingPaths[evaluatedPath] = unevalValue;
|
||||
return;
|
||||
}
|
||||
|
||||
const errors: EvaluationError[] = get(
|
||||
dataTree,
|
||||
getEvalErrorPath(evaluatedPath),
|
||||
|
|
@ -352,16 +373,44 @@ export function* logSuccessfulBindings(
|
|||
) as EvaluationError[];
|
||||
|
||||
const hasErrors = errors.length > 0;
|
||||
if (!hasErrors) {
|
||||
if (!isCreateFirstTree) {
|
||||
// we only aim to log binding success which were added by user
|
||||
// for first evaluation, bindings are not added by user hence skipping it.
|
||||
AnalyticsUtil.logEvent("BINDING_SUCCESS", {
|
||||
unevalValue,
|
||||
entityType,
|
||||
propertyPath,
|
||||
});
|
||||
|
||||
if (isABinding && !hasErrors && !(propertyPath in logBlackList)) {
|
||||
AnalyticsUtil.logEvent("BINDING_SUCCESS", {
|
||||
unevalValue,
|
||||
entityType,
|
||||
propertyPath,
|
||||
});
|
||||
/**Log the binding only if it doesn't already exist */
|
||||
if (
|
||||
!successfulBindingPaths[evaluatedPath] ||
|
||||
(successfulBindingPaths[evaluatedPath] &&
|
||||
successfulBindingPaths[evaluatedPath] !== unevalValue)
|
||||
) {
|
||||
AnalyticsUtil.logEvent("ENTITY_BINDING_SUCCESS", {
|
||||
unevalValue,
|
||||
entityType,
|
||||
propertyPath,
|
||||
});
|
||||
}
|
||||
}
|
||||
successfulBindingPaths[evaluatedPath] = unevalValue;
|
||||
} else {
|
||||
/**Remove the binding from the map so that in case it is added again, we log it*/
|
||||
if (successfulBindingPaths[evaluatedPath]) {
|
||||
delete successfulBindingPaths[evaluatedPath];
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!successfulBindingsMap) {
|
||||
successfulBindingsMap = new SuccessfulBindingMap(successfulBindingPaths);
|
||||
} else {
|
||||
successfulBindingsMap.set(successfulBindingPaths);
|
||||
}
|
||||
}
|
||||
|
||||
export function* postEvalActionDispatcher(actions: Array<AnyReduxAction>) {
|
||||
|
|
|
|||
|
|
@ -134,6 +134,7 @@ export type EventName =
|
|||
| "DISCORD_LINK_CLICK"
|
||||
| "INTERCOM_CLICK"
|
||||
| "BINDING_SUCCESS"
|
||||
| "ENTITY_BINDING_SUCCESS"
|
||||
| "APP_MENU_OPTION_CLICK"
|
||||
| "SLASH_COMMAND"
|
||||
| "DEBUGGER_NEW_ERROR"
|
||||
|
|
|
|||
20
app/client/src/utils/SuccessfulBindingsMap.ts
Normal file
20
app/client/src/utils/SuccessfulBindingsMap.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import type { UnEvalTreeEntity } from "entities/DataTree/dataTreeFactory";
|
||||
|
||||
export type SuccessfulBindings = {
|
||||
[entityName: string]: UnEvalTreeEntity;
|
||||
};
|
||||
export default class SuccessfulBindingMap {
|
||||
successfulBindings: SuccessfulBindings;
|
||||
|
||||
constructor(successfulBindings: SuccessfulBindings) {
|
||||
this.successfulBindings = successfulBindings;
|
||||
}
|
||||
|
||||
set(successfulBindings: SuccessfulBindings) {
|
||||
this.successfulBindings = successfulBindings;
|
||||
}
|
||||
|
||||
get() {
|
||||
return this.successfulBindings;
|
||||
}
|
||||
}
|
||||
|
|
@ -40,6 +40,7 @@ export default function (request: EvalWorkerSyncRequest) {
|
|||
let configTree: ConfigTree = {};
|
||||
let staleMetaIds: string[] = [];
|
||||
let pathsToClearErrorsFor: any[] = [];
|
||||
let isNewWidgetAdded = false;
|
||||
|
||||
const {
|
||||
allActionValidationConfig,
|
||||
|
|
@ -149,6 +150,7 @@ export default function (request: EvalWorkerSyncRequest) {
|
|||
jsUpdates = setupUpdateTreeResponse.jsUpdates;
|
||||
unEvalUpdates = setupUpdateTreeResponse.unEvalUpdates;
|
||||
pathsToClearErrorsFor = setupUpdateTreeResponse.pathsToClearErrorsFor;
|
||||
isNewWidgetAdded = setupUpdateTreeResponse.isNewWidgetAdded;
|
||||
|
||||
initiateLinting(
|
||||
lintOrder,
|
||||
|
|
@ -225,6 +227,7 @@ export default function (request: EvalWorkerSyncRequest) {
|
|||
configTree,
|
||||
staleMetaIds,
|
||||
pathsToClearErrorsFor,
|
||||
isNewWidgetAdded,
|
||||
};
|
||||
|
||||
return evalTreeResponse;
|
||||
|
|
|
|||
|
|
@ -53,4 +53,5 @@ export interface EvalTreeResponseData {
|
|||
configTree: ConfigTree;
|
||||
staleMetaIds: string[];
|
||||
pathsToClearErrorsFor: any[];
|
||||
isNewWidgetAdded: boolean;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ function eventRequestHandler({
|
|||
configTree,
|
||||
cloudHosting,
|
||||
);
|
||||
|
||||
lintTreeResponse.errors = lintErrors;
|
||||
} catch (e) {}
|
||||
return lintTreeResponse;
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ import {
|
|||
isNewEntity,
|
||||
getStaleMetaStateIds,
|
||||
convertJSFunctionsToString,
|
||||
DataTreeDiffEvent,
|
||||
} from "@appsmith/workers/Evaluation/evaluationUtils";
|
||||
import {
|
||||
difference,
|
||||
|
|
@ -378,6 +379,7 @@ export default class DataTreeEvaluator {
|
|||
jsUpdates: Record<string, JSUpdate>;
|
||||
nonDynamicFieldValidationOrder: string[];
|
||||
pathsToClearErrorsFor: any[];
|
||||
isNewWidgetAdded: boolean;
|
||||
} {
|
||||
const totalUpdateTreeSetupStartTime = performance.now();
|
||||
|
||||
|
|
@ -451,14 +453,32 @@ export default class DataTreeEvaluator {
|
|||
lintOrder: [],
|
||||
jsUpdates: {},
|
||||
nonDynamicFieldValidationOrder: [],
|
||||
isNewWidgetAdded: false,
|
||||
};
|
||||
}
|
||||
let isNewWidgetAdded = false;
|
||||
|
||||
//find all differences which can lead to updating of dependency map
|
||||
const translatedDiffs = flatten(
|
||||
differences.map((diff) =>
|
||||
translateDiffEventToDataTreeDiffEvent(diff, localUnEvalTree),
|
||||
),
|
||||
);
|
||||
|
||||
/** We need to know if a new widget was added so that we do not fire ENTITY_BINDING_SUCCESS event */
|
||||
for (let i = 0; i < translatedDiffs.length; i++) {
|
||||
const diffEvent = translatedDiffs[i];
|
||||
if (diffEvent.event === DataTreeDiffEvent.NEW) {
|
||||
const entity = localUnEvalTree[diffEvent.payload.propertyPath];
|
||||
|
||||
if (isWidget(entity)) {
|
||||
isNewWidgetAdded = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const diffCheckTimeStopTime = performance.now();
|
||||
this.logs.push({
|
||||
differences,
|
||||
|
|
@ -571,6 +591,7 @@ export default class DataTreeEvaluator {
|
|||
nonDynamicFieldValidationOrderSet,
|
||||
),
|
||||
pathsToClearErrorsFor,
|
||||
isNewWidgetAdded,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user