diff --git a/app/controllers/api/v1/accounts/csat_survey_responses_controller.rb b/app/controllers/api/v1/accounts/csat_survey_responses_controller.rb
index 347f028f7..7b5c51d6e 100644
--- a/app/controllers/api/v1/accounts/csat_survey_responses_controller.rb
+++ b/app/controllers/api/v1/accounts/csat_survey_responses_controller.rb
@@ -5,7 +5,7 @@ class Api::V1::Accounts::CsatSurveyResponsesController < Api::V1::Accounts::Base
RESULTS_PER_PAGE = 25
before_action :check_authorization
- before_action :set_csat_survey_responses, only: [:index, :metrics]
+ before_action :set_csat_survey_responses, only: [:index, :metrics, :download]
before_action :set_current_page, only: [:index]
before_action :set_current_page_surveys, only: [:index]
before_action :set_total_sent_messages_count, only: [:metrics]
@@ -19,6 +19,12 @@ class Api::V1::Accounts::CsatSurveyResponsesController < Api::V1::Accounts::Base
@ratings_count = @csat_survey_responses.group(:rating).count
end
+ def download
+ response.headers['Content-Type'] = 'text/csv'
+ response.headers['Content-Disposition'] = 'attachment; filename=csat_report.csv'
+ render layout: false, template: 'api/v1/accounts/csat_survey_responses/download.csv.erb', format: 'csv'
+ end
+
private
def set_total_sent_messages_count
diff --git a/app/javascript/dashboard/api/csatReports.js b/app/javascript/dashboard/api/csatReports.js
index cc0271d7d..4decc8de3 100644
--- a/app/javascript/dashboard/api/csatReports.js
+++ b/app/javascript/dashboard/api/csatReports.js
@@ -18,6 +18,17 @@ class CSATReportsAPI extends ApiClient {
});
}
+ download({ from, to, user_ids } = {}) {
+ return axios.get(`${this.url}/download`, {
+ params: {
+ since: from,
+ until: to,
+ sort: '-created_at',
+ user_ids,
+ },
+ });
+ }
+
getMetrics({ from, to, user_ids } = {}) {
return axios.get(`${this.url}/metrics`, {
params: { since: from, until: to, user_ids },
diff --git a/app/javascript/dashboard/api/specs/csatReports.spec.js b/app/javascript/dashboard/api/specs/csatReports.spec.js
index 0022a91ad..a1d6e50f2 100644
--- a/app/javascript/dashboard/api/specs/csatReports.spec.js
+++ b/app/javascript/dashboard/api/specs/csatReports.spec.js
@@ -33,5 +33,23 @@ describe('#Reports API', () => {
}
);
});
+ it('#download', () => {
+ csatReportsAPI.download({
+ from: 1622485800,
+ to: 1623695400,
+ user_ids: 1,
+ });
+ expect(context.axiosMock.get).toHaveBeenCalledWith(
+ '/api/v1/csat_survey_responses/download',
+ {
+ params: {
+ since: 1622485800,
+ until: 1623695400,
+ user_ids: 1,
+ sort: '-created_at',
+ },
+ }
+ );
+ });
});
});
diff --git a/app/javascript/dashboard/helper/downloadCsvFile.js b/app/javascript/dashboard/helper/downloadHelper.js
similarity index 51%
rename from app/javascript/dashboard/helper/downloadCsvFile.js
rename to app/javascript/dashboard/helper/downloadHelper.js
index f0a13a1fd..150ac1fd8 100644
--- a/app/javascript/dashboard/helper/downloadCsvFile.js
+++ b/app/javascript/dashboard/helper/downloadHelper.js
@@ -1,6 +1,12 @@
+import fromUnixTime from 'date-fns/fromUnixTime';
+import format from 'date-fns/format';
+
export const downloadCsvFile = (fileName, fileContent) => {
const link = document.createElement('a');
link.download = fileName;
link.href = `data:text/csv;charset=utf-8,` + encodeURI(fileContent);
link.click();
};
+
+export const generateFileName = ({ type, to }) =>
+ `${type}-report-${format(fromUnixTime(to), 'dd-MM-yyyy')}.csv`;
diff --git a/app/javascript/dashboard/helper/specs/downloadCsvFile.spec.js b/app/javascript/dashboard/helper/specs/downloadHelper.spec.js
similarity index 68%
rename from app/javascript/dashboard/helper/specs/downloadCsvFile.spec.js
rename to app/javascript/dashboard/helper/specs/downloadHelper.spec.js
index d05b0a841..b294dfe16 100644
--- a/app/javascript/dashboard/helper/specs/downloadCsvFile.spec.js
+++ b/app/javascript/dashboard/helper/specs/downloadHelper.spec.js
@@ -1,4 +1,4 @@
-import { downloadCsvFile } from '../downloadCsvFile';
+import { downloadCsvFile, generateFileName } from '../downloadHelper';
const fileName = 'test.csv';
const fileData = `Agent name,Conversations count,Avg first response time (Minutes),Avg resolution time (Minutes)
@@ -19,3 +19,11 @@ describe('#downloadCsvFile', () => {
expect(link.click).toHaveBeenCalledTimes(1);
});
});
+
+describe('#generateFileName', () => {
+ it('should generate the correct file name', () => {
+ expect(generateFileName({ type: 'csat', to: 1652812199 })).toEqual(
+ 'csat-report-17-05-2022.csv'
+ );
+ });
+});
diff --git a/app/javascript/dashboard/i18n/locale/en/report.json b/app/javascript/dashboard/i18n/locale/en/report.json
index 35a349b56..5f2c6ae48 100644
--- a/app/javascript/dashboard/i18n/locale/en/report.json
+++ b/app/javascript/dashboard/i18n/locale/en/report.json
@@ -354,6 +354,7 @@
"CSAT_REPORTS": {
"HEADER": "CSAT Reports",
"NO_RECORDS": "There are no CSAT survey responses available.",
+ "DOWNLOAD": "Download CSAT Reports",
"FILTERS": {
"AGENTS": {
"PLACEHOLDER": "Choose Agents"
diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/CsatResponses.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/CsatResponses.vue
index 4498ead8a..735c173ef 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/reports/CsatResponses.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/reports/CsatResponses.vue
@@ -3,9 +3,18 @@
+
+ {{ $t('CSAT_REPORTS.DOWNLOAD') }}
+
@@ -15,6 +24,7 @@ import CsatMetrics from './components/CsatMetrics';
import CsatTable from './components/CsatTable';
import ReportFilterSelector from './components/FilterSelector';
import { mapGetters } from 'vuex';
+import { generateFileName } from '../../../../helper/downloadHelper';
export default {
name: 'CsatResponses',
@@ -24,7 +34,7 @@ export default {
ReportFilterSelector,
},
data() {
- return { pageIndex: 1, from: 0, to: 0, user_ids: [] };
+ return { pageIndex: 1, from: 0, to: 0, userIds: [] };
},
computed: {
...mapGetters({
@@ -39,7 +49,7 @@ export default {
this.$store.dispatch('csat/getMetrics', {
from: this.from,
to: this.to,
- user_ids: this.user_ids,
+ user_ids: this.userIds,
});
this.getResponses();
},
@@ -48,7 +58,7 @@ export default {
page: this.pageIndex,
from: this.from,
to: this.to,
- user_ids: this.user_ids,
+ user_ids: this.userIds,
});
},
onPageNumberChange(pageIndex) {
@@ -61,9 +71,18 @@ export default {
this.getAllData();
},
onAgentsFilterChange(agents) {
- this.user_ids = agents.map(el => el.id);
+ this.userIds = agents.map(el => el.id);
this.getAllData();
},
+ downloadReports() {
+ const type = 'csat';
+ this.$store.dispatch('csat/downloadCSATReports', {
+ from: this.from,
+ to: this.to,
+ user_ids: this.userIds,
+ fileName: generateFileName({ type, to: this.to }),
+ });
+ },
},
};
diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/components/FilterSelector.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/components/FilterSelector.vue
index cadf4078e..848555a4d 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/reports/components/FilterSelector.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/reports/components/FilterSelector.vue
@@ -61,7 +61,10 @@
@input="handleAgentsFilterSelection"
/>
-
+
{{ $t('REPORT.BUSINESS_HOURS') }}
@@ -105,6 +108,10 @@ export default {
type: Boolean,
default: false,
},
+ showBusinessHoursSwitch: {
+ type: Boolean,
+ default: true,
+ },
},
data() {
return {
diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/components/WootReports.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/components/WootReports.vue
index 6d8b49c62..34188a12a 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/reports/components/WootReports.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/reports/components/WootReports.vue
@@ -61,6 +61,7 @@ import format from 'date-fns/format';
import { GROUP_BY_FILTER, METRIC_CHART } from '../constants';
import reportMixin from '../../../../../mixins/reportMixin';
import { formatTime } from '@chatwoot/utils';
+import { generateFileName } from '../../../../../helper/downloadHelper';
const REPORTS_KEYS = {
CONVERSATIONS: 'conversations_count',
@@ -250,26 +251,17 @@ export default {
});
},
downloadReports() {
- const { from, to } = this;
- const fileName = `${this.type}-report-${format(
- fromUnixTime(to),
- 'dd-MM-yyyy'
- )}.csv`;
- switch (this.type) {
- case 'agent':
- this.$store.dispatch('downloadAgentReports', { from, to, fileName });
- break;
- case 'label':
- this.$store.dispatch('downloadLabelReports', { from, to, fileName });
- break;
- case 'inbox':
- this.$store.dispatch('downloadInboxReports', { from, to, fileName });
- break;
- case 'team':
- this.$store.dispatch('downloadTeamReports', { from, to, fileName });
- break;
- default:
- break;
+ const { from, to, type } = this;
+ const dispatchMethods = {
+ agent: 'downloadAgentReports',
+ label: 'downloadLabelReports',
+ inbox: 'downloadInboxReports',
+ team: 'downloadTeamReports',
+ };
+ if (dispatchMethods[type]) {
+ const fileName = generateFileName({ type, to });
+ const params = { from, to, fileName };
+ this.$store.dispatch(dispatchMethods[type], params);
}
},
changeSelection(index) {
diff --git a/app/javascript/dashboard/store/modules/csat.js b/app/javascript/dashboard/store/modules/csat.js
index 7bc1dad6d..2557e2698 100644
--- a/app/javascript/dashboard/store/modules/csat.js
+++ b/app/javascript/dashboard/store/modules/csat.js
@@ -1,6 +1,7 @@
import * as MutationHelpers from 'shared/helpers/vuex/mutationHelpers';
import types from '../mutation-types';
import CSATReports from '../../api/csatReports';
+import { downloadCsvFile } from '../../helper/downloadHelper';
const computeDistribution = (value, total) =>
((value * 100) / total).toFixed(2);
@@ -107,6 +108,11 @@ export const actions = {
commit(types.SET_CSAT_RESPONSE_UI_FLAG, { isFetchingMetrics: false });
}
},
+ downloadCSATReports(_, params) {
+ return CSATReports.download(params).then(response => {
+ downloadCsvFile(params.fileName, response.data);
+ });
+ },
};
export const mutations = {
diff --git a/app/javascript/dashboard/store/modules/reports.js b/app/javascript/dashboard/store/modules/reports.js
index dc9a6c33b..9ae571803 100644
--- a/app/javascript/dashboard/store/modules/reports.js
+++ b/app/javascript/dashboard/store/modules/reports.js
@@ -5,7 +5,7 @@ import * as types from '../mutation-types';
import Report from '../../api/reports';
import Vue from 'vue';
-import { downloadCsvFile } from '../../helper/downloadCsvFile';
+import { downloadCsvFile } from '../../helper/downloadHelper';
const state = {
fetchingStatus: false,
diff --git a/app/policies/csat_survey_response_policy.rb b/app/policies/csat_survey_response_policy.rb
index c0ce8821b..afcce00e9 100644
--- a/app/policies/csat_survey_response_policy.rb
+++ b/app/policies/csat_survey_response_policy.rb
@@ -6,4 +6,8 @@ class CsatSurveyResponsePolicy < ApplicationPolicy
def metrics?
@account_user.administrator?
end
+
+ def download?
+ @account_user.administrator?
+ end
end
diff --git a/app/views/api/v1/accounts/csat_survey_responses/download.csv.erb b/app/views/api/v1/accounts/csat_survey_responses/download.csv.erb
new file mode 100644
index 000000000..8b694ed35
--- /dev/null
+++ b/app/views/api/v1/accounts/csat_survey_responses/download.csv.erb
@@ -0,0 +1,38 @@
+<%=
+ CSV.generate_line([
+ I18n.t('reports.csat.headers.agent_name'),
+ I18n.t('reports.csat.headers.rating'),
+ I18n.t('reports.csat.headers.feedback'),
+ I18n.t('reports.csat.headers.contact_name'),
+ I18n.t('reports.csat.headers.contact_email_address'),
+ I18n.t('reports.csat.headers.contact_phone_number'),
+ I18n.t('reports.csat.headers.link_to_the_conversation'),
+ I18n.t('reports.csat.headers.recorded_at')
+ ])
+-%>
+<% @csat_survey_responses.each do |csat_response| %>
+<% assigned_agent = csat_response.assigned_agent %>
+<% contact = csat_response.contact %>
+<% conversation = csat_response.conversation %>
+<%=
+ CSV.generate_line([
+ assigned_agent ? "#{assigned_agent.name} (#{assigned_agent.email})" : nil,
+ csat_response.rating,
+ csat_response.feedback_message.present? ? csat_response.feedback_message : nil,
+ contact&.name.present? ? contact&.name: nil,
+ contact&.email.present? ? contact&.email: nil,
+ contact&.phone_number.present? ? contact&.phone_number: nil,
+ conversation ? app_account_conversation_url(account_id: Current.account.id, id: conversation.display_id): nil,
+ csat_response.created_at,
+ ])
+-%>
+<% end %>
+<%=
+ CSV.generate_line([
+ I18n.t(
+ 'reports.period',
+ since: Date.strptime(params[:since], '%s'),
+ until: Date.strptime(params[:until], '%s')
+ )
+ ])
+-%>
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 0c92c32eb..bd7829b53 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -60,6 +60,16 @@ en:
avg_first_response_time: Avg first response time (Minutes)
avg_resolution_time: Avg resolution time (Minutes)
default_group_by: day
+ csat:
+ headers:
+ contact_name: Contact Name
+ contact_email_address: Contact Email Address
+ contact_phone_number: Contact Phone Number
+ link_to_the_conversation: Link to the conversation
+ agent_name: Agent Name
+ rating: Rating
+ feedback: Feedback Comment
+ recorded_at: Recorded date
notifications:
notification_title:
diff --git a/config/routes.rb b/config/routes.rb
index aa23be1e7..a80f6ca39 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -106,6 +106,7 @@ Rails.application.routes.draw do
resources :csat_survey_responses, only: [:index] do
collection do
get :metrics
+ get :download
end
end
resources :custom_attribute_definitions, only: [:index, :show, :create, :update, :destroy]
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
index abb4231d9..f431f0ed5 100644
--- a/spec/controllers/api/v1/accounts/csat_survey_responses_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts/csat_survey_responses_controller_spec.rb
@@ -148,4 +148,38 @@ RSpec.describe 'CSAT Survey Responses API', type: :request do
end
end
end
+
+ describe 'GET /api/v1/accounts/{account.id}/csat_survey_responses/download' do
+ context 'when it is an unauthenticated user' do
+ it 'returns unauthorized' do
+ get "/api/v1/accounts/#{account.id}/csat_survey_responses/download"
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+
+ context 'when it is an authenticated user' do
+ let(:params) { { since: 5.days.ago.to_time.to_i.to_s, until: Time.zone.tomorrow.to_time.to_i.to_s } }
+
+ it 'returns unauthorized for agents' do
+ get "/api/v1/accounts/#{account.id}/csat_survey_responses/download",
+ params: params,
+ headers: agent.create_new_auth_token
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'returns summary' do
+ get "/api/v1/accounts/#{account.id}/csat_survey_responses/download",
+ params: params,
+ headers: administrator.create_new_auth_token
+
+ expect(response).to have_http_status(:success)
+
+ content = CSV.parse(response.body)
+ # Check rating from CSAT Row
+ expect(content[1][1]).to eq '1'
+ expect(content.length).to eq 3
+ end
+ end
+ end
end