diff --git a/app/builders/csat_surveys/response_builder.rb b/app/builders/csat_surveys/response_builder.rb new file mode 100644 index 000000000..7c120ddb2 --- /dev/null +++ b/app/builders/csat_surveys/response_builder.rb @@ -0,0 +1,28 @@ +class CsatSurveys::ResponseBuilder + pattr_initialize [:message] + + def perform + raise 'Invalid Message' unless message.input_csat? + + conversation = message.conversation + rating = message.content_attributes.dig('submitted_values', 'csat_survey_response', 'rating') + feedback_message = message.content_attributes.dig('submitted_values', 'csat_survey_response', 'feedback_message') + + return if rating.blank? + + process_csat_response(conversation, rating, feedback_message) + end + + private + + def process_csat_response(conversation, rating, feedback_message) + csat_survey_response = message.csat_survey_response || CsatSurveyResponse.new( + message_id: message.id, account_id: message.account_id, conversation_id: message.conversation_id, + contact_id: conversation.contact_id, assigned_agent: conversation.assignee + ) + csat_survey_response.rating = rating + csat_survey_response.feedback_message = feedback_message + csat_survey_response.save! + csat_survey_response + end +end diff --git a/app/builders/v2/report_builder.rb b/app/builders/v2/report_builder.rb index 35f484f95..2a7a55a8a 100644 --- a/app/builders/v2/report_builder.rb +++ b/app/builders/v2/report_builder.rb @@ -1,4 +1,5 @@ class V2::ReportBuilder + include DateRangeHelper attr_reader :account, :params def initialize(account, params) @@ -83,10 +84,6 @@ class V2::ReportBuilder .average(:value) end - def range - parse_date_time(params[:since])..parse_date_time(params[:until]) - end - # Taking average of average is not too accurate # https://en.wikipedia.org/wiki/Simpson's_paradox # TODO: Will optimize this later @@ -101,11 +98,4 @@ class V2::ReportBuilder (avg_first_response_time.values.sum / avg_first_response_time.values.length) end - - def parse_date_time(datetime) - return datetime if datetime.is_a?(DateTime) - return datetime.to_datetime if datetime.is_a?(Time) || datetime.is_a?(Date) - - DateTime.strptime(datetime, '%s') - end end diff --git a/app/controllers/api/v1/accounts/csat_survey_responses_controller.rb b/app/controllers/api/v1/accounts/csat_survey_responses_controller.rb new file mode 100644 index 000000000..c449ecf96 --- /dev/null +++ b/app/controllers/api/v1/accounts/csat_survey_responses_controller.rb @@ -0,0 +1,22 @@ +class Api::V1::Accounts::CsatSurveyResponsesController < Api::V1::Accounts::BaseController + include DateRangeHelper + + RESULTS_PER_PAGE = 25 + + before_action :check_authorization + before_action :csat_survey_responses, only: [:index] + + def index; end + + private + + def csat_survey_responses + @csat_survey_responses = Current.account.csat_survey_responses + @csat_survey_responses = @csat_survey_responses.where(created_at: range) if range.present? + @csat_survey_responses.page(@current_page).per(RESULTS_PER_PAGE) + end + + def set_current_page + @current_page = params[:page] || 1 + end +end diff --git a/app/controllers/api/v1/widget/messages_controller.rb b/app/controllers/api/v1/widget/messages_controller.rb index f0286ecd2..9e6f770ad 100644 --- a/app/controllers/api/v1/widget/messages_controller.rb +++ b/app/controllers/api/v1/widget/messages_controller.rb @@ -54,7 +54,7 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController end def message_update_params - params.permit(message: [{ submitted_values: [:name, :title, :value, { csat_survey_response: [:feedback, :rating] }] }]) + params.permit(message: [{ submitted_values: [:name, :title, :value, { csat_survey_response: [:feedback_message, :rating] }] }]) end def permitted_params diff --git a/app/dispatchers/async_dispatcher.rb b/app/dispatchers/async_dispatcher.rb index 0e238e3a0..1cbb56747 100644 --- a/app/dispatchers/async_dispatcher.rb +++ b/app/dispatchers/async_dispatcher.rb @@ -10,10 +10,12 @@ class AsyncDispatcher < BaseDispatcher def listeners [ + CampaignListener.instance, + CsatSurveyListener.instance, EventListener.instance, - WebhookListener.instance, - InstallationWebhookListener.instance, HookListener.instance, - CampaignListener.instance + HookListener.instance, + InstallationWebhookListener.instance, + WebhookListener.instance ] end end diff --git a/app/helpers/date_range_helper.rb b/app/helpers/date_range_helper.rb new file mode 100644 index 000000000..175d6c72b --- /dev/null +++ b/app/helpers/date_range_helper.rb @@ -0,0 +1,19 @@ +############################################## +# Helpers to implement date range filtering to APIs +# Include in your controller or service class where params is available +############################################## + +module DateRangeHelper + def range + return if params[:since].blank? || params[:until].blank? + + parse_date_time(params[:since])..parse_date_time(params[:until]) + end + + def parse_date_time(datetime) + return datetime if datetime.is_a?(DateTime) + return datetime.to_datetime if datetime.is_a?(Time) || datetime.is_a?(Date) + + DateTime.strptime(datetime, '%s') + end +end diff --git a/app/javascript/dashboard/components/widgets/conversation/helpers/botMessageContentHelper.js b/app/javascript/dashboard/components/widgets/conversation/helpers/botMessageContentHelper.js index b7e105abf..2f4521f72 100644 --- a/app/javascript/dashboard/components/widgets/conversation/helpers/botMessageContentHelper.js +++ b/app/javascript/dashboard/components/widgets/conversation/helpers/botMessageContentHelper.js @@ -43,7 +43,7 @@ const generateCSATContent = ( const { submitted_values: { csat_survey_response: surveyResponse = {} } = {}, } = contentAttributes; - const { rating, feedback } = surveyResponse || {}; + const { rating, feedback_message } = surveyResponse || {}; let messageContent = ''; if (rating) { @@ -53,9 +53,9 @@ const generateCSATContent = ( messageContent += `
${ratingTitle}
`; messageContent += `

${ratingObject.emoji}

`; } - if (feedback) { + if (feedback_message) { messageContent += `
${feedbackTitle}
`; - messageContent += `

${feedback}

`; + messageContent += `

${feedback_message}

`; } return messageContent; }; diff --git a/app/javascript/dashboard/components/widgets/conversation/helpers/specs/botMessageContentHelper.spec.js b/app/javascript/dashboard/components/widgets/conversation/helpers/specs/botMessageContentHelper.spec.js index 9268b4559..a0901aa1a 100644 --- a/app/javascript/dashboard/components/widgets/conversation/helpers/specs/botMessageContentHelper.spec.js +++ b/app/javascript/dashboard/components/widgets/conversation/helpers/specs/botMessageContentHelper.spec.js @@ -21,7 +21,10 @@ describe('#generateBotMessageContent', () => { expect( generateBotMessageContent('input_csat', { submitted_values: { - csat_survey_response: { rating: 5, feedback: 'Great Service' }, + csat_survey_response: { + rating: 5, + feedback_message: 'Great Service', + }, }, }) ).toEqual( @@ -33,7 +36,7 @@ describe('#generateBotMessageContent', () => { 'input_csat', { submitted_values: { - csat_survey_response: { rating: 1, feedback: '' }, + csat_survey_response: { rating: 1, feedback_message: '' }, }, }, { csat: { ratingTitle: 'റേറ്റിംഗ്', feedbackTitle: 'പ്രതികരണം' } } diff --git a/app/javascript/widget/components/AgentMessageBubble.vue b/app/javascript/widget/components/AgentMessageBubble.vue index 071a37d26..bdb7ba981 100755 --- a/app/javascript/widget/components/AgentMessageBubble.vue +++ b/app/javascript/widget/components/AgentMessageBubble.vue @@ -130,7 +130,7 @@ export default { submittedValues: { csat_survey_response: { rating, - feedback, + feedback_message: feedback, }, }, messageId: this.messageId, diff --git a/app/listeners/csat_survey_listener.rb b/app/listeners/csat_survey_listener.rb new file mode 100644 index 000000000..abc366400 --- /dev/null +++ b/app/listeners/csat_survey_listener.rb @@ -0,0 +1,8 @@ +class CsatSurveyListener < BaseListener + def message_updated(event) + message = extract_message_and_account(event)[0] + return unless message.input_csat? + + CsatSurveys::ResponseBuilder.new(message: message).perform + end +end diff --git a/app/models/account.rb b/app/models/account.rb index e042b7a95..ba73d1eaa 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -34,6 +34,7 @@ class Account < ApplicationRecord has_many :account_users, dependent: :destroy has_many :agent_bot_inboxes, dependent: :destroy has_many :agent_bots, dependent: :destroy + has_many :csat_survey_responses, dependent: :destroy has_many :data_imports, dependent: :destroy has_many :users, through: :account_users has_many :inboxes, dependent: :destroy diff --git a/app/models/contact.rb b/app/models/contact.rb index 62d4970ea..f789eaf17 100644 --- a/app/models/contact.rb +++ b/app/models/contact.rb @@ -39,6 +39,7 @@ class Contact < ApplicationRecord belongs_to :account has_many :conversations, dependent: :destroy has_many :contact_inboxes, dependent: :destroy + has_many :csat_survey_responses, dependent: :destroy has_many :inboxes, through: :contact_inboxes has_many :messages, as: :sender, dependent: :destroy has_many :notes, dependent: :destroy diff --git a/app/models/conversation.rb b/app/models/conversation.rb index 613e2a1c1..97115dde6 100644 --- a/app/models/conversation.rb +++ b/app/models/conversation.rb @@ -60,6 +60,7 @@ class Conversation < ApplicationRecord belongs_to :campaign, optional: true has_many :messages, dependent: :destroy, autosave: true + has_one :csat_survey_response, dependent: :destroy before_create :set_bot_conversation diff --git a/app/models/csat_survey_response.rb b/app/models/csat_survey_response.rb new file mode 100644 index 000000000..eec70c8b4 --- /dev/null +++ b/app/models/csat_survey_response.rb @@ -0,0 +1,43 @@ +# == Schema Information +# +# Table name: csat_survey_responses +# +# id :bigint not null, primary key +# feedback_message :text +# rating :integer not null +# created_at :datetime not null +# updated_at :datetime not null +# account_id :bigint not null +# assigned_agent_id :bigint +# contact_id :bigint not null +# conversation_id :bigint not null +# message_id :bigint not null +# +# Indexes +# +# index_csat_survey_responses_on_account_id (account_id) +# index_csat_survey_responses_on_assigned_agent_id (assigned_agent_id) +# index_csat_survey_responses_on_contact_id (contact_id) +# index_csat_survey_responses_on_conversation_id (conversation_id) +# index_csat_survey_responses_on_message_id (message_id) UNIQUE +# +# Foreign Keys +# +# fk_rails_... (account_id => accounts.id) +# fk_rails_... (assigned_agent_id => users.id) +# fk_rails_... (contact_id => contacts.id) +# fk_rails_... (conversation_id => conversations.id) +# fk_rails_... (message_id => messages.id) +# +class CsatSurveyResponse < ApplicationRecord + belongs_to :account + belongs_to :conversation + belongs_to :contact + belongs_to :message + belongs_to :assigned_agent, class_name: 'User', optional: true + + validates :rating, presence: true, inclusion: { in: [1, 2, 3, 4, 5] } + validates :account_id, presence: true + validates :contact_id, presence: true + validates :conversation_id, presence: true +end diff --git a/app/models/message.rb b/app/models/message.rb index 35c175aa2..a3657ce8b 100644 --- a/app/models/message.rb +++ b/app/models/message.rb @@ -79,6 +79,7 @@ class Message < ApplicationRecord belongs_to :sender, polymorphic: true, required: false has_many :attachments, dependent: :destroy, autosave: true, before_add: :validate_attachments_limit + has_one :csat_survey_response, dependent: :destroy after_create :reopen_conversation, :notify_via_mail diff --git a/app/models/user.rb b/app/models/user.rb index d8d5931e1..56608f687 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -71,6 +71,7 @@ class User < ApplicationRecord has_many :assigned_conversations, foreign_key: 'assignee_id', class_name: 'Conversation', dependent: :nullify alias_attribute :conversations, :assigned_conversations + has_many :csat_survey_responses, foreign_key: 'assigned_agent_id', dependent: :nullify has_many :inbox_members, dependent: :destroy has_many :inboxes, through: :inbox_members, source: :inbox diff --git a/app/policies/csat_survey_response_policy.rb b/app/policies/csat_survey_response_policy.rb new file mode 100644 index 000000000..c49b284f0 --- /dev/null +++ b/app/policies/csat_survey_response_policy.rb @@ -0,0 +1,5 @@ +class CsatSurveyResponsePolicy < ApplicationPolicy + def index? + @account_user.administrator? + end +end diff --git a/app/views/api/v1/accounts/csat_survey_responses/index.json.jbuilder b/app/views/api/v1/accounts/csat_survey_responses/index.json.jbuilder new file mode 100644 index 000000000..e5e3dbc93 --- /dev/null +++ b/app/views/api/v1/accounts/csat_survey_responses/index.json.jbuilder @@ -0,0 +1,3 @@ +json.array! @csat_survey_responses do |csat_survey_response| + json.partial! 'api/v1/models/csat_survey_response.json.jbuilder', resource: csat_survey_response +end diff --git a/app/views/api/v1/models/_csat_survey_response.json.jbuilder b/app/views/api/v1/models/_csat_survey_response.json.jbuilder new file mode 100644 index 000000000..3fd60addb --- /dev/null +++ b/app/views/api/v1/models/_csat_survey_response.json.jbuilder @@ -0,0 +1,9 @@ +json.id resource.id +json.rating resource.rating +json.feedback_message resource.feedback_message +json.account_id resource.account_id +json.message_id resource.message_id +json.contact resource.contact +json.conversation_id resource.conversation.display_id +json.assigned_agent resource.assigned_agent +json.created_at resource.created_at diff --git a/config/routes.rb b/config/routes.rb index 60898723c..e8494a0ce 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -87,6 +87,7 @@ Rails.application.routes.draw do resources :labels, only: [:create, :index] end end + resources :csat_survey_responses, only: [:index] resources :custom_filters, only: [:index, :show, :create, :update, :destroy] resources :inboxes, only: [:index, :create, :update, :destroy] do get :assignable_agents, on: :member diff --git a/db/migrate/20210623155413_create_csat_survey_responses.rb b/db/migrate/20210623155413_create_csat_survey_responses.rb new file mode 100644 index 000000000..02c232b24 --- /dev/null +++ b/db/migrate/20210623155413_create_csat_survey_responses.rb @@ -0,0 +1,15 @@ +class CreateCsatSurveyResponses < ActiveRecord::Migration[6.0] + def change + create_table :csat_survey_responses do |t| + t.references :account, null: false, foreign_key: true + t.references :conversation, null: false, foreign_key: true + t.references :message, null: false, foreign_key: true, index: { unique: true } + t.integer :rating, null: false + t.text :feedback_message + t.references :contact, null: false, foreign_key: true + t.references :assigned_agent, foreign_key: { to_table: :users } + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 232aba915..cc0ad871d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2021_06_23_150613) do +ActiveRecord::Schema.define(version: 2021_06_23_155413) do # These are extensions that must be enabled in order to support this database enable_extension "pg_stat_statements" @@ -267,6 +267,23 @@ ActiveRecord::Schema.define(version: 2021_06_23_150613) do t.index ["team_id"], name: "index_conversations_on_team_id" end + create_table "csat_survey_responses", force: :cascade do |t| + t.bigint "account_id", null: false + t.bigint "conversation_id", null: false + t.bigint "message_id", null: false + t.integer "rating", null: false + t.text "feedback_message" + t.bigint "contact_id", null: false + t.bigint "assigned_agent_id" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["account_id"], name: "index_csat_survey_responses_on_account_id" + t.index ["assigned_agent_id"], name: "index_csat_survey_responses_on_assigned_agent_id" + t.index ["contact_id"], name: "index_csat_survey_responses_on_contact_id" + t.index ["conversation_id"], name: "index_csat_survey_responses_on_conversation_id" + t.index ["message_id"], name: "index_csat_survey_responses_on_message_id", unique: true + end + create_table "custom_filters", force: :cascade do |t| t.string "name", null: false t.integer "filter_type", default: 0, null: false @@ -652,6 +669,11 @@ ActiveRecord::Schema.define(version: 2021_06_23_150613) do add_foreign_key "conversations", "campaigns" add_foreign_key "conversations", "contact_inboxes" add_foreign_key "conversations", "teams" + add_foreign_key "csat_survey_responses", "accounts" + add_foreign_key "csat_survey_responses", "contacts" + add_foreign_key "csat_survey_responses", "conversations" + add_foreign_key "csat_survey_responses", "messages" + add_foreign_key "csat_survey_responses", "users", column: "assigned_agent_id" add_foreign_key "data_imports", "accounts" add_foreign_key "notes", "accounts" add_foreign_key "notes", "contacts" diff --git a/spec/builders/csat_surveys/response_builder_spec.rb b/spec/builders/csat_surveys/response_builder_spec.rb new file mode 100644 index 000000000..5400e9bfd --- /dev/null +++ b/spec/builders/csat_surveys/response_builder_spec.rb @@ -0,0 +1,30 @@ +require 'rails_helper' + +describe ::CsatSurveys::ResponseBuilder do + let(:message) do + create( + :message, content_type: :input_csat, + content_attributes: { 'submitted_values': { 'csat_survey_response': { 'rating': 5, 'feedback_message': 'hello' } } } + ) + end + + describe '#perform' do + it 'creates a new csat survey response' do + csat_survey_response = described_class.new( + message: message + ).perform + + expect(csat_survey_response.valid?).to eq(true) + end + + it 'updates the value of csat survey response if response already exists' do + existing_survey_response = create(:csat_survey_response, message: message) + csat_survey_response = described_class.new( + message: message + ).perform + + expect(csat_survey_response.id).to eq(existing_survey_response.id) + expect(csat_survey_response.rating).to eq(5) + end + end +end diff --git a/spec/controllers/api/v1/accounts/campaigns_controller_spec.rb b/spec/controllers/api/v1/accounts/campaigns_controller_spec.rb index ebe7d4dae..5371eb535 100644 --- a/spec/controllers/api/v1/accounts/campaigns_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/campaigns_controller_spec.rb @@ -18,7 +18,9 @@ RSpec.describe 'Campaigns API', type: :request do let!(:campaign) { create(:campaign, account: account) } it 'returns unauthorized for agents' do - get "/api/v1/accounts/#{account.id}/campaigns" + get "/api/v1/accounts/#{account.id}/campaigns", + headers: agent.create_new_auth_token, + as: :json expect(response).to have_http_status(:unauthorized) end diff --git a/spec/controllers/api/v1/accounts/csat_survey_responses_controller_spec.rb b/spec/controllers/api/v1/accounts/csat_survey_responses_controller_spec.rb new file mode 100644 index 000000000..23e7e96cd --- /dev/null +++ b/spec/controllers/api/v1/accounts/csat_survey_responses_controller_spec.rb @@ -0,0 +1,52 @@ +require 'rails_helper' + +RSpec.describe 'CSAT Survey Responses API', type: :request do + let(:account) { create(:account) } + let!(:csat_survey_response) { create(:csat_survey_response, account: account) } + let(:administrator) { create(:user, account: account, role: :administrator) } + let(:agent) { create(:user, account: account, role: :agent) } + + describe 'GET /api/v1/accounts/{account.id}/csat_survey_responses' do + context 'when it is an unauthenticated user' do + it 'returns unauthorized' do + get "/api/v1/accounts/#{account.id}/csat_survey_responses" + + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when it is an authenticated user' do + it 'returns unauthorized for agents' do + get "/api/v1/accounts/#{account.id}/csat_survey_responses", + headers: agent.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:unauthorized) + end + + it 'returns all the csat survey responses for administrators' do + get "/api/v1/accounts/#{account.id}/csat_survey_responses", + headers: administrator.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:success) + expect(JSON.parse(response.body).first['feedback_message']).to eq(csat_survey_response.feedback_message) + end + + it 'filters csat responsed based on a date range' do + csat_10_days_ago = create(:csat_survey_response, account: account, created_at: 10.days.ago) + csat_3_days_ago = create(:csat_survey_response, account: account, created_at: 3.days.ago) + + get "/api/v1/accounts/#{account.id}/csat_survey_responses", + params: { since: 5.days.ago.to_time.to_i.to_s, until: Time.zone.today.to_time.to_i.to_s }, + headers: administrator.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:success) + response_data = JSON.parse(response.body) + expect(response_data.pluck('id')).to include(csat_3_days_ago.id) + expect(response_data.pluck('id')).not_to include(csat_10_days_ago.id) + end + end + end +end diff --git a/spec/factories/csat_survey_responses.rb b/spec/factories/csat_survey_responses.rb new file mode 100644 index 000000000..92a8bdbe6 --- /dev/null +++ b/spec/factories/csat_survey_responses.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :csat_survey_response do + rating { 1 } + feedback_message { Faker::Movie.quote } + account + conversation + message + contact + end +end diff --git a/spec/listeners/csat_survey_listener_spec.rb b/spec/listeners/csat_survey_listener_spec.rb new file mode 100644 index 000000000..9161c4e71 --- /dev/null +++ b/spec/listeners/csat_survey_listener_spec.rb @@ -0,0 +1,36 @@ +require 'rails_helper' +describe CsatSurveyListener do + let(:listener) { described_class.instance } + let!(:account) { create(:account) } + let!(:user) { create(:user, account: account) } + let!(:inbox) { create(:inbox, account: account) } + let!(:conversation) { create(:conversation, account: account, inbox: inbox, assignee: user) } + let!(:message) do + create( + :message, message_type: 'outgoing', account: account, inbox: inbox, conversation: conversation, + content_type: :input_csat, + content_attributes: { 'submitted_values': { 'csat_survey_response': { 'rating': 5, 'feedback_message': 'hello' } } } + ) + end + let!(:event) { Events::Base.new(event_name, Time.zone.now, message: message) } + + describe '#message_updated' do + let(:event_name) { 'message.updated' } + let(:response_builder) { double } + + context 'when CsatSurveys::ResponseBuilder' do + it 'triggers if message is input csat' do + expect(response_builder).to receive(:perform) + expect(CsatSurveys::ResponseBuilder).to receive(:new).with(message: message).and_return(response_builder).once + listener.message_updated(event) + end + + it 'will not trigger if message is not input csat' do + message = create(:message) + event = Events::Base.new(event_name, Time.zone.now, message: message) + expect(CsatSurveys::ResponseBuilder).not_to receive(:new).with(message: message) + listener.message_updated(event) + end + end + end +end diff --git a/spec/models/csat_survey_response_spec.rb b/spec/models/csat_survey_response_spec.rb new file mode 100644 index 000000000..931ea15e2 --- /dev/null +++ b/spec/models/csat_survey_response_spec.rb @@ -0,0 +1,28 @@ +require 'rails_helper' + +RSpec.describe CsatSurveyResponse, type: :model do + describe 'validations' do + it { is_expected.to validate_presence_of(:rating) } + it { is_expected.to validate_presence_of(:account_id) } + it { is_expected.to validate_presence_of(:conversation_id) } + it { is_expected.to validate_presence_of(:contact_id) } + + it 'validates that the rating can only be in range 1-5' do + csat_survey_response = build(:csat_survey_response, rating: 6) + expect(csat_survey_response.valid?).to eq false + end + end + + describe 'associations' do + it { is_expected.to belong_to(:account) } + it { is_expected.to belong_to(:conversation) } + it { is_expected.to belong_to(:contact) } + end + + describe 'validates_factory' do + it 'creates valid csat_survey_response object' do + csat_survey_response = create(:csat_survey_response) + expect(csat_survey_response.valid?).to eq true + end + end +end