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

Double vendor toggles #4189

Merged
merged 8 commits into from
Oct 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ The types of changes are:

### Added
- Added an option to link to vendor tab from an experience config description [#4191](https://github.com/ethyca/fides/pull/4191)
- Added two toggles for vendors in the TCF overlay, one for Consent, and one for Legitimate Interest [#4189](https://github.com/ethyca/fides/pull/4189)


### Changed
- Removed `TCF_ENABLED` environment variable from the privacy center in favor of dynamically figuring out which `fides-js` bundle to send [#4131](https://github.com/ethyca/fides/pull/4131)
- Updated copy of info boxes on each TCF tab [#4191](https://github.com/ethyca/fides/pull/4191)
Expand Down
3 changes: 3 additions & 0 deletions clients/fides-js/src/components/DataUseToggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const DataUseToggle = ({
disabled,
isHeader,
includeToggle = true,
secondToggle,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is not a good pattern! it's weird that there'd be a toggle defined in the component, and a separate one passed in. but it works quite well, and time is short 🥲

eventually, I think we should totally rework TcfVendors to be a table, and maybe not even use DataUseToggle which was never really meant to be a table.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

follow up ticket: #4190

}: {
dataUse: DataUse;
checked: boolean;
Expand All @@ -27,6 +28,7 @@ const DataUseToggle = ({
disabled?: boolean;
isHeader?: boolean;
includeToggle?: boolean;
secondToggle?: ComponentChildren;
}) => {
const {
isOpen,
Expand Down Expand Up @@ -81,6 +83,7 @@ const DataUseToggle = ({
disabled={disabled}
/>
) : null}
{secondToggle || null}
</div>
{children ? <div {...getDisclosureProps()}>{children}</div> : null}
</div>
Expand Down
16 changes: 16 additions & 0 deletions clients/fides-js/src/components/fides.css
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,10 @@ div#fides-modal .fides-modal-button-group {
align-items: center;
}

.fides-margin-right {
margin-right: 0.2em;
}

/* TCF toggles */
.fides-tcf-toggle-content {
margin-right: 60px;
Expand Down Expand Up @@ -681,6 +685,18 @@ div#fides-modal .fides-modal-button-group {
margin-left: 0;
}

.fides-tcf-vendor-toggles {
display: flex;
}

.fides-legal-basis-labels {
display: flex;
align-items: end;
justify-content: end;
font-size: 0.8em;
font-weight: 500;
}

/* Vendor purpose table */
.fides-vendor-details-table {
width: 100%;
Expand Down
15 changes: 10 additions & 5 deletions clients/fides-js/src/components/tcf/TcfConsentButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { VNode, h } from "preact";

import { PrivacyExperience } from "../../lib/consent-types";
import { ConsentButtons } from "../ConsentButtons";
import type { EnabledIds } from "./TcfOverlay";
import type { EnabledIds } from "../../lib/tcf/types";
import {
TCFPurposeRecord,
TCFFeatureRecord,
Expand Down Expand Up @@ -40,13 +40,18 @@ export const TcfConsentButtons = ({
}

const handleAcceptAll = () => {
const vendorsAndSystems = [
...(experience.tcf_vendors || []),
...(experience.tcf_systems || []),
];
const allIds: EnabledIds = {
purposes: getAllIds(experience.tcf_purposes),
specialPurposes: getAllIds(experience.tcf_special_purposes),
features: getAllIds(experience.tcf_features),
specialFeatures: getAllIds(experience.tcf_special_features),
vendors: getAllIds(experience.tcf_vendors),
systems: getAllIds(experience.tcf_systems),
// TODO: make these read from separate fields once the backend supports it (fidesplus1128)
vendorsConsent: getAllIds(vendorsAndSystems),
vendorsLegint: getAllIds(vendorsAndSystems),
};
onSave(allIds);
};
Expand All @@ -56,8 +61,8 @@ export const TcfConsentButtons = ({
specialPurposes: [],
features: [],
specialFeatures: [],
vendors: [],
systems: [],
vendorsConsent: [],
vendorsLegint: [],
};
onSave(emptyIds);
};
Expand Down
173 changes: 132 additions & 41 deletions clients/fides-js/src/components/tcf/TcfOverlay.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { h, FunctionComponent } from "preact";
import { useState, useCallback, useMemo } from "preact/hooks";
import { TCString } from "@iabtechlabtcf/core";
import ConsentBanner from "../ConsentBanner";

import {
Expand All @@ -14,23 +15,26 @@ import Overlay from "../Overlay";
import { TcfConsentButtons } from "./TcfConsentButtons";
import { OverlayProps } from "../types";

import type {
TCFFeatureRecord,
TCFFeatureSave,
TCFPurposeRecord,
TCFPurposeSave,
TCFSpecialFeatureSave,
TCFSpecialPurposeSave,
TCFVendorRecord,
TCFVendorSave,
TcfSavePreferences,
import {
type EnabledIds,
LegalBasisForProcessingEnum,
type TCFFeatureRecord,
type TCFFeatureSave,
type TCFPurposeRecord,
type TCFPurposeSave,
type TCFSpecialFeatureSave,
type TCFSpecialPurposeSave,
type TCFVendorRecord,
type TCFVendorSave,
type TcfSavePreferences,
} from "../../lib/tcf/types";

import { updateConsentPreferences } from "../../lib/preferences";
import {
ButtonType,
ConsentMethod,
PrivacyExperience,
UserConsentPreference,
} from "../../lib/consent-types";
import { generateTcString } from "../../lib/tcf";
import {
Expand All @@ -41,6 +45,7 @@ import InitialLayer from "./InitialLayer";
import TcfTabs from "./TcfTabs";
import Button from "../Button";
import VendorInfoBanner from "./VendorInfoBanner";
import { vendorRecordsWithLegalBasis } from "../../lib/tcf/vendors";

const resolveConsentValueFromTcfModel = (
model: TCFPurposeRecord | TCFFeatureRecord | TCFVendorRecord
Expand Down Expand Up @@ -78,14 +83,24 @@ const getEnabledIds = (modelList: TcfModels) => {
.map((model) => `${model.id}`);
};

export interface EnabledIds {
purposes: string[];
specialPurposes: string[];
features: string[];
specialFeatures: string[];
vendors: string[];
systems: string[];
}
const getVendorEnabledIds = (
modelList: TCFVendorRecord[] | undefined,
legalBasis: LegalBasisForProcessingEnum
) => {
if (!modelList) {
return [];
}
const records = vendorRecordsWithLegalBasis(modelList, legalBasis);
if (legalBasis === LegalBasisForProcessingEnum.LEGITIMATE_INTERESTS) {
// TODO: the backend should eventually return legint fields with a default preference of OPT_IN
allisonking marked this conversation as resolved.
Show resolved Hide resolved
const modifiedRecords = records.map((record) => ({
...record,
default_preference: UserConsentPreference.OPT_IN,
}));
return getEnabledIds(modifiedRecords);
}
return getEnabledIds(records);
};

export interface UpdateEnabledIds {
newEnabledIds: string[];
Expand Down Expand Up @@ -119,32 +134,67 @@ const createTcfSavePayload = ({
}: {
experience: PrivacyExperience;
enabledIds: EnabledIds;
}): TcfSavePreferences => ({
purpose_preferences: transformTcfModelToTcfSave({
modelList: experience.tcf_purposes,
enabledIds: enabledIds.purposes,
}) as TCFPurposeSave[],
special_feature_preferences: transformTcfModelToTcfSave({
modelList: experience.tcf_special_features,
enabledIds: enabledIds.specialFeatures,
}) as TCFSpecialFeatureSave[],
vendor_preferences: transformTcfModelToTcfSave({
modelList: experience.tcf_vendors,
enabledIds: enabledIds.vendors,
}) as TCFVendorSave[],
system_preferences: transformTcfModelToTcfSave({
modelList: experience.tcf_systems,
enabledIds: enabledIds.systems,
}) as TCFVendorSave[],
});
}): TcfSavePreferences => {
// Because systems were combined with vendors to make the UI easier to work with,
// we need to separate them out now (the backend treats them as separate entities).
const systemIds = experience.tcf_systems
? experience.tcf_systems.map((s) => s.id)
: [];
const enabledSystemIds: string[] = [];
const enabledVendorIds: string[] = [];
enabledIds.vendorsConsent.forEach((id) => {
if (systemIds.includes(id)) {
enabledSystemIds.push(id);
} else {
enabledVendorIds.push(id);
}
});
enabledIds.vendorsLegint.forEach((id) => {
if (systemIds.includes(id)) {
enabledSystemIds.push(id);
} else {
enabledVendorIds.push(id);
}
});

return {
purpose_preferences: transformTcfModelToTcfSave({
modelList: experience.tcf_purposes,
enabledIds: enabledIds.purposes,
}) as TCFPurposeSave[],
special_feature_preferences: transformTcfModelToTcfSave({
modelList: experience.tcf_special_features,
enabledIds: enabledIds.specialFeatures,
}) as TCFSpecialFeatureSave[],
vendor_preferences: transformTcfModelToTcfSave({
modelList: experience.tcf_vendors,
// TODO: once the backend is storing this, we should send vendorsConsent
// and vendorsLegint to separate fields (fidesplus1128)
enabledIds: enabledVendorIds,
}) as TCFVendorSave[],
system_preferences: transformTcfModelToTcfSave({
modelList: experience.tcf_systems,
enabledIds: enabledSystemIds,
}) as TCFVendorSave[],
};
};

const updateCookie = async (
oldCookie: FidesCookie,
/**
* `tcf` and `enabledIds` should represent the same data, where `tcf` is what is
* sent to the backend, and `enabledIds` is what the FE uses. They have diverged
* because the backend has not implemented separate vendor legint/consents yet.
* Therefore, we need both entities right now, but eventually we should be able to
* only use one. In other words, `enabledIds` has a field for `vendorsConsent` and
* `vendorsLegint` but `tcf` only has `vendors`.
Copy link
Contributor

Choose a reason for hiding this comment

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

thanks for this code comment, it helps me understand approach here!

*/
tcf: TcfSavePreferences,
enabledIds: EnabledIds,
experience: PrivacyExperience
): Promise<FidesCookie> => {
const tcString = await generateTcString({
tcStringPreferences: tcf,
tcStringPreferences: enabledIds,
experience,
});
return {
Expand All @@ -170,15 +220,55 @@ const TcfOverlay: FunctionComponent<OverlayProps> = ({
tcf_systems: systems,
} = experience;

const vendorsAndSystems = [...(vendors || []), ...(systems || [])];
let vendorsConsent = getVendorEnabledIds(
vendorsAndSystems,
LegalBasisForProcessingEnum.CONSENT
);
let vendorsLegint = getVendorEnabledIds(
vendorsAndSystems,
LegalBasisForProcessingEnum.LEGITIMATE_INTERESTS
);

// Initialize vendor values from the TC string if it's available. Neither the
// backend nor the cookie store vendorsConsent or vendorsLegint yet, so we must
// look at the string. (fidesplus#1128)
if (cookie.tc_string && cookie.tc_string !== "") {
const tcModel = TCString.decode(cookie.tc_string || "");
vendorsConsent = [];
vendorsLegint = [];
tcModel.vendorConsents.forEach((consented, id) => {
if (consented) {
vendorsConsent.push(`${id}`);
}
});
tcModel.vendorLegitimateInterests.forEach((consented, id) => {
if (consented) {
vendorsLegint.push(`${id}`);
}
});
// but we still need to join system data to this
const systemConsents = getVendorEnabledIds(
systems,
LegalBasisForProcessingEnum.CONSENT
);
const systemLegints = getVendorEnabledIds(
systems,
LegalBasisForProcessingEnum.LEGITIMATE_INTERESTS
);
vendorsConsent = [...vendorsConsent, ...systemConsents];
vendorsLegint = [...vendorsLegint, ...systemLegints];
}

return {
purposes: getEnabledIds(purposes),
specialPurposes: getEnabledIds(specialPurposes),
features: getEnabledIds(features),
specialFeatures: getEnabledIds(specialFeatures),
vendors: getEnabledIds(vendors),
systems: getEnabledIds(systems),
vendorsConsent,
vendorsLegint,
};
}, [experience]);
}, [experience, cookie]);

const [draftIds, setDraftIds] = useState<EnabledIds>(initialEnabledIds);

Expand Down Expand Up @@ -208,7 +298,8 @@ const TcfOverlay: FunctionComponent<OverlayProps> = ({
debug: options.debug,
servedNotices: null, // TODO: served notices
tcf,
updateCookie: (oldCookie) => updateCookie(oldCookie, tcf, experience),
updateCookie: (oldCookie) =>
updateCookie(oldCookie, tcf, enabledIds, experience),
});
setDraftIds(enabledIds);
},
Expand Down
10 changes: 6 additions & 4 deletions clients/fides-js/src/components/tcf/TcfTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,12 @@ const TcfTabs = ({
assert.
</InfoBox>
<TcfVendors
allSystems={experience.tcf_systems}
allVendors={experience.tcf_vendors}
enabledVendorIds={enabledIds.vendors}
enabledSystemIds={enabledIds.systems}
vendors={[
...(experience.tcf_vendors || []),
...(experience.tcf_systems || []),
]}
enabledVendorConsentIds={enabledIds.vendorsConsent}
enabledVendorLegintIds={enabledIds.vendorsLegint}
onChange={onChange}
gvl={experience.gvl}
/>
Expand Down
Loading