diff --git a/app/javascript/dashboard/components/layout/config/sidebarItems/settings.js b/app/javascript/dashboard/components/layout/config/sidebarItems/settings.js index 176fd152c..0b78e8a21 100644 --- a/app/javascript/dashboard/components/layout/config/sidebarItems/settings.js +++ b/app/javascript/dashboard/components/layout/config/sidebarItems/settings.js @@ -18,6 +18,7 @@ const settings = accountId => ({ 'settings_integrations_webhook', 'settings_integrations_integration', 'settings_applications', + 'settings_integrations_dashboard_apps', 'settings_applications_webhook', 'settings_applications_integration', 'general_settings', diff --git a/app/javascript/dashboard/i18n/locale/en/integrations.json b/app/javascript/dashboard/i18n/locale/en/integrations.json index 2094f269b..6c6cecde9 100644 --- a/app/javascript/dashboard/i18n/locale/en/integrations.json +++ b/app/javascript/dashboard/i18n/locale/en/integrations.json @@ -35,7 +35,10 @@ "LIST": { "404": "There are no webhooks configured for this account.", "TITLE": "Manage webhooks", - "TABLE_HEADER": ["Webhook endpoint", "Actions"] + "TABLE_HEADER": [ + "Webhook endpoint", + "Actions" + ] }, "EDIT": { "BUTTON_TEXT": "Edit", @@ -68,7 +71,7 @@ } }, "SLACK": { - "HELP_TEXT" : { + "HELP_TEXT": { "TITLE": "Using Slack Integration", "BODY": "

Chatwoot will now sync all the incoming conversations into the customer-conversations channel inside your slack workplace.

Replying to a conversation thread in customer-conversations slack channel will create a response back to the customer through chatwoot.

Start the replies with note: to create private notes instead of replies.

If the replier on slack has an agent profile in chatwoot under the same email, the replies will be associated accordingly.

When the replier doesn't have an associated agent profile, the replies will be made from the bot profile.

" } @@ -81,6 +84,52 @@ }, "CONNECT": { "BUTTON_TEXT": "Connect" + }, + "DASHBOARD_APPS": { + "TITLE": "Dashboard Apps", + "HEADER_BTN_TXT": "Add a new dashboard app", + "SIDEBAR_TXT": "

Dashboard Apps

Dashboard Apps allow organizations to embed an application inside the Chatwoot dashboard to provide the context for customer support agents. This feature allows you to create an application independently and embed that inside the dashboard to provide user information, their orders, or their previous payment history.

When you embed your application using the dashboard in Chatwoot, your application will get the context of the conversation and contact as a window event. Implement a listener for the message event on your page to receive the context.

To add a new dashboard app, click on the button 'Add a new dashboard app'.

", + "DESCRIPTION": "Dashboard Apps allow organizations to embed an application inside the dashboard to provide the context for customer support agents. This feature allows you to create an application independently and embed that to provide user information, their orders, or their previous payment history.", + "LIST": { + "404": "There are no dashboard apps configured on this account yet", + "LOADING": "Fetching dashboard apps...", + "TABLE_HEADER": [ + "Name", + "Endpoint" + ], + "EDIT_TOOLTIP": "Edit app", + "DELETE_TOOLTIP": "Delete app" + }, + "FORM": { + "TITLE_LABEL": "Name", + "TITLE_PLACEHOLDER": "Enter a name for your dashboard app", + "TITLE_ERROR": "A name for the dashboard app is required", + "URL_LABEL": "Endpoint", + "URL_PLACEHOLDER": "Enter the endpoint URL where your app is hosted", + "URL_ERROR": "A valid URL is required" + }, + "CREATE": { + "HEADER": "Add a new dashboard app", + "FORM_SUBMIT": "Submit", + "FORM_CANCEL": "Cancel", + "API_SUCCESS": "Dashboard app configured successfully", + "API_ERROR": "We couldn't create an app. Please try again later" + }, + "UPDATE": { + "HEADER": "Edit dashboard app", + "FORM_SUBMIT": "Update", + "FORM_CANCEL": "Cancel", + "API_SUCCESS": "Dashboard app updated successfully", + "API_ERROR": "We couldn't update the app. Please try again later" + }, + "DELETE": { + "CONFIRM_YES": "Yes, delete it", + "CONFIRM_NO": "No, keep it", + "TITLE": "Confirm deletion", + "MESSAGE": "Are you sure to delete the app - %{appName}?", + "API_SUCCESS": "Dashboard app deleted successfully", + "API_ERROR": "We couldn't delete the app. Please try again later" + } } } } diff --git a/app/javascript/dashboard/routes/dashboard/settings/integrations/DashboardApps/DashboardAppModal.vue b/app/javascript/dashboard/routes/dashboard/settings/integrations/DashboardApps/DashboardAppModal.vue new file mode 100644 index 000000000..00e99edb8 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/integrations/DashboardApps/DashboardAppModal.vue @@ -0,0 +1,174 @@ + + + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/integrations/DashboardApps/DashboardAppsRow.vue b/app/javascript/dashboard/routes/dashboard/settings/integrations/DashboardApps/DashboardAppsRow.vue new file mode 100644 index 000000000..6b1cce332 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/integrations/DashboardApps/DashboardAppsRow.vue @@ -0,0 +1,45 @@ + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/integrations/DashboardApps/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/integrations/DashboardApps/Index.vue new file mode 100644 index 000000000..ce0b48e3e --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/integrations/DashboardApps/Index.vue @@ -0,0 +1,158 @@ + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/integrations/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/integrations/Index.vue index 5bfeceac2..c7452067e 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/integrations/Index.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/integrations/Index.vue @@ -17,6 +17,20 @@ :integration-action="item.action" /> +
+ +
diff --git a/app/javascript/dashboard/routes/dashboard/settings/integrations/integrations.routes.js b/app/javascript/dashboard/routes/dashboard/settings/integrations/integrations.routes.js index 4240062e6..211945308 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/integrations/integrations.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/integrations/integrations.routes.js @@ -1,6 +1,7 @@ import Index from './Index'; import SettingsContent from '../Wrapper'; import Webhook from './Webhooks/Index'; +import DashboardApps from './DashboardApps/Index'; import ShowIntegration from './ShowIntegration'; import { frontendURL } from '../../../../helper/URLHelper'; @@ -35,6 +36,12 @@ export default { name: 'settings_integrations_webhook', roles: ['administrator'], }, + { + path: 'dashboard-apps', + component: DashboardApps, + name: 'settings_integrations_dashboard_apps', + roles: ['administrator'], + }, { path: ':integration_id', name: 'settings_integrations_integration', diff --git a/app/javascript/dashboard/store/modules/dashboardApps.js b/app/javascript/dashboard/store/modules/dashboardApps.js index fda1f4de4..260b9409a 100644 --- a/app/javascript/dashboard/store/modules/dashboardApps.js +++ b/app/javascript/dashboard/store/modules/dashboardApps.js @@ -32,6 +32,40 @@ export const actions = { commit(types.SET_DASHBOARD_APPS_UI_FLAG, { isFetching: false }); } }, + create: async function createApp({ commit }, appObj) { + commit(types.SET_DASHBOARD_APPS_UI_FLAG, { isCreating: true }); + try { + const response = await DashboardAppsAPI.create(appObj); + commit(types.CREATE_DASHBOARD_APP, response.data); + } catch (error) { + const errorMessage = error?.response?.data?.message; + throw new Error(errorMessage); + } finally { + commit(types.SET_DASHBOARD_APPS_UI_FLAG, { isCreating: false }); + } + }, + update: async function updateApp({ commit }, { id, ...updateObj }) { + commit(types.SET_DASHBOARD_APPS_UI_FLAG, { isUpdating: true }); + try { + const response = await DashboardAppsAPI.update(id, updateObj); + commit(types.EDIT_DASHBOARD_APP, response.data); + } catch (error) { + throw new Error(error); + } finally { + commit(types.SET_DASHBOARD_APPS_UI_FLAG, { isUpdating: false }); + } + }, + delete: async function deleteApp({ commit }, id) { + commit(types.SET_DASHBOARD_APPS_UI_FLAG, { isDeleting: true }); + try { + await DashboardAppsAPI.delete(id); + commit(types.DELETE_DASHBOARD_APP, id); + } catch (error) { + throw new Error(error); + } finally { + commit(types.SET_DASHBOARD_APPS_UI_FLAG, { isDeleting: false }); + } + }, }; export const mutations = { @@ -43,6 +77,9 @@ export const mutations = { }, [types.SET_DASHBOARD_APPS]: MutationHelpers.set, + [types.CREATE_DASHBOARD_APP]: MutationHelpers.create, + [types.EDIT_DASHBOARD_APP]: MutationHelpers.update, + [types.DELETE_DASHBOARD_APP]: MutationHelpers.destroy, }; export default { diff --git a/app/javascript/dashboard/store/modules/specs/dashboardApps/actions.spec.js b/app/javascript/dashboard/store/modules/specs/dashboardApps/actions.spec.js index d24d879bf..0a214108f 100644 --- a/app/javascript/dashboard/store/modules/specs/dashboardApps/actions.spec.js +++ b/app/javascript/dashboard/store/modules/specs/dashboardApps/actions.spec.js @@ -1,7 +1,7 @@ import axios from 'axios'; import { actions } from '../../dashboardApps'; import types from '../../../mutation-types'; - +import { payload, automationsList } from './fixtures'; const commit = jest.fn(); global.axios = axios; jest.mock('axios'); @@ -18,4 +18,68 @@ describe('#actions', () => { ]); }); }); + + describe('#create', () => { + it('sends correct actions if API is success', async () => { + axios.post.mockResolvedValue({ data: payload }); + await actions.create({ commit }, payload); + expect(commit.mock.calls).toEqual([ + [types.SET_DASHBOARD_APPS_UI_FLAG, { isCreating: true }], + [types.CREATE_DASHBOARD_APP, payload], + [types.SET_DASHBOARD_APPS_UI_FLAG, { isCreating: false }], + ]); + }); + it('sends correct actions if API is error', async () => { + axios.post.mockRejectedValue({ message: 'Incorrect header' }); + await expect(actions.create({ commit })).rejects.toThrow(Error); + expect(commit.mock.calls).toEqual([ + [types.SET_DASHBOARD_APPS_UI_FLAG, { isCreating: true }], + [types.SET_DASHBOARD_APPS_UI_FLAG, { isCreating: false }], + ]); + }); + }); + + describe('#update', () => { + it('sends correct actions if API is success', async () => { + axios.patch.mockResolvedValue({ data: automationsList[0] }); + await actions.update({ commit }, automationsList[0]); + expect(commit.mock.calls).toEqual([ + [types.SET_DASHBOARD_APPS_UI_FLAG, { isUpdating: true }], + [types.EDIT_DASHBOARD_APP, automationsList[0]], + [types.SET_DASHBOARD_APPS_UI_FLAG, { isUpdating: false }], + ]); + }); + it('sends correct actions if API is error', async () => { + axios.patch.mockRejectedValue({ message: 'Incorrect header' }); + await expect( + actions.update({ commit }, automationsList[0]) + ).rejects.toThrow(Error); + expect(commit.mock.calls).toEqual([ + [types.SET_DASHBOARD_APPS_UI_FLAG, { isUpdating: true }], + [types.SET_DASHBOARD_APPS_UI_FLAG, { isUpdating: false }], + ]); + }); + }); + + describe('#delete', () => { + it('sends correct actions if API is success', async () => { + axios.delete.mockResolvedValue({ data: automationsList[0] }); + await actions.delete({ commit }, automationsList[0].id); + expect(commit.mock.calls).toEqual([ + [types.SET_DASHBOARD_APPS_UI_FLAG, { isDeleting: true }], + [types.DELETE_DASHBOARD_APP, automationsList[0].id], + [types.SET_DASHBOARD_APPS_UI_FLAG, { isDeleting: false }], + ]); + }); + it('sends correct actions if API is error', async () => { + axios.delete.mockRejectedValue({ message: 'Incorrect header' }); + await expect( + actions.delete({ commit }, automationsList[0].id) + ).rejects.toThrow(Error); + expect(commit.mock.calls).toEqual([ + [types.SET_DASHBOARD_APPS_UI_FLAG, { isDeleting: true }], + [types.SET_DASHBOARD_APPS_UI_FLAG, { isDeleting: false }], + ]); + }); + }); }); diff --git a/app/javascript/dashboard/store/modules/specs/dashboardApps/fixtures.js b/app/javascript/dashboard/store/modules/specs/dashboardApps/fixtures.js new file mode 100644 index 000000000..f6271682e --- /dev/null +++ b/app/javascript/dashboard/store/modules/specs/dashboardApps/fixtures.js @@ -0,0 +1,19 @@ +export const payload = { + title: 'Test', + content: [ + { url: 'https://example.com', type: 'frame' }, + { url: 'https://chatwoot.com', type: 'frame' }, + ], +}; + +export const automationsList = [ + { + id: 15, + title: 'Test', + content: [ + { url: 'https://example.com', type: 'frame' }, + { url: 'https://chatwoot.com', type: 'frame' }, + ], + created_at: '2022-06-27T08:28:29.841Z', + }, +]; diff --git a/app/javascript/dashboard/store/modules/specs/dashboardApps/mutations.spec.js b/app/javascript/dashboard/store/modules/specs/dashboardApps/mutations.spec.js index aaf4da33e..12359fc3d 100644 --- a/app/javascript/dashboard/store/modules/specs/dashboardApps/mutations.spec.js +++ b/app/javascript/dashboard/store/modules/specs/dashboardApps/mutations.spec.js @@ -1,5 +1,6 @@ import types from '../../../mutation-types'; import { mutations } from '../../dashboardApps'; +import { automationsList } from './fixtures'; describe('#mutations', () => { describe('#SET_DASHBOARD_APPS_UI_FLAG', () => { @@ -17,4 +18,31 @@ describe('#mutations', () => { expect(state.records).toEqual([{ title: 'Title 1' }]); }); }); + + describe('#ADD_DASHBOARD_APP', () => { + it('push newly created app to the store', () => { + const state = { records: [automationsList[0]] }; + mutations[types.CREATE_DASHBOARD_APP](state, automationsList[1]); + expect(state.records).toEqual([automationsList[0], automationsList[1]]); + }); + }); + + describe('#EDIT_DASHBOARD_APP', () => { + it('update label record', () => { + const state = { records: [automationsList[0]] }; + mutations[types.EDIT_DASHBOARD_APP](state, { + id: 15, + title: 'updated-title', + }); + expect(state.records[0].title).toEqual('updated-title'); + }); + }); + + describe('#DELETE_DASHBOARD_APP', () => { + it('delete label record', () => { + const state = { records: [automationsList[0]] }; + mutations[types.DELETE_DASHBOARD_APP](state, 15); + expect(state.records).toEqual([]); + }); + }); }); diff --git a/app/javascript/dashboard/store/mutation-types.js b/app/javascript/dashboard/store/mutation-types.js index e20efbf78..40a44c066 100755 --- a/app/javascript/dashboard/store/mutation-types.js +++ b/app/javascript/dashboard/store/mutation-types.js @@ -217,4 +217,7 @@ export default { // Dashboard Apps SET_DASHBOARD_APPS_UI_FLAG: 'SET_DASHBOARD_APPS_UI_FLAG', SET_DASHBOARD_APPS: 'SET_DASHBOARD_APPS', + CREATE_DASHBOARD_APP: 'CREATE_DASHBOARD_APP', + EDIT_DASHBOARD_APP: 'EDIT_DASHBOARD_APP', + DELETE_DASHBOARD_APP: 'DELETE_DASHBOARD_APP', }; diff --git a/public/dashboard/images/integrations/cable.svg b/public/dashboard/images/integrations/cable.svg index 2a9f7008d..b39742a29 100644 --- a/public/dashboard/images/integrations/cable.svg +++ b/public/dashboard/images/integrations/cable.svg @@ -1,64 +1,39 @@ - - - - - - - + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/public/dashboard/images/integrations/dashboard-apps.svg b/public/dashboard/images/integrations/dashboard-apps.svg new file mode 100644 index 000000000..76e165588 --- /dev/null +++ b/public/dashboard/images/integrations/dashboard-apps.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +