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

[Backend] Add Systems Applicable Filter to Privacy Experience List #3654

Merged
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -26,6 +26,7 @@ The types of changes are:
### Fixed
- Render linebreaks in the Fides.js overlay descriptions, etc. [#3665](https://github.com/ethyca/fides/pull/3665)
- Broken link to Fides docs site on the About Fides page in Admin UI [#3643](https://github.com/ethyca/fides/pull/3643)
- Add Systems Applicable Filter to Privacy Experience List [#3654](https://github.com/ethyca/fides/pull/3654)

### Developer Experience

Expand Down
2 changes: 1 addition & 1 deletion clients/admin-ui/src/features/system/system.slice.ts
Expand Up @@ -46,7 +46,7 @@ const systemApi = baseApi.injectEndpoints({
params: { resource_type: "system" },
method: "DELETE",
}),
invalidatesTags: ["System", "Datastore Connection"],
invalidatesTags: ["System", "Datastore Connection", "Privacy Notices"],
}),
upsertSystems: build.mutation<UpsertResponse, System[]>({
query: (systems) => ({
Expand Down
1 change: 1 addition & 0 deletions clients/fides-js/src/services/fides/api.ts
Expand Up @@ -34,6 +34,7 @@ export const fetchExperience = async (
component: ComponentType.OVERLAY,
has_notices: "true",
has_config: "true",
systems_applicable: "true",
fides_user_device_id: fidesUserDeviceId,
});
const response = await fetch(
Expand Down
2 changes: 2 additions & 0 deletions clients/privacy-center/features/consent/consent.slice.ts
Expand Up @@ -67,6 +67,8 @@ export const consentApi = baseApi.injectEndpoints({
component: ComponentType.PRIVACY_CENTER,
has_notices: true,
show_disabled: false,
has_config: true,
pattisdr marked this conversation as resolved.
Show resolved Hide resolved
systems_applicable: true,
...payload,
},
}),
Expand Down
Expand Up @@ -70,6 +70,7 @@ def privacy_experience_list(
has_notices: Optional[bool] = None,
has_config: Optional[bool] = None,
fides_user_device_id: Optional[str] = None,
systems_applicable: Optional[bool] = False,
request: Request, # required for rate limiting
response: Response, # required for rate limiting
) -> AbstractPage[PrivacyExperience]:
Expand Down Expand Up @@ -130,7 +131,7 @@ def privacy_experience_list(
privacy_notices: List[
PrivacyNotice
] = privacy_experience.get_related_privacy_notices(
db, show_disabled, fides_user_provided_identity
db, show_disabled, systems_applicable, fides_user_provided_identity
)
if should_unescape:
# Unescape both the experience config and the embedded privacy notices
Expand Down
8 changes: 8 additions & 0 deletions src/fides/api/models/privacy_experience.py
Expand Up @@ -19,6 +19,7 @@
)
from fides.api.models.privacy_preference import CurrentPrivacyPreference
from fides.api.models.privacy_request import ProvidedIdentity
from fides.api.models.sql_models import System # type: ignore[attr-defined]

BANNER_CONSENT_MECHANISMS: Set[ConsentMechanism] = {
ConsentMechanism.notice_only,
Expand Down Expand Up @@ -243,6 +244,7 @@ def get_related_privacy_notices(
self,
db: Session,
show_disabled: Optional[bool] = True,
systems_applicable: Optional[bool] = False,
fides_user_provided_identity: Optional[ProvidedIdentity] = None,
) -> List[PrivacyNotice]:
"""Return privacy notices that overlap on at least one region
Expand All @@ -260,6 +262,12 @@ def get_related_privacy_notices(
PrivacyNotice.disabled.is_(False)
)

if systems_applicable:
data_uses: set[str] = System.get_data_uses(
System.all(db), include_parents=True
)
privacy_notice_query = privacy_notice_query.filter(PrivacyNotice.data_uses.overlap(data_uses)) # type: ignore

if not fides_user_provided_identity:
return privacy_notice_query.order_by(PrivacyNotice.created_at.desc()).all()

Expand Down
56 changes: 56 additions & 0 deletions tests/ops/api/v1/endpoints/test_privacy_experience_endpoints.py
Expand Up @@ -367,6 +367,62 @@ def test_filter_on_notices_and_region(
assert notices[1]["id"] == privacy_notice.id
assert notices[1]["displayed_in_privacy_center"]

@pytest.mark.usefixtures(
"privacy_notice_us_co_provide_service_operations", # not displayed in overlay or privacy center
"privacy_notice_eu_cy_provide_service_frontend_only", # doesn't overlap with any regions,
"privacy_experience_overlay", # us_ca
"privacy_notice_eu_fr_provide_service_frontend_only", # eu_fr
"privacy_notice_us_ca_provide", # us_ca
"privacy_experience_privacy_center",
)
def test_filter_on_systems_applicable(
self,
api_client: TestClient,
url,
privacy_experience_privacy_center,
privacy_notice,
system,
privacy_notice_us_co_third_party_sharing,
):
"""For systems applicable filter, notices are only embedded if they are relevant to a system"""
resp = api_client.get(
url + "?region=us_co",
)
assert resp.status_code == 200
data = resp.json()

assert data["total"] == 1
assert len(data["items"]) == 1

notices = data["items"][0]["privacy_notices"]
assert len(notices) == 2
assert notices[0]["regions"] == ["us_co"]
assert notices[0]["id"] == privacy_notice_us_co_third_party_sharing.id
assert notices[0]["displayed_in_privacy_center"]
assert notices[0]["data_uses"] == ["third_party_sharing"]

assert notices[1]["regions"] == ["us_ca", "us_co"]
assert notices[1]["id"] == privacy_notice.id
assert notices[1]["displayed_in_privacy_center"]
assert notices[1]["data_uses"] == [
"marketing.advertising",
"third_party_sharing",
]

resp = api_client.get(
url + "?region=us_co&systems_applicable=True",
)
notices = resp.json()["items"][0]["privacy_notices"]
assert len(notices) == 1
assert notices[0]["regions"] == ["us_ca", "us_co"]
assert notices[0]["id"] == privacy_notice.id
assert notices[0]["displayed_in_privacy_center"]
assert notices[0]["data_uses"] == [
"marketing.advertising",
"third_party_sharing",
]
assert system.privacy_declarations[0].data_use == "marketing.advertising"

@pytest.mark.usefixtures(
"privacy_notice_us_co_provide_service_operations", # not displayed in overlay or privacy center
"privacy_notice_eu_cy_provide_service_frontend_only", # doesn't overlap with any regions,
Expand Down
19 changes: 18 additions & 1 deletion tests/ops/models/test_privacy_experience.py
Expand Up @@ -314,7 +314,7 @@ def test_update_privacy_experience(self, db, experience_config_overlay):

exp.delete(db)

def test_get_related_privacy_notices(self, db):
def test_get_related_privacy_notices(self, db, system):
"""Test PrivacyExperience.get_related_privacy_notices that are embedded in PrivacyExperience request"""
privacy_experience = PrivacyExperience.create(
db=db,
Expand Down Expand Up @@ -369,6 +369,23 @@ def test_get_related_privacy_notices(self, db):
== []
)

# Privacy notice is applicable to a system - they share a data use
assert privacy_experience.get_related_privacy_notices(
db, systems_applicable=True
) == [privacy_notice]

system.privacy_declarations[0].delete(db)
db.refresh(system)

# Privacy notice is no longer applicable to any systems
assert (
privacy_experience.get_related_privacy_notices(db, systems_applicable=True)
== []
)

privacy_notice.histories[0].delete(db)
privacy_notice.delete(db)

def test_get_should_show_banner(self, db):
"""Test PrivacyExperience.get_should_show_banner that is calculated at runtime"""
privacy_experience = PrivacyExperience.create(
Expand Down