From e7f62ab52bae775d46be7223ee2620f75d58ba8e Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 1 Nov 2024 09:12:19 +1100 Subject: [PATCH] FEATURE: add custom fields to chat (channel/message/thread) (#29504) This allows various extensions to store extra information the 3 most popular chat entities Useful for certain plugins and migrations --- plugins/chat/app/models/chat/channel.rb | 1 + .../app/models/chat/channel_custom_field.rb | 23 ++++++++++++++ plugins/chat/app/models/chat/message.rb | 1 + .../app/models/chat/message_custom_field.rb | 23 ++++++++++++++ plugins/chat/app/models/chat/thread.rb | 1 + .../app/models/chat/thread_custom_field.rb | 23 ++++++++++++++ ...0241031050638_add_custom_fields_to_chat.rb | 30 ++++++++++++++++++ plugins/chat/spec/models/chat/channel_spec.rb | 8 +++++ plugins/chat/spec/models/chat/message_spec.rb | 10 +++++- plugins/chat/spec/models/chat/thread_spec.rb | 31 +++++++++++++------ 10 files changed, 140 insertions(+), 11 deletions(-) create mode 100644 plugins/chat/app/models/chat/channel_custom_field.rb create mode 100644 plugins/chat/app/models/chat/message_custom_field.rb create mode 100644 plugins/chat/app/models/chat/thread_custom_field.rb create mode 100644 plugins/chat/db/migrate/20241031050638_add_custom_fields_to_chat.rb diff --git a/plugins/chat/app/models/chat/channel.rb b/plugins/chat/app/models/chat/channel.rb index dc7e9cd631d..808fac24df2 100644 --- a/plugins/chat/app/models/chat/channel.rb +++ b/plugins/chat/app/models/chat/channel.rb @@ -4,6 +4,7 @@ module Chat class Channel < ActiveRecord::Base include Trashable include TypeMappable + include HasCustomFields # TODO (martin) Remove once we are using last_message instead, # should be around August 2023. diff --git a/plugins/chat/app/models/chat/channel_custom_field.rb b/plugins/chat/app/models/chat/channel_custom_field.rb new file mode 100644 index 00000000000..13ec8a3b5f2 --- /dev/null +++ b/plugins/chat/app/models/chat/channel_custom_field.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Chat + class ChannelCustomField < ActiveRecord::Base + belongs_to :channel + end +end + +# == Schema Information +# +# Table name: chat_channel_custom_fields +# +# id :bigint not null, primary key +# channel_id :bigint not null +# name :string(256) not null +# value :string(1000000) +# created_at :datetime not null +# updated_at :datetime not null +# +# Indexes +# +# index_chat_channel_custom_fields_on_channel_id_and_name (channel_id,name) UNIQUE +# diff --git a/plugins/chat/app/models/chat/message.rb b/plugins/chat/app/models/chat/message.rb index 1e03efc2db4..7eac16609cf 100644 --- a/plugins/chat/app/models/chat/message.rb +++ b/plugins/chat/app/models/chat/message.rb @@ -4,6 +4,7 @@ module Chat class Message < ActiveRecord::Base include Trashable include TypeMappable + include HasCustomFields self.table_name = "chat_messages" diff --git a/plugins/chat/app/models/chat/message_custom_field.rb b/plugins/chat/app/models/chat/message_custom_field.rb new file mode 100644 index 00000000000..1ab63efa59d --- /dev/null +++ b/plugins/chat/app/models/chat/message_custom_field.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Chat + class MessageCustomField < ActiveRecord::Base + belongs_to :message + end +end + +# == Schema Information +# +# Table name: chat_message_custom_fields +# +# id :bigint not null, primary key +# message_id :bigint not null +# name :string(256) not null +# value :string(1000000) +# created_at :datetime not null +# updated_at :datetime not null +# +# Indexes +# +# index_chat_message_custom_fields_on_message_id_and_name (message_id,name) UNIQUE +# diff --git a/plugins/chat/app/models/chat/thread.rb b/plugins/chat/app/models/chat/thread.rb index 1a104c4f36a..ad301d0601a 100644 --- a/plugins/chat/app/models/chat/thread.rb +++ b/plugins/chat/app/models/chat/thread.rb @@ -5,6 +5,7 @@ module Chat MAX_TITLE_LENGTH = 100 include Chat::ThreadCache + include HasCustomFields self.table_name = "chat_threads" diff --git a/plugins/chat/app/models/chat/thread_custom_field.rb b/plugins/chat/app/models/chat/thread_custom_field.rb new file mode 100644 index 00000000000..a1c489dbd10 --- /dev/null +++ b/plugins/chat/app/models/chat/thread_custom_field.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Chat + class ThreadCustomField < ActiveRecord::Base + belongs_to :thread + end +end + +# == Schema Information +# +# Table name: chat_thread_custom_fields +# +# id :bigint not null, primary key +# thread_id :bigint not null +# name :string(256) not null +# value :string(1000000) +# created_at :datetime not null +# updated_at :datetime not null +# +# Indexes +# +# index_chat_thread_custom_fields_on_thread_id_and_name (thread_id,name) UNIQUE +# diff --git a/plugins/chat/db/migrate/20241031050638_add_custom_fields_to_chat.rb b/plugins/chat/db/migrate/20241031050638_add_custom_fields_to_chat.rb new file mode 100644 index 00000000000..7253b1d0720 --- /dev/null +++ b/plugins/chat/db/migrate/20241031050638_add_custom_fields_to_chat.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +class AddCustomFieldsToChat < ActiveRecord::Migration[7.1] + def change + create_table :chat_thread_custom_fields do |t| + t.bigint :thread_id, null: false + t.string :name, limit: 256, null: false + t.string :value, limit: 1_000_000 + t.timestamps null: false + end + + create_table :chat_message_custom_fields do |t| + t.bigint :message_id, null: false + t.string :name, limit: 256, null: false + t.string :value, limit: 1_000_000 + t.timestamps null: false + end + + create_table :chat_channel_custom_fields do |t| + t.bigint :channel_id, null: false + t.string :name, limit: 256, null: false + t.string :value, limit: 1_000_000 + t.timestamps null: false + end + + add_index :chat_thread_custom_fields, %i[thread_id name], unique: true + add_index :chat_message_custom_fields, %i[message_id name], unique: true + add_index :chat_channel_custom_fields, %i[channel_id name], unique: true + end +end diff --git a/plugins/chat/spec/models/chat/channel_spec.rb b/plugins/chat/spec/models/chat/channel_spec.rb index fb95c1e588d..0b56a9cb793 100644 --- a/plugins/chat/spec/models/chat/channel_spec.rb +++ b/plugins/chat/spec/models/chat/channel_spec.rb @@ -11,6 +11,14 @@ RSpec.describe Chat::Channel do it { is_expected.to validate_length_of(:chatable_type).is_at_most(100) } it { is_expected.to validate_length_of(:type).is_at_most(100) } + it "supports custom fields" do + channel.custom_fields["test"] = "test" + channel.save_custom_fields + loaded_channel = Chat::Channel.find(channel.id) + expect(loaded_channel.custom_fields["test"]).to eq("test") + expect(Chat::ChannelCustomField.first.channel.id).to eq(channel.id) + end + describe ".last_message" do context "when there are no last message" do it "returns an instance of NullMessage" do diff --git a/plugins/chat/spec/models/chat/message_spec.rb b/plugins/chat/spec/models/chat/message_spec.rb index 924aa83ecec..2db7804c48a 100644 --- a/plugins/chat/spec/models/chat/message_spec.rb +++ b/plugins/chat/spec/models/chat/message_spec.rb @@ -5,6 +5,14 @@ describe Chat::Message do it { is_expected.to have_many(:chat_mentions).dependent(:destroy) } + it "supports custom fields" do + message.custom_fields["test"] = "test" + message.save_custom_fields + loaded_message = Chat::Message.find(message.id) + expect(loaded_message.custom_fields["test"]).to eq("test") + expect(Chat::MessageCustomField.first.message.id).to eq(message.id) + end + describe "validations" do subject(:message) { described_class.new(message: "") } @@ -509,7 +517,7 @@ describe Chat::Message do it "destroys upload_references" do message_1 = Fabricate(:chat_message) upload_reference_1 = Fabricate(:upload_reference, target: message_1) - upload_1 = Fabricate(:upload) + _upload_1 = Fabricate(:upload) message_1.destroy! diff --git a/plugins/chat/spec/models/chat/thread_spec.rb b/plugins/chat/spec/models/chat/thread_spec.rb index cc9483df9b7..2e4735b5b0b 100644 --- a/plugins/chat/spec/models/chat/thread_spec.rb +++ b/plugins/chat/spec/models/chat/thread_spec.rb @@ -9,18 +9,16 @@ RSpec.describe Chat::Thread do fab!(:thread_2) { Fabricate(:chat_thread, channel: channel) } fab!(:thread_3) { Fabricate(:chat_thread, channel: channel) } - before do - Fabricate(:chat_message, chat_channel: channel, thread: thread_1) - Fabricate(:chat_message, chat_channel: channel, thread: thread_1) - Fabricate(:chat_message, chat_channel: channel, thread: thread_1) + fab!(:thread_1_message_1) { Fabricate(:chat_message, chat_channel: channel, thread: thread_1) } + fab!(:thread_1_message_2) { Fabricate(:chat_message, chat_channel: channel, thread: thread_1) } + fab!(:thread_1_message_3) { Fabricate(:chat_message, chat_channel: channel, thread: thread_1) } - Fabricate(:chat_message, chat_channel: channel, thread: thread_2) - Fabricate(:chat_message, chat_channel: channel, thread: thread_2) - Fabricate(:chat_message, chat_channel: channel, thread: thread_2) - Fabricate(:chat_message, chat_channel: channel, thread: thread_2) + fab!(:thread_2_message_1) { Fabricate(:chat_message, chat_channel: channel, thread: thread_2) } + fab!(:thread_2_message_2) { Fabricate(:chat_message, chat_channel: channel, thread: thread_2) } + fab!(:thread_2_message_3) { Fabricate(:chat_message, chat_channel: channel, thread: thread_2) } + fab!(:thread_2_message_4) { Fabricate(:chat_message, chat_channel: channel, thread: thread_2) } - Fabricate(:chat_message, chat_channel: channel, thread: thread_3) - end + fab!(:thread_3_message_1) { Fabricate(:chat_message, chat_channel: channel, thread: thread_3) } describe "updating replies_count for all threads" do it "counts correctly and does not include the original message" do @@ -233,4 +231,17 @@ RSpec.describe Chat::Thread do expect(thread.latest_not_deleted_message_id).to eq(old_message.id) end end + + describe "custom fields" do + fab!(:channel) { Fabricate(:category_channel) } + fab!(:thread) { Fabricate(:chat_thread, channel: channel) } + + it "allows create and save" do + thread.custom_fields["test"] = "test" + thread.save_custom_fields + loaded_thread = Chat::Thread.find(thread.id) + expect(loaded_thread.custom_fields["test"]).to eq("test") + expect(Chat::ThreadCustomField.first.thread.id).to eq(thread.id) + end + end end