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(campaign-exports): send one email after multiple campaign exports #1661

Open
wants to merge 33 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
7dea763
feat: wip, routing and boilerplate react component
henryk1229 Jul 13, 2023
2977ef0
feat: wip - add mutation, initial task
henryk1229 Jul 13, 2023
bb4ea78
feat: graphql mutation
henryk1229 Jul 18, 2023
a79b4ef
feat: wip, frontend - use CampaignListMenu to select for export
henryk1229 Jul 18, 2023
3aae8c5
feat(export-multiple-campaigns): simple backend solution
henryk1229 Jul 20, 2023
506b02d
feat(campaignexportmodal): share ModalContent between components
henryk1229 Jul 20, 2023
10a567e
feat(admincampaignlist): show snackbar after kicking off exports
henryk1229 Jul 20, 2023
99abef1
feat(campaign-list-row): add color to export tag
henryk1229 Jul 20, 2023
ddc3d28
feat(export-campaign-snackbar): snackbar component for success and er…
henryk1229 Jul 20, 2023
7f19a3d
feat: handle de-selecting for export in CampaignListMenu
henryk1229 Jul 20, 2023
ee721c1
feat: reset idsForExport on complete
henryk1229 Jul 20, 2023
cea4766
feat(campaign-list): initial card approach, break out components and …
henryk1229 Jul 25, 2023
bef6de3
feat(campaign-list): hook up export campaign button
henryk1229 Jul 25, 2023
4add064
refactor: rm mvp solution
henryk1229 Jul 25, 2023
67ac4d3
feat(admin-campaign-list): styling, search field
henryk1229 Jul 26, 2023
4807092
feat(campaign-list): filter by campaign title
henryk1229 Jul 26, 2023
ebd991a
feat(campaign-export-modal): display camaign details for export, rela…
henryk1229 Jul 27, 2023
d14b20c
feat(campaign-list): improve card styling
henryk1229 Jul 27, 2023
e847c34
feat(campaign-list): use rounded icons, improve export dialog styling
henryk1229 Jul 28, 2023
abb78ff
refactor: require campaign ids, spoke options, rm export type from mu…
henryk1229 Aug 3, 2023
7ef2246
refactor: improve var names
henryk1229 Aug 3, 2023
b5bc27a
feat: improve typing
henryk1229 Aug 3, 2023
45ea951
feat: destructure props in component declaration
henryk1229 Aug 3, 2023
338b169
fix: revert campaign list menu changes
henryk1229 Aug 3, 2023
dd93365
feat(campaign-list-row): improve typing
henryk1229 Aug 3, 2023
b8348d0
refactor(campaign-list-row): styling changes
henryk1229 Aug 4, 2023
16c9467
refactor(multi-export-dialog): styling changes
henryk1229 Aug 4, 2023
3c541f1
feat(export-multiple-campaigns): wip, breakout tasks
henryk1229 Aug 8, 2023
f8c9de5
feat(export-multiple-campaigns): wip, map campaign title, export urls…
henryk1229 Aug 10, 2023
1ed058b
feat(task-utils): add cleanup fn to progress job wrapper fn
henryk1229 Aug 11, 2023
46db8cc
feat(export-multiple-campaigns): wip, format and send email after cam…
henryk1229 Aug 11, 2023
e6d2859
feat(export-multiple-campaigns): add key to email template div
henryk1229 Aug 29, 2023
9eb66c7
feat(email-multiple-campaigns): re-queue job if query returns no rows
henryk1229 Aug 29, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions libs/gql-schema/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,11 @@ const rootSchema = `
vanOptions: ExportForVanInput
}

input MultipleCampaignExportInput {
Copy link
Member

Choose a reason for hiding this comment

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

nit: fix indentation

Suggested change
input MultipleCampaignExportInput {
input MultipleCampaignExportInput {

campaignIds: [String!]!
spokeOptions: ExportForSpokeInput!
}

input QuestionResponseSyncConfigInput {
id: String!
}
Expand Down Expand Up @@ -287,6 +292,7 @@ const rootSchema = `
copyCampaign(id: String!): Campaign
copyCampaigns(sourceCampaignId: String!, quantity: Int!, targetOrgId: String): [Campaign!]!
exportCampaign(options: CampaignExportInput!): JobRequest
exportCampaigns(options: MultipleCampaignExportInput!): [JobRequest]
createCannedResponse(cannedResponse:CannedResponseInput!): CannedResponse
createOrganization(name: String!, userId: String!, inviteId: String!): Organization
editOrganization(id: String! input: EditOrganizationInput!): Organization!
Expand Down
19 changes: 16 additions & 3 deletions libs/spoke-codegen/src/graphql/campaign-stats.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,22 @@ mutation ExportCampaign($options: CampaignExportInput!) {
}
}

mutation CopyCampaigns($templateId: String!, $quantity: Int!, $targetOrgId: String) {
copyCampaigns(sourceCampaignId: $templateId, quantity: $quantity, targetOrgId: $targetOrgId) {
mutation ExportCampaigns($options: MultipleCampaignExportInput!) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: move mutations to be below queries

exportCampaigns(options: $options) {
id
}
}

mutation CopyCampaigns(
$templateId: String!
$quantity: Int!
$targetOrgId: String
) {
copyCampaigns(
sourceCampaignId: $templateId
quantity: $quantity
targetOrgId: $targetOrgId
) {
id
}
}
Expand All @@ -82,7 +96,6 @@ query GetCampaignSyncConfigs($campaignId: String!) {
}
}


query GetSyncTargets($campaignId: String!) {
campaign(id: $campaignId) {
id
Expand Down
30 changes: 30 additions & 0 deletions src/components/ExportCampaignDataSnackbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import Snackbar from "@material-ui/core/Snackbar";
import Alert from "@material-ui/lab/Alert";
import React from "react";

interface Props {
Copy link
Contributor

Choose a reason for hiding this comment

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

required: export props, and name ExportCampaignDataSnackbarProps, more details in https://github.com/politics-rewired/Spoke/blob/main/conventions.md

open: boolean;
errorMessage: string | null;
onClose: () => void;
}

const ExportCampaignDataSnackbar: React.FC<Props> = ({
open,
errorMessage,
onClose
}) => {
return errorMessage ? (
<Snackbar open={open} autoHideDuration={5000} onClose={onClose}>
<Alert severity="error">{errorMessage}</Alert>
</Snackbar>
) : (
<Snackbar
open={open}
message="Exports started - we'll e-mail you when they're done"
autoHideDuration={5000}
onClose={onClose}
/>
);
};

export default ExportCampaignDataSnackbar;
140 changes: 140 additions & 0 deletions src/components/ExportMultipleCampaignDataDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import Button from "@material-ui/core/Button";
import Dialog from "@material-ui/core/Dialog";
import DialogActions from "@material-ui/core/DialogActions";
import DialogContent from "@material-ui/core/DialogContent";
import DialogContentText from "@material-ui/core/DialogContentText";
import DialogTitle from "@material-ui/core/DialogTitle";
import Divider from "@material-ui/core/Divider";
import Typography from "@material-ui/core/Typography";
import { useExportCampaignsMutation } from "@spoke/spoke-codegen";
import React, { useState } from "react";

import { CampaignExportModalContent } from "../containers/AdminCampaignStats/components/CampaignExportModal";

export type CampaignDetailsForExport = {
id: string;
title: string;
};
interface Props {
Copy link
Contributor

Choose a reason for hiding this comment

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

required: export props, and name ExportMultipleCampaignDataDialogProps, more details in https://github.com/politics-rewired/Spoke/blob/main/conventions.md

campaignDetailsForExport: CampaignDetailsForExport[];
open: boolean;
onClose: () => void;
onError: (errorMessage: string) => void;
onComplete(): void;
Copy link
Member

Choose a reason for hiding this comment

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

nit: consistent function typing

Suggested change
onComplete(): void;
onComplete: () => void;

}

const ExportMultipleCampaignDataDialog: React.FC<Props> = ({
campaignDetailsForExport,
open,
onClose,
onError,
onComplete
}) => {
const [exportCampaign, setExportCampaign] = useState<boolean>(true);
const [exportMessages, setExportMessages] = useState<boolean>(true);
const [exportOptOut, setExportOptOut] = useState<boolean>(false);
const [exportFiltered, setExportFiltered] = useState<boolean>(false);

const [exportCampaignsMutation] = useExportCampaignsMutation();

const handleChange = (
Copy link
Member

Choose a reason for hiding this comment

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

Suggestion: rename to handleChangeFactory to make it clear that this creates handlers rather than handling change itself

setStateFunction: React.Dispatch<React.SetStateAction<boolean>>
) => (event: React.ChangeEvent<HTMLInputElement>) => {
setStateFunction(event.target.checked);
};

const handleExportClick = async () => {
const campaignIds = campaignDetailsForExport.map(
(campaign: CampaignDetailsForExport) => campaign.id
);
const result = await exportCampaignsMutation({
variables: {
options: {
campaignIds,
spokeOptions: {
campaign: exportCampaign,
messages: exportMessages,
optOuts: exportOptOut,
filteredContacts: exportFiltered
}
}
}
});
if (result.errors) {
const message = result.errors.map((e) => e.message).join(", ");
return onError(message);
}
onComplete();
};

return (
<Dialog
onClose={onClose}
aria-labelledby="export-multiple-campaign-data"
open={open}
fullWidth
maxWidth="sm"
>
<DialogTitle id="export-multiple-campaign-data">
<Typography variant="h6" style={{ margin: "4px", cursor: "pointer" }}>
Export Campaigns
</Typography>
</DialogTitle>
<CampaignExportModalContent
exportCampaign={exportCampaign}
exportMessages={exportMessages}
exportOptOut={exportOptOut}
exportFiltered={exportFiltered}
handleChange={handleChange}
setExportCampaign={setExportCampaign}
setExportMessages={setExportMessages}
setExportOptOut={setExportOptOut}
setExportFiltered={setExportFiltered}
/>
<Divider variant="middle" />
<DialogContent>
<DialogContentText>
<Typography variant="subtitle1" style={{ margin: "4px" }}>
Selected campaigns:
</Typography>
{campaignDetailsForExport.map(
(campaign: CampaignDetailsForExport) => {
return (
<div
key={campaign.id}
style={{
display: "flex",
alignItems: "center",
margin: "4px"
}}
>
<Typography variant="body1" style={{ margin: "4px" }}>
{campaign.title}
</Typography>
<Typography
variant="body1"
style={{ marginLeft: "4px", color: "#666666" }}
>
ID: {campaign.id}
</Typography>
</div>
);
Comment on lines +102 to +121
Copy link
Member

Choose a reason for hiding this comment

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

Suggestion: pull this up into its own component

Comment on lines +102 to +121
Copy link
Member

Choose a reason for hiding this comment

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

Suggestion: pull this up into its own component

}
)}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={onClose}>Cancel</Button>
<Button
color="primary"
disabled={campaignDetailsForExport.length < 1}
Copy link
Member

Choose a reason for hiding this comment

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

Suggestion: pull campaignDetailsForExport.length < 1 up into a variable with a more human friendly name -- maybe const hasCampaignsForExport = campaignDetailsForExport.length >= 1

onClick={handleExportClick}
>
Export data
</Button>
</DialogActions>
</Dialog>
);
};

export default ExportMultipleCampaignDataDialog;
Loading