[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
This commit is contained in:
Ben Halpern 2020-04-16 17:02:06 -04:00 committed by GitHub
parent 2343785927
commit 03c28a5671
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 109 additions and 30 deletions

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -20,7 +20,7 @@
<div class="settings-form-userlist">
<h3>Members</h3>
<% @channel.active_memberships.includes(:user).each do |active_membership| %>
<div class="settings-form-userlistrow" >
<div class="settings-form-userlistrow">
<a href="<%= active_membership.user.path %>"><img src="<%= ProfileImage.new(active_membership.user).get(width: 90) %>" /><%= active_membership.user.name %></a>
<% 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 @@
<div class="settings-form-userlist">
<h3>Pending Invitations</h3>
<% @channel.pending_memberships.includes(:user).each do |pending_membership| %>
<div class="settings-form-userlistrow" >
<div class="settings-form-userlistrow">
<a href="<%= pending_membership.user.path %>"><img src="<%= ProfileImage.new(pending_membership.user).get(width: 90) %>" /><%= pending_membership.user.name %></a>
<%= form_tag "/chat_channel_memberships/remove_membership", class: "inline-button-form" do %>
<%= hidden_field_tag(:status, "pending") %>
@ -47,32 +47,33 @@
</div>
<% end %>
</div>
<%= form_for ChatChannelMembership.new do |f| %>
<div class="field">
<%= f.hidden_field :chat_channel_id, value: @channel.id %>
<%= f.label "Usernames to Invite" %>
<%= f.text_field :invitation_usernames, placeholder: "Comma separated" %>
</div>
<div class="field">
<%= f.submit "SUBMIT", class: "cta" %>
</div>
<% if @channel.private_org_channel? %>
<p><em>Invite new members by <a href="/settings/organization">inviting them to this organization.</a></em></p>
<% else %>
<%= form_for ChatChannelMembership.new do |f| %>
<div class="field">
<%= f.hidden_field :chat_channel_id, value: @channel.id %>
<%= f.label "Usernames to Invite" %>
<%= f.text_field :invitation_usernames, placeholder: "Comma separated" %>
</div>
<div class="field">
<%= f.submit "SUBMIT", class: "cta" %>
</div>
<% end %>
<hr class="settings-hr" />
<h1>Channel Settings</h1>
<%= form_for(@channel) do |f| %>
<div class="field">
<%= f.label :description %>
<%= f.text_area :description %>
</div>
<div class="field">
<%= f.submit "SUBMIT", class: "cta" %>
</div>
<% end %>
<% end %>
<% end %>
<% if @membership.role == "mod" %>
<hr class="settings-hr"/>
<h1>Channel Settings</h1>
<h3>Notifications</h3>
<%= form_for(@channel) do |f| %>
<div class="field">
<%= f.label :description %>
<%= f.text_area :description %>
</div>
<div class="field">
<%= f.submit "SUBMIT", class: "cta" %>
</div>
<% end %>
<% end %>
<hr class="settings-hr"/>
<hr class="settings-hr" />
<h1>Personal Settings</h1>
<h3>Notifications</h3>
<%= form_for(@membership) do |f| %>
@ -84,12 +85,12 @@
<%= f.submit "SUBMIT", class: "cta" %>
</div>
<% end %>
<hr class="settings-hr"/>
<hr class="settings-hr" />
<% if @membership.role == "mod" %>
<p>Questions about Connect Channel moderation? Contact <a href="mailto:<%= SiteConfig.default_site_email %>"><%= SiteConfig.default_site_email %></a></p>
<% else %>
<h3>Danger Zone</h3>
<%= 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| %>
<div class="field">
<%= f.submit "LEAVE CHANNEL", class: "cta cta-danger" %>
</div>

View file

@ -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

View file

@ -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

View file

@ -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