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
Show file tree
Hide file tree
Changes from 56 commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
9aecdf8
Fix Slack permission scopes
mxstbr Mar 21, 2018
e064237
console.log -> console.error
mxstbr Mar 21, 2018
d2d162d
Refactor importSlack UI settings pane
mxstbr Mar 22, 2018
e4117e3
Fix invitation mutation
mxstbr Mar 22, 2018
6f24bd5
Im a dummy
mxstbr Mar 22, 2018
adc243a
Move slackImportQueue.add from db query
mxstbr Mar 23, 2018
2c405d7
Lazily import members from Slack
mxstbr Mar 23, 2018
8510218
Store Slack scope with slackImports record
mxstbr Mar 23, 2018
27b3a53
Fix copy on Slack integration
mxstbr Mar 23, 2018
a87d18d
Change Slack scope to not include admin permissions
mxstbr Mar 23, 2018
f7d7d65
Send new threads in a community to Slack
mxstbr Mar 23, 2018
8dbcdff
Fix danger warnings
brianlovin Mar 26, 2018
b615558
Eslint cleanup
brianlovin Mar 26, 2018
5f304cd
Merge branch 'alpha' of https://github.com/withspectrum/spectrum into…
brianlovin Mar 30, 2018
610155a
Move files from old iris
brianlovin Mar 30, 2018
7adfa58
Merge alpha and fix conflicts
brianlovin Apr 24, 2018
eeadaef
Refactor front end components to set it up for reusability in many co…
brianlovin Apr 25, 2018
94f75c4
Update api graphql types for community slack settings
brianlovin Apr 25, 2018
0a9d8c1
Update community settings db type
brianlovin Apr 25, 2018
89e199f
Refactor graphql queries and mutations for slack settings
brianlovin Apr 25, 2018
bdfd504
Add new worker queues for slack invitations
brianlovin Apr 25, 2018
74840e7
Update the way slack auth routing saves records in api
brianlovin Apr 25, 2018
a399dc2
Add slack settings query resolver to community
brianlovin Apr 25, 2018
6f31aef
Add community settings model functions in api
brianlovin Apr 25, 2018
14803e6
Add mutation to api to send slack invitations
brianlovin Apr 25, 2018
cc65095
Add athena queue to handle sending slack invites
brianlovin Apr 25, 2018
6ecb80a
Implements new slack settings resolver for communities
brianlovin Apr 25, 2018
3e8305a
Adds client components to get channel list from slack
brianlovin Apr 25, 2018
4d247d0
Adds graphql queries for slack channel list
brianlovin Apr 25, 2018
7249157
Schema type updates for slack settings
brianlovin Apr 25, 2018
262103b
Simplifies community slack settings resolver
brianlovin Apr 25, 2018
14ae9c1
Adds first channel slack settings resolver
brianlovin Apr 25, 2018
68bd273
Adds api request to slack for channel list
brianlovin Apr 25, 2018
38c434b
Scope updates
brianlovin Apr 25, 2018
7e28120
Add channel slack settings resolver def
brianlovin Apr 25, 2018
2a97b3c
Experimental isAuthed directive resolver to remove redundant auth checks
brianlovin Apr 26, 2018
f587107
Add components to manage individual channel slack bot settings
brianlovin Apr 26, 2018
667cb8e
Update dbchannelsettings type
brianlovin Apr 26, 2018
0f10716
Add graphql queries and mutations to manage single channel slack bot …
brianlovin Apr 26, 2018
6759783
Add consistent permission check utils for channel resolvers
brianlovin Apr 26, 2018
b01d251
Add new channelSettings type and resolvers
brianlovin Apr 26, 2018
f995c49
Add mutation resolver to update channel slack bot settings
brianlovin Apr 26, 2018
3dc3ba7
Delete unused mutation resolver file
brianlovin Apr 26, 2018
ca2a7d4
Clean up remaining api channel resolvers based on new permission util…
brianlovin Apr 26, 2018
90fdfb5
Set slackChannelId to null instead of no length string
brianlovin Apr 26, 2018
423729a
Update slack notification send logic and formatting
brianlovin Apr 26, 2018
0ab4920
Allow cross posting to private channels on slack
brianlovin Apr 26, 2018
ecab032
Adds smart error handling to reset slack connection without losing ch…
brianlovin Apr 27, 2018
00b3baf
Clean up console logs
brianlovin Apr 27, 2018
342f005
More console cleanup
brianlovin Apr 27, 2018
ac96407
Eslint cleanup, remove old slack import component
brianlovin Apr 27, 2018
e930a21
Fix onboarding state for slack component
brianlovin Apr 27, 2018
64e3dde
Implement slack bot controls into channel settings
brianlovin Apr 27, 2018
937fd5f
Fix a bug that causes channel list to crash after creating a new channel
brianlovin Apr 27, 2018
1377ee9
Clean up styling to make channel slack bot management more clear
brianlovin Apr 27, 2018
52a866b
Fix flow issues
brianlovin Apr 27, 2018
d2c416b
Add migration to remove all stored member data
brianlovin Apr 28, 2018
2bd603d
Fix permission check in user can create channel
brianlovin Apr 28, 2018
ac54fcb
More consistent threadCreated event type
brianlovin Apr 28, 2018
6973607
Proposal for moving permissions to user context fields
brianlovin Apr 28, 2018
3d9be60
Refactor context to only call permissions function once
brianlovin Apr 29, 2018
a6cef25
Refactor permissions naming and logic to be more clear
brianlovin Apr 29, 2018
2e31239
Community mods can administer channels
brianlovin Apr 29, 2018
a33baa8
Add slack icons
brianlovin Apr 30, 2018
8d5affc
Merge branch 'alpha' of github.com:withspectrum/spectrum into slack-bot
brianlovin Apr 30, 2018
3273164
Polish up ui, copy, implement icon
brianlovin Apr 30, 2018
0f63210
Fix permissions for moderator managing slack team
brianlovin Apr 30, 2018
8df305f
Fix file name
brianlovin Apr 30, 2018
2276f46
Dont prettier long functions, breaks tests
brianlovin May 1, 2018
cbe12ff
Merge branch 'slack-bot' of github.com:withspectrum/spectrum into sla…
brianlovin May 1, 2018
4b2ef51
Update packages to attempt to fix directives
brianlovin May 1, 2018
d22588b
Attempt to fix direct resolver
brianlovin May 1, 2018
19bc315
Attempt to fix tests
brianlovin May 1, 2018
3f2c24f
Swap isauthed directive for resolver wrapping function
brianlovin May 1, 2018
a1ebd8d
Fix test suite with new permission functions and updates snapshots
brianlovin May 1, 2018
a7f631c
ESLint
brianlovin May 1, 2018
c835d03
Fix e2e tests
brianlovin May 1, 2018
c077ce8
Merge branch 'alpha' of github.com:withspectrum/spectrum into slack-bot
brianlovin May 1, 2018
53b47dc
Remove console
brianlovin May 1, 2018
ffc8408
Remove console
brianlovin May 1, 2018
901ff5c
Move permission logic out of context construction
brianlovin May 2, 2018
fd05e6e
Fix resolvers to use new permission checks
brianlovin May 2, 2018
1f7b86d
Fix tests context construction
brianlovin May 2, 2018
6b3852e
Fix small bug in caching with channel creation
brianlovin May 2, 2018
871560d
Simplify type for context user
brianlovin May 2, 2018
5743014
Add tests for querying channel slack settings
brianlovin May 2, 2018
d8fdedb
Adds tests for querying slacksettings, fixes some client fallback ren…
brianlovin May 2, 2018
cf1632d
botConnection => botLinks
brianlovin May 2, 2018
466b5e9
Remove console
brianlovin May 2, 2018
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 api/directiveResolvers/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// @flow
import isAuthed from './isAuthed';

export default {
isAuthed,
};
8 changes: 8 additions & 0 deletions api/directiveResolvers/isAuthed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// @flow
import UserError from '../utils/UserError';
import type { GraphQLContext } from '../';

export default (next: Function, _: any, __: any, { user }: GraphQLContext) => {
if (user && user.id) return next();
throw new UserError(`You must be signed in to do this`);
};
108 changes: 83 additions & 25 deletions api/models/channelSettings.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @flow
const { db } = require('./db');
import type { DBChannelSettings } from 'shared/types';
import type { DBChannelSettings, DBChannel } from 'shared/types';
import { getChannelById } from './channel';
import shortid from 'shortid';

Expand All @@ -9,19 +9,36 @@ const defaultSettings = {
tokenJoinEnabled: false,
message: null,
},
slackSettings: {
botConnection: {
threadCreated: null,
},
},
};

export const getChannelSettings = (id: string) => {
return db
export const getOrCreateChannelSettings = async (
channelId: string
): Promise<DBChannelSettings> => {
const settings = await db
.table('channelSettings')
.getAll(id, { index: 'channelId' })
.run()
.then(data => {
if (!data || data.length === 0) {
return defaultSettings;
}
return data[0];
});
.getAll(channelId, { index: 'channelId' })
.run();

if (!settings || settings.length === 0) {
return await db
.table('channelSettings')
.insert(
{
...defaultSettings,
channelId,
},
{ returnChanges: true }
)
.run()
.then(results => results.changes[0].new_val);
}

return settings[0];
};

export const getChannelsSettings = (
Expand Down Expand Up @@ -50,20 +67,6 @@ export const getChannelsSettings = (
});
};

export const createChannelSettings = (id: string) => {
return db
.table('channelSettings')
.insert({
channelId: id,
joinSettings: {
token: null,
tokenJoinEnabled: false,
},
})
.run()
.then(async () => await getChannelById(id));
};

export const enableChannelTokenJoin = (id: string) => {
return db
.table('channelSettings')
Expand Down Expand Up @@ -104,3 +107,58 @@ export const resetChannelJoinToken = (id: string) => {
.run()
.then(async () => await getChannelById(id));
};

type UpdateInput = {
channelId: string,
slackChannelId: ?string,
eventType: 'THREAD_CREATED',
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Why not just make this eventType: 'threadCreated'? Then you don't have to do that awkward switch further below to match threadCreated to THREAD_CREATED, that seems pretty unnecessary.

};
export const updateChannelSlackBotConnection = async ({
channelId,
slackChannelId,
eventType,
}: UpdateInput): Promise<DBChannel> => {
const settings: DBChannelSettings = await getOrCreateChannelSettings(
channelId
);

const botConnectionKey = () => {
switch (eventType) {
case 'THREAD_CREATED': {
return 'threadCreated';
}
default: {
return '';
}
}
};

let newSettings;
if (!settings.slackSettings) {
settings.slackSettings = {
botConnection: {
[botConnectionKey()]:
slackChannelId && slackChannelId.length > 0 ? slackChannelId : null,
},
};
newSettings = Object.assign({}, settings);
} else {
newSettings = Object.assign({}, settings, {
slackSettings: {
botConnection: {
[botConnectionKey()]:
slackChannelId && slackChannelId.length > 0 ? slackChannelId : null,
},
},
});
}

return db
.table('channelSettings')
.getAll(channelId, { index: 'channelId' })
.update({
...newSettings,
})
.run()
.then(() => getChannelById(channelId));
};
196 changes: 190 additions & 6 deletions api/models/communitySettings.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,31 @@
// @flow
const { db } = require('./db');
import type { DBCommunitySettings } from 'shared/types';
import type { DBCommunitySettings, DBCommunity } from 'shared/types';
import { getCommunityById } from './community';
import axios from 'axios';
const querystring = require('querystring');

const defaultSettings = {
brandedLogin: {
isEnabled: false,
message: null,
},
slackSettings: {
connectedAt: null,
connectedBy: null,
teamName: null,
teamId: null,
scope: null,
token: null,
invitesSentAt: null,
invitesMemberCount: null,
invitesCustomMessage: null,
},
};

export const getCommunitySettings = (id: string) => {
export const getCommunitySettings = (
id: string
): Promise<DBCommunitySettings> => {
return db
.table('communitySettings')
.getAll(id, { index: 'communityId' })
Expand Down Expand Up @@ -62,7 +77,7 @@ export const getCommunitiesSettings = (
});
};

export const createCommunitySettings = (id: string) => {
export const createCommunitySettings = (id: string): Promise<DBCommunity> => {
return db
.table('communitySettings')
.insert({
Expand All @@ -71,12 +86,25 @@ export const createCommunitySettings = (id: string) => {
isEnabled: false,
message: null,
},
slackSettings: {
connectedAt: null,
connectedBy: null,
invitesSentAt: null,
teamName: null,
teamId: null,
invitesMemberCount: null,
invitesCustomMessage: null,
scope: null,
token: null,
},
})
.run()
.then(async () => await getCommunityById(id));
};

export const enableCommunityBrandedLogin = (id: string) => {
export const enableCommunityBrandedLogin = (
id: string
): Promise<DBCommunity> => {
return db
.table('communitySettings')
.getAll(id, { index: 'communityId' })
Expand All @@ -89,7 +117,9 @@ export const enableCommunityBrandedLogin = (id: string) => {
.then(async () => await getCommunityById(id));
};

export const disableCommunityBrandedLogin = (id: string) => {
export const disableCommunityBrandedLogin = (
id: string
): Promise<DBCommunity> => {
return db
.table('communitySettings')
.getAll(id, { index: 'communityId' })
Expand All @@ -105,7 +135,7 @@ export const disableCommunityBrandedLogin = (id: string) => {
export const updateCommunityBrandedLoginMessage = (
id: string,
message: ?string
) => {
): Promise<DBCommunity> => {
return db
.table('communitySettings')
.getAll(id, { index: 'communityId' })
Expand All @@ -117,3 +147,157 @@ export const updateCommunityBrandedLoginMessage = (
.run()
.then(async () => await getCommunityById(id));
};

type UpdateSlackSettingsInput = {
token: string,
teamName: string,
teamId: string,
connectedBy: string,
scope: string,
};
export const updateSlackSettingsAfterConnection = async (
communityId: string,
input: UpdateSlackSettingsInput
): Promise<DBCommunity> => {
const settings = await db
.table('communitySettings')
.getAll(communityId, { index: 'communityId' })
.run();

if (!settings || settings.length === 0) {
return await createCommunitySettings(communityId)
.then(() => {
return db
.table('communitySettings')
.getAll(communityId, { index: 'communityId' })
.update({
slackSettings: {
...defaultSettings.slackSettings,
...input,
connectedAt: new Date(),
},
})
.run();
})
.then(async () => await getCommunityById(communityId));
}

return await db
.table('communitySettings')
.getAll(communityId, { index: 'communityId' })
.update({
slackSettings: {
...defaultSettings.slackSettings,
...input,
connectedAt: new Date(),
},
})
.run()
.then(async () => await getCommunityById(communityId));
};

export const markInitialSlackInvitationsSent = async (
communityId: string,
inviteCustomMessage: ?string
): Promise<DBCommunity> => {
return db
.table('communitySettings')
.getAll(communityId, { index: 'communityId' })
.update({
slackSettings: {
invitesSentAt: new Date(),
invitesCustomMessage: inviteCustomMessage,
},
})
.run()
.then(async () => await getCommunityById(communityId));
};

const resetSlackSettings = async (communityId: string) => {
return db
.table('communitySettings')
.getAll(communityId, { index: 'communityId' })
.update({
slackSettings: {
...defaultSettings.slackSettings,
},
})
.run()
.then(() => []);
};

export const getSlackPublicChannelList = (
communityId: string,
token: string
) => {
if (!token) return [];
return axios
.get(
`https://slack.com/api/channels.list?token=${token}&exclude_archived=true&exclude_members=true`
)
.then(response => {
return handleSlackChannelResponse(response.data, communityId);
})
.catch(error => {
console.error('\n\nerror', error);
return [];
});
};

export const getSlackPrivateChannelList = (
communityId: string,
token: ?string
) => {
if (!token) return [];
return axios
.get(
`https://slack.com/api/groups.list?token=${token}&exclude_archived=true&exclude_members=true`
)
.then(response => {
return handleSlackChannelResponse(response.data, communityId);
})
.catch(error => {
console.error('\n\nerror', error);
return [];
});
};

const handleSlackChannelResponse = async (
data: Object,
communityId: string
) => {
const mapData = (arr: Array<any>) =>
arr &&
arr.length > 0 &&
arr.map(
o =>
o && {
id: o.id,
name: o.name,
}
);

if (data && data.ok) {
if (data.groups) {
return mapData(data.groups) || [];
}

if (data.channels) {
return mapData(data.channels) || [];
}
}

const errorsToTriggerRest = [
'token_revoked',
'not_authed',
'invalid_auth',
'account_inactive',
'no_permission',
];

if (data.error && errorsToTriggerRest.indexOf(data.error) >= 0) {
return resetSlackSettings(communityId);
}

return [];
};
Loading