From 03c28a567108b826fbae3ddcac2e91c3b18c93d5 Mon Sep 17 00:00:00 2001 From: Ben Halpern Date: Thu, 16 Apr 2020 17:02:06 -0400 Subject: [PATCH] [deploy] Create private group chat channel for orgs (#7270) * WIP chat channel for orgs * Finalize * Enable upsert functionality for role switch * Private group chat * Rearrange permissions code * fix typo --- app/models/chat_channel.rb | 4 ++ app/models/organization_membership.rb | 29 +++++++++- app/policies/chat_channel_policy.rb | 3 +- .../chat_channel_memberships/edit.html.erb | 57 ++++++++++--------- spec/models/chat_channel_spec.rb | 12 ++++ spec/models/organization_membership_spec.rb | 21 +++++++ .../requests/chat_channel_memberships_spec.rb | 13 +++++ 7 files changed, 109 insertions(+), 30 deletions(-) diff --git a/app/models/chat_channel.rb b/app/models/chat_channel.rb index 99ac57413..0727e2f4e 100644 --- a/app/models/chat_channel.rb +++ b/app/models/chat_channel.rb @@ -35,6 +35,10 @@ class ChatChannel < ApplicationRecord channel_type != "direct" end + def private_org_channel? + channel_name.to_s.ends_with?(" private group chat") #e.g. @devteam private group chat + end + def clear_channel messages.destroy_all Pusher.trigger(pusher_channels, "channel-cleared", { chat_channel_id: id }.to_json) diff --git a/app/models/organization_membership.rb b/app/models/organization_membership.rb index ff1ef4b4e..98ea1aff3 100644 --- a/app/models/organization_membership.rb +++ b/app/models/organization_membership.rb @@ -8,10 +8,37 @@ class OrganizationMembership < ApplicationRecord validates :user_id, uniqueness: { scope: :organization_id } validates :type_of_user, inclusion: { in: USER_TYPES } - after_create :update_user_organization_info_updated_at + after_save :upsert_chat_channel_membership + after_create :update_user_organization_info_updated_at after_destroy :update_user_organization_info_updated_at def update_user_organization_info_updated_at user.touch(:organization_info_updated_at) end + + private + + def upsert_chat_channel_membership + return if type_of_user == "guest" + + role = type_of_user == "admin" ? "mod" : "member" + name = "@#{organization.slug} private group chat" + channel = ChatChannel.find_by(channel_name: name) + if channel + add_chat_channel_membership(user, channel, role) + else + ChatChannel.create_with_users( + users: [user], + channel_type: "invite_only", + contrived_name: name, + membership_role: role, + ) + end + end + + def add_chat_channel_membership(user, channel, role) + membership = ChatChannelMembership.find_or_initialize_by(user_id: user.id, chat_channel_id: channel.id) + membership.role = role + membership.save + end end diff --git a/app/policies/chat_channel_policy.rb b/app/policies/chat_channel_policy.rb index f2b9c0e30..998bcefef 100644 --- a/app/policies/chat_channel_policy.rb +++ b/app/policies/chat_channel_policy.rb @@ -39,7 +39,8 @@ class ChatChannelPolicy < ApplicationPolicy def user_can_edit_channel record.present? && - (user.has_role?(:super_admin) || record.channel_mod_ids.include?(user.id)) + (user.has_role?(:super_admin) || record.channel_mod_ids.include?(user.id)) && + !record.private_org_channel? end def user_part_of_channel_or_open diff --git a/app/views/chat_channel_memberships/edit.html.erb b/app/views/chat_channel_memberships/edit.html.erb index f74ce32dc..85a13b7f0 100644 --- a/app/views/chat_channel_memberships/edit.html.erb +++ b/app/views/chat_channel_memberships/edit.html.erb @@ -20,7 +20,7 @@

Members

<% @channel.active_memberships.includes(:user).each do |active_membership| %> -
+
<%= active_membership.user.name %> <% if @membership.role == "mod" && active_membership.role != "mod" %> <%= form_tag "/chat_channel_memberships/remove_membership", class: "inline-button-form-hidden" do %> @@ -36,7 +36,7 @@

Pending Invitations

<% @channel.pending_memberships.includes(:user).each do |pending_membership| %> -
+
<%= pending_membership.user.name %> <%= form_tag "/chat_channel_memberships/remove_membership", class: "inline-button-form" do %> <%= hidden_field_tag(:status, "pending") %> @@ -47,32 +47,33 @@
<% end %>
- <%= form_for ChatChannelMembership.new do |f| %> -
- <%= f.hidden_field :chat_channel_id, value: @channel.id %> - <%= f.label "Usernames to Invite" %> - <%= f.text_field :invitation_usernames, placeholder: "Comma separated" %> -
-
- <%= f.submit "SUBMIT", class: "cta" %> -
+ <% if @channel.private_org_channel? %> +

Invite new members by inviting them to this organization.

+ <% else %> + <%= form_for ChatChannelMembership.new do |f| %> +
+ <%= f.hidden_field :chat_channel_id, value: @channel.id %> + <%= f.label "Usernames to Invite" %> + <%= f.text_field :invitation_usernames, placeholder: "Comma separated" %> +
+
+ <%= f.submit "SUBMIT", class: "cta" %> +
+ <% end %> +
+

Channel Settings

+ <%= form_for(@channel) do |f| %> +
+ <%= f.label :description %> + <%= f.text_area :description %> +
+
+ <%= f.submit "SUBMIT", class: "cta" %> +
+ <% end %> <% end %> <% end %> - <% if @membership.role == "mod" %> -
-

Channel Settings

-

Notifications

- <%= form_for(@channel) do |f| %> -
- <%= f.label :description %> - <%= f.text_area :description %> -
-
- <%= f.submit "SUBMIT", class: "cta" %> -
- <% end %> - <% end %> -
+

Personal Settings

Notifications

<%= form_for(@membership) do |f| %> @@ -84,12 +85,12 @@ <%= f.submit "SUBMIT", class: "cta" %>
<% end %> -
+
<% if @membership.role == "mod" %>

Questions about Connect Channel moderation? Contact <%= SiteConfig.default_site_email %>

<% else %>

Danger Zone

- <%= form_for(@membership, html: { method: :delete, onsubmit: "return confirm('Are you absolutely sure you want to leave this channel? This action is permanent.');" } ) do |f| %> + <%= form_for(@membership, html: { method: :delete, onsubmit: "return confirm('Are you absolutely sure you want to leave this channel? This action is permanent.');" }) do |f| %>
<%= f.submit "LEAVE CHANNEL", class: "cta cta-danger" %>
diff --git a/spec/models/chat_channel_spec.rb b/spec/models/chat_channel_spec.rb index 8e97e7925..5a779c341 100644 --- a/spec/models/chat_channel_spec.rb +++ b/spec/models/chat_channel_spec.rb @@ -56,4 +56,16 @@ RSpec.describe ChatChannel, type: :model do expect(chat_channel.chat_channel_memberships.exists?(user_id: users.first.id)).to be(false) end end + + describe "#private_org_channel?" do + it "detects private org channel if name matches" do + chat_channel.channel_name = "@org private group chat" + expect(chat_channel.private_org_channel?).to be(true) + end + + it "detects not private org channel if name does not match" do + chat_channel.channel_name = "@org magoo" + expect(chat_channel.private_org_channel?).to be(false) + end + end end diff --git a/spec/models/organization_membership_spec.rb b/spec/models/organization_membership_spec.rb index d935e7cfd..212adceee 100644 --- a/spec/models/organization_membership_spec.rb +++ b/spec/models/organization_membership_spec.rb @@ -4,10 +4,31 @@ RSpec.describe OrganizationMembership, type: :model do describe "validations" do subject { build(:organization_membership) } + let(:organization) { create(:organization) } + it { is_expected.to validate_presence_of(:user_id) } it { is_expected.to validate_presence_of(:organization_id) } it { is_expected.to validate_presence_of(:type_of_user) } it { is_expected.to validate_uniqueness_of(:user_id).scoped_to(:organization_id) } it { is_expected.to validate_inclusion_of(:type_of_user).in_array(OrganizationMembership::USER_TYPES) } + + it "creates member chat channel after save" do + create(:organization_membership, type_of_user: "member", organization: organization) + expect(ChatChannelMembership.last.role).to eq("member") + expect(ChatChannelMembership.last.chat_channel.channel_name).to eq("@#{organization.slug} private group chat") + end + + it "adds user to existing org chat channel after save" do + chat_channel = create(:chat_channel, channel_name: "@#{organization.slug} private group chat") + organization_membership = create(:organization_membership, type_of_user: "member", organization: organization) + expect(chat_channel.active_users).to include(organization_membership.user) + end + + it "updates chat channel membership if org membership is updated" do + organization_membership = create(:organization_membership, type_of_user: "member", organization: organization) + expect(ChatChannelMembership.last.role).to eq("member") + organization_membership.update(type_of_user: "admin") + expect(ChatChannelMembership.last.role).to eq("mod") + end end end diff --git a/spec/requests/chat_channel_memberships_spec.rb b/spec/requests/chat_channel_memberships_spec.rb index d40272504..4d65955b1 100644 --- a/spec/requests/chat_channel_memberships_spec.rb +++ b/spec/requests/chat_channel_memberships_spec.rb @@ -157,6 +157,19 @@ RSpec.describe "ChatChannelMemberships", type: :request do expect(ChatChannelMembership.all.size).to eq(chat_channel_members_count + 1) expect(ChatChannelMembership.last.status).to eq("pending") end + + it "disallows invitation creation when org private group" do + chat_channel.update_column(:channel_name, "@org private group chat") + chat_channel.chat_channel_memberships.where(user_id: user.id).update(role: "mod") + expect do + post "/chat_channel_memberships", params: { + chat_channel_membership: { + invitation_usernames: second_user.username.to_s, + chat_channel_id: chat_channel.id + } + } + end.to raise_error(Pundit::NotAuthorizedError) + end end context "when user is not authorized to add channel membership" do