Skip to content

Commit

Permalink
Merge pull request #1056 from adobe/fixClickViewName
Browse files Browse the repository at this point in the history
Fix an issue where web.webPageDetails.viewName was included in the click notification.
  • Loading branch information
jonsnyder authored Oct 24, 2023
2 parents 56b06c5 + 3a1a9b6 commit 7b9694e
Show file tree
Hide file tree
Showing 18 changed files with 247 additions and 106 deletions.
2 changes: 2 additions & 0 deletions src/components/Personalization/constants/scopeType.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ governing permissions and limitations under the License.
*/

export const VIEW_SCOPE_TYPE = "view";
export const PAGE_SCOPE_TYPE = "page";
export const PROPOSITION_SCOPE_TYPE = "proposition";
22 changes: 12 additions & 10 deletions src/components/Personalization/createClickStorage.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,26 @@ const metasToArray = metas => {
return Object.keys(metas).map(key => {
return {
id: key,
scope: metas[key].scope,
scopeDetails: metas[key].scopeDetails,
trackingLabel: metas[key].trackingLabel
...metas[key]
};
});
};

export default () => {
const clickStorage = {};

const storeClickMetrics = value => {
if (!clickStorage[value.selector]) {
clickStorage[value.selector] = {};
const storeClickMetrics = ({
selector,
meta: { id, scope, scopeDetails, trackingLabel, scopeType }
}) => {
if (!clickStorage[selector]) {
clickStorage[selector] = {};
}
clickStorage[value.selector][value.meta.id] = {
scope: value.meta.scope,
scopeDetails: value.meta.scopeDetails,
trackingLabel: value.meta.trackingLabel
clickStorage[selector][id] = {
scope,
scopeDetails,
trackingLabel,
scopeType
};
};

Expand Down
10 changes: 4 additions & 6 deletions src/components/Personalization/createOnClickHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ OF ANY KIND, either express or implied. See the License for the specific languag
governing permissions and limitations under the License.
*/

import { isNonEmptyArray, isNonEmptyString } from "../../utils";
import { isNonEmptyArray } from "../../utils";
import { INTERACT } from "./constants/eventType";
import { PropositionEventType } from "./constants/propositionEventType";
import PAGE_WIDE_SCOPE from "../../constants/pageWideScope";

export default ({
mergeDecisionsMeta,
Expand All @@ -25,20 +24,19 @@ export default ({
return ({ event, clickedElement }) => {
const selectors = getClickSelectors();
if (isNonEmptyArray(selectors)) {
const { decisionsMeta, eventLabel } = collectClicks(
const { decisionsMeta, eventLabel, viewName } = collectClicks(
clickedElement,
selectors,
getClickMetasBySelector
);

if (isNonEmptyArray(decisionsMeta)) {
const xdm = { eventType: INTERACT };
const scope = decisionsMeta[0].scope;

if (isNonEmptyString(scope) && scope !== PAGE_WIDE_SCOPE) {
if (viewName) {
xdm.web = {
webPageDetails: {
viewName: scope.toLowerCase()
viewName
}
};
}
Expand Down
3 changes: 2 additions & 1 deletion src/components/Personalization/createViewCacheManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ governing permissions and limitations under the License.
import { groupBy } from "../../utils";
import defer from "../../utils/defer";
import { DEFAULT_CONTENT_ITEM } from "./constants/schema";
import { VIEW_SCOPE_TYPE } from "./constants/scopeType";

export default ({ createProposition }) => {
let cacheUpdateCreatedAtLeastOnce = false;
Expand All @@ -29,7 +30,7 @@ export default ({ createProposition }) => {
scope: viewName,
scopeDetails: {
characteristics: {
scopeType: "view"
scopeType: VIEW_SCOPE_TYPE
}
},
items: [
Expand Down
37 changes: 25 additions & 12 deletions src/components/Personalization/dom-actions/clicks/collectClicks.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ governing permissions and limitations under the License.
*/

import matchesSelectorWithEq from "../dom/matchesSelectorWithEq";
import { VIEW_SCOPE_TYPE } from "../../constants/scopeType";

const getMetasIfMatches = (
clickedElement,
Expand All @@ -24,17 +25,22 @@ const getMetasIfMatches = (
while (element && element !== documentElement) {
if (matchesSelectorWithEq(selector, element)) {
const matchedMetas = getClickMetasBySelector(selector);
const returnValue = {
metas: matchedMetas
};
const foundMetaWithLabel = matchedMetas.find(meta => meta.trackingLabel);
if (foundMetaWithLabel) {
return {
metas: matchedMetas,
label: foundMetaWithLabel.trackingLabel,
weight: i
};
returnValue.label = foundMetaWithLabel.trackingLabel;
returnValue.weight = i;
}
return {
metas: matchedMetas
};
const foundMetaWithScopeTypeView = matchedMetas.find(
meta => meta.scopeType === VIEW_SCOPE_TYPE
);
if (foundMetaWithScopeTypeView) {
returnValue.viewName = foundMetaWithScopeTypeView.scope;
returnValue.weight = i;
}
return returnValue;
}

element = element.parentNode;
Expand All @@ -48,8 +54,8 @@ const getMetasIfMatches = (

const cleanMetas = metas =>
metas.map(meta => {
delete meta.trackingLabel;
return meta;
const { trackingLabel, scopeType, ...rest } = meta;
return rest;
});

const dedupMetas = metas =>
Expand All @@ -67,10 +73,12 @@ export default (clickedElement, selectors, getClickMetasBySelector) => {
const result = [];
let resultLabel = "";
let resultLabelWeight = Number.MAX_SAFE_INTEGER;
let resultViewName;
let resultViewNameWeight = Number.MAX_SAFE_INTEGER;

/* eslint-disable no-continue */
for (let i = 0; i < selectors.length; i += 1) {
const { metas, label, weight } = getMetasIfMatches(
const { metas, label, weight, viewName } = getMetasIfMatches(
clickedElement,
selectors[i],
getClickMetasBySelector
Expand All @@ -84,11 +92,16 @@ export default (clickedElement, selectors, getClickMetasBySelector) => {
resultLabel = label;
resultLabelWeight = weight;
}
if (viewName && weight <= resultViewNameWeight) {
resultViewName = viewName;
resultViewNameWeight = weight;
}
result.push(...cleanMetas(metas));
}

return {
decisionsMeta: dedupMetas(result),
eventLabel: resultLabel
eventLabel: resultLabel,
viewName: resultViewName
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,14 @@ export default ({ modules, logger, storeClickMetrics }) => item => {
logger.warn("Invalid DOM action data: missing selector.", item.getData());
return { setRenderAttempted: false, includeInNotification: false };
}
storeClickMetrics({ selector, meta: item.getMeta() });
storeClickMetrics({
selector,
meta: {
...item.getProposition().getNotification(),
trackingLabel: item.getTrackingLabel(),
scopeType: item.getProposition().getScopeType()
}
});
return { setRenderAttempted: true, includeInNotification: false };
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default ({ logger, executeRedirect, collect }) => item => {

const render = () => {
return collect({
decisionsMeta: [item.getMeta()],
decisionsMeta: [item.getProposition().getNotification()],
documentMayUnload: true
}).then(() => {
executeRedirect(content);
Expand Down
28 changes: 16 additions & 12 deletions src/components/Personalization/handlers/injectCreateProposition.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,30 @@ governing permissions and limitations under the License.
*/

import PAGE_WIDE_SCOPE from "../../../constants/pageWideScope";
import {
VIEW_SCOPE_TYPE,
PAGE_SCOPE_TYPE,
PROPOSITION_SCOPE_TYPE
} from "../constants/scopeType";

export default ({ preprocess, isPageWideSurface }) => {
const createItem = (item, meta) => {
const createItem = (item, proposition) => {
const { schema, data, characteristics: { trackingLabel } = {} } = item;

const processedData = preprocess(data);

if (trackingLabel) {
meta.trackingLabel = trackingLabel;
}

return {
getSchema() {
return schema;
},
getData() {
return processedData;
},
getMeta() {
return meta;
getProposition() {
return proposition;
},
getTrackingLabel() {
return trackingLabel;
},
getOriginalItem() {
return item;
Expand All @@ -57,15 +61,15 @@ export default ({ preprocess, isPageWideSurface }) => {
},
getScopeType() {
if (scope === PAGE_WIDE_SCOPE || isPageWideSurface(scope)) {
return "page";
return PAGE_SCOPE_TYPE;
}
if (scopeType === "view") {
return "view";
if (scopeType === VIEW_SCOPE_TYPE) {
return VIEW_SCOPE_TYPE;
}
return "proposition";
return PROPOSITION_SCOPE_TYPE;
},
getItems() {
return items.map(item => createItem(item, { id, scope, scopeDetails }));
return items.map(item => createItem(item, this));
},
getNotification() {
return { id, scope, scopeDetails };
Expand Down
16 changes: 0 additions & 16 deletions src/components/Personalization/utils/isPageWideScope.js

This file was deleted.

79 changes: 79 additions & 0 deletions test/functional/specs/Personalization/C14286730.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
Copyright 2023 Adobe. All rights reserved.
This file is licensed to you under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/
import { t } from "testcafe";
import createNetworkLogger from "../../helpers/networkLogger";
import { responseStatus } from "../../helpers/assertions/index";
import createFixture from "../../helpers/createFixture";
import {
compose,
orgMainConfigMain,
debugEnabled
} from "../../helpers/constants/configParts";
import { TEST_PAGE as TEST_PAGE_URL } from "../../helpers/constants/url";
import createAlloyProxy from "../../helpers/createAlloyProxy";
import addHtmlToBody from "../../helpers/dom/addHtmlToBody";

const networkLogger = createNetworkLogger();
const config = compose(orgMainConfigMain, debugEnabled);

createFixture({
title: "C14286730: Target SPA click interaction includes viewName",
requestHooks: [networkLogger.edgeEndpointLogs],
url: `${TEST_PAGE_URL}?test=C14286730`
});

test.meta({
ID: "C28755",
SEVERITY: "P0",
TEST_RUN: "Regression"
});

test("Test C14286730: Target SPA click interaction includes viewName", async () => {
const alloy = createAlloyProxy();
await alloy.configure(config);

await addHtmlToBody(
`<div id="personalization-products-container">Products</div>`
);
await alloy.sendEvent({
renderDecisions: true,
xdm: {
web: {
webPageDetails: {
viewName: "products"
}
}
}
});

await responseStatus(networkLogger.edgeEndpointLogs.requests, 200);

await t.expect(networkLogger.edgeEndpointLogs.count(() => true)).eql(2);

await t.click(".clickme");

await t.expect(networkLogger.edgeEndpointLogs.count(() => true)).eql(3);

const displayNotification = JSON.parse(
networkLogger.edgeEndpointLogs.requests[1].request.body
);
const interactNotification = JSON.parse(
networkLogger.edgeEndpointLogs.requests[2].request.body
);

await t
.expect(displayNotification.events[0].xdm.web.webPageDetails.viewName)
.eql("products");
await t
.expect(interactNotification.events[0].xdm.web.webPageDetails.viewName)
.eql("products");
});
1 change: 1 addition & 0 deletions test/functional/specs/Personalization/C9932846.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,4 +134,5 @@ test("Test C9932846: AJO click-tracking offers are delivered", async () => {
await t
.expect(interactEvent.xdm._experience.decisioning.propositions[0].id)
.eql(personalizationPayload.id);
await t.expect(interactEvent.xdm.web.webPageDetails.viewName).notOk();
});
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ describe("Personalization::createClickStorage", () => {
scope: "__view__",
scopeDetails: {
test: "blah1"
}
},
trackingLabel: "mylabel",
scopeType: "myscopetype"
}
};
const SECOND_CLICK = {
Expand Down Expand Up @@ -103,7 +105,8 @@ describe("Personalization::createClickStorage", () => {
2
);
});
it("getClickMetasBySelector returns the id, scopeDetails, scope", () => {

it("getClickMetasBySelector returns the id, scopeDetails, scope, trackingLabel, and scopeType", () => {
clickStorage.storeClickMetrics(FIRST_CLICK);

const meta = clickStorage.getClickMetasBySelector("div:123:h2");
Expand All @@ -114,7 +117,8 @@ describe("Personalization::createClickStorage", () => {
id: "AT:123",
scope: "__view__",
scopeDetails: { test: "blah1" },
trackingLabel: undefined
trackingLabel: "mylabel",
scopeType: "myscopetype"
});
});
});
Loading

0 comments on commit 7b9694e

Please sign in to comment.