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

Commit 6b84315

Browse files
authored
Merge pull request #4823 from withspectrum/watercooler-settings
Add watercooler to community settings
2 parents bc054e6 + a4eb5fd commit 6b84315

File tree

15 files changed

+382
-4
lines changed

15 files changed

+382
-4
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
exports.up = function(r, conn) {
2+
return r
3+
.table('threads')
4+
.indexCreate('communityIdAndWatercooler', [
5+
r.row('communityId'),
6+
r.row('watercooler'),
7+
])
8+
.run(conn);
9+
};
10+
11+
exports.down = function(r, conn) {
12+
return r
13+
.table('threads')
14+
.indexDrop('communityIdAndWatercooler')
15+
.run(conn);
16+
};

api/models/community.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ export type EditCommunityInput = {
212212
coverFile: Object,
213213
coverPhoto: string,
214214
communityId: string,
215+
watercoolerId?: boolean,
215216
},
216217
};
217218

@@ -410,7 +411,7 @@ export const createCommunity = ({ input }: CreateCommunityInput, user: DBUser):
410411

411412
// prettier-ignore
412413
export const editCommunity = async ({ input }: EditCommunityInput, userId: string): Promise<DBCommunity> => {
413-
const { name, slug, description, website, file, coverPhoto, coverFile, communityId } = input
414+
const { name, slug, description, website, watercoolerId, file, coverPhoto, coverFile, communityId } = input
414415

415416
let community = await db.table('communities').get(communityId).run()
416417

@@ -432,6 +433,7 @@ export const editCommunity = async ({ input }: EditCommunityInput, userId: strin
432433
slug,
433434
description,
434435
website,
436+
watercoolerId: watercoolerId || community.watercoolerId,
435437
coverPhoto: coverFile
436438
? await uploadImage(coverFile, 'communities', community.id)
437439
: updatedCoverPhoto,
@@ -474,6 +476,29 @@ export const editCommunity = async ({ input }: EditCommunityInput, userId: strin
474476
})
475477
};
476478

479+
export const setCommunityWatercoolerId = (
480+
communityId: string,
481+
threadId: ?string
482+
) => {
483+
return db
484+
.table('communities')
485+
.get(communityId)
486+
.update(
487+
{
488+
watercoolerId: threadId,
489+
},
490+
{
491+
returnChanges: true,
492+
}
493+
)
494+
.run()
495+
.then(result => {
496+
if (!Array.isArray(result.changes) || result.changes.length === 0)
497+
return getCommunityById(communityId);
498+
return result.changes[0].new_val;
499+
});
500+
};
501+
477502
// prettier-ignore
478503
export const deleteCommunity = (communityId: string, userId: string): Promise<DBCommunity> => {
479504
return db

api/models/thread.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,19 @@ export const getPublicParticipantThreadsByUser = (evalUser: string, options: Pag
418418
});
419419
};
420420

421+
export const getWatercoolerThread = (
422+
communityId: string
423+
): Promise<?DBThread> => {
424+
return db
425+
.table('threads')
426+
.getAll([communityId, true], { index: 'communityIdAndWatercooler' })
427+
.run()
428+
.then(result => {
429+
if (!Array.isArray(result) || result.length === 0) return null;
430+
return result[0];
431+
});
432+
};
433+
421434
export const publishThread = (
422435
// eslint-disable-next-line
423436
{ filesToUpload, ...thread }: Object,
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// @flow
2+
import UserError from '../../utils/UserError';
3+
import {
4+
isAuthedResolver as requireAuth,
5+
canModerateCommunity,
6+
} from '../../utils/permissions';
7+
import { events } from 'shared/analytics';
8+
import { trackQueue } from 'shared/bull/queues';
9+
import { publishThread, getWatercoolerThread } from '../../models/thread';
10+
import { getChannelBySlug } from '../../models/channel';
11+
import { setCommunityWatercoolerId } from '../../models/community';
12+
import type { DBCommunity } from 'shared/types';
13+
import type { GraphQLContext } from '../../';
14+
15+
type Args = {
16+
input: {
17+
id: string,
18+
},
19+
};
20+
21+
export default requireAuth(async (_: any, args: Args, ctx: GraphQLContext) => {
22+
const { id: communityId } = args.input;
23+
const { user, loaders } = ctx;
24+
25+
if (!(await canModerateCommunity(user.id, communityId, loaders))) {
26+
trackQueue.add({
27+
userId: user.id,
28+
event: events.COMMUNITY_WATERCOOLER_DISABLED_FAILED,
29+
context: { communityId },
30+
properties: {
31+
reason: 'no permission',
32+
},
33+
});
34+
35+
return new UserError("You don't have permission to do this.");
36+
}
37+
38+
const community: DBCommunity = await loaders.community.load(communityId);
39+
40+
if (!community.watercoolerId) return community;
41+
42+
return setCommunityWatercoolerId(community.id, null);
43+
});
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// @flow
2+
import UserError from '../../utils/UserError';
3+
import {
4+
isAuthedResolver as requireAuth,
5+
canModerateCommunity,
6+
} from '../../utils/permissions';
7+
import { events } from 'shared/analytics';
8+
import { trackQueue } from 'shared/bull/queues';
9+
import { publishThread, getWatercoolerThread } from '../../models/thread';
10+
import { getChannelBySlug } from '../../models/channel';
11+
import { setCommunityWatercoolerId } from '../../models/community';
12+
import type { DBCommunity } from 'shared/types';
13+
import type { GraphQLContext } from '../../';
14+
15+
type Args = {
16+
input: {
17+
id: string,
18+
},
19+
};
20+
21+
export default requireAuth(async (_: any, args: Args, ctx: GraphQLContext) => {
22+
const { id: communityId } = args.input;
23+
const { user, loaders } = ctx;
24+
25+
if (!(await canModerateCommunity(user.id, communityId, loaders))) {
26+
trackQueue.add({
27+
userId: user.id,
28+
event: events.COMMUNITY_WATERCOOLER_ENABLED_FAILED,
29+
context: { communityId },
30+
properties: {
31+
reason: 'no permission',
32+
},
33+
});
34+
35+
return new UserError("You don't have permission to do this.");
36+
}
37+
38+
const community: DBCommunity = await loaders.community.load(communityId);
39+
40+
if (community.watercoolerId) return community;
41+
42+
const channel = await getChannelBySlug('general', community.slug);
43+
44+
if (!channel) {
45+
throw new Error(
46+
`Community ${community.slug} does not have general channel.`
47+
);
48+
}
49+
50+
const existingWatercooler = await getWatercoolerThread(community.id);
51+
52+
if (existingWatercooler)
53+
return setCommunityWatercoolerId(community.id, existingWatercooler.id);
54+
55+
const watercooler = await publishThread(
56+
{
57+
channelId: channel.id,
58+
communityId: community.id,
59+
content: {
60+
title: `${community.name} watercooler`,
61+
},
62+
type: 'DRAFTJS',
63+
watercooler: true,
64+
},
65+
user.id
66+
);
67+
68+
return setCommunityWatercoolerId(community.id, watercooler.id);
69+
});

api/mutations/community/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import importSlackMembers from './importSlackMembers';
1414
import enableCommunityTokenJoin from './enableCommunityTokenJoin';
1515
import disableCommunityTokenJoin from './disableCommunityTokenJoin';
1616
import resetCommunityJoinToken from './resetCommunityJoinToken';
17+
import enableCommunityWatercooler from './enableCommunityWatercooler';
18+
import disableCommunityWatercooler from './disableCommunityWatercooler';
1719

1820
module.exports = {
1921
Mutation: {
@@ -32,5 +34,7 @@ module.exports = {
3234
enableCommunityTokenJoin,
3335
disableCommunityTokenJoin,
3436
resetCommunityJoinToken,
37+
enableCommunityWatercooler,
38+
disableCommunityWatercooler,
3539
},
3640
};

api/types/Community.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ const Community = /* GraphQL */ `
169169
brandedLogin: BrandedLogin
170170
joinSettings: JoinSettings
171171
slackSettings: CommunitySlackSettings @cost(complexity: 2)
172+
watercoolerId: String
172173
slackImport: SlackImport
173174
@cost(complexity: 2)
174175
@deprecated(reason: "Use slack settings field instead")
@@ -298,6 +299,14 @@ const Community = /* GraphQL */ `
298299
id: ID!
299300
}
300301
302+
input EnableCommunityWatercoolerInput {
303+
id: ID!
304+
}
305+
306+
input DisableCommunityWatercoolerInput {
307+
id: ID!
308+
}
309+
301310
extend type Mutation {
302311
createCommunity(input: CreateCommunityInput!): Community
303312
@rateLimit(max: 3, window: "15m")
@@ -328,6 +337,12 @@ const Community = /* GraphQL */ `
328337
enableCommunityTokenJoin(input: EnableCommunityTokenJoinInput!): Community
329338
disableCommunityTokenJoin(input: DisableCommunityTokenJoinInput!): Community
330339
resetCommunityJoinToken(input: ResetCommunityJoinTokenInput!): Community
340+
enableCommunityWatercooler(
341+
input: EnableCommunityWatercoolerInput!
342+
): Community
343+
disableCommunityWatercooler(
344+
input: DisableCommunityWatercoolerInput!
345+
): Community
331346
}
332347
`;
333348

cypress/integration/community_settings_overview_spec.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ describe('Community settings overview tab', () => {
4242
.clear()
4343
.type(website);
4444
// Submit changes
45-
cy.get('button[type="submit"]').click();
45+
cy.get('[data-cy="community-settings-edit-save-button"]').click();
4646
cy.visit(`/${community.slug}`);
4747
cy.location('pathname').should('eq', `/${community.slug}`);
4848
// Make sure changes were applied
@@ -60,7 +60,7 @@ describe('Community settings overview tab', () => {
6060
cy.get('[data-cy="community-settings-website-input"]')
6161
.clear()
6262
.type(community.website);
63-
cy.get('button[type="submit"]').click();
63+
cy.get('[data-cy="community-settings-edit-save-button"]').click();
6464
cy.visit(`/${community.slug}`);
6565
cy.location('pathname').should('eq', `/${community.slug}`);
6666
cy.contains(community.name);

shared/analytics/event-types/community.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,7 @@ export const COMMUNITY_VIEWED = 'Community Viewed';
4141
export const COMMUNITY_SETTINGS_VIEWED = 'Community Settings Viewed'
4242
export const COMMUNITY_ANALYTICS_VIEWED = 'Community Analytics Viewed'
4343
export const COMMUNITY_ANALYTICS_VIEWED_UPSELL = 'Community Analytics Viewed Upsell'
44-
export const COMMUNITY_ANALYTICS_LEARN_MORE_CLICKED = 'Community Analytics Learn More Clicked'
44+
export const COMMUNITY_ANALYTICS_LEARN_MORE_CLICKED = 'Community Analytics Learn More Clicked'
45+
46+
export const COMMUNITY_WATERCOOLER_ENABLED_FAILED = 'Community Watercooler Enabled Failed';
47+
export const COMMUNITY_WATERCOOLER_DISABLED_FAILED = 'Community Watercooler Disabled Failed'

shared/graphql/fragments/community/communityInfo.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export type CommunityInfoType = {
1111
profilePhoto: string,
1212
coverPhoto: string,
1313
pinnedThreadId: ?string,
14+
watercoolerId: ?string,
1415
isPrivate: boolean,
1516
communityPermissions: {
1617
isMember: boolean,
@@ -37,6 +38,7 @@ export default gql`
3738
profilePhoto
3839
coverPhoto
3940
pinnedThreadId
41+
watercoolerId
4042
isPrivate
4143
communityPermissions {
4244
isMember

0 commit comments

Comments
 (0)