Skip to content

Commit 265d062

Browse files
authored
Fix transform function for submit_form event (close #1241)
1 parent 4aea889 commit 265d062

File tree

6 files changed

+132
-72
lines changed

6 files changed

+132
-72
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"changes": [
3+
{
4+
"packageName": "@snowplow/browser-plugin-form-tracking",
5+
"comment": "Fix transform function for submit_form event",
6+
"type": "none"
7+
}
8+
],
9+
"packageName": "@snowplow/browser-plugin-form-tracking"
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"changes": [
3+
{
4+
"packageName": "@snowplow/javascript-tracker",
5+
"comment": "",
6+
"type": "none"
7+
}
8+
],
9+
"packageName": "@snowplow/javascript-tracker"
10+
}

plugins/browser-plugin-form-tracking/src/helpers.ts

Lines changed: 25 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,3 @@
1-
/*
2-
* Copyright (c) 2022 Snowplow Analytics Ltd, 2010 Anthon Pang
3-
* All rights reserved.
4-
*
5-
* Redistribution and use in source and binary forms, with or without
6-
* modification, are permitted provided that the following conditions are met:
7-
*
8-
* 1. Redistributions of source code must retain the above copyright notice, this
9-
* list of conditions and the following disclaimer.
10-
*
11-
* 2. Redistributions in binary form must reproduce the above copyright notice,
12-
* this list of conditions and the following disclaimer in the documentation
13-
* and/or other materials provided with the distribution.
14-
*
15-
* 3. Neither the name of the copyright holder nor the names of its
16-
* contributors may be used to endorse or promote products derived from
17-
* this software without specific prior written permission.
18-
*
19-
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20-
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21-
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22-
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23-
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24-
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25-
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26-
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27-
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28-
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29-
*/
30-
311
import {
322
getCssClasses,
333
addEventListener,
@@ -90,12 +60,18 @@ export interface ElementData extends Record<string, string | null | undefined> {
9060
type?: string;
9161
}
9262

93-
export type transformFn = (x: string | null, elt: ElementData | TrackedHTMLElement) => string | null;
63+
export type transformFn = (
64+
elementValue: string | null,
65+
elementInfo: ElementData | TrackedHTMLElement,
66+
elt: TrackedHTMLElement
67+
) => string | null;
9468

9569
export const innerElementTags: Array<keyof TrackedHTMLElementTagNameMap> = ['textarea', 'input', 'select'];
9670

9771
type TrackedHTMLElementWithMarker = TrackedHTMLElement & Record<string, boolean>;
9872

73+
type ElementDataWrapper = { elementData: ElementData; originalElement: TrackedHTMLElement };
74+
9975
const defaultTransformFn: transformFn = (x) => x;
10076

10177
interface FormConfiguration {
@@ -242,7 +218,7 @@ function getParentFormIdentifier(elt: Node | null) {
242218
* Returns a list of the input, textarea, and select elements inside a form along with their values
243219
*/
244220
function getInnerFormElements(trackingMarker: string, elt: HTMLFormElement) {
245-
var innerElements: Array<ElementData> = [];
221+
var innerElements: Array<ElementDataWrapper> = [];
246222
Array.prototype.slice.call(innerElementTags).forEach((tagname: 'textarea' | 'input' | 'select') => {
247223
let trackedChildren = Array.prototype.slice.call(elt.getElementsByTagName(tagname)).filter(function (child) {
248224
return child.hasOwnProperty(trackingMarker);
@@ -252,17 +228,20 @@ function getInnerFormElements(trackingMarker: string, elt: HTMLFormElement) {
252228
if (child.type === 'submit') {
253229
return;
254230
}
255-
var elementJson: ElementData = {
256-
name: getElementIdentifier(child),
257-
value: child.value,
258-
nodeName: child.nodeName,
231+
var elementJson: ElementDataWrapper = {
232+
elementData: {
233+
name: getElementIdentifier(child),
234+
value: child.value,
235+
nodeName: child.nodeName,
236+
},
237+
originalElement: child,
259238
};
260239
if (child.type && child.nodeName.toUpperCase() === 'INPUT') {
261-
elementJson.type = child.type;
240+
elementJson.elementData.type = child.type;
262241
}
263242

264243
if ((child.type === 'checkbox' || child.type === 'radio') && !(child as HTMLInputElement).checked) {
265-
elementJson.value = null;
244+
elementJson.elementData.value = null;
266245
}
267246
innerElements.push(elementJson);
268247
});
@@ -285,7 +264,9 @@ function getFormChangeListener(
285264
if (elt) {
286265
var type = elt.nodeName && elt.nodeName.toUpperCase() === 'INPUT' ? elt.type : null;
287266
var value =
288-
elt.type === 'checkbox' && !(elt as HTMLInputElement).checked ? null : config.fieldTransform(elt.value, elt);
267+
elt.type === 'checkbox' && !(elt as HTMLInputElement).checked
268+
? null
269+
: config.fieldTransform(elt.value, elt, elt);
289270
if (event_type === 'change_form' || (type !== 'checkbox' && type !== 'radio')) {
290271
tracker.core.track(
291272
buildFormFocusOrChange({
@@ -317,15 +298,17 @@ function getFormSubmissionListener(
317298
var elt = e.target as HTMLFormElement;
318299
var innerElements = getInnerFormElements(trackingMarker, elt);
319300
innerElements.forEach(function (innerElement) {
320-
innerElement.value = config.fieldTransform(innerElement.value, innerElement) ?? innerElement.value;
301+
var eltData = innerElement.elementData;
302+
eltData.value = config.fieldTransform(eltData.value, eltData, innerElement.originalElement) ?? eltData.value;
321303
});
304+
var elementsData = innerElements.map((elt) => elt.elementData);
322305
tracker.core.track(
323306
buildFormSubmission({
324307
formId: getElementIdentifier(elt) ?? '',
325308
formClasses: getCssClasses(elt),
326-
elements: innerElements,
309+
elements: elementsData,
327310
}),
328-
resolveDynamicContext(context, elt, innerElements)
311+
resolveDynamicContext(context, elt, elementsData)
329312
);
330313
};
331314
}

plugins/browser-plugin-form-tracking/src/index.ts

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,3 @@
1-
/*
2-
* Copyright (c) 2022 Snowplow Analytics Ltd, 2010 Anthon Pang
3-
* All rights reserved.
4-
*
5-
* Redistribution and use in source and binary forms, with or without
6-
* modification, are permitted provided that the following conditions are met:
7-
*
8-
* 1. Redistributions of source code must retain the above copyright notice, this
9-
* list of conditions and the following disclaimer.
10-
*
11-
* 2. Redistributions in binary form must reproduce the above copyright notice,
12-
* this list of conditions and the following disclaimer in the documentation
13-
* and/or other materials provided with the distribution.
14-
*
15-
* 3. Neither the name of the copyright holder nor the names of its
16-
* contributors may be used to endorse or promote products derived from
17-
* this software without specific prior written permission.
18-
*
19-
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20-
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21-
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22-
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23-
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24-
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25-
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26-
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27-
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28-
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29-
*/
30-
311
import { BrowserPlugin, BrowserTracker } from '@snowplow/browser-tracker-core';
322
import { addFormListeners, FormTrackingConfiguration } from './helpers';
333

trackers/javascript-tracker/test/integration/autoTracking.test.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,11 @@ describe('Auto tracking', () => {
280280
await $('#fname').click();
281281
await $('#lname').click();
282282

283+
await loadUrlAndWait('/form-tracking.html?filter=transform');
284+
285+
await $('#pid').click();
286+
await $('#submit').click();
287+
283288
await browser.pause(1000);
284289

285290
await loadUrlAndWait('/form-tracking.html?filter=excludedForm');
@@ -808,4 +813,69 @@ describe('Auto tracking', () => {
808813
})
809814
).toBe(true);
810815
});
816+
817+
it('should use transform function for pii field', () => {
818+
expect(
819+
logContains({
820+
event: {
821+
event: 'unstruct',
822+
app_id: 'autotracking-form-' + testIdentifier,
823+
unstruct_event: {
824+
data: {
825+
schema: 'iglu:com.snowplowanalytics.snowplow/focus_form/jsonschema/1-0-0',
826+
data: {
827+
formId: 'myForm',
828+
elementId: 'pname',
829+
nodeName: 'INPUT',
830+
elementClasses: [],
831+
value: 'redacted',
832+
elementType: 'text',
833+
},
834+
},
835+
},
836+
},
837+
})
838+
).toBe(true);
839+
840+
expect(
841+
logContains({
842+
event: {
843+
event: 'unstruct',
844+
app_id: 'autotracking-form-' + testIdentifier,
845+
unstruct_event: {
846+
data: {
847+
schema: 'iglu:com.snowplowanalytics.snowplow/submit_form/jsonschema/1-0-0',
848+
data: {
849+
formId: 'myForm',
850+
formClasses: ['formy-mcformface'],
851+
elements: [
852+
{
853+
name: 'message',
854+
value: 'This is a message',
855+
nodeName: 'TEXTAREA',
856+
},
857+
{
858+
name: 'fname',
859+
value: 'John',
860+
nodeName: 'INPUT',
861+
type: 'text',
862+
},
863+
{ name: 'lname', value: 'Doe', nodeName: 'INPUT', type: 'text' },
864+
{
865+
name: 'pname',
866+
value: 'redacted',
867+
nodeName: 'INPUT',
868+
type: 'text',
869+
},
870+
{ name: 'vehicle', value: null, nodeName: 'INPUT', type: 'radio' },
871+
{ name: 'terms', value: null, nodeName: 'INPUT', type: 'checkbox' },
872+
{ name: 'cars', value: 'volvo', nodeName: 'SELECT' },
873+
],
874+
},
875+
},
876+
},
877+
},
878+
})
879+
).toBe(true);
880+
});
811881
});

trackers/javascript-tracker/test/pages/form-tracking.html

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@
4141
<input type="text" id="fname" name="fname" value="John" class="test" /><br />
4242
<label for="lname">Last name:</label><br />
4343
<input type="text" id="lname" name="lname" value="Doe" /><br /><br />
44+
45+
<label for="pii">Personal Info:</label><br />
46+
<input type="text" id="pid" name="pname" value="danger" /><br /><br />
47+
4448
<input type="radio" id="bike" name="vehicle" value="Bike" />
4549
<label for="bike"> I have a bike</label><br />
4650
</fieldset>
@@ -106,6 +110,12 @@
106110
function formFilter(formElement) {
107111
return formElement.id !== 'lname';
108112
}
113+
function redactPII(eltValue, _, elt) {
114+
if (elt.id === 'pid') {
115+
return 'redacted';
116+
}
117+
return eltValue;
118+
}
109119

110120
switch (parseQuery().filter) {
111121
case 'exclude':
@@ -122,6 +132,13 @@
122132
},
123133
});
124134
break;
135+
case 'transform':
136+
snowplow('enableFormTracking', {
137+
options: {
138+
fields: { transform: redactPII },
139+
},
140+
});
141+
break;
125142
case 'excludedForm':
126143
snowplow('enableFormTracking', { options: { forms: { denylist: ['excluded-form'] } } });
127144
break;

0 commit comments

Comments
 (0)