Skip to content

Commit

Permalink
Fix transform function for submit_form event (close #1241)
Browse files Browse the repository at this point in the history
  • Loading branch information
adatzer committed Sep 29, 2023
1 parent de97c3b commit c136619
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 72 deletions.
67 changes: 25 additions & 42 deletions plugins/browser-plugin-form-tracking/src/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,3 @@
/*
* Copyright (c) 2022 Snowplow Analytics Ltd, 2010 Anthon Pang
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

import {
getCssClasses,
addEventListener,
Expand Down Expand Up @@ -90,12 +60,18 @@ export interface ElementData extends Record<string, string | null | undefined> {
type?: string;
}

export type transformFn = (x: string | null, elt: ElementData | TrackedHTMLElement) => string | null;
export type transformFn = (
elementValue: string | null,
elementInfo: ElementData | TrackedHTMLElement,
elt: TrackedHTMLElement
) => string | null;

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

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

type ElementDataWrapper = { elementData: ElementData; originalElement: TrackedHTMLElement };

const defaultTransformFn: transformFn = (x) => x;

interface FormConfiguration {
Expand Down Expand Up @@ -242,7 +218,7 @@ function getParentFormIdentifier(elt: Node | null) {
* Returns a list of the input, textarea, and select elements inside a form along with their values
*/
function getInnerFormElements(trackingMarker: string, elt: HTMLFormElement) {
var innerElements: Array<ElementData> = [];
var innerElements: Array<ElementDataWrapper> = [];
Array.prototype.slice.call(innerElementTags).forEach((tagname: 'textarea' | 'input' | 'select') => {
let trackedChildren = Array.prototype.slice.call(elt.getElementsByTagName(tagname)).filter(function (child) {
return child.hasOwnProperty(trackingMarker);
Expand All @@ -252,17 +228,20 @@ function getInnerFormElements(trackingMarker: string, elt: HTMLFormElement) {
if (child.type === 'submit') {
return;
}
var elementJson: ElementData = {
name: getElementIdentifier(child),
value: child.value,
nodeName: child.nodeName,
var elementJson: ElementDataWrapper = {
elementData: {
name: getElementIdentifier(child),
value: child.value,
nodeName: child.nodeName,
},
originalElement: child,
};
if (child.type && child.nodeName.toUpperCase() === 'INPUT') {
elementJson.type = child.type;
elementJson.elementData.type = child.type;
}

if ((child.type === 'checkbox' || child.type === 'radio') && !(child as HTMLInputElement).checked) {
elementJson.value = null;
elementJson.elementData.value = null;
}
innerElements.push(elementJson);
});
Expand All @@ -285,7 +264,9 @@ function getFormChangeListener(
if (elt) {
var type = elt.nodeName && elt.nodeName.toUpperCase() === 'INPUT' ? elt.type : null;
var value =
elt.type === 'checkbox' && !(elt as HTMLInputElement).checked ? null : config.fieldTransform(elt.value, elt);
elt.type === 'checkbox' && !(elt as HTMLInputElement).checked
? null
: config.fieldTransform(elt.value, elt, elt);
if (event_type === 'change_form' || (type !== 'checkbox' && type !== 'radio')) {
tracker.core.track(
buildFormFocusOrChange({
Expand Down Expand Up @@ -317,15 +298,17 @@ function getFormSubmissionListener(
var elt = e.target as HTMLFormElement;
var innerElements = getInnerFormElements(trackingMarker, elt);
innerElements.forEach(function (innerElement) {
innerElement.value = config.fieldTransform(innerElement.value, innerElement) ?? innerElement.value;
var eltData = innerElement.elementData;
eltData.value = config.fieldTransform(eltData.value, eltData, innerElement.originalElement) ?? eltData.value;
});
var elementsData = innerElements.map((elt) => elt.elementData);
tracker.core.track(
buildFormSubmission({
formId: getElementIdentifier(elt) ?? '',
formClasses: getCssClasses(elt),
elements: innerElements,
elements: elementsData,
}),
resolveDynamicContext(context, elt, innerElements)
resolveDynamicContext(context, elt, elementsData)
);
};
}
30 changes: 0 additions & 30 deletions plugins/browser-plugin-form-tracking/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,3 @@
/*
* Copyright (c) 2022 Snowplow Analytics Ltd, 2010 Anthon Pang
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

import { BrowserPlugin, BrowserTracker } from '@snowplow/browser-tracker-core';
import { addFormListeners, FormTrackingConfiguration } from './helpers';

Expand Down
70 changes: 70 additions & 0 deletions trackers/javascript-tracker/test/integration/autoTracking.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,11 @@ describe('Auto tracking', () => {
await $('#fname').click();
await $('#lname').click();

await loadUrlAndWait('/form-tracking.html?filter=transform');

await $('#pid').click();
await $('#submit').click();

await browser.pause(1000);

await loadUrlAndWait('/form-tracking.html?filter=excludedForm');
Expand Down Expand Up @@ -808,4 +813,69 @@ describe('Auto tracking', () => {
})
).toBe(true);
});

it('should use transform function for pii field', () => {
expect(
logContains({
event: {
event: 'unstruct',
app_id: 'autotracking-form-' + testIdentifier,
unstruct_event: {
data: {
schema: 'iglu:com.snowplowanalytics.snowplow/focus_form/jsonschema/1-0-0',
data: {
formId: 'myForm',
elementId: 'pname',
nodeName: 'INPUT',
elementClasses: [],
value: 'redacted',
elementType: 'text',
},
},
},
},
})
).toBe(true);

expect(
logContains({
event: {
event: 'unstruct',
app_id: 'autotracking-form-' + testIdentifier,
unstruct_event: {
data: {
schema: 'iglu:com.snowplowanalytics.snowplow/submit_form/jsonschema/1-0-0',
data: {
formId: 'myForm',
formClasses: ['formy-mcformface'],
elements: [
{
name: 'message',
value: 'This is a message',
nodeName: 'TEXTAREA',
},
{
name: 'fname',
value: 'John',
nodeName: 'INPUT',
type: 'text',
},
{ name: 'lname', value: 'Doe', nodeName: 'INPUT', type: 'text' },
{
name: 'pname',
value: 'redacted',
nodeName: 'INPUT',
type: 'text',
},
{ name: 'vehicle', value: null, nodeName: 'INPUT', type: 'radio' },
{ name: 'terms', value: null, nodeName: 'INPUT', type: 'checkbox' },
{ name: 'cars', value: 'volvo', nodeName: 'SELECT' },
],
},
},
},
},
})
).toBe(true);
});
});
17 changes: 17 additions & 0 deletions trackers/javascript-tracker/test/pages/form-tracking.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@
<input type="text" id="fname" name="fname" value="John" class="test" /><br />
<label for="lname">Last name:</label><br />
<input type="text" id="lname" name="lname" value="Doe" /><br /><br />

<label for="pii">Personal Info:</label><br />
<input type="text" id="pid" name="pname" value="danger" /><br /><br />

<input type="radio" id="bike" name="vehicle" value="Bike" />
<label for="bike"> I have a bike</label><br />
</fieldset>
Expand Down Expand Up @@ -106,6 +110,12 @@
function formFilter(formElement) {
return formElement.id !== 'lname';
}
function redactPII(eltValue, _, elt) {
if (elt.id === 'pid') {
return 'redacted';
}
return eltValue;
}

switch (parseQuery().filter) {
case 'exclude':
Expand All @@ -122,6 +132,13 @@
},
});
break;
case 'transform':
snowplow('enableFormTracking', {
options: {
fields: { transform: redactPII },
},
});
break;
case 'excludedForm':
snowplow('enableFormTracking', { options: { forms: { denylist: ['excluded-form'] } } });
break;
Expand Down

0 comments on commit c136619

Please sign in to comment.