Giter VIP home page Giter VIP logo

spoke's Introduction

GitHub release (latest SemVer) CircleCI

Spoke

Spoke is an open source text-distribution tool for organizations to mobilize supporters and members into action. Spoke allows you to upload phone numbers, customize scripts and assign volunteers to communicate with supporters while allowing organizations to manage the process.

Spoke was created by Saikat Chakrabarti and Sheena Pakanati, and is now maintained by MoveOn.org at https://github.com/MoveOnOrg/Spoke.

This repository is a branch of MoveOn/Spoke created by Politics Rewired, a small campaign tech consultancy created in 2019.

Due to a desire to develop more quickly, we did not maintain compatibility with MoveOn/Spoke, which means although this repository will be a useful source of ideas, it may more work than is worth it to merge it back into MoveOn/Spoke, although we welcome any efforts towards that goal. See HOWTO_migrate-from-moveon-main.md.

Getting Started

Prerequisites

Runtimes and package managers:

  • Node (^16.14)
  • Yarn (>= 1.19.1)

External services:

  • Postgres (>= 11)

Recommended:

Setting up a development environment

Install Node and Yarn

We recommend using the asdf version manager.

# Example using `asdf` (https://github.com/asdf-vm/asdf)
asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git
asdf plugin add yarn https://github.com/twuni/asdf-yarn
asdf install

You can also install the prereqs manually:

Install and run a Postgres server

If you have Docker installed, we recommend using Postgres as a container.

Spoke Rewired comes with a postgres container in docker-compose.yml, which you can start with the following command:

# Run in the foreground, so you can watch logs and stop with Ctrl-C
docker compose up postgres

# Run in the background so you can use the terminal for other things
docker compose up postgres -d

# (if you have an older version of Docker installed, you may have to run
# "docker-compose" with a hyphen instead of "docker compose" with a space)

The postgres container will automatically start up a server with the following configuration:

  • connection string (for DATABASE_URL): postgres://spoke:spoke@localhost:15432/spokedev
  • port: 15432
  • default database: spokedev
  • user: spoke
  • password: spoke

To stop all containers, including Postgres, run:

docker compose down

To delete all container data, including the Postgres database, and stop all containers, run:

docker compose down -v

After the database container is taken down, you can run the up commands above to restart it. For more information, see the Docker Compose reference documentation.

You can also install and run a Postgres server manually without Docker:

Clone the repo

git clone [email protected]:politics-rewired/Spoke.git
cd Spoke
git config --local blame.ignoreRevsFile .git-blame-ignore-revs

Install Node dependencies

yarn install

Copy the example environment

You will need to update the database connection string: it should contain the correct host, port, and username/password credentials to your development Postgres server.

cp .env.example .env
vi .env

# in this case, the server is running at port 5432 (the default Postgres port)
# DATABASE_URL=postgres://spoke:spoke@localhost:5432/spokedev

Create the spokedev database (if it doesn't yet exist)

psql -c "create database spokedev;"

Run the migrations

yarn migrate:worker
yarn knex migrate:latest

Run codegen

yarn codegen

Start the Spoke application in develpoment mode

yarn dev

SMS

For development, you can set DEFAULT_SERVICE=fakeservice to skip using an SMS provider (Assemble Switchboard or Twilio) and insert the message directly into the database. This is set by default in .env.

To simulate receiving a reply from a contact you can use the Send Replies utility: http://localhost:3000/admin/1/campaigns/1/send-replies, updating the app and campaign IDs as necessary.

Migrations

Spoke uses knex to manage application schema. Spoke also uses graphile-worker as it's database-backed job queue.

graphile-worker

The graphile-worker migrations only need to be run once:

yarn migrate:worker

knex

The knex migrations need to be run any time a new release has made changes to the application schema, as indicated by a new migration file in ./migrations. Some migrations require application downtime, some do not. It is up to YOU to review migration notes before rolling out a new release.

yarn knex migrate:latest

To create a new knex migration, run

yarn knex migrate:make my-migration

Next Steps

All configuration environment variables are documented in config.js.

You can also find developer documentation and guides in the docs directory.

Contributing

Commit Messages

This project adheres to the Conventional Commits specification. You can use yarn commit instead of git commit.

Merging PRs

Pull Request merges should use the "Squash and merge" strategy. The final commit message should include relevant information from the component commits and its heading should reflect the purpose of the PR itself; if the PR adds a feature, it should be a feat: add feature x even if it includes a necessary bug fix (ideally unrelated bug fixes are submitted as separate PRs in the first place).

Releases

Each release gets its own commit on main that includes the version bump and changelog updates. The version bump, changelog updates, commit, and tag are generated by standard-version:

yarn release

Other helpful options are:

# Preview the changes
yarn release --dry-run

# Specify the version manually
yarn release --release-as 1.5.0
# or the semver version type to bump
yarn release --release-as minor

# Specify an alpha release
yarn release --prerelease
# or the pre-release type
yarn release --prerelease alpha

Building container images locally

If you plan to build container images locally for use in production you may want to set the default architecture by adding the following to your shell config (e.g. ~/.bash_profile):

export DOCKER_DEFAULT_PLATFORM=linux/amd64

or pass --platform=linux/amd64 to all docker buildx commands.

Deploying

Spoke can be deployed in a variety of different ways. We use Kubernetes internally and are only currently maintaining the Docker image. See the developer documentation for more information about other deployment methods.

License

See LICENSE.

spoke's People

Contributors

ajohn25 avatar azuzunaga avatar bchrobot avatar bdatkins avatar ben-pr-p avatar benmort avatar codygordon avatar cp4r3z avatar etothepiipower avatar harpojaeger avatar hblumberg avatar henryk1229 avatar hiemanshu avatar jamesr2323 avatar jmcarp avatar joemcl avatar jparkrr avatar lady3bean avatar lediur avatar lh00000000 avatar lperson avatar mathemagica avatar millsoper avatar mkoontz-rewired avatar saikat avatar sandramchung avatar schuyler1d avatar shakalee14 avatar spakanati avatar sreynen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

spoke's Issues

Tag dropdown should show all tags after receiving focus

Is your feature request related to a problem? Please describe.
Currently, you must begin typing a tag name before the Select component shows a list of matching tags. This can be confusing if you do not know exactly what tag you are looking for.

Describe the solution you'd like
The entire list of tags should be shown in the Select dropdown when initially clicked into.

Describe alternatives you've considered
N/A

Additional context
Related to #343

Start Campaign button doesn't update after successfully starting

Your issue may already be reported!
Please search on the issue tracker before creating one.

  • I have searched through existing issues and did not find an existing report

Describe the bug

The Start Campaign button does not updated after starting the campaign.

To Reproduce
Steps to reproduce the behavior:

  1. Create a campaign
  2. Start the campaign
  3. See that the start campaign button has not changed state

Expected behavior

The Start campaign button should be updated to reflect that the campaign has been started.

Screenshots

N/A

Additional context

N/A

Show Teams in Message Review

Is your feature request related to a problem? Please describe.

Teams are not visible in Message Review.

Describe the solution you'd like

Teams should be visible from Message Review

Describe alternatives you've considered

N/A

Additional context

Awaiting additional details from requesting client.

Support Typescript

Is your feature request related to a problem? Please describe.

Many bugs and runtime issues could be avoided by the compile-time type checking offered by TypeScript. Especially problematic are changes to method signatures that only change how it is called in a few, but not all, places in codebase.

Describe the solution you'd like

We should incrementally move to TypeScript and enforce typing of JSON received from external services using pelotom/runtypes.

Resources:

Describe alternatives you've considered

Rewired is a TypeScript shop so Flow was not considered.

Feature Request: Show the name of the list at the top of the conversation UI (fix confusion about context)

Problem
When texting for multiple lists, to recipients in multiple locations, the context is important and because they conversation UI is completely disconnected from the lists UI (not sure what the actual view files are called) it's hard to know the context of the conversation.

Example
You're texting and a person you've texted asks a question that you can't answer. You ask in Slack for advice and an admin says, "Which list is this from?" ... You have no way of knowing unless you remember which list you clicked into, but you are responding to replies in 15 different lists. The only way to know is to go back to home, guess which list it might be in, click in and browse for the conversation. ... this is time consuming and confusing.

Solution
Somewhere in the conversation UI indicate which list the conversation belongs to

Design Suggestion
Add it to the top of the view before the recipient's name, and if the list title is long, truncate the title of the list but show full title on hover or tap. Bonus: This could also be a place for list specific resources.

Escalated Conversation count includes archived campaigns

Your issue may already be reported!
Please search on the issue tracker before creating one.

  • I have searched through existing issues and did not find an existing report

Describe the bug

The Escalated Conversation count includes escalated conversations belonging to archived campaigns.

To Reproduce
Steps to reproduce the behavior:

  1. Escalate a conversation within Campaign A
  2. See that count of Escalated Conversations increases
  3. Archive Campaign A
  4. See that count of Escalated Conversations remains unchanged

Expected behavior

Escalated conversations in archived campaigns should not be counted.

Screenshots

N/A

Additional context

N/A

Sweeper Opt-Out button pushed off screen on small devices

Your issue may already be reported!
Please search on the issue tracker before creating one.

  • I have searched through existing issues and did not find an existing report

Describe the bug

On small screens, the Opt Out button in a Message Review conversation detail is pushed off the screen.

To Reproduce
Steps to reproduce the behavior:

  1. Be using smaller screen
  2. Go to Message Review
  3. Select conversation with either a) many messages, or b) many question responses
  4. See that Opt Out button is pushed off the screen

Expected behavior

All control elements should be accessible regardless of screen size, conversation length, or number of question responses.

Screenshots

N/A

Desktop (please complete the following information):

N/A

Additional context

N/A

Support script defaults

Is your feature request related to a problem? Please describe.

Campaign contact fields are not always available for a given contact. This results in awkward messages with oddly places spaces such as:

Hey , this is Ben with Organization. Do you support Initiative?

Describe the solution you'd like

Script fields should support defaults (i.e. Hey {firstname|'friend'}, this is Ben).

These default should not be subject to the capitalization processing that other name variables are.

Describe alternatives you've considered

Pre-processing the source CSV to add the default. This results in capitalization of the default, however.

Additional context

Implementation may be related to #438 if processing details for fields is stored at the campaign level.

Validate delivery report payloads

Currently, Spoke only cares about whether a log record insert was kicked off for a delivery report before returning a 200, but not whether the parsing of the delivery report was successful.

It should:

  1. Validate the payload, return 400 if improperly formatted
  2. Kick off async log record insert
  3. Return 200 response
  4. Log any insert errors

Send button is pushed off the edge of the screen on mobile during initials

Your issue may already be reported!
Please search on the issue tracker before creating one.

  • I have searched through existing issues and did not find an existing report

Describe the bug

The texter's Send button is pushed off the bottom of the screen on mobile when sending out initials if the contact's name is too long.

To Reproduce
Steps to reproduce the behavior:

  1. Create campaign with a contact whose first name is longer than 4-5 characters
  2. Begin texting on mobile device
  3. See that Send button pushed off the screen

Expected behavior

The Send button should always be visible.

Screenshots

Lost the client thread with these.

Smartphone (please complete the following information):

Lost the client thread with this information. It's pretty widespread though.

Allow navigating to first or last page in Message Review

Is your feature request related to a problem? Please describe.

When there are many pages of conversations in Message Review it is difficult to navigate to the beginning/end.

Describe the solution you'd like

There should be buttons to navigate to the first and last pages in Message Review

Describe alternatives you've considered

N/A

Additional context

N/A

Big button to release all replies

Is your feature request related to a problem? Please describe.

When managing many campaigns, it can be a pain to go through and release unhandled replies one by one.

Describe the solution you'd like

There should be a big "Release all unhandled replies" button on the campaign page. This should allow timezone selection/limitation.

Describe alternatives you've considered

N/A

Additional context

N/A

Texter notifications should be configurable

Texters should have the option of receiving digests of replies, or some other method of batched notifications after a period of Texter inactivity.

There would be four levels of notification frequency:

  • daily digest
  • periodic digest (cluster in ~4 hour buckets)
  • one email for every notification
  • off

    At first this would be one setting for all types of notifications. Eventually we might want to allow setting preference for each type of notification.

Campaign must be started for assignment notification emails to be sent

Your issue may already be reported!
Please search on the issue tracker before creating one.

  • I have searched through existing issues and did not find an existing report

Describe the bug
Notification emails for assignments are only sent if the assignment was created after its campaign had been started.

To Reproduce
Steps to reproduce the behavior:

  1. Create campaign, upload contacts, create script
  2. Assign yourself some texts (no emails sent)
  3. Start the campaign (no emails sent)
  4. Delete and recreate your assignment (emails sent)

Expected behavior
Starting the campaign should send emails for all "pending" assignments created (step 2 above)

Add character/segment count to message composition field

Is your feature request related to a problem? Please describe.

There are two issues:

  1. Organizations may accidentally send, and be billed for, many more segments than anticipated
  2. Texters may spend time crafting a very length response only to have the message fail because it is too long.

Describe the solution you'd like

The message composition field should:

  • Display current character count
  • Display current segment count
  • Prevent text entry when maximum character count has been reached
  • (Optionally) display encoding (GSM vs UCS2) required

Resources

Use proper pagination endpoint for People query

Is your feature request related to a problem? Please describe.

The People query uses a very strange pagination strategy that does not scale well.

Describe the solution you'd like

The People query should support a pagination strategy as outlined in Apollo's Pagination - Client (React) guide.

Describe alternatives you've considered

N/A

Additional context

This is true for most list queries.

View-only version of Edit Campaign page

Is your feature request related to a problem? Please describe.

It is frustrating and an admin workflow impediment for Supervolunteer members not to have access to campaign details.

Describe the solution you'd like

Supervolunteers should have access to a read-only version of the Edit Campaign page.

Describe alternatives you've considered

N/A

Additional context

N/A

Organization texting hours should always be enabled

Texting hours are required for campaigns so it does not make sense for organizational texting hours to be optional. Instead, texting hours at the organization level should be treated as the default texting hours to use for new campaigns.

Message text pre-processors should be an array of mutating functions

Is your feature request related to a problem? Please describe.

The current implementation of explicitly calling each mutator is a little ungainly and difficult to maintain.

Describe the solution you'd like

To make it clearer to read and easier to maintain, each outbound message pre-processor should be a function with a standard signature. An array of these pre-processors should be defined forming a chain that the message can be passed through.

Describe alternatives you've considered

N/A

Additional context

N/A

Upgrade to [email protected]

[email protected] is the last version of material-ui to support React 15 while still providing the 1.x components. We can update to 1.0.0-beta.47, perform other dependency updates, update React to v16, and finally come back to update to a stable version of @material-ui.core (the renamed project as of v1.0.0-rc.0.

See change logs for 1.0.0-beta.47

organization.currentAssignmentTargets returns multiple targets for each team

Your issue may already be reported!
Please search on the issue tracker before creating one.

  • I have searched through existing issues and did not find an existing report

Describe the bug

The organization.currentAssignmentTargets query returns all current assignment targets, not just the active one.

React complains about duplicate child keys and only renders the first for each. This is likely responsible for the AssignmentsHUD sometimes not matching reality.

To Reproduce
Steps to reproduce the behavior:

  1. Create multiple Autoassign campaigns for a given team
  2. Go to campaign list
  3. Open Javascript console and see React errors
  4. Maybe see incorrect campaign in AssignmentsHUD component

Expected behavior

AssignmentsHUD should display only the active target.

Screenshots

N/A

Additional context

N/A

Feature Request: Make sure texts don't get sent at wee hours

Problem
Not sure how to reproduce this but several people have reported complaints that the people they texted mid-day got texts in the middle of the night. (Possible it was another group, without seeing their phones it's hard to know)

Solution to prevent this
Make sure texts don't get sent at wee hours

Error from improperly formatted zip-code is not surfaced to user

Describe the bug

An improperly formatted zip code (ex O4551 -- note the O instead of a zero) throws an unhandled error. This causes the contact upload job to fail silently.

To Reproduce
Steps to reproduce the behavior:

  1. Create contact list using zip code O4551
  2. Upload the list
  3. On the client, see that the job does not fail and no error is displayed to the user
  4. On the server, see an unhandled promise rejection error in the logs

Expected behavior

An invalid zip code should cause the job to fail to with an error message. This error message should be displayed on the web client.

Allow exporting campaign's Question tree

Is your feature request related to a problem? Please describe.

N/A

Describe the solution you'd like

There should be a way to export a campaign's Question tree to a Word document.

Describe alternatives you've considered

N/A

Additional context

N/A

Add date filter to Message Review

Is your feature request related to a problem? Please describe.

It is impossible to restrict Message Review queries to a date range.

Describe the solution you'd like

Add a date filter to Message Review.

Describe alternatives you've considered

N/A

Additional context

N/A

Unselecting filter options in Incoming Message review does reset to All

Your issue may already be reported!
Please search on the issue tracker before creating one.

  • I have searched through existing issues and did not find an existing report

Describe the bug

Unselecting either Campaign or Texter in Incoming Message review does not result in the query filter being reset back to All.

To Reproduce
Steps to reproduce the behavior:

  1. Go to Incoming Message Review
  2. Filter by Campaign, wait for result to load
  3. Remove Campaign filter
  4. See that conversation list is still limited to the Campaign selected.

Expected behavior

Unselecting a filter UI field should result in that query filter being reset.

Screenshots
N/A

Desktop (please complete the following information):

  • OS: macOS 10.15
  • Browser: Brave
  • Version: v1.2.42

Additional context

N/A

Add sort/filter options for campaign list

Is your feature request related to a problem? Please describe.

With many active campaigns, it can be difficult to find the one you are looking for

Describe the solution you'd like

There should be sorting and filtering options for the campaign list (e.g. created_at, due_by, teams)

Describe alternatives you've considered

N/A

Additional context

Waiting on more information from users about what sort/filter options would be most useful.

Message Review does not include the latest messages in some cases

Your issue may already be reported!
Please search on the issue tracker before creating one.

  • I have searched through existing issues and did not find an existing report

Describe the bug

Sometimes Message Review is showing the survey answer for a conversation without displaying the actual message/response. This prevents sweepers from knowing whether a conversation was marked correctly or not.

To Reproduce

No reproduction steps at this time.

Expected behavior

Message Review should always show the latest conversation.

Screenshots

N/A

Additional context

N/A

Campaign's archived state does not update after archiving from context menu

Your issue may already be reported!
Please search on the issue tracker before creating one.

  • I have searched through existing issues and did not find an existing report

Describe the bug

When a campaign is archived from its context menu in the campaign list, its archived state (tag) does not change.

To Reproduce
Steps to reproduce the behavior:

  1. Go to Campaign List
  2. Click Archive from a campaign's context menu
  3. See that campaign's archive state has not changed

Expected behavior

After toggling archive for a campaign, the archived state should update in the UI.

Screenshots

N/A

Additional context

N/A

cacheableData.organization undefined

When updating texting hour enforcement the update itself works but returning a value from cacheableData fails due to the organization property being undefined.

Printing the value from different places shows it existing in the incomingmessagehandler process but not in server. I'm not even sure why it's running two processes with JOBS_SAME_PROCESS=1 to begin with.

Server Logs
16:38:39 incomingmessagehandler.1 |  organizationCache { clear: [Function: clear], load: [Function: load] }
16:38:39 incomingmessagehandler.1 |  cacheable_queries { campaign:
16:38:39 incomingmessagehandler.1 |     { clear: [Function: clear],
16:38:39 incomingmessagehandler.1 |       load: [Function: load],
16:38:39 incomingmessagehandler.1 |       reload: [Function: loadDeep],
16:38:39 incomingmessagehandler.1 |       dbCustomFields: [Function: dbCustomFields],
16:38:39 incomingmessagehandler.1 |       dbInteractionSteps: [Function: dbInteractionSteps] },
16:38:39 incomingmessagehandler.1 |    campaignContact:
16:38:39 incomingmessagehandler.1 |     { clear: [Function: clear],
16:38:39 incomingmessagehandler.1 |       load: [Function: load],
16:38:39 incomingmessagehandler.1 |       loadMany: [Function: loadMany] },
16:38:39 incomingmessagehandler.1 |    cannedResponse: { clearQuery: [Function: clearQuery], query: [Function: query] },
16:38:39 incomingmessagehandler.1 |    optOut:
16:38:39 incomingmessagehandler.1 |     { clearQuery: [Function: clearQuery],
16:38:39 incomingmessagehandler.1 |       query: [Function: query],
16:38:39 incomingmessagehandler.1 |       save: [Function: save],
16:38:39 incomingmessagehandler.1 |       loadMany: [Function: loadMany] },
16:38:39 incomingmessagehandler.1 |    organization: { clear: [Function: clear], load: [Function: load] } }
16:38:39 incomingmessagehandler.1 |  models { campaign:
16:38:39 incomingmessagehandler.1 |     { clear: [Function: clear],
16:38:39 incomingmessagehandler.1 |       load: [Function: load],
16:38:39 incomingmessagehandler.1 |       reload: [Function: loadDeep],
16:38:39 incomingmessagehandler.1 |       dbCustomFields: [Function: dbCustomFields],
16:38:39 incomingmessagehandler.1 |       dbInteractionSteps: [Function: dbInteractionSteps] },
16:38:39 incomingmessagehandler.1 |    campaignContact:
16:38:39 incomingmessagehandler.1 |     { clear: [Function: clear],
16:38:39 incomingmessagehandler.1 |       load: [Function: load],
16:38:39 incomingmessagehandler.1 |       loadMany: [Function: loadMany] },
16:38:39 incomingmessagehandler.1 |    cannedResponse: { clearQuery: [Function: clearQuery], query: [Function: query] },
16:38:39 incomingmessagehandler.1 |    optOut:
16:38:39 incomingmessagehandler.1 |     { clearQuery: [Function: clearQuery],
16:38:39 incomingmessagehandler.1 |       query: [Function: query],
16:38:39 incomingmessagehandler.1 |       save: [Function: save],
16:38:39 incomingmessagehandler.1 |       loadMany: [Function: loadMany] },
16:38:39 incomingmessagehandler.1 |    organization: { clear: [Function: clear], load: [Function: load] } }
16:38:39 incomingmessagehandler.1 |  backend warn NO TWILIO CONNECTION
16:38:39 incomingmessagehandler.1 |  backend warn Twilio will not be able to send without TWILIO_MESSAGE_SERVICE_SID set
16:38:40 server.1                 |  cacheable_queries { campaign:
16:38:40 server.1                 |     { clear: [Function: clear],
16:38:40 server.1                 |       load: [Function: load],
16:38:40 server.1                 |       reload: [Function: loadDeep],
16:38:40 server.1                 |       dbCustomFields: [Function: dbCustomFields],
16:38:40 server.1                 |       dbInteractionSteps: [Function: dbInteractionSteps] },
16:38:40 server.1                 |    campaignContact:
16:38:40 server.1                 |     { clear: [Function: clear],
16:38:40 server.1                 |       load: [Function: load],
16:38:40 server.1                 |       loadMany: [Function: loadMany] },
16:38:40 server.1                 |    cannedResponse: { clearQuery: [Function: clearQuery], query: [Function: query] },
16:38:40 server.1                 |    optOut:
16:38:40 server.1                 |     { clearQuery: [Function: clearQuery],
16:38:40 server.1                 |       query: [Function: query],
16:38:40 server.1                 |       save: [Function: save],
16:38:40 server.1                 |       loadMany: [Function: loadMany] },
16:38:40 server.1                 |    organization: undefined }
16:38:40 server.1                 |  models { campaign:
16:38:40 server.1                 |     { clear: [Function: clear],
16:38:40 server.1                 |       load: [Function: load],
16:38:40 server.1                 |       reload: [Function: loadDeep],
16:38:40 server.1                 |       dbCustomFields: [Function: dbCustomFields],
16:38:40 server.1                 |       dbInteractionSteps: [Function: dbInteractionSteps] },
16:38:40 server.1                 |    campaignContact:
16:38:40 server.1                 |     { clear: [Function: clear],
16:38:40 server.1                 |       load: [Function: load],
16:38:40 server.1                 |       loadMany: [Function: loadMany] },
16:38:40 server.1                 |    cannedResponse: { clearQuery: [Function: clearQuery], query: [Function: query] },
16:38:40 server.1                 |    optOut:
16:38:40 server.1                 |     { clearQuery: [Function: clearQuery],
16:38:40 server.1                 |       query: [Function: query],
16:38:40 server.1                 |       save: [Function: save],
16:38:40 server.1                 |       loadMany: [Function: loadMany] },
16:38:40 server.1                 |    organization: undefined }

Notification emails choose random OWNER's email to use as Reply To

Your issue may already be reported!
Please search on the issue tracker before creating one.

  • I have searched through existing issues and did not find an existing report

Describe the bug
Spoke's notification emails are sent with a replyTo parameter. The email address used for this is chosen at random from the origination organization's OWNERs. This behavior is unpredictable and does not allow an organization to have multiple OWNERs and still maintain control over email settings.

To Reproduce
Steps to reproduce the behavior:

  1. Create multiple OWNER users
  2. Create an assignment
  3. View the Reply To field of the assignment notification email
  4. See an owner's email address distinct from the From address (EMAIL_FROM envvar)

Expected behavior
One of:

  • Drop the replyTo param (as this is [email protected] most of the time)
  • Make it an organization setting
  • Make it YAEV (Yet Another Envvar ™️)

Feature Request: Streamline tagging for texters

Problem
Many clicks are required to apply a tag
It's difficult to see if a tag is applied to a conversation

Proposed Solution
Streamline tag management. I suggest taking inspiration from an app like Trello where the interaction is: (1) click on "TAGS", (2) click any applicable tags (make a checkmark appear on the clicked label), then (3) click outside --> 3 clicks to get tags, no more save button.
(Trello uses labels, not tags, but same thing)

Make applied tags show at the top of the conversation
Once a tag is applied, show it at the top (ideally color coded) and make it clickable to reveal the same tag menu that you can use to apply tags from the "TAGS" button.

Wrinkle: the Escalate tag could be in a separate area with a note about how to use it. Something like: horizontal line, "Use this tag sparingly: [escalate]"

Visuals
I've started a Figma file with some screen shots of the current design (Fall 2019), some inspiration, and the beginning of some mockups. Let me know if you are going to work on this and I'll happily dial in the mockups based on available libraries, etc.
https://www.figma.com/file/mx3shfewd8IETe10qaSMFu/Streamline-Tagging-in-Spoke?node-id=0%3A1

(Note: this was initially submitted as an issue to the upstream version of Spoke, makes more sense here MoveOnOrg/Spoke#1263 )

Refactor uploads to support bigger files

Right now, the CSV is uploaded and serialized to JSON on the client. This has a few drawbacks:

  1. Any API gateway or server will have a single request size limit, and JSON is a very size inefficient serialization format. This means that list uploads, in practice, have a size limit of around 200-300k rows.

  2. All of this works makes users's browsers crash / freeze sometimes, especially if they switch tabs while waiting, which leaves the intensive computation in a throttled background tab.

There are a few options for how to refactor this –

A) Verify the CSV structure (required fields, etc.) on the client, and then send the file over as a file to the server, which copies it locally to /tmp, and asynchronously queues its processing.

B) Keep everything on the client, but refactor csv to JSON serialization to happen in a web worker, and use a new mutation addContacts that incrementally adds batches of contacts.

The big downside of B is that the user can't navigate away from the page while the upload is happening. The slight downside of A is that it presents some additional complications around hosting and the different types and sizes of /tmp directories available on different containers and serverless platforms.

I think overall A will provide better and faster user experience with more predictable performance

Add full text search to Message Review

Is your feature request related to a problem? Please describe.

Sometimes the only way to find a conversation is by searching for specific text. Spoke cannot do that currently.

Describe the solution you'd like

Allow full text search.

Describe alternatives you've considered

Using Metabase to perform the text search, getting the campaign and campaign contact information, and searching by that within Spoke.

Additional context

N/A

Purge environment variables

There are a number of envvars in config.js that are either unused, non-application runtime vars, or provide duplicate functionality. These need to get cleaned up.

No pluralization on request text count

Your issue may already be reported!
Please search on the issue tracker before creating one.

  • I have searched through existing issues and did not find an existing report

Describe the bug

The Request Texts count is not pluralized.

To Reproduce
Steps to reproduce the behavior:

  1. Go to Texter ToDo page
  2. See that, for different request counts, the pluralization is incorrect

Expected behavior

The request count should be pluralized correctly.

Screenshots

N/A

Additional context

N/A

Allow password reset via email

The only way to reset a password is for an admin to generate a reset link and send it to the user. This is annoying for admins and does not even work if the user was never successfully added to the organization in the first place.

Password reset should be possible via a "reset password" link that sends an email to the user with a reset link.

Auto-release conversations after sending

Is your feature request related to a problem? Please describe.

Some texters are not equipped to handle replies (e.g. don't have the time, don't have the familiarity with candidate policy) and only send initials. Keeping track of these texters and reassigning these conversations to someone else after the initials have been sent can be a heavy lift for admins.

Describe the solution you'd like

It should be possible to mark assignments as "send-only" and have them be automatically unassigned after the initial message has been sent. This should also be a property for teams such that a member of a this team has this happen for all assignments. Maybe also a property of user_organization.

Describe alternatives you've considered

A time-based solution where conversations are automatically released after a certain period of inactivity has also been discussed.

Additional context

N/A

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.