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

Basic consent reporting UI #4488

Merged
merged 10 commits into from
Dec 7, 2023
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ The types of changes are:
- Paging to vendors in the TCF overlay [#4463](https://github.com/ethyca/fides/pull/4463)
- New purposes endpoint and indices to improve system lookups [#4452](https://github.com/ethyca/fides/pull/4452)
- Add support for global TCF Purpose Overrides [#4464](https://github.com/ethyca/fides/pull/4464)
- Add new UI for CSV consent reporting [#4488](https://github.com/ethyca/fides/pull/4488)

### Fixed
- Fix type errors when TCF vendors have no dataDeclaration [#4465](https://github.com/ethyca/fides/pull/4465)
Expand Down
79 changes: 79 additions & 0 deletions clients/admin-ui/cypress/e2e/consent-reporting.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { stubPlus } from "cypress/support/stubs";

import { CONSENT_REPORTING_ROUTE } from "~/features/common/nav/v2/routes";

describe("Consent reporting", () => {
beforeEach(() => {
cy.login();
});

describe("access", () => {
it("can access the consent reporting page", () => {
stubPlus(true, {
core_fides_version: "1.9.6",
fidesplus_server: "healthy",
fidesplus_version: "1.9.6",
system_scanner: {
enabled: false,
cluster_health: null,
cluster_error: null,
},
dictionary: {
enabled: false,
service_health: null,
service_error: null,
},
tcf: {
enabled: false,
},
fides_cloud: {
enabled: false,
},
});
cy.visit(CONSENT_REPORTING_ROUTE);
cy.getByTestId("consent-reporting");
});
it("can't access without plus", () => {
stubPlus(false);
cy.visit(CONSENT_REPORTING_ROUTE);
cy.getByTestId("home-content");
});
});

describe("downloading reports", () => {
beforeEach(() => {
stubPlus(true, {
core_fides_version: "1.9.6",
fidesplus_server: "healthy",
fidesplus_version: "1.9.6",
system_scanner: {
enabled: false,
cluster_health: null,
cluster_error: null,
},
dictionary: {
enabled: false,
service_health: null,
service_error: null,
},
tcf: {
enabled: false,
},
fides_cloud: {
enabled: false,
},
});
cy.visit(CONSENT_REPORTING_ROUTE);
});
it("can request a report", () => {
cy.intercept({
url: "/api/v1/plus/consent_reporting*",
method: "GET",
}).as("getConsentReport");
cy.getByTestId("input-from-date").type("2023-11-01");
cy.getByTestId("input-to-date").type("2023-11-07");
cy.getByTestId("download-btn").click();
cy.wait("@getConsentReport");
});
});
});
1 change: 1 addition & 0 deletions clients/admin-ui/src/features/common/api.slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export const baseApi = createApi({
"Roles",
"User",
"Configuration Settings",
"Consent Reporting",
],
endpoints: () => ({}),
});
7 changes: 7 additions & 0 deletions clients/admin-ui/src/features/common/nav/v2/nav-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,13 @@ export const NAV_CONFIG: NavConfigGroup[] = [
requiresPlus: true,
scopes: [ScopeRegistryEnum.PRIVACY_EXPERIENCE_READ],
},
{
title: "Consent reporting",
path: routes.CONSENT_REPORTING_ROUTE,
requiresFlag: "consentReporting",
requiresPlus: true,
scopes: [ScopeRegistryEnum.PRIVACY_NOTICE_READ],
},
],
},
{
Expand Down
1 change: 1 addition & 0 deletions clients/admin-ui/src/features/common/nav/v2/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const PRIVACY_EXPERIENCE_ROUTE = "/consent/privacy-experience";
export const PRIVACY_NOTICES_ROUTE = "/consent/privacy-notices";
export const CONFIGURE_CONSENT_ROUTE = "/consent/configure";
export const ADD_MULTIPLE_VENDORS_ROUTE = "/consent/configure/add-vendors";
export const CONSENT_REPORTING_ROUTE = "/consent/reporting";

// Management group
export const USER_MANAGEMENT_ROUTE = "/user-management";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import {
Button,
HStack,
Input,
InputGroup,
InputLeftAddon,
useToast,
} from "@fidesui/react";
import { useState } from "react";

import { getErrorMessage } from "~/features/common/helpers";
import { useLazyDownloadReportQuery } from "~/features/consent-reporting/consent-reporting.slice";

const ConsentReporting = () => {
const [startDate, setStartDate] = useState<string>("");
const [endDate, setEndDate] = useState<string>("");

const toast = useToast();

const [downloadReportTrigger, { isLoading }] = useLazyDownloadReportQuery();

const handleDownloadClicked = async () => {
const result = await downloadReportTrigger({ startDate, endDate });
if (result.isError) {
const message = getErrorMessage(
result.error,
"A problem occurred while generating your consent report. Please try again."
);
toast({ status: "error", description: message });
}
};

return (
<HStack gap={4} maxWidth="720px" data-testid="consent-reporting">
<InputGroup size="sm" flex={1}>
<InputLeftAddon borderRadius="md">From</InputLeftAddon>
<Input
type="date"
name="From"
value={startDate}
max={endDate || undefined}
onChange={(e) => setStartDate(e.target.value)}
borderRadius="md"
data-testid="input-from-date"
/>
</InputGroup>
<InputGroup size="sm" flex={1}>
<InputLeftAddon borderRadius="md">To</InputLeftAddon>
<Input
type="date"
name="To"
value={endDate}
min={startDate || undefined}
onChange={(e) => setEndDate(e.target.value)}
borderRadius="md"
data-testid="input-to-date"
/>
</InputGroup>
<Button
onClick={handleDownloadClicked}
isLoading={isLoading}
colorScheme="primary"
size="sm"
data-testid="download-btn"
>
Download report
</Button>
</HStack>
);
};

export default ConsentReporting;
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { baseApi } from "~/features/common/api.slice";

type DateRange = {
startDate?: string;
endDate?: string;
};

export function convertDateRangeToSearchParams({
startDate,
endDate,
}: DateRange) {
let startDateISO;
if (startDate) {
startDateISO = new Date(startDate);
startDateISO.setUTCHours(0, 0, 0);
}

let endDateISO;
if (endDate) {
endDateISO = new Date(endDate);
endDateISO.setUTCHours(0, 0, 0);
}

return {
...(startDateISO ? { created_gt: startDateISO.toISOString() } : {}),
...(endDateISO ? { created_lt: endDateISO.toISOString() } : {}),
};
}

export const consentReportingApi = baseApi.injectEndpoints({
endpoints: (build) => ({
downloadReport: build.query<any, DateRange>({
query: ({ startDate, endDate }) => {
const params = {
...convertDateRangeToSearchParams({ startDate, endDate }),
download_csv: "true",
};
return {
url: "plus/consent_reporting",
params,
responseHandler: (response) => response.blob(),
};
},
providesTags: ["Consent Reporting"],
transformResponse: (data: Blob) => {
const a = document.createElement("a");
a.href = window.URL.createObjectURL(data);
a.download = "consent-reports.csv";
a.click();
},
}),
}),
});

export const { useLazyDownloadReportQuery } = consentReportingApi;
6 changes: 6 additions & 0 deletions clients/admin-ui/src/flags.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,11 @@
"development": true,
"test": true,
"production": false
},
"consentReporting": {
"description": "Page to download consent reports by date",
"development": true,
"test": true,
"production": false
}
}
44 changes: 44 additions & 0 deletions clients/admin-ui/src/pages/consent/reporting/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Box, Breadcrumb, BreadcrumbItem, Heading, Text } from "@fidesui/react";
import NextLink from "next/link";
import React from "react";

import Layout from "~/features/common/Layout";
import { CONSENT_REPORTING_ROUTE } from "~/features/common/nav/v2/routes";
import ConsentReporting from "~/features/consent-reporting/ConsentReporting";

const ConsentReportingPage = () => (
<Layout title="Configure consent">
<Box mb={4}>
<Heading fontSize="2xl" fontWeight="semibold" mb={2} data-testid="header">
Configure consent
</Heading>
<Box>
<Breadcrumb
fontWeight="medium"
fontSize="sm"
color="gray.600"
data-testid="breadcrumbs"
>
<BreadcrumbItem>
<NextLink href={CONSENT_REPORTING_ROUTE}>Consent</NextLink>
</BreadcrumbItem>
<BreadcrumbItem color="complimentary.500">
<NextLink href="#">Reporting</NextLink>
</BreadcrumbItem>
</Breadcrumb>
</Box>
</Box>
<Text fontSize="sm" mb={8} width={{ base: "100%", lg: "50%" }}>
Download a CSV containing a report of consent preferences made by users on
your sites. Select a date range below and click &quot;Download
report&quot;. Depending on the number of records in the date range you
select, it may take several minutes to prepare the file after you click
&quot;Download report&quot;.
</Text>
<Box data-testid="consent">
<ConsentReporting />
</Box>
</Layout>
);

export default ConsentReportingPage;