Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added FidesUpdating event #4816

Merged
merged 11 commits into from
Apr 21, 2024
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ The types of changes are:
- Added carets to collapsible sections in the overlay modal [#4793](https://github.com/ethyca/fides/pull/4793)
- Added erasure support for OpenWeb [#4735](https://github.com/ethyca/fides/pull/4735)
- Added support for configuration of pre-approval webhooks [#4795](https://github.com/ethyca/fides/pull/4795)
- Added CMP API fidesClearCookie to load CMP without preferences on refresh [#4810](https://github.com/ethyca/fides/pull/4810)
- Added fides_clear_cookie option to FidesJS SDK to load CMP without preferences on refresh [#4810](https://github.com/ethyca/fides/pull/4810)
- Added FidesUpdating event to FidesJS SDK [#4816](https://github.com/ethyca/fides/pull/4816)

### Changed
- Removed the Celery startup banner from the Fides worker logs [#4814](https://github.com/ethyca/fides/pull/4814)
Expand All @@ -30,6 +31,9 @@ The types of changes are:
- Fixed bug prevented adding new privacy center translations [#4786](https://github.com/ethyca/fides/pull/4786)
- Fixed bug where Privacy Policy links would be shown without a configured URL [#4801](https://github.com/ethyca/fides/pull/4801)

### Changed
- Removed the Celery startup banner from the Fides worker logs [#4814](https://github.com/ethyca/fides/pull/4814)

## [2.34.0](https://github.com/ethyca/fides/compare/2.33.1...2.34.0)

### Added
Expand Down
16 changes: 13 additions & 3 deletions clients/fides-js/docs/interfaces/FidesEvent.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,19 @@ the browser, see the MDN docs:
current user's consent preferences - either previously saved or applicable
defaults - have been set on the `Fides` global object.

- `FidesUpdated`: Dispatched whenever the current user's consent preferences
are updated on the `Fides` global object due to a user action (e.g. accepting
all, applying GPC). The
- `FidesUpdating`: Dispatched when a user action (e.g. accepting all, saving
changes, applying GPC) has started updating the user's consent preferences.
This event is dispatched immediately once the changes are made, but before
they are saved to the `Fides` object, `fides_consent` cookie on the user's
device, and the Fides API. To wait until the changes are fully
applied, use the `FidesUpdated` event instead.

- `FidesUpdated`: Dispatched when a user action (e.g. accepting all, saving
changes, applying GPC) has finished updating the user's consent preferences.
This event is dispatched once the changes are fully saved to the `Fides`
object, `fides_consent` cookie on the user's device, and the Fides API. To
receive an event that fires before these changes are saved, use the
`FidesUpdating` event instead.

- `FidesUIShown`: Dispatched whenever a FidesJS UI component is rendered and
shown to the current user (banner, modal, etc.). The specific component shown
Expand Down
24 changes: 13 additions & 11 deletions clients/fides-js/docs/interfaces/FidesOptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,28 @@ order of precedence:

### Properties

- [fides\_clear\_cookie](FidesOptions.md#fides_clear_cookie)
- [fides\_disable\_banner](FidesOptions.md#fides_disable_banner)
- [fides\_disable\_save\_api](FidesOptions.md#fides_disable_save_api)
- [fides\_embed](FidesOptions.md#fides_embed)
- [fides\_locale](FidesOptions.md#fides_locale)
- [fides\_string](FidesOptions.md#fides_string)
- [fides\_tcf\_gdpr\_applies](FidesOptions.md#fides_tcf_gdpr_applies)
- [fides\_clear\_cookie](FidesOptions.md#fides_clear_cookie)

## Properties

### fides\_clear\_cookie

• **fides\_clear\_cookie**: `boolean`

When `true`, deletes the `fides_consent` cookie when FidesJS is
initialized, to clear any previously saved consent preferences from the
user's device.

Defaults to `false`.

___

### fides\_disable\_banner

• **fides\_disable\_banner**: `boolean`
Expand Down Expand Up @@ -150,13 +162,3 @@ overriden at the page-level as needed. Only applicable to a TCF experience.
For more details, see the [TCF CMP API technical specification](https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework/blob/master/TCFv2/IAB%20Tech%20Lab%20-%20CMP%20API%20v2.md#what-does-the-gdprapplies-value-mean) *

Defaults to `true`.

___

### fides\_clear\_cookie

• **fides\_clear\_cookie**: `boolean`

When `true`, shows fides.js overlay UI on load. This deletes the fides_consent cookie as if no preferences have been saved on reload.

Defaults to `false`.
19 changes: 15 additions & 4 deletions clients/fides-js/src/docs/fides-event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
*/
export type FidesEventType =
| "FidesInitialized"
| "FidesUpdating"
| "FidesUpdated"
| "FidesUIShown"
| "FidesUIChanged"
Expand Down Expand Up @@ -43,11 +44,21 @@ export type FidesEventType =
* - `FidesInitialized`: Dispatched when initialization is complete and the
* current user's consent preferences - either previously saved or applicable
* defaults - have been set on the `Fides` global object.
*
* - `FidesUpdating`: Dispatched when a user action (e.g. accepting all, saving
* changes, applying GPC) has started updating the user's consent preferences.
* This event is dispatched immediately once the changes are made, but before
* they are saved to the `Fides` object, `fides_consent` cookie on the user's
* device, and the Fides API. To wait until the changes are fully
* applied, use the `FidesUpdated` event instead.
*
* - `FidesUpdated`: Dispatched whenever the current user's consent preferences
* are updated on the `Fides` global object due to a user action (e.g. accepting
* all, applying GPC). The
*
* - `FidesUpdated`: Dispatched when a user action (e.g. accepting all, saving
* changes, applying GPC) has finished updating the user's consent preferences.
* This event is dispatched once the changes are fully saved to the `Fides`
* object, `fides_consent` cookie on the user's device, and the Fides API. To
* receive an event that fires before these changes are saved, use the
* `FidesUpdating` event instead.
*
* - `FidesUIShown`: Dispatched whenever a FidesJS UI component is rendered and
* shown to the current user (banner, modal, etc.). The specific component shown
* can be obtained from the `detail.extraDetails.servingComponent` property on
Expand Down
17 changes: 9 additions & 8 deletions clients/fides-js/src/docs/fides-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@
* ```
*/
export interface FidesOptions {
/**
* When `true`, deletes the `fides_consent` cookie when FidesJS is
* initialized, to clear any previously saved consent preferences from the
* user's device.
*
* Defaults to `false`.
*/
fides_clear_cookie: boolean;

/**
* When `true`, disable the FidesJS banner from being shown.
*
Expand Down Expand Up @@ -127,12 +136,4 @@ export interface FidesOptions {
* Defaults to `true`.
*/
fides_tcf_gdpr_applies: boolean;

/**
* When `true`, shows fides.js overlay UI on load. This deletes the fides_consent cookie as if no preferences have been saved on reload.
*
* Defaults to `false`.
*/

fides_clear_cookie: boolean;
};
3 changes: 3 additions & 0 deletions clients/fides-js/src/integrations/gtm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ export const gtm = () => {
window.addEventListener("FidesInitialized", (event) =>
pushFidesVariableToGTM(event)
);
window.addEventListener("FidesUpdating", (event) =>
pushFidesVariableToGTM(event)
);
window.addEventListener("FidesUpdated", (event) =>
pushFidesVariableToGTM(event)
);
Expand Down
8 changes: 1 addition & 7 deletions clients/fides-js/src/lib/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,7 @@ import type { FidesEventType } from "../docs";

// Bonus points: update the WindowEventMap interface with our custom event types
declare global {
interface WindowEventMap {
FidesInitialized: FidesEvent;
FidesUpdated: FidesEvent;
FidesUIShown: FidesEvent;
FidesUIChanged: FidesEvent;
FidesModalClosed: FidesEvent;
}
interface WindowEventMap extends Record<FidesEventType, FidesEvent> {}
}

/**
Expand Down
24 changes: 14 additions & 10 deletions clients/fides-js/src/lib/preferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,12 @@ async function savePreferencesApi(
/**
* Updates the user's consent preferences, going through the following steps:
* 1. Update the cookie object based on new preferences
* 2. Update the window.Fides object
* 3. Save preferences to the `fides_consent` cookie in the browser
* 4. Save preferences to Fides API or a custom function (`savePreferencesFn`)
* 5. Remove any cookies from notices that were opted-out from the browser
* 6. Dispatch a "FidesUpdated" event
* 2. Dispatch a "FidesUpdating" event with the new preferences
* 3. Update the window.Fides object
* 4. Save preferences to the `fides_consent` cookie in the browser
* 5. Save preferences to Fides API or a custom function (`savePreferencesFn`)
* 6. Remove any cookies from notices that were opted-out from the browser
* 7. Dispatch a "FidesUpdated" event
*/
export const updateConsentPreferences = async ({
consentPreferencesToSave,
Expand Down Expand Up @@ -96,18 +97,21 @@ export const updateConsentPreferences = async ({
Object.assign(cookie, updatedCookie);
Object.assign(cookie.fides_meta, extraDetails); // save extra details to meta (i.e. consentMethod)

// 2. Update the window.Fides object
// 2. Dispatch a "FidesUpdating" event with the new preferences
dispatchFidesEvent("FidesUpdating", cookie, options.debug, extraDetails);
guncha marked this conversation as resolved.
Show resolved Hide resolved

// 3. Update the window.Fides object
debugLog(options.debug, "Updating window.Fides");
window.Fides.consent = cookie.consent;
window.Fides.fides_string = cookie.fides_string;
window.Fides.tcf_consent = cookie.tcf_consent;

// 3. Save preferences to the cookie in the browser
// 4. Save preferences to the cookie in the browser
debugLog(options.debug, "Saving preferences to cookie");
saveFidesCookie(cookie, options.base64Cookie);
window.Fides.saved_consent = cookie.consent;

// 4. Save preferences to API (if not disabled)
// 5. Save preferences to API (if not disabled)
if (!options.fidesDisableSaveApi) {
try {
await savePreferencesApi(
Expand All @@ -130,7 +134,7 @@ export const updateConsentPreferences = async ({
}
}

// 5. Remove cookies associated with notices that were opted-out from the browser
// 6. Remove cookies associated with notices that were opted-out from the browser
if (consentPreferencesToSave) {
consentPreferencesToSave
.filter(
Expand All @@ -142,6 +146,6 @@ export const updateConsentPreferences = async ({
});
}

// 6. Dispatch a "FidesUpdated" event
// 7. Dispatch a "FidesUpdated" event
dispatchFidesEvent("FidesUpdated", cookie, options.debug, extraDetails);
};
9 changes: 9 additions & 0 deletions clients/privacy-center/cypress/e2e/consent-banner-tcf.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2893,6 +2893,15 @@ describe("Fides-js TCF", () => {
cy.wait("@patchPrivacyPreference").then((interception) => {
expect(interception.request.body.method).to.eql(ConsentMethod.ACCEPT);
});
// Both FidesUpdating & FidesUpdated should include the fides_string
cy.get("@FidesUpdating")
.should("have.been.calledOnce")
.its("lastCall.args.0.detail.fides_string")
.then((fidesString) => {
const parts = fidesString.split(",");
expect(parts.length).to.eql(2);
expect(parts[1]).to.eql(acceptAllAcString);
});
Comment on lines +2896 to +2904
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this test to be sure 👍

cy.get("@FidesUpdated")
.should("have.been.calledOnce")
.its("lastCall.args.0.detail.fides_string")
Expand Down
70 changes: 59 additions & 11 deletions clients/privacy-center/cypress/e2e/consent-banner.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1629,7 +1629,7 @@ describe("Consent overlay", () => {

// NOTE: See definition of cy.visitConsentDemo in commands.ts for where we
// register listeners for these window events
it("emits a FidesInitialized but not a FidesUpdated event when initialized", () => {
it("emits a FidesInitialized but not any other events when initialized", () => {
cy.window()
.its("Fides")
.its("consent")
Expand All @@ -1646,12 +1646,14 @@ describe("Consent overlay", () => {
[PRIVACY_NOTICE_KEY_2]: true,
[PRIVACY_NOTICE_KEY_3]: true,
});
cy.get("@FidesUpdating").should("not.have.been.called");
cy.get("@FidesUpdated").should("not.have.been.called");
cy.get("@FidesUIShown").should("not.have.been.called");
cy.get("@FidesUIChanged").should("not.have.been.called");
});

describe("when preferences are changed / saved", () => {
it("emits a FidesUpdated event when reject all is clicked", () => {
it("emits FidesUpdating -> FidesUpdated events when reject all is clicked", () => {
cy.contains("button", "Opt out of all").should("be.visible").click();
cy.get("@FidesUIChanged").should("not.have.been.called");
cy.get("@FidesInitialized")
Expand All @@ -1663,8 +1665,17 @@ describe("Consent overlay", () => {
[PRIVACY_NOTICE_KEY_2]: true,
[PRIVACY_NOTICE_KEY_3]: true,
});
cy.get("@FidesUpdating")
// Updating event, when the user rejects all
.should("have.been.calledOnce")
.its("firstCall.args.0.detail.consent")
.should("deep.equal", {
[PRIVACY_NOTICE_KEY_1]: false,
[PRIVACY_NOTICE_KEY_2]: true,
[PRIVACY_NOTICE_KEY_3]: false,
});
Comment on lines +1668 to +1676
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated this test (and others) to confirm this event does what we expect

cy.get("@FidesUpdated")
// Update event, when the user rejects all
// Updated event, when the preferences have finished updating
.should("have.been.calledOnce")
.its("firstCall.args.0.detail.consent")
.should("deep.equal", {
Expand All @@ -1679,7 +1690,7 @@ describe("Consent overlay", () => {
});
});

it("emits a FidesUpdated event when accept all is clicked", () => {
it("emits FidesUpdating -> FidesUpdated events when accept all is clicked", () => {
cy.contains("button", "Opt in to all").should("be.visible").click();
cy.get("@FidesUIChanged").should("not.have.been.called");
cy.get("@FidesInitialized")
Expand All @@ -1691,8 +1702,17 @@ describe("Consent overlay", () => {
[PRIVACY_NOTICE_KEY_2]: true,
[PRIVACY_NOTICE_KEY_3]: true,
});
cy.get("@FidesUpdating")
// Updating event, when the user accepts all
.should("have.been.calledOnce")
.its("firstCall.args.0.detail.consent")
.should("deep.equal", {
[PRIVACY_NOTICE_KEY_1]: true,
[PRIVACY_NOTICE_KEY_2]: true,
[PRIVACY_NOTICE_KEY_3]: true,
});
cy.get("@FidesUpdated")
// Update event, when the user accepts all
// Updated event, when the preferences have finished updating
.should("have.been.calledOnce")
.its("firstCall.args.0.detail.consent")
.should("deep.equal", {
Expand All @@ -1707,13 +1727,10 @@ describe("Consent overlay", () => {
});
});

it("emits a FidesUIChanged event when preferences are changed and a FidesUpdated event when preferences are saved", () => {
it("emits a FidesUIChanged event when preferences are changed and FidesUpdating -> FidesUpdated events when preferences are saved", () => {
cy.contains("button", "Manage preferences")
.should("be.visible")
.click();
cy.getByTestId("toggle-Advertising").click();
cy.getByTestId("consent-modal").contains("Save").click();
cy.get("@FidesUIChanged").should("have.been.calledOnce");
cy.get("@FidesInitialized")
// First event, before the user saved preferences
.should("have.been.calledOnce")
Expand All @@ -1724,8 +1741,26 @@ describe("Consent overlay", () => {
[PRIVACY_NOTICE_KEY_3]: true,
});

// Toggle the notice, but don't save yet
cy.getByTestId("toggle-Advertising").click();
cy.get("@FidesUIChanged").should("have.been.calledOnce");
cy.get("@FidesUpdating").should("not.have.been.called");
cy.get("@FidesUpdated").should("not.have.been.called");
Comment on lines +1744 to +1748
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated to this change, but I made this test a bit better.


// Save the changes
cy.getByTestId("consent-modal").contains("Save").click();
cy.get("@FidesUIChanged").should("have.been.calledOnce"); // still only once
cy.get("@FidesUpdating")
// Updating event, when the user saved preferences and opted-in to the first notice
.should("have.been.calledOnce")
.its("firstCall.args.0.detail.consent")
.should("deep.equal", {
[PRIVACY_NOTICE_KEY_1]: true,
[PRIVACY_NOTICE_KEY_2]: true,
[PRIVACY_NOTICE_KEY_3]: true,
});
cy.get("@FidesUpdated")
// Update event, when the user saved preferences and opted-in to the first notice
// Updated event, when the preferences have finished updating
.should("have.been.calledOnce")
.its("firstCall.args.0.detail.consent")
.should("deep.equal", {
Expand All @@ -1744,7 +1779,7 @@ describe("Consent overlay", () => {
it("pushes events to the GTM integration", () => {
cy.contains("button", "Opt in to all").should("be.visible").click();
cy.get("@dataLayerPush")
.should("have.been.calledTwice")
.should("have.been.calledThrice")
// First call should be from initialization, before the user accepts all
.its("firstCall.args.0")
.should("deep.equal", {
Expand All @@ -1760,6 +1795,19 @@ describe("Consent overlay", () => {
cy.get("@dataLayerPush")
// Second call is when the user accepts all
.its("secondCall.args.0")
.should("deep.equal", {
event: "FidesUpdating",
Fides: {
consent: {
[PRIVACY_NOTICE_KEY_1]: true,
[PRIVACY_NOTICE_KEY_2]: true,
[PRIVACY_NOTICE_KEY_3]: true,
},
},
});
cy.get("@dataLayerPush")
// Third call is when the preferences finish updating
.its("thirdCall.args.0")
.should("deep.equal", {
event: "FidesUpdated",
Fides: {
Expand Down
Loading
Loading