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

Recording interactions with TCF notices #4161

Merged
merged 27 commits into from
Oct 25, 2023

Conversation

galvana
Copy link
Contributor

@galvana galvana commented Sep 26, 2023

Description Of Changes

Adding served_notice_history_id to the TCF fields of the PATCH /privacy-preference request.

Code Changes

  • Created a distinction between banner and tcf_banner for FidesUIShown events
  • PATCH /notices-served is called from the tcf_banner and the consent overlay
  • Updated Cypress tests for the TCF banner worfklows

Steps to Confirm

  • Set FIDES__CONSENT__TCF_ENABLED to true
  • Start fidesplus
  • Navigate to http://localhost:3000/fides-js-demo.html?geolocation=fi-18 to view the TCF banner
  • Look for the Dispatching event type FidesUIShown from tcf_banner in the browser console
  • Opt-in or opt-out and verify a call to PATCH /privacy-preferences is made, the TCF fields should have the served_notice_history_id
image

Pre-Merge Checklist

  • All CI Pipelines Succeeded
  • Issue Requirements are Met
  • Update CHANGELOG.md

@cypress
Copy link

cypress bot commented Sep 26, 2023

Passing run #4799 ↗︎

0 4 0 0 Flakiness 0
⚠️ You've recorded test results over your free plan limit.
Upgrade your plan to view test results.

Details:

Merge 9a00147 into cf5814b...
Project: fides Commit: e507d1d864 ℹ️
Status: Passed Duration: 00:55 💡
Started: Oct 25, 2023 3:23 AM Ended: Oct 25, 2023 3:24 AM

Review all test suite changes for PR #4161 ↗︎

Copy link
Contributor

@allisonking allisonking left a comment

Choose a reason for hiding this comment

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

nice job @galvana this is looking like the right approach so far! we'll want to make sure the types are right—they make sense to me, but we should verify with a npm run openapi:generate. let me know if you'd like help with that!


const isAllNoticeOnly = privacyNotices.every(
(n) => n.consent_mechanism === ConsentMechanism.NOTICE_ONLY
);
Copy link
Contributor

Choose a reason for hiding this comment

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

we don't need this right now—all TCF payloads will return an empty privacy_notices array, so we can just pass [] to notices. also, TCF doesn't have a concept of notice-only yet (see #4141), so for now, let's just pass in false for acknowledgeMode

const handleUpdateAllPreferences = useCallback(
(enabledIds: EnabledIds) => {
const tcf = createTcfSavePayload({ experience, enabledIds });
(enabledIds: EnabledIds, servedNotices: LastServedNoticeSchema[]) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think servedNotices needs to be an arg here—I think you can just reference it above (since it's function inside the component, it has access to its vars)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's what I tried originally but when I logged out the value of servedNotices inside handleUpdateAllPreferences it was empty. The only way I could get it to work is by passing it in as a param. Could it be something to do with useConsentServed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Never mind, this is working without the servedNotices arg

@galvana galvana linked an issue Sep 28, 2023 that may be closed by this pull request
@galvana galvana changed the title First pass Recording interactions with TCF notices Sep 28, 2023
@codecov
Copy link

codecov bot commented Sep 29, 2023

Codecov Report

All modified and coverable lines are covered by tests ✅

Comparison is base (cf5814b) 87.83% compared to head (9a00147) 87.84%.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #4161   +/-   ##
=======================================
  Coverage   87.83%   87.84%           
=======================================
  Files         333      333           
  Lines       21065    21066    +1     
  Branches     2742     2742           
=======================================
+ Hits        18503    18505    +2     
+ Misses       2097     2096    -1     
  Partials      465      465           
Files Coverage Δ
src/fides/api/models/privacy_preference.py 97.72% <100.00%> (+<0.01%) ⬆️

... and 1 file with indirect coverage changes

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@galvana galvana marked this pull request as ready for review October 12, 2023 07:45
@galvana galvana requested a review from allisonking October 12, 2023 16:55
Copy link
Contributor

@allisonking allisonking left a comment

Choose a reason for hiding this comment

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

awesome job figuring this out @galvana ! I left a comment for something I think we can improve, curious to hear your thoughts!

| "vendor_legitimate_interests"
| "system_consent"
| "system_legitimate_interests";
type NoticeSubMap = {
Copy link
Contributor

Choose a reason for hiding this comment

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

these aren't technically Notices (we typically don't use notices for TCF). maybe we can change this to be TcfEntitiesMap?


type NoticeMap = Record<Category, NoticeSubMap>;

const noticeMap = (servedNotices: LastServedConsentSchema[]): NoticeMap => {
Copy link
Contributor

Choose a reason for hiding this comment

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

heh I guess these shouldn't really be called servedNotices either (you probably noticed the backend type changed from LastServedNoticeSchema --> LastServedConsentSchema) so I guess this should be servedConsent. it's definitely a nit, but I think not conflating notices and TCF is a good distinction that helps with understanding all that's going on here


servedNotices.forEach((notice) => {
(Object.keys(map) as Category[]).forEach((key) => {
const value = notice[key as keyof LastServedConsentSchema];
Copy link
Contributor

Choose a reason for hiding this comment

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

it might help TS to do something like this https://github.com/ethyca/fides/blob/main/clients/fides-js/src/lib/tcf/types.ts/#L177-L186 for Category and LastServedConsentSchema. that said, it might not get you as far as you want, sometimes TS is picky about this kind of thing. but it might save you an as here or there

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Let's discuss this next week, I couldn't figure out a way to remove the as keyof without adding more code than we currently have for the related type definitions.

Copy link
Contributor

Choose a reason for hiding this comment

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

okay no worries—I figured TS might get picky about it. let's leave it as-is then!

@@ -104,17 +147,21 @@ const transformTcfModelToTcfSave = ({
return {
id: model.id,
preference,
served_notice_history_id: noticeSubMap[String(model.id)],
Copy link
Contributor

Choose a reason for hiding this comment

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

ah I guess this type is still called notice in the backend 😅 ok I take back what I said about distinguishing between notices and TCF...

clients/fides-js/src/lib/hooks.ts Show resolved Hide resolved
@@ -246,14 +312,14 @@ const TcfOverlay: FunctionComponent<OverlayProps> = ({
userLocationString: fidesRegionString,
cookie,
debug: options.debug,
servedNotices: null, // TODO: served notices
servedNotices: null,
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the thing to do here is to actually refactor NoticeOverlay a little too... I know that's not what this ticket calls for, but I think it'd be a good thing to do if you're up for it. basically, before TCF ever came along, we would:

  1. In NoticeOverlay get the servedNotices
  2. When the user saves, pass the servedNotices to updateConsentPreferences along with the consentPreferencesToSave
  3. updateConsentPreferences would then do the unenviable task of mapping servedNotices to consentPreferencesToSave

Now that we have TCF, we:

  1. In TcfOverlay get the servedNotices
  2. TcfOverlay does the unenviable task of mapping servedNotices to each of the tcf entities (what your noticeSubMap is doing
  3. We pass the tcf obj directly to updateConsentPreferences and set servedNotices=null

This is a good approach, and I think we should do the same for notices. In other words, I think updateConsentPreferences should no longer take a servedNotices, and we should pass an object that already has the servedNotices matched into updateConsentPreferences, same as we do for TCF. so then we can remove some code from preferences.ts. this might involve updating the type that updateConsentPreferences takes but hopefully it's not arduous. what do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good! I gave it a shot, let me know if that's what you had in mind.

Copy link
Contributor

Choose a reason for hiding this comment

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

nice job, I think you handled it perfectly! 💯

@galvana galvana requested a review from allisonking October 14, 2023 00:34
Copy link
Contributor

@allisonking allisonking left a comment

Choose a reason for hiding this comment

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

I'm pretty much ready to approve this as I tested locally and it looks great, and I super appreciate the refactor you did for notices! I'm just going to go ask product one more question, and then hopefully we can get this merged 💥

(event.detail.extraDetails.servingComponent !==
ServingComponent.OVERLAY &&
event.detail.extraDetails.servingComponent !==
ServingComponent.TCF_BANNER)
Copy link
Contributor

Choose a reason for hiding this comment

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

now that I'm testing this out, I think there's something we should ask product for clarity on... we had a discussion back when we did this for notices of "when" to say consent was served. for notices, we decided only for when the modal pops up, since the banner doesn't actually render notices.

for TCF, as you know, we decided the banner does render what the user is consenting to (via the "stacks") and so we put it on the banner. however, nothing happens if only the modal is opened. for example, if I've already opted in to everything, the banner doesn't render the next time I visit the site. but if I decide I want to make a change and open the modal (by clicking "Manage Consent" on the page in the screenshot), then we don't send another notices-served patch.

image

I suspect we want to send it for both banner and overlay for TCF, but that we don't want to send it twice, if the user goes through the flow of 1. banner opens 2. user clicks "manage preferences" in the banner 3. the modal pops up. that was actually my first implementation of notices back when I was POC-ing it—we can add a state to the hook and it should sort itself out: 5ad7ebc

but let's confirm with product first, I can ask...

@galvana galvana requested a review from allisonking October 25, 2023 03:09
@galvana
Copy link
Contributor Author

galvana commented Oct 25, 2023

I was finally able to get back to this. I updated the conditions to trigger the notices served to include the TCF_OVERLAY. The only time we don't trigger the notice is for the consent banner, since that's the only component where notices aren't shown directly.

Copy link
Contributor

@allisonking allisonking left a comment

Choose a reason for hiding this comment

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

whew, way to persist through this PR! I know it's probably been haunting you for a while now 😄 great job! ⭐

ServingComponent.OVERLAY &&
event.detail.extraDetails.servingComponent !==
ServingComponent.TCF_BANNER)
event.detail.extraDetails.servingComponent === ServingComponent.BANNER
Copy link
Contributor

Choose a reason for hiding this comment

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

ah nice, this is clean, and the comment helps a lot 👍

@galvana galvana merged commit 0dfa690 into main Oct 25, 2023
@galvana galvana deleted the PROD-914-recording-interactions-with-tcf-notices branch October 25, 2023 22:10
@galvana
Copy link
Contributor Author

galvana commented Oct 25, 2023

whew, way to persist through this PR! I know it's probably been haunting you for a while now 😄 great job! ⭐

I'm free!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Recording interactions with TCF notices
2 participants