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

feat: add VAC reporting #886

Merged
merged 12 commits into from
Sep 22, 2023
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.

Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export function EngagementsOverview({
const options = prepareChart(metrics, processedData);

return (
<Box display="flex" flexDirection="row">
<Box display="flex" flexDirection="row" gap="5px">
<MetricFilter
processedStats={processedStats}
metrics={metrics}
Expand Down
147 changes: 147 additions & 0 deletions src/user/reporting/ReportMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
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";
import { uInt8Array } from "util/uInt8Array";

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 get = () =>
document.getElementById("private-key")?.getAttribute("value");
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
IanKrieger marked this conversation as resolved.
Show resolved Hide resolved
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)}
IanKrieger marked this conversation as resolved.
Show resolved Hide resolved
disabled={loading}
>
Cancel
</Button>
<Button
variant="contained"
onClick={() => {
download(campaignId, true, uInt8Array(get()));
}}
disabled={loading}
>
Export
</Button>
</DialogActions>
</Dialog>
</>
);
};
Loading