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:
Druthi Polisetty 2023-03-24 15:45:11 +05:30 committed by GitHub
parent 74102897a1
commit 93ab966e92
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 137 additions and 21 deletions

View File

@ -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>[] }> => {

View File

@ -43,7 +43,7 @@ export enum DataTreeDiffEvent {
NEW = "NEW",
DELETE = "DELETE",
EDIT = "EDIT",
NOOP = "NOOP",
NOOP = "NOOP", // No Operation (dont do anything)
}
export type DataTreeDiff = {

View File

@ -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),
);
}
}

View File

@ -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>) {

View File

@ -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"

View 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;
}
}

View File

@ -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;

View File

@ -53,4 +53,5 @@ export interface EvalTreeResponseData {
configTree: ConfigTree;
staleMetaIds: string[];
pathsToClearErrorsFor: any[];
isNewWidgetAdded: boolean;
}

View File

@ -74,6 +74,7 @@ function eventRequestHandler({
configTree,
cloudHosting,
);
lintTreeResponse.errors = lintErrors;
} catch (e) {}
return lintTreeResponse;

View File

@ -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,
};
}