+
-
-
-
-
diff --git a/app/javascript/dashboard/featureFlags.js b/app/javascript/dashboard/featureFlags.js
index 2936d22ea..f2fd3e757 100644
--- a/app/javascript/dashboard/featureFlags.js
+++ b/app/javascript/dashboard/featureFlags.js
@@ -19,4 +19,5 @@ export const FEATURE_FLAGS = {
INSERT_ARTICLE_IN_REPLY: 'insert_article_in_reply',
INBOX_VIEW: 'inbox_view',
SLA: 'sla',
+ RESPONSE_BOT: 'response_bot',
};
diff --git a/app/javascript/dashboard/i18n/locale/en/contactFilters.json b/app/javascript/dashboard/i18n/locale/en/contactFilters.json
index 09a543984..02d5dcf89 100644
--- a/app/javascript/dashboard/i18n/locale/en/contactFilters.json
+++ b/app/javascript/dashboard/i18n/locale/en/contactFilters.json
@@ -44,7 +44,8 @@
"CUSTOM_ATTRIBUTE_CHECKBOX": "Checkbox",
"CREATED_AT": "Created At",
"LAST_ACTIVITY": "Last Activity",
- "REFERER_LINK": "Referrer link"
+ "REFERER_LINK": "Referrer link",
+ "BLOCKED": "Blocked"
},
"GROUPS": {
"STANDARD_FILTERS": "Standard Filters",
diff --git a/app/javascript/dashboard/i18n/locale/en/report.json b/app/javascript/dashboard/i18n/locale/en/report.json
index a6d476bc9..56ca21773 100644
--- a/app/javascript/dashboard/i18n/locale/en/report.json
+++ b/app/javascript/dashboard/i18n/locale/en/report.json
@@ -35,6 +35,14 @@
"NAME": "Resolution Count",
"DESC": "( Total )"
},
+ "BOT_RESOLUTION_COUNT": {
+ "NAME": "Resolution Count",
+ "DESC": "( Total )"
+ },
+ "BOT_HANDOFF_COUNT": {
+ "NAME": "Handoff Count",
+ "DESC": "( Total )"
+ },
"REPLY_TIME": {
"NAME": "Customer waiting time",
"TOOLTIP_TEXT": "Waiting time is %{metricValue} (based on %{conversationCount} replies)"
@@ -86,20 +94,49 @@
"MONTH": "Month",
"YEAR": "Year"
},
- "GROUP_BY_DAY_OPTIONS": [{ "id": 1, "groupBy": "Day" }],
+ "GROUP_BY_DAY_OPTIONS": [
+ {
+ "id": 1,
+ "groupBy": "Day"
+ }
+ ],
"GROUP_BY_WEEK_OPTIONS": [
- { "id": 1, "groupBy": "Day" },
- { "id": 2, "groupBy": "Week" }
+ {
+ "id": 1,
+ "groupBy": "Day"
+ },
+ {
+ "id": 2,
+ "groupBy": "Week"
+ }
],
"GROUP_BY_MONTH_OPTIONS": [
- { "id": 1, "groupBy": "Day" },
- { "id": 2, "groupBy": "Week" },
- { "id": 3, "groupBy": "Month" }
+ {
+ "id": 1,
+ "groupBy": "Day"
+ },
+ {
+ "id": 2,
+ "groupBy": "Week"
+ },
+ {
+ "id": 3,
+ "groupBy": "Month"
+ }
],
"GROUP_BY_YEAR_OPTIONS": [
- { "id": 2, "groupBy": "Week" },
- { "id": 3, "groupBy": "Month" },
- { "id": 4, "groupBy": "Year" }
+ {
+ "id": 2,
+ "groupBy": "Week"
+ },
+ {
+ "id": 3,
+ "groupBy": "Month"
+ },
+ {
+ "id": 4,
+ "groupBy": "Year"
+ }
],
"BUSINESS_HOURS": "Business Hours"
},
@@ -404,6 +441,27 @@
}
}
},
+ "BOT_REPORTS": {
+ "HEADER": "Bot Reports",
+ "METRIC": {
+ "TOTAL_CONVERSATIONS": {
+ "LABEL": "No. of Conversations",
+ "TOOLTIP": "Total number of conversations handled by the bot"
+ },
+ "TOTAL_RESPONSES": {
+ "LABEL": "Total Responses",
+ "TOOLTIP": "Total number of responses sent by the bot"
+ },
+ "RESOLUTION_RATE": {
+ "LABEL": "Resolution Rate",
+ "TOOLTIP": "Total number of conversations resolved by the bot / Total number of conversations handled by the bot * 100"
+ },
+ "HANDOFF_RATE": {
+ "LABEL": "Handoff Rate",
+ "TOOLTIP": "Total number of conversations handed off to agents / Total number of conversations handled by the bot * 100"
+ }
+ }
+ },
"OVERVIEW_REPORTS": {
"HEADER": "Overview",
"LIVE": "Live",
diff --git a/app/javascript/dashboard/i18n/locale/en/settings.json b/app/javascript/dashboard/i18n/locale/en/settings.json
index 63725f949..9a4bde2c8 100644
--- a/app/javascript/dashboard/i18n/locale/en/settings.json
+++ b/app/javascript/dashboard/i18n/locale/en/settings.json
@@ -234,6 +234,7 @@
"CAMPAIGNS": "Campaigns",
"ONGOING": "Ongoing",
"ONE_OFF": "One off",
+ "REPORTS_BOT": "Bot",
"REPORTS_AGENT": "Agents",
"REPORTS_LABEL": "Labels",
"REPORTS_INBOX": "Inbox",
diff --git a/app/javascript/dashboard/i18n/locale/en/sla.json b/app/javascript/dashboard/i18n/locale/en/sla.json
index d87f057eb..228f87066 100644
--- a/app/javascript/dashboard/i18n/locale/en/sla.json
+++ b/app/javascript/dashboard/i18n/locale/en/sla.json
@@ -55,11 +55,17 @@
"ERROR_MESSAGE": "There was an error, please try again"
}
},
- "EDIT": {
- "TITLE": "Edit SLA",
+ "DELETE": {
+ "TITLE": "Delete SLA",
"API": {
- "SUCCESS_MESSAGE": "SLA updated successfully",
+ "SUCCESS_MESSAGE": "SLA deleted successfully",
"ERROR_MESSAGE": "There was an error, please try again"
+ },
+ "CONFIRM": {
+ "TITLE": "Confirm Deletion",
+ "MESSAGE": "Are you sure you want to delete ",
+ "YES": "Yes, Delete ",
+ "NO": "No, Keep "
}
}
}
diff --git a/app/javascript/dashboard/mixins/reportMixin.js b/app/javascript/dashboard/mixins/reportMixin.js
index 2b8a5f87d..d57af1ad2 100644
--- a/app/javascript/dashboard/mixins/reportMixin.js
+++ b/app/javascript/dashboard/mixins/reportMixin.js
@@ -2,11 +2,19 @@ import { mapGetters } from 'vuex';
import { formatTime } from '@chatwoot/utils';
export default {
+ props: {
+ accountSummaryKey: {
+ type: String,
+ default: 'getAccountSummary',
+ },
+ },
computed: {
...mapGetters({
- accountSummary: 'getAccountSummary',
accountReport: 'getAccountReports',
}),
+ accountSummary() {
+ return this.$store.getters[this.accountSummaryKey];
+ },
},
methods: {
calculateTrend(key) {
diff --git a/app/javascript/dashboard/mixins/specs/reportMixin.spec.js b/app/javascript/dashboard/mixins/specs/reportMixin.spec.js
index d981de4e9..c0bc1e15f 100644
--- a/app/javascript/dashboard/mixins/specs/reportMixin.spec.js
+++ b/app/javascript/dashboard/mixins/specs/reportMixin.spec.js
@@ -11,11 +11,42 @@ describe('reportMixin', () => {
beforeEach(() => {
getters = {
getAccountSummary: () => reportFixtures.summary,
+ getBotSummary: () => reportFixtures.botSummary,
getAccountReports: () => reportFixtures.report,
};
store = new Vuex.Store({ getters });
});
+ it('display the metric for account', async () => {
+ const Component = {
+ render() {},
+ title: 'TestComponent',
+ mixins: [reportMixin],
+ };
+ const wrapper = shallowMount(Component, { store, localVue });
+ await wrapper.setProps({
+ accountSummaryKey: 'getAccountSummary',
+ });
+ expect(wrapper.vm.displayMetric('conversations_count')).toEqual('5,000');
+ expect(wrapper.vm.displayMetric('avg_first_response_time')).toEqual(
+ '3 Min 18 Sec'
+ );
+ });
+
+ it('display the metric for bot', async () => {
+ const Component = {
+ render() {},
+ title: 'TestComponent',
+ mixins: [reportMixin],
+ };
+ const wrapper = shallowMount(Component, { store, localVue });
+ await wrapper.setProps({
+ accountSummaryKey: 'getBotSummary',
+ });
+ expect(wrapper.vm.displayMetric('bot_resolutions_count')).toEqual('10');
+ expect(wrapper.vm.displayMetric('bot_handoffs_count')).toEqual('20');
+ });
+
it('display the metric', () => {
const Component = {
render() {},
diff --git a/app/javascript/dashboard/mixins/specs/reportMixinFixtures.js b/app/javascript/dashboard/mixins/specs/reportMixinFixtures.js
index ab6b6fecf..591bf7c1f 100644
--- a/app/javascript/dashboard/mixins/specs/reportMixinFixtures.js
+++ b/app/javascript/dashboard/mixins/specs/reportMixinFixtures.js
@@ -15,6 +15,14 @@ export default {
},
resolutions_count: 3,
},
+ botSummary: {
+ bot_resolutions_count: 10,
+ bot_handoffs_count: 20,
+ previous: {
+ bot_resolutions_count: 8,
+ bot_handoffs_count: 5,
+ },
+ },
report: {
data: [
{ value: '0.00', timestamp: 1647541800, count: 0 },
diff --git a/app/javascript/dashboard/routes/dashboard/contacts/components/ContactsAdvancedFilters.vue b/app/javascript/dashboard/routes/dashboard/contacts/components/ContactsAdvancedFilters.vue
index 41ceb25e5..6f5e92017 100644
--- a/app/javascript/dashboard/routes/dashboard/contacts/components/ContactsAdvancedFilters.vue
+++ b/app/javascript/dashboard/routes/dashboard/contacts/components/ContactsAdvancedFilters.vue
@@ -243,7 +243,7 @@ export default {
attr.attribute_display_type === 'checkbox'
);
});
- if (isCustomAttributeCheckbox) {
+ if (isCustomAttributeCheckbox || type === 'blocked') {
return [
{
id: true,
diff --git a/app/javascript/dashboard/routes/dashboard/contacts/contactFilterItems/index.js b/app/javascript/dashboard/routes/dashboard/contacts/contactFilterItems/index.js
index 2d54c37bc..59376f8ef 100644
--- a/app/javascript/dashboard/routes/dashboard/contacts/contactFilterItems/index.js
+++ b/app/javascript/dashboard/routes/dashboard/contacts/contactFilterItems/index.js
@@ -76,6 +76,14 @@ const filterTypes = [
filterOperators: OPERATOR_TYPES_5,
attributeModel: 'standard',
},
+ {
+ attributeKey: 'blocked',
+ attributeI18nKey: 'BLOCKED',
+ inputType: 'search_select',
+ dataType: 'text',
+ filterOperators: OPERATOR_TYPES_1,
+ attributeModel: 'standard',
+ },
];
export const filterAttributeGroups = [
@@ -115,6 +123,10 @@ export const filterAttributeGroups = [
key: 'last_activity_at',
i18nKey: 'LAST_ACTIVITY',
},
+ {
+ key: 'blocked',
+ i18nKey: 'BLOCKED',
+ },
],
},
];
diff --git a/app/javascript/dashboard/routes/dashboard/settings/profile/ChangePassword.vue b/app/javascript/dashboard/routes/dashboard/settings/profile/ChangePassword.vue
index 8eb7201ae..99cee0325 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/profile/ChangePassword.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/profile/ChangePassword.vue
@@ -74,6 +74,7 @@
import { required, minLength } from 'vuelidate/lib/validators';
import { mapGetters } from 'vuex';
import alertMixin from 'shared/mixins/alertMixin';
+import { parseAPIErrorResponse } from 'dashboard/store/utils/api';
export default {
mixins: [alertMixin],
@@ -125,10 +126,9 @@ export default {
});
this.errorMessage = this.$t('PROFILE_SETTINGS.PASSWORD_UPDATE_SUCCESS');
} catch (error) {
- this.errorMessage = this.$t('RESET_PASSWORD.API.ERROR_MESSAGE');
- if (error?.response?.data?.message) {
- this.errorMessage = error.response.data.message;
- }
+ this.errorMessage =
+ parseAPIErrorResponse(error) ||
+ this.$t('RESET_PASSWORD.API.ERROR_MESSAGE');
} finally {
this.isPasswordChanging = false;
this.showAlert(this.errorMessage);
diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/BotReports.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/BotReports.vue
new file mode 100644
index 000000000..a75051aaf
--- /dev/null
+++ b/app/javascript/dashboard/routes/dashboard/settings/reports/BotReports.vue
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/ReportContainer.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/ReportContainer.vue
index c601b4277..273f814b7 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/reports/ReportContainer.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/reports/ReportContainer.vue
@@ -7,7 +7,7 @@
:key="metric.KEY"
class="p-4 rounded-md mb-3"
>
-
+
+import { ref, watch, onMounted } from 'vue';
+import ReportMetricCard from './ReportMetricCard.vue';
+import ReportsAPI from 'dashboard/api/reports';
+
+const props = defineProps({
+ filters: {
+ type: Object,
+ required: true,
+ },
+});
+
+const conversationCount = ref('0');
+const messageCount = ref('0');
+const resolutionRate = ref('0');
+const handoffRate = ref('0');
+
+const formatToPercent = value => {
+ return value ? `${value}%` : '--';
+};
+
+const fetchMetrics = () => {
+ if (!props.filters.to || !props.filters.from) {
+ return;
+ }
+ ReportsAPI.getBotMetrics(props.filters).then(response => {
+ conversationCount.value = response.data.conversation_count.toLocaleString();
+ messageCount.value = response.data.message_count.toLocaleString();
+ resolutionRate.value = response.data.resolution_rate.toString();
+ handoffRate.value = response.data.handoff_rate.toString();
+ });
+};
+
+watch(() => props.filters, fetchMetrics, { deep: true });
+
+onMounted(fetchMetrics);
+
+
+
+
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/components/ChartElements/ChartStats.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/components/ChartElements/ChartStats.vue
index 94fe1359d..4faf4de2b 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/reports/components/ChartElements/ChartStats.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/reports/components/ChartElements/ChartStats.vue
@@ -1,6 +1,8 @@
-
-
{{ metric.NAME }}
+
+
+ {{ metric.NAME }}
+
{{ displayMetric(metric.KEY) }}
diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/components/CsatMetrics.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/components/CsatMetrics.vue
index 5fe1fa358..77106e8d5 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/reports/components/CsatMetrics.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/reports/components/CsatMetrics.vue
@@ -6,17 +6,20 @@
:label="$t('CSAT_REPORTS.METRIC.TOTAL_RESPONSES.LABEL')"
:info-text="$t('CSAT_REPORTS.METRIC.TOTAL_RESPONSES.TOOLTIP')"
:value="responseCount"
+ class="xs:w-full sm:max-w-[50%] lg:w-1/6 lg:max-w-[16%]"
/>
-
-
-
+
+
+
`;
diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/constants.js b/app/javascript/dashboard/routes/dashboard/settings/reports/constants.js
index cb4e5bb6b..d465c93b6 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/reports/constants.js
+++ b/app/javascript/dashboard/routes/dashboard/settings/reports/constants.js
@@ -194,6 +194,8 @@ export const METRIC_CHART = {
reply_time: TIME_CHART_CONFIG,
avg_resolution_time: TIME_CHART_CONFIG,
resolutions_count: DEFAULT_CHART,
+ bot_resolutions_count: DEFAULT_CHART,
+ bot_handoffs_count: DEFAULT_CHART,
};
export const OVERVIEW_METRICS = {
diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/reports.routes.js b/app/javascript/dashboard/routes/dashboard/settings/reports/reports.routes.js
index 31429e9c2..5e363553d 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/reports/reports.routes.js
+++ b/app/javascript/dashboard/routes/dashboard/settings/reports/reports.routes.js
@@ -7,6 +7,7 @@ const LabelReports = () => import('./LabelReports.vue');
const InboxReports = () => import('./InboxReports.vue');
const TeamReports = () => import('./TeamReports.vue');
const CsatResponses = () => import('./CsatResponses.vue');
+const BotReports = () => import('./BotReports.vue');
const LiveReports = () => import('./LiveReports.vue');
export default {
@@ -66,6 +67,23 @@ export default {
},
],
},
+ {
+ path: frontendURL('accounts/:accountId/reports'),
+ component: SettingsContent,
+ props: {
+ headerTitle: 'BOT_REPORTS.HEADER',
+ icon: 'bot',
+ keepAlive: false,
+ },
+ children: [
+ {
+ path: 'bot',
+ name: 'bot_reports',
+ roles: ['administrator'],
+ component: BotReports,
+ },
+ ],
+ },
{
path: frontendURL('accounts/:accountId/reports'),
component: SettingsContent,
diff --git a/app/javascript/dashboard/routes/dashboard/settings/sla/EditSLA.vue b/app/javascript/dashboard/routes/dashboard/settings/sla/EditSLA.vue
deleted file mode 100644
index 2fc577bad..000000000
--- a/app/javascript/dashboard/routes/dashboard/settings/sla/EditSLA.vue
+++ /dev/null
@@ -1,60 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/app/javascript/dashboard/routes/dashboard/settings/sla/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/sla/Index.vue
index c6bdb6968..8728a236c 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/sla/Index.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/sla/Index.vue
@@ -58,14 +58,14 @@
|
@@ -81,9 +81,16 @@
-
-
-
+