From a8b302d4cd1950c76184488b4b1ae01ca4faba7b Mon Sep 17 00:00:00 2001 From: Pranav Date: Thu, 15 Jan 2026 19:53:57 -0800 Subject: [PATCH] feat(ee): Review Notes for CSAT Reports (#13289) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CSAT scores are helpful, but on their own they rarely tell the full story. A drop in rating can come from delayed timelines, unclear expectations, or simple misunderstandings, even when the issue itself was handled correctly. Review Notes for CSAT let admins/report manager roles add internal-only context next to each CSAT response. This makes it easier to interpret scores properly and focus on patterns and root causes, not just numbers. image ### Why this matters * Capture the real context behind individual CSAT ratings * Clarify whether a low score points to a genuine service issue or a process gap * Spot recurring themes across conversations and teams * Make CSAT reviews more useful for leadership reviews and retrospectives ### How Review Notes work **View CSAT responses** Open the CSAT report to see overall metrics, rating distribution, and individual responses. **Add a Review Note** For any CSAT entry, managers can add a Review Note directly below the customer’s feedback. **Document internal insights** Use Review Notes to capture things like: * Why a score was lower or higher than expected * Patterns you are seeing across similar cases * Observations around communication, timelines, or customer expectations Review Notes are visible only to administrators and people with report access only. We may expand visibility to agents in the future based on feedback. However, customers never see them. Each note clearly shows who added it and when, making it easy to review context and changes over time. --- .../csat_survey_responses_controller.rb | 2 + .../components/ui/DatePicker/DatePicker.vue | 6 +- .../dashboard/components/widgets/ShowMore.vue | 2 +- .../dashboard/i18n/locale/en/report.json | 55 +++- .../settings/reports/CsatResponses.vue | 15 +- .../reports/components/ConversationCell.vue | 26 -- .../components/Csat/CsatFilterHelpers.js | 36 +++ .../reports/components/Csat/CsatFilters.vue | 267 ++++++++++++++++++ .../reports/components/CsatContactCell.vue | 52 ++++ .../reports/components/CsatEmptyState.vue | 32 +++ .../reports/components/CsatExpandedRow.vue | 162 +++++++++++ .../components/CsatMetricCard.story.vue | 53 ++++ .../reports/components/CsatMetricCard.vue | 41 +++ .../reports/components/CsatMetrics.vue | 180 ++++-------- .../CsatRatingDistribution.story.vue | 75 +++++ .../components/CsatRatingDistribution.vue | 101 +++++++ .../components/CsatReviewNotesPaywall.vue | 28 ++ .../settings/reports/components/CsatTable.vue | 264 +++++++++++------ .../reports/components/CsatTableLoader.vue | 36 +++ .../components/specs/CSATMetrics.spec.js | 42 ++- .../__snapshots__/CSATMetrics.spec.js.snap | 10 - .../dashboard/store/modules/csat.js | 11 + .../store/modules/specs/csat/getters.spec.js | 15 + .../dashboard/store/mutation-types.js | 1 + app/models/csat_survey_response.rb | 1 + app/models/user.rb | 2 + .../csat_survey_responses/download.csv.erb | 28 +- .../update.json.jbuilder | 1 + .../_csat_survey_response.json.jbuilder | 8 + config/features.yml | 4 + config/locales/en.yml | 1 + config/routes.rb | 3 + ...l_observations_to_csat_survey_responses.rb | 5 + ...rvations_audit_to_csat_survey_responses.rb | 6 + db/schema.rb | 6 +- .../csat_survey_responses_controller.rb | 12 + .../enterprise/csat_survey_response_policy.rb | 4 + .../billing/handle_stripe_event_service.rb | 2 +- enterprise/config/premium_features.yml | 1 + .../csat_survey_responses_controller_spec.rb | 85 ++++++ 40 files changed, 1376 insertions(+), 305 deletions(-) delete mode 100644 app/javascript/dashboard/routes/dashboard/settings/reports/components/ConversationCell.vue create mode 100644 app/javascript/dashboard/routes/dashboard/settings/reports/components/Csat/CsatFilterHelpers.js create mode 100644 app/javascript/dashboard/routes/dashboard/settings/reports/components/Csat/CsatFilters.vue create mode 100644 app/javascript/dashboard/routes/dashboard/settings/reports/components/CsatContactCell.vue create mode 100644 app/javascript/dashboard/routes/dashboard/settings/reports/components/CsatEmptyState.vue create mode 100644 app/javascript/dashboard/routes/dashboard/settings/reports/components/CsatExpandedRow.vue create mode 100644 app/javascript/dashboard/routes/dashboard/settings/reports/components/CsatMetricCard.story.vue create mode 100644 app/javascript/dashboard/routes/dashboard/settings/reports/components/CsatMetricCard.vue create mode 100644 app/javascript/dashboard/routes/dashboard/settings/reports/components/CsatRatingDistribution.story.vue create mode 100644 app/javascript/dashboard/routes/dashboard/settings/reports/components/CsatRatingDistribution.vue create mode 100644 app/javascript/dashboard/routes/dashboard/settings/reports/components/CsatReviewNotesPaywall.vue create mode 100644 app/javascript/dashboard/routes/dashboard/settings/reports/components/CsatTableLoader.vue delete mode 100644 app/javascript/dashboard/routes/dashboard/settings/reports/components/specs/__snapshots__/CSATMetrics.spec.js.snap create mode 100644 app/views/api/v1/accounts/csat_survey_responses/update.json.jbuilder create mode 100644 db/migrate/20260114192518_add_internal_observations_to_csat_survey_responses.rb create mode 100644 db/migrate/20260114201315_add_observations_audit_to_csat_survey_responses.rb create mode 100644 enterprise/app/controllers/enterprise/api/v1/accounts/csat_survey_responses_controller.rb create mode 100644 spec/enterprise/controllers/enterprise/api/v1/accounts/csat_survey_responses_controller_spec.rb 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 f5bed6c34..0cde5f5c1 100644 --- a/app/controllers/api/v1/accounts/csat_survey_responses_controller.rb +++ b/app/controllers/api/v1/accounts/csat_survey_responses_controller.rb @@ -50,3 +50,5 @@ class Api::V1::Accounts::CsatSurveyResponsesController < Api::V1::Accounts::Base @current_page = params[:page] || 1 end end + +Api::V1::Accounts::CsatSurveyResponsesController.prepend_mod_with('Api::V1::Accounts::CsatSurveyResponsesController') diff --git a/app/javascript/dashboard/components/ui/DatePicker/DatePicker.vue b/app/javascript/dashboard/components/ui/DatePicker/DatePicker.vue index bbb31d72c..886bc30a0 100644 --- a/app/javascript/dashboard/components/ui/DatePicker/DatePicker.vue +++ b/app/javascript/dashboard/components/ui/DatePicker/DatePicker.vue @@ -206,10 +206,14 @@ const emitDateRange = () => { emit('dateRangeChanged', [selectedStartDate.value, selectedEndDate.value]); } }; + +const closeDatePicker = () => { + showDatePicker.value = false; +};