[deploy] 🚀Feature: chat action messages if a member added, left or removed from the group (#7105)

* Feature 🚀 : Ability to delete messages in chat channels

- Sending message ID to frontend
- Deleting Message
- Use pusher to delete message realtime

* Minor Bug 🐞: Show message action only for current user

- User can delete or edit their own messages

* Test cases added

* Bug 🐞: Update message id for receiver

Message id was not sent to receiver by pusher

* Refactoring🛠: Message controller refactoring

* Test Cases📝 : Specs for Delete message added

* Feature 🚀 : Ability to edit messages

* Test Cases📝 : Specs for Edit message added

* added chat action field and backend logic

* 🚀Action Messages: Someone Joins | Leaves | Removed

* chat action messages made realtion

* remove redundancy, message helper added

* 🛠  Style of action message changed

* 🔩 Specs added

Co-authored-by: Parasgr-code <paras.gaur@skynox.tech>
This commit is contained in:
Sarthak Sharma 2020-04-09 03:44:34 +05:30 committed by GitHub
parent 4d0959783f
commit f088a672de
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 206 additions and 58 deletions

View file

@ -1287,3 +1287,22 @@
.active__message__list {
background: var(--theme-container-accent-background, #f5f6f7);
}
.chatmessage__action {
&:hover {
background: none;
}
.chatmessage__bodytext {
margin: 0;
font-size: 12px;
font-style: italic;
color: var(--card-color-tertiary);
a {
text-decoration: none;
color: var(--card-color-tertiary);
&:hover {
text-decoration: underline;
}
}
}
}

View file

@ -1,5 +1,6 @@
class ChatChannelMembershipsController < ApplicationController
after_action :verify_authorized
include MessagesHelper
def index
skip_authorization
@ -46,6 +47,7 @@ class ChatChannelMembershipsController < ApplicationController
@chat_channel_membership.destroy
flash[:settings_notice] = "Invitation removed."
else
send_chat_action_message("@#{current_user.username} removed @#{@chat_channel_membership.user.username} from #{@chat_channel_membership.channel_name}", current_user, @chat_channel_membership.chat_channel_id, "removed_from_channel")
@chat_channel_membership.update(status: "removed_from_channel")
flash[:settings_notice] = "Removed #{@chat_channel_membership.user.name}"
end
@ -69,6 +71,7 @@ class ChatChannelMembershipsController < ApplicationController
@chat_channel_membership = ChatChannelMembership.find(params[:id])
authorize @chat_channel_membership
channel_name = @chat_channel_membership.chat_channel.channel_name
send_chat_action_message("@#{current_user.username} left #{@chat_channel_membership.channel_name}", current_user, @chat_channel_membership.chat_channel_id, "left_channel")
@chat_channel_membership.update(status: "left_channel")
@chat_channels_memberships = []
flash[:settings_notice] = "You have left the channel #{channel_name}. It may take a moment to be removed from your list."
@ -85,6 +88,7 @@ class ChatChannelMembershipsController < ApplicationController
if permitted_params[:user_action] == "accept"
@chat_channel_membership.update(status: "active")
channel_name = @chat_channel_membership.chat_channel.channel_name
send_chat_action_message("@#{current_user.username} joined #{@chat_channel_membership.channel_name}", current_user, @chat_channel_membership.chat_channel_id, "joined")
flash[:settings_notice] = "Invitation to #{channel_name} accepted. It may take a moment to show up in your list."
else
@chat_channel_membership.update(status: "rejected")
@ -92,4 +96,10 @@ class ChatChannelMembershipsController < ApplicationController
end
redirect_to chat_channel_memberships_path
end
def send_chat_action_message(message, user, channel_id, action)
temp_message_id = (0...20).map { ("a".."z").to_a[rand(8)] }.join
message = Message.create("message_markdown" => message, "user_id" => user.id, "chat_channel_id" => channel_id, "chat_action" => action)
pusher_message_created(false, message, temp_message_id)
end
end

View file

@ -1,6 +1,7 @@
class MessagesController < ApplicationController
before_action :set_message, only: %i[destroy update]
before_action :authenticate_user!, only: %i[create]
include MessagesHelper
def create
@message = Message.new(message_params)
@ -9,9 +10,9 @@ class MessagesController < ApplicationController
authorize @message
# sending temp message only to sender
pusher_message_created(true)
pusher_message_created(true, @message, @temp_message_id)
if @message.save
pusher_message_created(false)
pusher_message_created(false, @message, @temp_message_id)
notify_users(@message.chat_channel.channel_users_ids, "all")
notify_users(@mentioned_users_id, "mention")
render json: { status: "success", message: { temp_id: @temp_message_id, id: @message.id } }, status: :created
@ -79,30 +80,6 @@ class MessagesController < ApplicationController
private
def create_pusher_payload(new_message, temp_id)
payload = {
temp_id: temp_id,
id: new_message.id,
user_id: new_message.user.id,
chat_channel_id: new_message.chat_channel.id,
chat_channel_adjusted_slug: new_message.chat_channel.adjusted_slug(current_user, "sender"),
channel_type: new_message.chat_channel.channel_type,
username: new_message.user.username,
profile_image_url: ProfileImage.new(new_message.user).get(width: 90),
message: new_message.message_html,
markdown: new_message.message_markdown,
edited_at: new_message.edited_at,
timestamp: Time.current,
color: new_message.preferred_user_color,
reception_method: "pushed"
}
if new_message.chat_channel.group?
payload[:chat_channel_adjusted_slug] = new_message.chat_channel.adjusted_slug
end
payload.to_json
end
def message_params
@mentioned_users_id = params[:message][:mentioned_users_id]
params.require(:message).permit(:message_markdown, :user_id, :chat_channel_id)
@ -127,21 +104,6 @@ class MessagesController < ApplicationController
end
end
def pusher_message_created(is_single)
return unless @message.valid?
begin
message_json = create_pusher_payload(@message, @temp_message_id)
if is_single
Pusher.trigger("private-message-notifications-#{@message.user_id}", "message-created", message_json)
else
Pusher.trigger(@message.chat_channel.pusher_channels, "message-created", message_json)
end
rescue Pusher::Error => e
logger.info "PUSHER ERROR: #{e.message}"
end
end
def notify_users(user_ids, type)
return unless user_ids

View file

@ -0,0 +1,41 @@
module MessagesHelper
def create_pusher_payload(new_message, temp_id)
payload = {
temp_id: temp_id,
id: new_message.id,
user_id: new_message.user.id,
chat_channel_id: new_message.chat_channel.id,
chat_channel_adjusted_slug: new_message.chat_channel.adjusted_slug(current_user, "sender"),
channel_type: new_message.chat_channel.channel_type,
username: new_message.user.username,
profile_image_url: ProfileImage.new(new_message.user).get(width: 90),
message: new_message.message_html,
markdown: new_message.message_markdown,
edited_at: new_message.edited_at,
timestamp: Time.current,
color: new_message.preferred_user_color,
reception_method: "pushed",
action: new_message.chat_action
}
if new_message.chat_channel.group?
payload[:chat_channel_adjusted_slug] = new_message.chat_channel.adjusted_slug
end
payload.to_json
end
def pusher_message_created(is_single, message, temp_message_id)
return unless message.valid?
begin
message_json = create_pusher_payload(message, temp_message_id)
if is_single
Pusher.trigger("private-message-notifications-#{message.user_id}", "message-created", message_json)
else
Pusher.trigger(message.chat_channel.pusher_channels, "message-created", message_json)
end
rescue Pusher::Error => e
logger.info "PUSHER ERROR: #{e.message}"
end
end
end

View file

@ -0,0 +1,90 @@
import { h } from 'preact';
import PropTypes from 'prop-types';
import { adjustTimestamp } from './util';
const ActionMessage = ({
user,
message,
color,
timestamp,
profileImageUrl,
onContentTrigger,
}) => {
const spanStyle = { color };
const messageArea = (
<span
className="chatmessagebody__message"
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html: message }}
/>
);
return (
<div className="chatmessage chatmessage__action ">
<div className="chatmessage__profilepic">
<a
href={`/${user}`}
target="_blank"
rel="noopener noreferrer"
data-content="sidecar-user"
onClick={onContentTrigger}
>
<img
role="presentation"
className="chatmessagebody__profileimage"
src={profileImageUrl}
alt={`${user} profile`}
data-content="sidecar-user"
onClick={onContentTrigger}
/>
</a>
</div>
<div
role="presentation"
className="chatmessage__body"
onClick={onContentTrigger}
>
<div className="message__info__actions">
<div className="message__info">
<span
className="chatmessagebody__username not-dark-theme-text-compatible"
style={spanStyle}
>
<a
className="chatmessagebody__username--link"
href={`/${user}`}
target="_blank"
rel="noopener noreferrer"
data-content="sidecar-user"
onClick={onContentTrigger}
>
{user}
</a>
</span>
<span className="chatmessage__timestamp">
{`${adjustTimestamp(timestamp)}`}
</span>
</div>
</div>
<div className="chatmessage__bodytext">{messageArea}</div>
</div>
</div>
);
};
ActionMessage.propTypes = {
user: PropTypes.string.isRequired,
color: PropTypes.string.isRequired,
message: PropTypes.string.isRequired,
timestamp: PropTypes.string,
profileImageUrl: PropTypes.string,
onContentTrigger: PropTypes.func.isRequired,
};
ActionMessage.defaultProps = {
profileImageUrl: '',
timestamp: null,
};
export default ActionMessage;

View file

@ -19,6 +19,7 @@ import Alert from './alert';
import Channels from './channels';
import Compose from './compose';
import Message from './message';
import ActionMessage from './actionMessage';
import Content from './content';
import Video from './video';
@ -957,23 +958,34 @@ export default class Chat extends Component {
);
}
}
return messages[activeChannelId].map((message) => (
<Message
currentUserId={currentUserId}
id={message.id}
user={message.username}
userID={message.user_id}
profileImageUrl={message.profile_image_url}
message={message.message}
timestamp={showTimestamp ? message.timestamp : null}
editedAt={message.edited_at}
color={message.color}
type={message.type}
onContentTrigger={this.triggerActiveContent}
onDeleteMessageTrigger={this.triggerDeleteMessage}
onEditMessageTrigger={this.triggerEditMessage}
/>
));
return messages[activeChannelId].map((message) =>
message.action ? (
<ActionMessage
user={message.username}
profileImageUrl={message.profile_image_url}
message={message.message}
timestamp={showTimestamp ? message.timestamp : null}
color={message.color}
onContentTrigger={this.triggerActiveContent}
/>
) : (
<Message
currentUserId={currentUserId}
id={message.id}
user={message.username}
userID={message.user_id}
profileImageUrl={message.profile_image_url}
message={message.message}
timestamp={showTimestamp ? message.timestamp : null}
editedAt={message.edited_at}
color={message.color}
onContentTrigger={this.triggerActiveContent}
onDeleteMessageTrigger={this.triggerDeleteMessage}
onEditMessageTrigger={this.triggerEditMessage}
/>
),
);
};
triggerChannelFilter = (e) => {

View file

@ -8,6 +8,7 @@ json.messages @chat_messages.reverse do |message|
json.edited_at message.edited_at
json.timestamp message.created_at
json.color message.preferred_user_color
json.action message.chat_action
end
json.key_format! camelize: :lower

View file

@ -0,0 +1,5 @@
class AddColumnChatActionToMessages < ActiveRecord::Migration[5.2]
def change
add_column :messages, :chat_action, :string
end
end

View file

@ -12,6 +12,7 @@
ActiveRecord::Schema.define(version: 2020_04_07_091449) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -307,6 +308,7 @@ ActiveRecord::Schema.define(version: 2020_04_07_091449) do
t.string "slug"
t.string "status", default: "active"
t.datetime "updated_at", null: false
t.index ["slug"], name: "index_chat_channels_on_slug", unique: true
end
create_table "classified_listings", force: :cascade do |t|
@ -578,6 +580,7 @@ ActiveRecord::Schema.define(version: 2020_04_07_091449) do
end
create_table "messages", force: :cascade do |t|
t.string "chat_action"
t.bigint "chat_channel_id", null: false
t.datetime "created_at", null: false
t.datetime "edited_at"

View file

@ -186,6 +186,7 @@ RSpec.describe "ChatChannelMemberships", type: :request do
context "when second user accept invitation" do
it "sets chat channl membership status to rejected" do
allow(Pusher).to receive(:trigger).and_return(true)
membership = ChatChannelMembership.last
sign_in second_user
put "/chat_channel_memberships/#{membership.id}", params: {
@ -200,6 +201,7 @@ RSpec.describe "ChatChannelMemberships", type: :request do
context "when second user rejects invitation" do
it "sets chat channl membership status to rejected" do
allow(Pusher).to receive(:trigger).and_return(true)
membership = ChatChannelMembership.last
sign_in second_user
put "/chat_channel_memberships/#{membership.id}", params: {
@ -238,6 +240,7 @@ RSpec.describe "ChatChannelMemberships", type: :request do
describe "DELETE /chat_channel_memberships/:id" do
context "when user is logged in" do
it "leaves chat channel" do
allow(Pusher).to receive(:trigger).and_return(true)
chat_channel.add_users([second_user])
membership = ChatChannelMembership.last
sign_in second_user
@ -265,6 +268,7 @@ RSpec.describe "ChatChannelMemberships", type: :request do
context "when user is super admin" do
it "removes member from channel" do
allow(Pusher).to receive(:trigger).and_return(true)
user.add_role(:super_admin)
membership = chat_channel.chat_channel_memberships.where(user_id: user.id).last
removed_channel_membership = ChatChannelMembership.last
@ -279,6 +283,7 @@ RSpec.describe "ChatChannelMemberships", type: :request do
context "when user is moderator of channel" do
it "removes member from channel" do
allow(Pusher).to receive(:trigger).and_return(true)
membership = chat_channel.chat_channel_memberships.where(user_id: user.id).last
membership.update(role: "mod")
removed_channel_membership = ChatChannelMembership.last