Skip to content

Commit

Permalink
Canned Response CSV
Browse files Browse the repository at this point in the history
  • Loading branch information
agreenspan24 committed Jun 22, 2021
1 parent a36d34f commit 2928512
Show file tree
Hide file tree
Showing 2 changed files with 190 additions and 5 deletions.
113 changes: 108 additions & 5 deletions src/components/CampaignCannedResponsesForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ import ListItemText from "@material-ui/core/ListItemText";
import Divider from "@material-ui/core/Divider";
import DeleteIcon from "@material-ui/icons/Delete";
import CreateIcon from "@material-ui/icons/Create";
import PublishIcon from "@material-ui/icons/Publish";
import ClearIcon from "@material-ui/icons/Clear";
import IconButton from "@material-ui/core/IconButton";
import Tooltip from "@material-ui/core/Tooltip";

import GSForm from "./forms/GSForm";
import CampaignFormSectionHeading from "./CampaignFormSectionHeading";
Expand All @@ -23,6 +26,7 @@ import { dataTest } from "../lib/attributes";
import loadData from "../containers/hoc/load-data";
import gql from "graphql-tag";
import TagChips from "./TagChips";
import { parseCannedResponseCsv } from "../lib/parse_csv";

const Span = ({ children }) => <span>{children}</span>;

Expand Down Expand Up @@ -53,14 +57,27 @@ const styles = StyleSheet.create({
WebkitLineClamp: 2,
overflow: "hidden",
height: 32
},
redText: {
color: theme.colors.red
},
spaceBetween: {
display: 'flex',
justifyContent: 'space-between'
},
flexEnd: {
display: 'flex',
justifyContent: 'flex-end'
}
});

export class CampaignCannedResponsesForm extends React.Component {
state = {
showForm: false,
formButtonText: "",
responseId: null
responseId: null,
uploadingCsv: false,
uploadCsvError: null
};

formSchema = yup.object({
Expand All @@ -72,10 +89,63 @@ export class CampaignCannedResponsesForm extends React.Component {
)
});

getCannedResponseId() {
return Math.random()
.toString(36)
.replace(/[^a-zA-Z1-9]+/g, "")
}

showUploadCsvButton() {
this.uploadCsvInputRef = React.createRef();

return (
<div>
<div className={css(styles.flexEnd)}>
<Tooltip
title="Upload a CSV of canned responses with columns for Title, Text, and Tags"
>
<IconButton
onClick={() => this.uploadCsvInputRef.current.click()}
disabled={this.state.uploadingCsv}
>
<PublishIcon />
</IconButton>
</Tooltip>
{this.props.formValues.cannedResponses.length > 0 ? (
<Tooltip
title="Remove all Canned Responses"
>
<IconButton
onClick={() => this.props.onChange({
cannedResponses: []
})}
>
<ClearIcon />
</IconButton>
</Tooltip>
) : ""}
</div>
<input
type="file"
accept=".csv"
ref={this.uploadCsvInputRef}
onChange={this.handleCsvUpload}
onClick={e => (e.target.value = null)}
style={{ display: "none" }}
/>
{this.state.uploadCsvError && (
<div className={css(styles.redText)}>
{this.state.uploadCsvError}
</div>
)}
</div>
);
}

showAddButton() {
if (!this.state.showForm) {
return (
<div>
<div className={css(styles.spaceBetween)}>
<Button
color="secondary"
startIcon={<CreateIcon color="secondary" />}
Expand All @@ -89,6 +159,7 @@ export class CampaignCannedResponsesForm extends React.Component {
>
Add new canned response
</Button>
{this.showUploadCsvButton()}
</div>
);
}
Expand Down Expand Up @@ -117,9 +188,7 @@ export class CampaignCannedResponsesForm extends React.Component {
...ele
};
if (!this.state.responseId) {
newEle.id = Math.random()
.toString(36)
.replace(/[^a-zA-Z1-9]+/g, "");
newEle.id = this.getCannedResponseId();
newVals.push(newEle);
} else {
const resToEditIndex = newVals.findIndex(
Expand Down Expand Up @@ -198,6 +267,39 @@ export class CampaignCannedResponsesForm extends React.Component {
return listItems;
}

handleCsvUpload = event => {
event.preventDefault();

const file = event.target.files[0];
const tags = this.props.data.organization.tags;

if (!file) return;

this.setState({ uploadingCsv: true, uploadCsvError: null }, () => {
parseCannedResponseCsv(
file,
tags,
({ error, cannedResponses }) => {
this.setState({
uploadingCsv: false,
uploadCsvError: error
});

if (!error) {
this.props.onChange({
cannedResponses: this.props.formValues.cannedResponses.concat(
cannedResponses.map(r => ({
...r,
id: this.getCannedResponseId()
}))
)
});
}
}
);
});
};

render() {
const { formValues } = this.props;
const cannedResponses = formValues.cannedResponses;
Expand All @@ -208,6 +310,7 @@ export class CampaignCannedResponsesForm extends React.Component {
<Divider />
</List>
);

return (
<React.Fragment>
<GSForm
Expand Down
82 changes: 82 additions & 0 deletions src/lib/parse_csv.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,85 @@ export const parseCSV = (file, onCompleteCallback, options) => {
}
});
};

export const parseCannedResponseCsv = (file, tags, onCompleteCallback) => {
Papa.parse(file, {
header: true,
skipEmptyLines: true,
// eslint-disable-next-line no-shadow, no-unused-vars
complete: ({ data: parserData, meta, errors }, file) => {
let cannedResponseRows = parserData;

const titleLabel = meta.fields.find(f => f.toLowerCase().trim() == "title");
const textLabel = meta.fields.find(f => f.toLowerCase().trim() == "text");
const tagsLabel = meta.fields.find(f => f.toLowerCase().trim() == "tags");

const requiredFields = [titleLabel, textLabel];

const missingFields = requiredFields.filter(
f => meta.fields.indexOf(f) == -1
);

if (missingFields.length) {
onCompleteCallback({
error: `Missing columns: ${missingFields.join(", ")}`
});
return;
}

const cannedResponses = [];

// Loop through canned responses in CSV
for (var i in cannedResponseRows) {
const response = cannedResponseRows[i];

// Get basic details of canned response
const newCannedResponse = {
title: response[titleLabel].trim(),
text: response[textLabel].trim(),
};

// Skip line if no title/text
if (!newCannedResponse.title && !newCannedResponse.text) {
continue;
}

if (!newCannedResponse.title || !newCannedResponse.text) {
onCompleteCallback({
error:
`Incomplete Line. Title ${newCannedResponse.title}; Text: ${newCannedResponse.text}`
});
return;
}

const tagIds = [];

for (var t of response[tagsLabel].split(',')) {
const tag_name = t.trim();

if (!tag_name) continue;

const tag = tags.find(tag => tag.name.toLowerCase() == tag_name.toLowerCase());

if (!tag) {
onCompleteCallback({
error: `"${tag_name}" cannot be found in your organization's tags`
});
return;
}

tagIds.push(tag.id);
}

newCannedResponse.tagIds = tagIds;

cannedResponses.push(newCannedResponse);
}

onCompleteCallback({
error: null,
cannedResponses
});
}
});
};

0 comments on commit 2928512

Please sign in to comment.