mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-31 02:57:57 +00:00 
			
		
		
		
	chore(cleanup): Delete sentiment feature (#9304)
- The feature is unused, removing it for now, will bring it back with better models later.
This commit is contained in:
		| @@ -249,9 +249,6 @@ AZURE_APP_SECRET= | |||||||
| ## OpenAI key | ## OpenAI key | ||||||
| # OPENAI_API_KEY= | # OPENAI_API_KEY= | ||||||
|  |  | ||||||
| # Sentiment analysis model file path |  | ||||||
| SENTIMENT_FILE_PATH= |  | ||||||
|  |  | ||||||
| # Housekeeping/Performance related configurations | # Housekeeping/Performance related configurations | ||||||
| # Set to true if you want to remove stale contact inboxes | # Set to true if you want to remove stale contact inboxes | ||||||
| # contact_inboxes with no conversation older than 90 days will be removed | # contact_inboxes with no conversation older than 90 days will be removed | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								Gemfile
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								Gemfile
									
									
									
									
									
								
							| @@ -175,9 +175,6 @@ gem 'pgvector' | |||||||
| # Convert Website HTML to Markdown | # Convert Website HTML to Markdown | ||||||
| gem 'reverse_markdown' | gem 'reverse_markdown' | ||||||
|  |  | ||||||
| # Sentiment analysis |  | ||||||
| gem 'informers' |  | ||||||
|  |  | ||||||
| ### Gems required only in specific deployment environments ### | ### Gems required only in specific deployment environments ### | ||||||
| ############################################################## | ############################################################## | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										40
									
								
								Gemfile.lock
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								Gemfile.lock
									
									
									
									
									
								
							| @@ -151,7 +151,6 @@ GEM | |||||||
|     base64 (0.1.1) |     base64 (0.1.1) | ||||||
|     bcrypt (3.1.20) |     bcrypt (3.1.20) | ||||||
|     bindex (0.8.1) |     bindex (0.8.1) | ||||||
|     blingfire (0.1.8) |  | ||||||
|     bootsnap (1.16.0) |     bootsnap (1.16.0) | ||||||
|       msgpack (~> 1.2) |       msgpack (~> 1.2) | ||||||
|     brakeman (5.4.1) |     brakeman (5.4.1) | ||||||
| @@ -316,15 +315,15 @@ GEM | |||||||
|     google-cloud-translate-v3 (0.6.0) |     google-cloud-translate-v3 (0.6.0) | ||||||
|       gapic-common (>= 0.17.1, < 2.a) |       gapic-common (>= 0.17.1, < 2.a) | ||||||
|       google-cloud-errors (~> 1.0) |       google-cloud-errors (~> 1.0) | ||||||
|     google-protobuf (3.25.2) |     google-protobuf (3.25.3) | ||||||
|     google-protobuf (3.25.2-arm64-darwin) |     google-protobuf (3.25.3-arm64-darwin) | ||||||
|     google-protobuf (3.25.2-x86_64-darwin) |     google-protobuf (3.25.3-x86_64-darwin) | ||||||
|     google-protobuf (3.25.2-x86_64-linux) |     google-protobuf (3.25.3-x86_64-linux) | ||||||
|     googleapis-common-protos (1.4.0) |     googleapis-common-protos (1.4.0) | ||||||
|       google-protobuf (~> 3.14) |       google-protobuf (~> 3.14) | ||||||
|       googleapis-common-protos-types (~> 1.2) |       googleapis-common-protos-types (~> 1.2) | ||||||
|       grpc (~> 1.27) |       grpc (~> 1.27) | ||||||
|     googleapis-common-protos-types (1.11.0) |     googleapis-common-protos-types (1.14.0) | ||||||
|       google-protobuf (~> 3.18) |       google-protobuf (~> 3.18) | ||||||
|     googleauth (1.5.2) |     googleauth (1.5.2) | ||||||
|       faraday (>= 0.17.3, < 3.a) |       faraday (>= 0.17.3, < 3.a) | ||||||
| @@ -335,14 +334,17 @@ GEM | |||||||
|       signet (>= 0.16, < 2.a) |       signet (>= 0.16, < 2.a) | ||||||
|     groupdate (6.2.1) |     groupdate (6.2.1) | ||||||
|       activesupport (>= 5.2) |       activesupport (>= 5.2) | ||||||
|     grpc (1.54.3) |     grpc (1.62.0) | ||||||
|       google-protobuf (~> 3.21) |       google-protobuf (~> 3.25) | ||||||
|       googleapis-common-protos-types (~> 1.0) |       googleapis-common-protos-types (~> 1.0) | ||||||
|     grpc (1.54.3-x86_64-darwin) |     grpc (1.62.0-arm64-darwin) | ||||||
|       google-protobuf (~> 3.21) |       google-protobuf (~> 3.25) | ||||||
|       googleapis-common-protos-types (~> 1.0) |       googleapis-common-protos-types (~> 1.0) | ||||||
|     grpc (1.54.3-x86_64-linux) |     grpc (1.62.0-x86_64-darwin) | ||||||
|       google-protobuf (~> 3.21) |       google-protobuf (~> 3.25) | ||||||
|  |       googleapis-common-protos-types (~> 1.0) | ||||||
|  |     grpc (1.62.0-x86_64-linux) | ||||||
|  |       google-protobuf (~> 3.25) | ||||||
|       googleapis-common-protos-types (~> 1.0) |       googleapis-common-protos-types (~> 1.0) | ||||||
|     haikunator (1.1.1) |     haikunator (1.1.1) | ||||||
|     hairtrigger (1.0.0) |     hairtrigger (1.0.0) | ||||||
| @@ -371,10 +373,6 @@ GEM | |||||||
|     image_processing (1.12.2) |     image_processing (1.12.2) | ||||||
|       mini_magick (>= 4.9.5, < 5) |       mini_magick (>= 4.9.5, < 5) | ||||||
|       ruby-vips (>= 2.0.17, < 3) |       ruby-vips (>= 2.0.17, < 3) | ||||||
|     informers (0.2.0) |  | ||||||
|       blingfire (>= 0.1.7) |  | ||||||
|       numo-narray |  | ||||||
|       onnxruntime (>= 0.5.1) |  | ||||||
|     io-console (0.6.0) |     io-console (0.6.0) | ||||||
|     irb (1.7.2) |     irb (1.7.2) | ||||||
|       reline (>= 0.3.6) |       reline (>= 0.3.6) | ||||||
| @@ -500,7 +498,6 @@ GEM | |||||||
|       racc (~> 1.4) |       racc (~> 1.4) | ||||||
|     nokogiri (1.16.4-x86_64-linux) |     nokogiri (1.16.4-x86_64-linux) | ||||||
|       racc (~> 1.4) |       racc (~> 1.4) | ||||||
|     numo-narray (0.9.2.1) |  | ||||||
|     oauth (1.1.0) |     oauth (1.1.0) | ||||||
|       oauth-tty (~> 1.0, >= 1.0.1) |       oauth-tty (~> 1.0, >= 1.0.1) | ||||||
|       snaky_hash (~> 2.0) |       snaky_hash (~> 2.0) | ||||||
| @@ -529,14 +526,6 @@ GEM | |||||||
|     omniauth-rails_csrf_protection (1.0.1) |     omniauth-rails_csrf_protection (1.0.1) | ||||||
|       actionpack (>= 4.2) |       actionpack (>= 4.2) | ||||||
|       omniauth (~> 2.0) |       omniauth (~> 2.0) | ||||||
|     onnxruntime (0.7.6) |  | ||||||
|       ffi |  | ||||||
|     onnxruntime (0.7.6-arm64-darwin) |  | ||||||
|       ffi |  | ||||||
|     onnxruntime (0.7.6-x86_64-darwin) |  | ||||||
|       ffi |  | ||||||
|     onnxruntime (0.7.6-x86_64-linux) |  | ||||||
|       ffi |  | ||||||
|     openssl (3.1.0) |     openssl (3.1.0) | ||||||
|     orm_adapter (0.5.0) |     orm_adapter (0.5.0) | ||||||
|     os (1.1.4) |     os (1.1.4) | ||||||
| @@ -892,7 +881,6 @@ DEPENDENCIES | |||||||
|   hashie |   hashie | ||||||
|   html2text! |   html2text! | ||||||
|   image_processing |   image_processing | ||||||
|   informers |  | ||||||
|   jbuilder |   jbuilder | ||||||
|   json_refs |   json_refs | ||||||
|   json_schemer |   json_schemer | ||||||
|   | |||||||
| @@ -310,5 +310,4 @@ class Conversation < ApplicationRecord | |||||||
| end | end | ||||||
|  |  | ||||||
| Conversation.include_mod_with('Concerns::Conversation') | Conversation.include_mod_with('Concerns::Conversation') | ||||||
| Conversation.include_mod_with('SentimentAnalysisHelper') |  | ||||||
| Conversation.prepend_mod_with('Conversation') | Conversation.prepend_mod_with('Conversation') | ||||||
|   | |||||||
| @@ -269,7 +269,6 @@ class Message < ApplicationRecord | |||||||
|     reopen_conversation |     reopen_conversation | ||||||
|     notify_via_mail |     notify_via_mail | ||||||
|     set_conversation_activity |     set_conversation_activity | ||||||
|     update_message_sentiments |  | ||||||
|     dispatch_create_events |     dispatch_create_events | ||||||
|     send_reply |     send_reply | ||||||
|     execute_message_template_hooks |     execute_message_template_hooks | ||||||
| @@ -406,10 +405,6 @@ class Message < ApplicationRecord | |||||||
|     conversation.update_columns(last_activity_at: created_at) |     conversation.update_columns(last_activity_at: created_at) | ||||||
|     # rubocop:enable Rails/SkipsModelValidations |     # rubocop:enable Rails/SkipsModelValidations | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   def update_message_sentiments |  | ||||||
|     # override in the enterprise ::Enterprise::SentimentAnalysisJob.perform_later(self) |  | ||||||
|   end |  | ||||||
| end | end | ||||||
|  |  | ||||||
| Message.prepend_mod_with('Message') | Message.prepend_mod_with('Message') | ||||||
|   | |||||||
| @@ -1,57 +0,0 @@ | |||||||
| class Enterprise::SentimentAnalysisJob < ApplicationJob |  | ||||||
|   queue_as :low |  | ||||||
|  |  | ||||||
|   def perform(message) |  | ||||||
|     return if message.account.locale != 'en' || !valid_incoming_message?(message) |  | ||||||
|  |  | ||||||
|     save_message_sentiment(message) |  | ||||||
|   rescue StandardError => e |  | ||||||
|     Rails.logger.error("Sentiment Analysis Error for message #{message.id}: #{e}") |  | ||||||
|     ChatwootExceptionTracker.new(e, account: message.account).capture_exception |  | ||||||
|   end |  | ||||||
|  |  | ||||||
|   def save_message_sentiment(message) |  | ||||||
|     # We are truncating the data here to avoind the OnnxRuntime::Error |  | ||||||
|     # Indices element out of data bounds, idx=512 must be within the inclusive range [-512,511] |  | ||||||
|     # While gathering the maningfull node the Array/tensor index is going out of bound |  | ||||||
|  |  | ||||||
|     text = message.content&.truncate(2900) |  | ||||||
|     return if model.blank? |  | ||||||
|  |  | ||||||
|     sentiment = model.predict(text) |  | ||||||
|     message.sentiment = sentiment.merge(value: label_val(sentiment)) |  | ||||||
|  |  | ||||||
|     message.save! |  | ||||||
|   end |  | ||||||
|  |  | ||||||
|   # Model initializes OnnxRuntime::Model, with given file for inference session and to create the tensor |  | ||||||
|   def model |  | ||||||
|     model_file = save_and_open_sentiment_file |  | ||||||
|  |  | ||||||
|     return if File.empty?(model_file) |  | ||||||
|  |  | ||||||
|     Informers::SentimentAnalysis.new(model_file) |  | ||||||
|   end |  | ||||||
|  |  | ||||||
|   def label_val(sentiment) |  | ||||||
|     sentiment[:label] == 'positive' ? 1 : -1 |  | ||||||
|   end |  | ||||||
|  |  | ||||||
|   def valid_incoming_message?(message) |  | ||||||
|     message.incoming? && message.content.present? && !message.private? |  | ||||||
|   end |  | ||||||
|  |  | ||||||
|   # returns the sentiment file from vendor folder else download it to the path from AWS-S3 |  | ||||||
|   def save_and_open_sentiment_file |  | ||||||
|     model_path = ENV.fetch('SENTIMENT_FILE_PATH', nil) |  | ||||||
|  |  | ||||||
|     sentiment_file = Rails.root.join('vendor/db/sentiment-analysis.onnx') |  | ||||||
|  |  | ||||||
|     return sentiment_file if File.exist?(sentiment_file) |  | ||||||
|  |  | ||||||
|     source_file = Down.download(model_path) # Download file from AWS-S3 |  | ||||||
|     File.rename(source_file, sentiment_file) # Change the file path |  | ||||||
|  |  | ||||||
|     sentiment_file |  | ||||||
|   end |  | ||||||
| end |  | ||||||
| @@ -1,5 +0,0 @@ | |||||||
| module Enterprise::Message |  | ||||||
|   def update_message_sentiments |  | ||||||
|     ::Enterprise::SentimentAnalysisJob.perform_later(self) if ENV.fetch('SENTIMENT_FILE_PATH', nil).present? |  | ||||||
|   end |  | ||||||
| end |  | ||||||
| @@ -1,45 +0,0 @@ | |||||||
| module Enterprise::SentimentAnalysisHelper |  | ||||||
|   extend ActiveSupport::Concern |  | ||||||
|  |  | ||||||
|   included do |  | ||||||
|     def opening_sentiments |  | ||||||
|       records = incoming_messages.first(average_message_count) |  | ||||||
|       average_sentiment(records) |  | ||||||
|     end |  | ||||||
|  |  | ||||||
|     def closing_sentiments |  | ||||||
|       return unless resolved? |  | ||||||
|  |  | ||||||
|       records = incoming_messages.last(average_message_count) |  | ||||||
|       average_sentiment(records) |  | ||||||
|     end |  | ||||||
|  |  | ||||||
|     def average_sentiment(records) |  | ||||||
|       { |  | ||||||
|         label: average_sentiment_label(records), |  | ||||||
|         score: average_sentiment_score(records) |  | ||||||
|       } |  | ||||||
|     end |  | ||||||
|  |  | ||||||
|     private |  | ||||||
|  |  | ||||||
|     def average_sentiment_label(records) |  | ||||||
|       value = records.pluck(:sentiment).sum { |a| a['value'].to_i } |  | ||||||
|       value.negative? ? 'negative' : 'positive' |  | ||||||
|     end |  | ||||||
|  |  | ||||||
|     def average_sentiment_score(records) |  | ||||||
|       total = records.pluck(:sentiment).sum { |a| a['score'].to_f } |  | ||||||
|       total / average_message_count |  | ||||||
|     end |  | ||||||
|  |  | ||||||
|     def average_message_count |  | ||||||
|       # incoming_messages.count >= 10 ? 5 : ((incoming_messages.count / 2) - 1) |  | ||||||
|       5 |  | ||||||
|     end |  | ||||||
|  |  | ||||||
|     def incoming_messages |  | ||||||
|       messages.incoming.where(private: false) |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| end |  | ||||||
| @@ -1,117 +0,0 @@ | |||||||
| require 'rails_helper' |  | ||||||
|  |  | ||||||
| RSpec.describe Enterprise::SentimentAnalysisJob do |  | ||||||
|   context 'when account locale set to english language' do |  | ||||||
|     let(:account) { create(:account, locale: 'en') } |  | ||||||
|     let(:message) { build(:message, content_type: nil, account: account) } |  | ||||||
|  |  | ||||||
|     context 'when update the message sentiments' do |  | ||||||
|       let(:model_path) { Rails.root.join('vendor/db/sentiment-analysis.onnx') } |  | ||||||
|       let(:model) { double } |  | ||||||
|  |  | ||||||
|       before do |  | ||||||
|         allow(Informers::SentimentAnalysis).to receive(:new).with(model_path).and_return(model) |  | ||||||
|         allow(model).to receive(:predict).and_return({ label: 'positive', score: '0.6' }) |  | ||||||
|       end |  | ||||||
|  |  | ||||||
|       it 'with incoming message' do |  | ||||||
|         with_modified_env SENTIMENT_FILE_PATH: 'sentiment-analysis.onnx' do |  | ||||||
|           message.update(message_type: :incoming) |  | ||||||
|  |  | ||||||
|           described_class.perform_now(message) |  | ||||||
|  |  | ||||||
|           expect(message.sentiment).not_to be_empty |  | ||||||
|         end |  | ||||||
|       end |  | ||||||
|  |  | ||||||
|       it 'update sentiment label for positive message' do |  | ||||||
|         with_modified_env SENTIMENT_FILE_PATH: 'sentiment-analysis.onnx' do |  | ||||||
|           message.update(message_type: :incoming, content: 'I like your product') |  | ||||||
|  |  | ||||||
|           described_class.perform_now(message) |  | ||||||
|  |  | ||||||
|           expect(message.sentiment).not_to be_empty |  | ||||||
|           expect(message.sentiment['label']).to eq('positive') |  | ||||||
|           expect(message.sentiment['value']).to eq(1) |  | ||||||
|         end |  | ||||||
|       end |  | ||||||
|  |  | ||||||
|       it 'update sentiment label for negative message' do |  | ||||||
|         with_modified_env SENTIMENT_FILE_PATH: 'sentiment-analysis.onnx' do |  | ||||||
|           message.update(message_type: :incoming, content: 'I did not like your product') |  | ||||||
|           allow(model).to receive(:predict).and_return({ label: 'negative', score: '0.6' }) |  | ||||||
|  |  | ||||||
|           described_class.perform_now(message) |  | ||||||
|  |  | ||||||
|           expect(message.sentiment).not_to be_empty |  | ||||||
|           expect(message.sentiment['label']).to eq('negative') |  | ||||||
|           expect(message.sentiment['value']).to eq(-1) |  | ||||||
|         end |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
|  |  | ||||||
|     context 'with download sentiment files' do |  | ||||||
|       let(:model_path) { nil } |  | ||||||
|       let(:model) { double } |  | ||||||
|  |  | ||||||
|       before do |  | ||||||
|         allow(Informers::SentimentAnalysis).to receive(:new).with(model_path).and_return(model) |  | ||||||
|         allow(model).to receive(:predict).and_return({ label: 'positive', score: '0.6' }) |  | ||||||
|       end |  | ||||||
|  |  | ||||||
|       it 'fetch saved file in the server' do |  | ||||||
|         with_modified_env SENTIMENT_FILE_PATH: 'sentiment-analysis.onnx' do |  | ||||||
|           message.update(message_type: :incoming, content: 'I did not like your product') |  | ||||||
|  |  | ||||||
|           described_class.new(message).save_and_open_sentiment_file |  | ||||||
|  |  | ||||||
|           sentiment_file = Rails.root.join('vendor/db/sentiment-analysis.onnx') |  | ||||||
|           expect(File).to exist(sentiment_file) |  | ||||||
|         end |  | ||||||
|       end |  | ||||||
|  |  | ||||||
|       it 'fetch file from the storage' do |  | ||||||
|         with_modified_env SENTIMENT_FILE_PATH: 'sentiment-analysis.onnx' do |  | ||||||
|           message.update(message_type: :incoming, content: 'I did not like your product') |  | ||||||
|           allow(File).to receive(:exist?).and_return(false) |  | ||||||
|           allow(Down).to receive(:download).and_return('./sentiment-analysis.onnx') |  | ||||||
|           allow(File).to receive(:rename).and_return(100) |  | ||||||
|  |  | ||||||
|           described_class.new(message).save_and_open_sentiment_file |  | ||||||
|  |  | ||||||
|           sentiment_file = Rails.root.join('vendor/db/sentiment-analysis.onnx') |  | ||||||
|           expect(sentiment_file).to be_present |  | ||||||
|         end |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
|  |  | ||||||
|     context 'when does not update the message sentiments' do |  | ||||||
|       it 'with outgoing message' do |  | ||||||
|         message.update(message_type: :outgoing) |  | ||||||
|  |  | ||||||
|         described_class.perform_now(message) |  | ||||||
|  |  | ||||||
|         expect(message.sentiment).to be_empty |  | ||||||
|       end |  | ||||||
|  |  | ||||||
|       it 'with private message' do |  | ||||||
|         message.update(private: true) |  | ||||||
|  |  | ||||||
|         described_class.perform_now(message) |  | ||||||
|  |  | ||||||
|         expect(message.sentiment).to be_empty |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
|  |  | ||||||
|   context 'when account locale is not set to english language' do |  | ||||||
|     let(:account) { create(:account, locale: 'es') } |  | ||||||
|     let(:message) { build(:message, content_type: nil, account: account) } |  | ||||||
|  |  | ||||||
|     it 'does not update the message sentiments' do |  | ||||||
|       described_class.perform_now(message) |  | ||||||
|  |  | ||||||
|       expect(message.sentiment).to be_empty |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| end |  | ||||||
| @@ -36,36 +36,6 @@ RSpec.describe Conversation, type: :model do | |||||||
|     # end |     # end | ||||||
|   end |   end | ||||||
|  |  | ||||||
|   describe 'conversation sentiments' do |  | ||||||
|     include ActiveJob::TestHelper |  | ||||||
|  |  | ||||||
|     let(:conversation) { create(:conversation, additional_attributes: { referer: 'https://www.chatwoot.com/' }) } |  | ||||||
|  |  | ||||||
|     before do |  | ||||||
|       10.times do |  | ||||||
|         message = create(:message, conversation_id: conversation.id, account_id: conversation.account_id, message_type: 'incoming') |  | ||||||
|         message.update(sentiment: { 'label': 'positive', score: '0.4' }) |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
|  |  | ||||||
|     it 'returns opening sentiments' do |  | ||||||
|       sentiments = conversation.opening_sentiments |  | ||||||
|       expect(sentiments[:label]).to eq('positive') |  | ||||||
|     end |  | ||||||
|  |  | ||||||
|     it 'returns closing sentiments if conversation is not resolved' do |  | ||||||
|       sentiments = conversation.closing_sentiments |  | ||||||
|       expect(sentiments).to be_nil |  | ||||||
|     end |  | ||||||
|  |  | ||||||
|     it 'returns closing sentiments if it is resolved' do |  | ||||||
|       conversation.resolved! |  | ||||||
|  |  | ||||||
|       sentiments = conversation.closing_sentiments |  | ||||||
|       expect(sentiments[:label]).to eq('positive') |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
|  |  | ||||||
|   describe 'sla_policy' do |   describe 'sla_policy' do | ||||||
|     let(:account) { create(:account) } |     let(:account) { create(:account) } | ||||||
|     let(:conversation) { create(:conversation, account: account) } |     let(:conversation) { create(:conversation, account: account) } | ||||||
|   | |||||||
| @@ -1,20 +0,0 @@ | |||||||
| # frozen_string_literal: true |  | ||||||
|  |  | ||||||
| require 'rails_helper' |  | ||||||
| require Rails.root.join 'spec/models/concerns/liquidable_shared.rb' |  | ||||||
|  |  | ||||||
| RSpec.describe Message do |  | ||||||
|   context 'with sentiment analysis' do |  | ||||||
|     let(:message) { build(:message, message_type: :incoming, content_type: nil, account: create(:account)) } |  | ||||||
|  |  | ||||||
|     it 'calls SentimentAnalysisJob' do |  | ||||||
|       with_modified_env SENTIMENT_FILE_PATH: 'sentiment-analysis.onnx' do |  | ||||||
|         allow(Enterprise::SentimentAnalysisJob).to receive(:perform_later).and_return(:perform_later).with(message) |  | ||||||
|  |  | ||||||
|         message.save! |  | ||||||
|  |  | ||||||
|         expect(Enterprise::SentimentAnalysisJob).to have_received(:perform_later) |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| end |  | ||||||
		Reference in New Issue
	
	Block a user
	 Pranav
					Pranav