Skip to content
This repository has been archived by the owner on Oct 11, 2022. It is now read-only.

Slack Bot #2632

Merged
merged 89 commits into from
May 2, 2018
Merged

Slack Bot #2632

merged 89 commits into from
May 2, 2018

Conversation

mxstbr
Copy link
Contributor

@mxstbr mxstbr commented Mar 21, 2018

  • Figure out permission scopes necessary
  • Figure out how to send messages (#2015)
  • Send messages on new threads from athena
  • Get members on click, rather than automatically
  • Build UI in community settings
    • Refactor pane to handle more general purpose
    • Separate import step
    • Re-connecting with new permissions
    • Build UI for webhooks
  • Figure out how to handle when we have a token with the old permissions
  • Check whether we need admin perms to load user emails
    • We don't, so how do we make sure a normal member can't spam all members of a team without prompting for admin access?

Status

  • WIP
  • Ready for review
  • Needs testing

Deploy after merge (delete what needn't be deployed)

  • iris (api)
  • hyperion (frontend)

Release notes for users (delete if codebase-only change)

  • Slack bot! You can now get notified in your Slack team whenever a new thread is posted in your community on Spectrum

@mxstbr
Copy link
Contributor Author

mxstbr commented Mar 21, 2018

This works now!! 🎉

  1. Authenticate community with Slack team
  2. Copy token from slackImports table
  3. Send POST request to Slack with:
curl \
  -X POST \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer {token}' \
  -d '{"channel":"#general","text": "Hello World again!"}' \
  https://slack.com/api/chat.postMessage

This is what it'll look like:

screen shot 2018-03-21 at 12 38 14

Note: This will show "Spectrum" with our logo, I just need admin access to the Space Program Slack team @brianlovin to edit the actual apps permission setup—for now I created a separate one in my own test workspace as I couldn't change them in the actual one.

I've updated the OP with the two main leftover questions.

@brianlovin I'll need your help on the settings UI for this—basically users need to be able to say "On {action} (either new message or new thread atm) send message to {channel}" (where {channel} is just a text input and we show an error if that channel doesn't exist) I don't know where to fit that with the existing invite stuff to be completely honest.

There's also the existing connections that won't have the right permissions, I'm trying to figure out how we check that and get us the right permissions for those, that'll also need a UI state to maybe connect again or something? Don't know yet.

@brianlovin
Copy link
Contributor

brianlovin commented Mar 21, 2018

Woo woo!!!!

Stoked to see this. And I think @uberbryn is the person to make you an admin of the space program slack team.

Here are my thoughts from reading your last comment:

  • we should nail down what some of the core user flows are and build around that
  • we should consider keeping slack imports and slack bot tokens separate, since they serve different purposes, and we will have the problem of mixed read/write permission tokens now with the old slackimports code.

Here are my thoughts on the above:

  • I think a key flow will be that an owner says "When a new thread happens in {channel a} on Spectrum, I want it to post in {channel b} on my Slack team"
  • I'm having a hard time thinking of a good use case for wanting all new messages to ping a Slack team; do you have something in mind?
  • It seems likely that community owners will want multiple channels to go to multiple channels. For example we'd have the feature-requests channel on spectrum ping our 'product' slack channel, and our hugs-n-bugs channel on spectrum ping our 'support team' slack channel.

If those assumptions hold, we might end up with something like this:

  • on communitySettings record we store a slack token with read/write auth
  • on channelSettings we store preferences for slack cross posting, maybe like:
{
  // ...channelSettings
  slackBotSettings: {
    isEnabled: boolean,
    channels: Array<string>,
    eventTypes: Array<string>
  }
}

where channels is an array of strings that map to channels they have on their slack team. We should start with only supporting one, but make it an array so that in the future we could allow them to cross post into multiple slack channels. This might be edge case?

where eventTypes maps to events that happen on our end. We should start with supporting 'new thread published' to keep things easy to start, but in the future could add anything like 'moderation event X', or 'user requested to join Y' etc etc

@mxstbr
Copy link
Contributor Author

mxstbr commented Mar 21, 2018

we should consider keeping slack imports and slack bot tokens separate, since they serve different purposes, and we will have the problem of mixed read/write permission tokens now with the old slackimports code.

That seems like a really unnecessary abstraction? Users would have to click Connect Slack Team twice for essentially the same thing, and we'd have to duplicate all the routing logic on the backend just to get two tokens when a single one can do all the things too?

I'm having a hard time thinking of a good use case for wanting all new messages to ping a Slack team; do you have something in mind?

Syndicating all content to Slack I guess. We should make this happen and see what people do with it imo.

If those assumptions hold, we might end up with something like this:

That data model makes sense to me!

@brianlovin
Copy link
Contributor

That seems like a really unnecessary abstraction? Users would have to click Connect Slack Team twice for essentially the same thing, and we'd have to duplicate all the routing logic on the backend just to get two tokens when a single one can do all the things too?

Fair; only case I could think would be people needed different slack teams for importing (eg. import their community) and a team for cross posting (e.g. internal team slack). Guess we'll find this out soon enough...

Syndicating all content to Slack I guess. We should make this happen and see what people do with it imo.

Fair enough, if we're handling all kinds of event types it makes sense to me to add it and see what happens.

@brianlovin
Copy link
Contributor

Was just thinking on this a bit more, and I think we will want two Slack auth flows in many cases.

Imagine a community first sets up the slack bot. We should definitely not grab member info to queue for invites - we only need the token. So if they do this first, then if they ever choose to import a slack team they'd need to re-auth so we can get the members data with their permission.

The opposite flow (import => bot) seems fine to re-use the token, although I don't know how we'll sort out which tokens need extra permissions at this point. We can dig in though.

@mxstbr
Copy link
Contributor Author

mxstbr commented Mar 21, 2018

That seems so hard to do, let's not over-engineer and make it single-connect for v1?

@brianlovin
Copy link
Contributor

I do not think that we should gather member information if the user is not expecting us to do so. If anything were to happen there it would be pretty bad and hurt the trust we've been building.

@mxstbr
Copy link
Contributor Author

mxstbr commented Mar 21, 2018

I do not think that we should gather member information if the user is not expecting us to do so.

"Connect a Slack team—To invite the members of your Slack team and send new threads to a channel connect it below!"

How is that not expected? I feel like we're way overthinking this for the MVP, let's just send new threads to a channel and be done with it?

@brianlovin
Copy link
Contributor

How is that not expected? I feel like we're way overthinking this for the MVP, let's just send new threads to a channel and be done with it?

It just needs to be useful at the start, and I don't think that having one action to set up both is right. I think that inviting a Slack team is a pretty big decision, and people (especially internal teams, or communities thinking of migrating) will be very intentional about deciding when to pull that trigger.

Where sending new thread notifications to a channel is super low pressure, and is actually a huge opportunity for us to build that trust and help communities eventually build up to the decision of doing a full import.

I don't want to overthink this for an MVP, but I also don't want to ship something that people are scared to use because they think we're about to swallow their whole team's data.

It should be two sections for importing a team versus managing outbound bot pings. We can figure out how to connect those with a single 'connect with slack' button, but I don't feel that the first connection should get all their member info until they explicitly want to import.

@mxstbr
Copy link
Contributor Author

mxstbr commented Mar 21, 2018

I think that inviting a Slack team is a pretty big decision, and people (especially internal teams, or communities thinking of migrating) will be very intentional about deciding when to pull that trigger.

I 100% agree, that's why once you connect a Slack team you'll get two buttons: One to invite and one to add a new message sending thingy, right?

Let me figure out tomorrow how hard it would be to add more permissions later, if it's easy let's do it your way, if not let's figure out what else to do.

@superbryntendo
Copy link
Contributor

superbryntendo commented Mar 21, 2018 via email

@brianlovin
Copy link
Contributor

Disagree based on the imports we've seen so far though. but maybe.

Anyways, synced on video chat and Max and I were talking about the same thing: we request the permissions up front, but let the import itself be explicit so that we never store member data until the user is ready.

@mxstbr
Copy link
Contributor Author

mxstbr commented Mar 22, 2018

To ask for an additional permission, a user must first interact with your application one of two ways: 1. Invoke a slash command, if your app offers one 2. Interact with a message button or message menu as part of the interactive message framework.

Well damn, as far as I can tell this basically means anybody who connected a Slack team before this PR will need to do it again. 😕

@mxstbr
Copy link
Contributor Author

mxstbr commented Mar 22, 2018

There's two different flows here:

First-time connection

  • A community admin goes to /settings and clicks on "Connect to Slack"
  • Authorizes usage with their Slack team

Previously connected Slack teams

At the end, both of them see two buttons: One to "Invite their Slack team members to the Spectrum community" and one to "Send new threads in their community to a channel in Slack".

I'm thinking about where to put the API call to check the permissions. Maybe we just add a flag in a migration to all existing connections that says "They don't have permissions, ask again", then we don't even have to do that API call?

@mxstbr
Copy link
Contributor Author

mxstbr commented Mar 22, 2018

Turns out, we actually import members on connect right now from athena. Good news is that it'll be easy to refactor since that's just a job we can trigger at any time!

@mxstbr
Copy link
Contributor Author

mxstbr commented Mar 22, 2018

Doing a bunch of UI tweaks in the settings pane right now to make it fit with no longer only being there to invite folks, it'd be great to get a hand with those.

This is a first rough WIP:

screen shot 2018-03-22 at 09 55 59
screen shot 2018-03-22 at 09 56 12
screen shot 2018-03-22 at 10 00 02

Note: The separate "Invite members" step happens automatically right now, to see it you have to turn off athena. Once you turn athena on, it'll import the members automatically and the state will change on the next refresh—this'll be way different once that is a manual action, haven't built that just yet.

Obviously missing is the second section to enable webhooks, no clue how to make that happen just yet!

- Split into two components, one for members inviting and one general
- Add intermediate state for importing members (WIP)
@mxstbr
Copy link
Contributor Author

mxstbr commented Mar 22, 2018

I really want to e2e test this UI but the existing community settings tests are in #2354 ugh

@brianlovin
Copy link
Contributor

I really want to e2e test this UI but the existing community settings tests are in #2354 ugh

😵 oh no! Hopefully we can get it on alpha today, I don't see why not :)

@brianlovin
Copy link
Contributor

Happy to help with the UI and wiring up the new channelSettings stuff

@mxstbr
Copy link
Contributor Author

mxstbr commented Mar 22, 2018

Please jump in! Would love any help!

Instead of immediately loading the entire members data from Slack when
folks connect their team, we now only do it once they click "Import"!
:tada: :tada: :tada:

This works really nicely, see https://i.imgur.com/QLX75wF.gif
@mxstbr
Copy link
Contributor Author

mxstbr commented Mar 23, 2018

The latest commit now means we only import member data once folks click "Import members"!!! 🎉 Look at it:

@withspectrum withspectrum deleted a comment from spectrum-bot bot May 1, 2018
@withspectrum withspectrum deleted a comment from spectrum-bot bot May 1, 2018
@brianlovin
Copy link
Contributor

@mxstbr we lost the rule to ignore console logs in migrations :)

@mxstbr
Copy link
Contributor Author

mxstbr commented May 1, 2018

We never had that rule in the first place, console.logs aren't allowed anywhere—only flow 😉

/cc @orta that patch with the rate limiting fixed Peril for large PRs! 🎉

@brianlovin
Copy link
Contributor

We had an ignore directory rule there. But no worries, will just switch to error

@brianlovin
Copy link
Contributor

Writing tests for this is a bit tricky as it would require handling 3rd party oauth (not possibly with cypress afaik) or mocking out entire request flows to and from slack api. I'm honestly not sure how to go about this properly - in the meantime I at least added checks around who should have access to querying slack settings on communities and channels.

So @mxstbr this is ready for your code review. I also refactored the permission logic today to be separated from the context constructor; lmk if you're happy with how all that looks!

@mxstbr
Copy link
Contributor Author

mxstbr commented May 2, 2018

I also refactored the permission logic today to be separated from the context constructor

Wait hold on, why? I thought we were happy with that?

}

type ChannelSlackSettings {
botConnection: BotConnection
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This name is confusing: botConnection is not a connection. (as per the Relay spec and how we use it elsewhere) If you want this to be a connection just like all our other connections (which it should be), please make it conformant by adding edges, nodes and page info:

type BotSettings {
  threadCreated: String
}

type BotConnectionEdge {
  node: BotSettings!
}

type BotConnection {
  pageInfo: PageInfo!
  edges: [BotConnectionEdge!]
}

You don't have to build the actual pagination for now, but at least this sets us up to do it properly in the future.

@mxstbr
Copy link
Contributor Author

mxstbr commented May 2, 2018

Getting this locally after connecting a Slack team:

screen shot 2018-05-02 at 10 14 13

Maybe I need some updated secrets or something?

@brianlovin
Copy link
Contributor

Wait hold on, why? I thought we were happy with that?

Asked about this on Twitter and you said it looked good! Anyways, the reason is to keep permissions away from the schema and pushed down into business logic. Writing them as resolver wrappers is still a bit more tightly integrated, but it makes for a nice experience.

This name is confusing: botConnection is not a connection.

I don't want it to be a relay connection spec field. I am trying to communicate how the bot is connected to the slack team, so connection came to mind. Open to other naming ideas!

Getting this locally after connecting a Slack team:

Console logs?

@mxstbr
Copy link
Contributor Author

mxstbr commented May 2, 2018

Anyways, the reason is to keep permissions away from the schema and pushed down into business logic.

They weren't in the schema before though, just packed into the GraphQL context so that they're easy to call. @isAuthed was in the schema, and I thought that's what we were talking about moving out. Anyways, not a biggie, I liked the user.canManageChannel(channelId) API more than canManageChannel(user.id, channelId, loaders) one but it doesn't matter too much.

I am trying to communicate how the bot is connected to the slack team, so connection came to mind.

Oh I see, I totally misinterpreted that name. I think we probably shouldn't call it *Connection since that's pretty reserved for the Relay stuff—what about slackSettings.links or just slackSettings.botConfig or something like that?

@brianlovin
Copy link
Contributor

Re the error you're seeing after connecting a slack team: for some reason we have 2 spectrum apps on Slack, and the secrets were mismatched. I updated the now-secrets in 1password, can you grab that and try again? Any reason we have 2 apps for this?

@brianlovin
Copy link
Contributor

Oh I see, I totally misinterpreted that name. I think we probably shouldn't call it *Connection since that's pretty reserved for the Relay stuff—what about slackSettings.links or just slackSettings.botConfig or something like that?

Will update now.

@brianlovin
Copy link
Contributor

Named botLinks - how does that feel to you?

@brianlovin brianlovin merged commit 0c4c737 into alpha May 2, 2018
@brianlovin brianlovin deleted the slack-bot branch May 2, 2018 16:29
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants