From 6f03b0bfdd9aa7c0104c6dbb83cf03758cd45a94 Mon Sep 17 00:00:00 2001 From: ben-pr-p Date: Fri, 22 Feb 2019 14:26:26 -0500 Subject: [PATCH 1/3] wip --- src/api/schema.js | 1 + src/containers/CampaignList.jsx | 31 +++++++++++++++++++++---------- src/server/api/schema.js | 10 ++++++++++ 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/api/schema.js b/src/api/schema.js index 842e7113b..422898267 100644 --- a/src/api/schema.js +++ b/src/api/schema.js @@ -217,6 +217,7 @@ const rootSchema = ` bulkReassignCampaignContacts(organizationId:String!, campaignsFilter:CampaignsFilter, assignmentsFilter:AssignmentsFilter, contactsFilter:ContactsFilter, newTexterUserId:String!):[CampaignIdAssignmentId] megaBulkReassignCampaignContacts(organizationId:String!, campaignsFilter:CampaignsFilter, assignmentsFilter:AssignmentsFilter, contactsFilter:ContactsFilter, newTexterUserIds:[String]!):[CampaignIdAssignmentId] requestTexts(count: Int!, email: String!): String! + releaseUnsentMessages(campaignId: String!): Int! } schema { diff --git a/src/containers/CampaignList.jsx b/src/containers/CampaignList.jsx index ec01fd518..735699edc 100644 --- a/src/containers/CampaignList.jsx +++ b/src/containers/CampaignList.jsx @@ -16,6 +16,7 @@ import wrapMutations from './hoc/wrap-mutations' import Empty from '../components/Empty' import LoadingIndicator from '../components/LoadingIndicator' import { dataTest } from '../lib/attributes' +import RaisedButton from 'material-ui/RaisedButton' const campaignInfoFragment = ` id @@ -114,22 +115,26 @@ class CampaignList extends React.Component { this.props.router.push(campaignUrl))} secondaryText={secondaryText} leftIcon={leftIcon} - rightIconButton={adminPerms ? - (campaign.isArchived ? ( - this.props.mutations.unarchiveCampaign(campaign.id)} - > - - - ) : ( + rightIconButton={adminPerms ? [] + .concat(campaign.isArchived ? [( + this.props.mutations.unarchiveCampaign(campaign.id)} > + + + )] : [( this.props.mutations.archiveCampaign(campaign.id)} > - )) : null} + )] + ).concat(campaign.hasUnsentInitialMessages ? [ + this.props.mutations.releaseUnsentMessages(campaign.id)}> + Release Unsent Messages + + ] : []) + : [] + } /> ) } @@ -183,6 +188,12 @@ const mapMutationsToProps = () => ({ } }`, variables: { campaignId } + }), + releaseUnsentMessages: (campaignId) ({ + mutation: gql`mutation releaseUnsentMessages($campaignId: String!) { + releaseUnsentMessages:(id: $campaignId) + }`, + variables: { campaignId } }) }) diff --git a/src/server/api/schema.js b/src/server/api/schema.js index a14664f67..4956924ca 100644 --- a/src/server/api/schema.js +++ b/src/server/api/schema.js @@ -1541,6 +1541,16 @@ const rootMutations = { } catch (e) { return e.response.body.message; } + }, + releaseUnsentMessages: async (_, { campaignId }, { user }) => { + const updatedCount = await r.knex('campaign_contact').where({ + campaign_id: parseInt(campaignId), + message_status: 'needsMessage' + }).update({ + assignment_id: null + }) + + return updatedCount; } } }; From eaa53eed49214413d4766f59d915e152d9c7f802 Mon Sep 17 00:00:00 2001 From: ben-pr-p Date: Wed, 27 Feb 2019 10:34:59 -0500 Subject: [PATCH 2/3] fix merge conflict --- src/api/schema.js | 4 +- src/containers/CampaignList.jsx | 139 +++++++++++++++++++++++++------- src/server/api/schema.js | 60 +++++++------- 3 files changed, 146 insertions(+), 57 deletions(-) diff --git a/src/api/schema.js b/src/api/schema.js index 422898267..967340d23 100644 --- a/src/api/schema.js +++ b/src/api/schema.js @@ -213,11 +213,11 @@ const rootSchema = ` userAgreeTerms(userId: String!): User reassignCampaignContacts(organizationId:String!, campaignIdsContactIds:[CampaignIdContactId]!, newTexterUserId:String!):[CampaignIdAssignmentId], megaReassignCampaignContacts(organizationId:String!, campaignIdsContactIds:[CampaignIdContactId]!, newTexterUserIds:[String]!):[CampaignIdAssignmentId], - markForSecondPass(organizationId: String!, campaignIdsContactIds: [CampaignIdContactId]!): [CampaignContact] bulkReassignCampaignContacts(organizationId:String!, campaignsFilter:CampaignsFilter, assignmentsFilter:AssignmentsFilter, contactsFilter:ContactsFilter, newTexterUserId:String!):[CampaignIdAssignmentId] megaBulkReassignCampaignContacts(organizationId:String!, campaignsFilter:CampaignsFilter, assignmentsFilter:AssignmentsFilter, contactsFilter:ContactsFilter, newTexterUserIds:[String]!):[CampaignIdAssignmentId] requestTexts(count: Int!, email: String!): String! - releaseUnsentMessages(campaignId: String!): Int! + releaseUnsentMessages(campaignId: String!): String! + markForSecondPass(campaignId: String!): String! } schema { diff --git a/src/containers/CampaignList.jsx b/src/containers/CampaignList.jsx index 735699edc..a07cf997e 100644 --- a/src/containers/CampaignList.jsx +++ b/src/containers/CampaignList.jsx @@ -16,7 +16,11 @@ import wrapMutations from './hoc/wrap-mutations' import Empty from '../components/Empty' import LoadingIndicator from '../components/LoadingIndicator' import { dataTest } from '../lib/attributes' -import RaisedButton from 'material-ui/RaisedButton' +import Dialog from 'material-ui/Dialog' +import IconMenu from 'material-ui/IconMenu' +import MenuItem from 'material-ui/MenuItem' +import FlatButton from 'material-ui/FlatButton' +import MoreVertIcon from 'material-ui/svg-icons/navigation/more-vert'; const campaignInfoFragment = ` id @@ -44,7 +48,52 @@ const inlineStyles = { } } +const operations = { + releaseUnsentMessages: { + title: campaign => `Release Unsent Messages for ${campaign.title}`, + body: () => `Releasing unsent messages for this campaign will cause unsent messages in this campaign\ + from texter's assignments. This means that these texters will no longer be able to send\ + these messages, but these messages will become available to assign via the autoassignment\ + functionality.` + }, + markForSecondPass: { + title: campaign => `Mark Unresponded to Messages in ${campaign.title} for a Second Pass`, + body: () => `Marking unresponded to messages for this campaign will reset the state of messages that have\ + not been responded to by the contact, causing them to show up as needing a first text, as long as the campaign\ + is not past due. After running this operation, the texts will still be assigned to the same texter, so please\ + run 'Release Unsent Messages' after if you'd like these second pass messages to be available for auto-assignment.` + } +} + class CampaignList extends React.Component { + state ={ + inProgress: undefined, + error: undefined, + executing: false, + finished: undefined + } + + start = (operation, campaign) => () => this.setState({ inProgress: [operation, campaign] }) + clearInProgress = () => this.setState({ + inProgress: undefined, + error: undefined, + executing: false, + finished: undefined + }) + + executeOperation = () => { + this.setState({ executing: true }) + const [operationName, campaign] = this.state.inProgress + + this.props.mutations[operationName](campaign.id) + .then(resp => { + this.setState({finished: resp.data[operationName], executing: false }) + }) + .catch(error => { + this.setState({ error, executing: false }) + }) + } + renderRow(campaign) { const { isStarted, @@ -110,36 +159,20 @@ class CampaignList extends React.Component { style={listItemStyle} key={campaign.id} primaryText={primaryText} - onTouchTap={() => (!isStarted ? + onClick={() => (!isStarted ? this.props.router.push(`${campaignUrl}/edit`) : this.props.router.push(campaignUrl))} secondaryText={secondaryText} leftIcon={leftIcon} - rightIconButton={adminPerms ? [] - .concat(campaign.isArchived ? [( - this.props.mutations.unarchiveCampaign(campaign.id)} > - - - )] : [( - this.props.mutations.archiveCampaign(campaign.id)} - > - - - )] - ).concat(campaign.hasUnsentInitialMessages ? [ - this.props.mutations.releaseUnsentMessages(campaign.id)}> - Release Unsent Messages - - ] : []) - : [] - } + rightIconButton={adminPerms && this.renderMenu(campaign)} /> ) } render() { + const { inProgress, error, finished, executing } = this.state + console.log(this.state) + if (this.props.data.loading) { return } @@ -150,11 +183,57 @@ class CampaignList extends React.Component { icon={} /> ) : ( - - {campaigns.campaigns.map((campaign) => this.renderRow(campaign))} - +
+ {inProgress && + ] + : [, + ] + } + > + {executing + ? + : error + ? {JSON.stringify(error)} + : finished + ? finished + : operations[inProgress[0]].body(inProgress[1]) + } + + } + + {campaigns.campaigns.map((campaign) => this.renderRow(campaign))} + +
) } + + renderMenu(campaign) { + return ( + } + onClick={console.log} + > + + + {!campaign.isArchived && } onClick={() => this.props.mutations.archiveCampaign(campaign.id)} />} + {campaign.isArchived && } onClick={() => this.props.mutations.unarchiveCampaign(campaign.id)} />} + + + ) + } } CampaignList.propTypes = { @@ -191,8 +270,14 @@ const mapMutationsToProps = () => ({ }), releaseUnsentMessages: (campaignId) ({ mutation: gql`mutation releaseUnsentMessages($campaignId: String!) { - releaseUnsentMessages:(id: $campaignId) - }`, + releaseUnsentMessages(campaignId: $campaignId) + }`, + variables: { campaignId } + }), + markForSecondPass: (campaignId) => ({ + mutation: gql`mutation markForSecondPass($campaignId: String!) { + markForSecondPass(campaignId: $campaignId) + }`, variables: { campaignId } }) }) diff --git a/src/server/api/schema.js b/src/server/api/schema.js index 4956924ca..10f171e6e 100644 --- a/src/server/api/schema.js +++ b/src/server/api/schema.js @@ -1316,37 +1316,41 @@ const rootMutations = { }, markForSecondPass: async ( _ignore, - { organizationId, campaignIdsContactIds }, + { campaignId }, { user } ) => { // verify permissions - await accessRequired(user, organizationId, "SUPERVOLUNTEER", true); - - let affectedCampaignContactIds = []; - const groupedByCampaign = _.groupBy( - campaignIdsContactIds, - c => c.campaignId - ); - - await Promise.all( - Object.keys(groupedByCampaign).map(async campaignId => { - const campaignContactIds = groupedByCampaign[campaignId].map( - c => c.campaignContactId - ); - affectedCampaignContactIds = affectedCampaignContactIds.concat( - campaignContactIds - ); - return r - .knex("campaign_contact") - .update({ message_status: "needsMessage" }) - .where({ campaign_id: campaignId }) - .whereIn("id", campaignContactIds); - }) - ); + const organizationId = (await r.knex('campaign') + .where({ id: parseInt(campaignId) }))[0].organization_id + + await accessRequired(user, organizationId, "ADMIN", true); + + /* + "Mark Campaign for Second Pass", will only mark contacts for a second + pass that do not have a more recently created membership in another campaign. + */ + const [skippingCells, _dbJunk] = await r.knex.raw(` + select cell from campaign_contact + where campaign_id = ${campaignId} + and campaign_contact.message_status = 'messaged' + and campaign_contact.cell in ( + select cell + from campaign_contact as other_campaign_contact + where other_campaign_contact.created_at > campaign_contact.created_at + ) + `) - return affectedCampaignContactIds.map(id => { - id; - }); + const updateResult = await r.knex('campaign_contact') + .update({ message_status: 'needsMessage' }) + .where({ + id: parseInt(campaignId), + message_status: 'messaged' + }) + .whereNotIn('cell', skippingCells) + + return `Marked ${updateResult} campaign contacts for a second pass.\ + Did not mark ${skippingCells.length} contacts because they were\ + present in another, more recent campaign.` }, reassignCampaignContacts: async ( _, @@ -1550,7 +1554,7 @@ const rootMutations = { assignment_id: null }) - return updatedCount; + return `Released ${updatedCount} unsent messages for reassignment`; } } }; From 6ccfa631a865f6a2dde5bd0d2b13d5db33319bc3 Mon Sep 17 00:00:00 2001 From: ben-pr-p Date: Wed, 27 Feb 2019 12:06:09 -0500 Subject: [PATCH 3/3] debugged and derawed --- src/containers/CampaignList.jsx | 2 +- src/server/api/schema.js | 29 ++++++++++++++++------------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/containers/CampaignList.jsx b/src/containers/CampaignList.jsx index a07cf997e..f8ecd2177 100644 --- a/src/containers/CampaignList.jsx +++ b/src/containers/CampaignList.jsx @@ -268,7 +268,7 @@ const mapMutationsToProps = () => ({ }`, variables: { campaignId } }), - releaseUnsentMessages: (campaignId) ({ + releaseUnsentMessages: (campaignId) => ({ mutation: gql`mutation releaseUnsentMessages($campaignId: String!) { releaseUnsentMessages(campaignId: $campaignId) }`, diff --git a/src/server/api/schema.js b/src/server/api/schema.js index 10f171e6e..4b7bf605a 100644 --- a/src/server/api/schema.js +++ b/src/server/api/schema.js @@ -1329,25 +1329,28 @@ const rootMutations = { "Mark Campaign for Second Pass", will only mark contacts for a second pass that do not have a more recently created membership in another campaign. */ - const [skippingCells, _dbJunk] = await r.knex.raw(` - select cell from campaign_contact - where campaign_id = ${campaignId} - and campaign_contact.message_status = 'messaged' - and campaign_contact.cell in ( - select cell - from campaign_contact as other_campaign_contact - where other_campaign_contact.created_at > campaign_contact.created_at - ) - `) + const skippingCells = await r.knex('campaign_contact') + .select('cell') + .where({ + campaign_id: parseInt(campaignId), + message_status: 'messaged' + }) + .whereRaw(` + campaign_contact.cell in ( + select cell + from campaign_contact as other_campaign_contact + where other_campaign_contact.created_at > campaign_contact.created_at + ) + `) const updateResult = await r.knex('campaign_contact') .update({ message_status: 'needsMessage' }) .where({ - id: parseInt(campaignId), + campaign_id: parseInt(campaignId), message_status: 'messaged' }) - .whereNotIn('cell', skippingCells) - + .whereNotIn('cell', skippingCells.map(cc => cc.cell)) + return `Marked ${updateResult} campaign contacts for a second pass.\ Did not mark ${skippingCells.length} contacts because they were\ present in another, more recent campaign.`