[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:
parent
4d0959783f
commit
f088a672de
10 changed files with 206 additions and 58 deletions
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
41
app/helpers/messages_helper.rb
Normal file
41
app/helpers/messages_helper.rb
Normal 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
|
||||
90
app/javascript/chat/actionMessage.jsx
Normal file
90
app/javascript/chat/actionMessage.jsx
Normal 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;
|
||||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
class AddColumnChatActionToMessages < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
add_column :messages, :chat_action, :string
|
||||
end
|
||||
end
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue