Skip to content

Commit

Permalink
feat: add VAC reporting (#886)
Browse files Browse the repository at this point in the history
* feat: add VAC reporting

* wip: decrypt conversion data

* fix: exact version

* feat: decode with private key

* fix: remove empty lines

* fix: only modify file if we have private key

* fix: more loosely check column header

* fix: use only the DOM for key

* fix: no variable assigns

* fix: cleanup

* fix: if succesful, 0 out private key

* fix: move document get down to lowest level
  • Loading branch information
IanKrieger authored Sep 22, 2023
1 parent de5bbc8 commit 4f256ba
Show file tree
Hide file tree
Showing 10 changed files with 331 additions and 73 deletions.
22 changes: 19 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"lodash": "4.17.21",
"moment": "2.29.4",
"npm-audit-resolver": "3.0.0-RC.0",
"papaparse": "5.4.1",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-router-dom": "5.3.4",
Expand Down Expand Up @@ -61,6 +62,7 @@
"@types/jest": "29.5.4",
"@types/jwt-decode": "2.2.1",
"@types/lodash": "4.14.198",
"@types/papaparse": "5.3.8",
"@types/react": "18.2.21",
"@types/react-dom": "18.2.7",
"@types/react-router-dom": "5.3.3",
Expand Down
9 changes: 7 additions & 2 deletions src/graphql/analytics-overview.generated.tsx

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/graphql/analytics-overview.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ fragment CampaignWithEngagements on Campaign {
adSets {
conversions {
type
extractExternalId
}
}
engagements {
Expand Down
52 changes: 25 additions & 27 deletions src/user/analytics/analyticsOverview/components/ReportUtils.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import { Box } from "@mui/material";
import { LoadingButton } from "@mui/lab";
import SaveIcon from "@mui/icons-material/Save";
import { useState } from "react";
import { downloadCSV } from "../lib/csv.library";
import { DateRangePicker } from "components/Date/DateRangePicker";
import { useUser } from "auth/hooks/queries/useUser";
import { DashboardButton } from "components/Button/DashboardButton";
import { CampaignFormat } from "graphql/types";
import _ from "lodash";
import { ReportMenu } from "user/reporting/ReportMenu";

interface DownloaderProps {
startDate: Date | undefined;
endDate: Date;
campaign: { id: string; name: string; format?: CampaignFormat };
campaign: {
id: string;
name: string;
format?: CampaignFormat;
adSets?:
| {
conversions?: { type: string; extractExternalId: boolean }[] | null;
}[]
| null;
};
onSetDate: (val: Date, type: "start" | "end") => void;
}

Expand All @@ -21,8 +27,11 @@ export default function ReportUtils({
campaign,
onSetDate,
}: DownloaderProps) {
const [downloadingCSV, setDownloadingCSV] = useState(false);
const { userId } = useUser();
const conversions = _.flatMap(campaign.adSets ?? [], "conversions");
const maybeHasVerifiedConversions = _.some(
conversions ?? [],
(c) => c.extractExternalId,
);

return (
<Box
Expand All @@ -37,34 +46,23 @@ export default function ReportUtils({
alignItems="center"
justifyContent="flex-end"
marginLeft="auto"
gap="5px"
>
{startDate && (
<DateRangePicker
from={startDate}
to={endDate}
onFromChange={(d) => onSetDate(d, "start")}
onToChange={(d) => onSetDate(d, "end")}
></DateRangePicker>
/>
)}

<LoadingButton
variant="contained"
loading={downloadingCSV}
loadingPosition="start"
startIcon={<SaveIcon />}
disabled={campaign.format === CampaignFormat.NtpSi}
onClick={() =>
downloadCSV(
campaign.id,
campaign.name,
userId ?? "",
false,
setDownloadingCSV,
)
}
>
Download Report
</LoadingButton>
{campaign.format !== CampaignFormat.NtpSi && (
<ReportMenu
hasVerifiedConversions={maybeHasVerifiedConversions}
campaignId={campaign.id}
/>
)}
</Box>
</Box>
);
Expand Down
40 changes: 0 additions & 40 deletions src/user/analytics/analyticsOverview/lib/csv.library.ts

This file was deleted.

144 changes: 144 additions & 0 deletions src/user/reporting/ReportMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { useState } from "react";
import { useDownloadCSV } from "user/reporting/csv.library";
import {
Alert,
Button,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
LinearProgress,
ListItemIcon,
Menu,
MenuItem,
Snackbar,
TextField,
} from "@mui/material";
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp";
import DownloadIcon from "@mui/icons-material/Download";

interface ReportMenuProps {
hasVerifiedConversions: boolean;
campaignId: string;
}
export const ReportMenu = ({
campaignId,
hasVerifiedConversions,
}: ReportMenuProps) => {
const [dialogue, setDialogue] = useState(false);
const [isError, setIsError] = useState(false);
const set = (s: string) =>
document.getElementById("private-key")?.setAttribute("value", s);
const { download, loading, error } = useDownloadCSV({
onComplete() {
setAnchorEl(null);
setDialogue(false);
},
onError() {
setIsError(true);
setDialogue(false);
},
});

const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const menu = Boolean(anchorEl);
return (
<>
<Button
variant="contained"
onClick={(e) => setAnchorEl(e.currentTarget)}
endIcon={
anchorEl === null ? (
<KeyboardArrowDownIcon />
) : (
<KeyboardArrowUpIcon />
)
}
disabled={loading}
>
Download Report
</Button>

<Menu anchorEl={anchorEl} open={menu} onClose={() => setAnchorEl(null)}>
<MenuItem
onClick={() => download(campaignId, false)}
disabled={loading}
>
<ListItemIcon>
<DownloadIcon />
</ListItemIcon>
Performance Report
</MenuItem>
,
{hasVerifiedConversions && (
<MenuItem onClick={() => setDialogue(true)} disabled={loading}>
<ListItemIcon>
<DownloadIcon />
</ListItemIcon>
Verified Conversions Report
</MenuItem>
)}
</Menu>

<Snackbar
open={isError}
autoHideDuration={6000}
onClose={() => setIsError(false)}
anchorOrigin={{ vertical: "top", horizontal: "center" }}
>
<Alert
onClose={() => setIsError(false)}
severity="error"
sx={{ width: "100%" }}
>
{error}
</Alert>
</Snackbar>

<Dialog open={dialogue} onClose={() => setDialogue(false)}>
<DialogTitle>Decrypt Conversion Data?</DialogTitle>
<DialogContent>
<DialogContentText>
To protect user&rsquo;s privacy, verified Ad conversion data is
encrypted so that the identities of converted users remain anonymous
to Brave. You can decrypt the conversion data in the CSV file by
providing your private key here. If no key is provided, you will
receive the encrypted conversion data. Your private key will never
be sent to or stored on any Brave servers.
</DialogContentText>
<TextField
autoComplete="off"
onChange={(e) => set(e.target.value)}
autoFocus
margin="normal"
label="Private key"
fullWidth
variant="standard"
/>
<input type="hidden" id="private-key" />
{loading && <LinearProgress />}
</DialogContent>
<DialogActions>
<Button
variant="outlined"
onClick={() => setDialogue(false)}
disabled={loading}
>
Cancel
</Button>
<Button
variant="contained"
onClick={() => {
download(campaignId, true);
}}
disabled={loading}
>
Export
</Button>
</DialogActions>
</Dialog>
</>
);
};
Loading

0 comments on commit 4f256ba

Please sign in to comment.