From 5ddc46c474ce51856b9e48e40dc97c143206d1e9 Mon Sep 17 00:00:00 2001 From: Pranav Raj S Date: Sat, 28 Dec 2019 21:56:42 +0530 Subject: [PATCH] Refactor: Inbox store, remove inboxes from sidebar (#387) * Refactor: Inbox store, remove inboxes from sidebar * Add a new page for inbox settings * Show inboxes on sidebar * Add inbox_members API * Disable similar-code check * Fix codeclimate scss issues * Add widget_color update API and actions * Add specs for inbox store * Fix Facebook auth flow * Fix agent loading, inbox name --- .codeclimate.yml | 4 +- .../api/v1/widget/inboxes_controller.rb | 26 ++- app/javascript/dashboard/api/ApiClient.js | 3 +- app/javascript/dashboard/api/account.js | 32 --- .../dashboard/api/channel/fbChannel.js | 7 + app/javascript/dashboard/api/channels.js | 13 -- app/javascript/dashboard/api/endPoints.js | 40 ---- app/javascript/dashboard/api/inboxMembers.js | 17 ++ app/javascript/dashboard/api/inboxes.js | 9 + .../dashboard/api/specs/inboxes.spec.js | 13 ++ .../assets/scss/views/settings/inbox.scss | 61 ++---- .../dashboard/assets/scss/widgets/_modal.scss | 16 +- .../dashboard/components/ChatList.vue | 46 ++-- .../dashboard/components/ModalHeader.vue | 8 +- .../components/SettingsFormHeader.vue | 15 +- .../dashboard/components/layout/Sidebar.vue | 66 ++++-- .../widgets/conversation/ConversationCard.vue | 9 +- .../widgets/conversation/EmptyState.vue | 10 +- .../widgets/conversation/MessagesView.vue | 5 +- .../dashboard/i18n/default-sidebar.js | 190 ++++++++-------- .../dashboard/i18n/locale/en/inboxMgmt.json | 14 +- .../conversation/ConversationView.vue | 1 - .../settings/SettingsSubPageHeader.vue | 5 +- .../dashboard/settings/inbox/AddAgents.vue | 49 ++--- .../dashboard/settings/inbox/FinishSetup.vue | 9 +- .../routes/dashboard/settings/inbox/Index.vue | 71 +++--- .../dashboard/settings/inbox/Settings.vue | 206 ++++++++++-------- .../settings/inbox/channels/Facebook.vue | 9 +- .../settings/inbox/channels/Website.vue | 36 +-- .../dashboard/settings/inbox/inbox.routes.js | 7 + app/javascript/dashboard/store/index.js | 6 +- .../dashboard/store/modules/inboxMembers.js | 24 ++ .../dashboard/store/modules/inboxes.js | 109 +++++++++ .../dashboard/store/modules/sidebar.js | 195 ----------------- .../modules/specs/inboxes/actions.spec.js | 116 ++++++++++ .../store/modules/specs/inboxes/fixtures.js | 42 ++++ .../modules/specs/inboxes/getters.spec.js | 46 ++++ .../modules/specs/inboxes/mutations.spec.js | 94 ++++++++ .../dashboard/store/mutation-types.js | 11 +- .../shared/helpers/vuex/mutationHelpers.js | 9 + app/javascript/widget/assets/scss/_forms.scss | 6 + app/models/channel/web_widget.rb | 1 + app/policies/inbox_policy.rb | 4 + .../api/v1/inbox_members/show.json.jbuilder | 10 +- app/views/api/v1/inboxes/index.json.jbuilder | 25 +-- .../v1/widget/inboxes/create.json.jbuilder | 1 + .../v1/widget/inboxes/update.json.jbuilder | 6 + config/routes.rb | 4 +- .../api/v1/widget/inboxes_controller_spec.rb | 45 +++- spec/factories/channel_widget.rb | 1 + spec/factories/inboxes.rb | 2 +- 51 files changed, 1028 insertions(+), 726 deletions(-) delete mode 100644 app/javascript/dashboard/api/account.js create mode 100644 app/javascript/dashboard/api/inboxMembers.js create mode 100644 app/javascript/dashboard/api/inboxes.js create mode 100644 app/javascript/dashboard/api/specs/inboxes.spec.js create mode 100644 app/javascript/dashboard/store/modules/inboxMembers.js create mode 100644 app/javascript/dashboard/store/modules/inboxes.js delete mode 100644 app/javascript/dashboard/store/modules/sidebar.js create mode 100644 app/javascript/dashboard/store/modules/specs/inboxes/actions.spec.js create mode 100644 app/javascript/dashboard/store/modules/specs/inboxes/fixtures.js create mode 100644 app/javascript/dashboard/store/modules/specs/inboxes/getters.spec.js create mode 100644 app/javascript/dashboard/store/modules/specs/inboxes/mutations.spec.js create mode 100644 app/views/api/v1/widget/inboxes/update.json.jbuilder diff --git a/.codeclimate.yml b/.codeclimate.yml index d0cde2858..f12f590a2 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -11,7 +11,9 @@ plugins: enabled: true brakeman: enabled: true - +checks: + similar-code: + enabled: false exclude_patterns: - "spec/" - "**/specs/" diff --git a/app/controllers/api/v1/widget/inboxes_controller.rb b/app/controllers/api/v1/widget/inboxes_controller.rb index a16f5bfed..ce739fef0 100644 --- a/app/controllers/api/v1/widget/inboxes_controller.rb +++ b/app/controllers/api/v1/widget/inboxes_controller.rb @@ -1,17 +1,25 @@ class Api::V1::Widget::InboxesController < Api::BaseController before_action :authorize_request + before_action :set_web_widget_channel, only: [:update] + before_action :set_inbox, only: [:update] def create ActiveRecord::Base.transaction do channel = web_widgets.create!( - website_name: permitted_params[:website_name], - website_url: permitted_params[:website_url], - widget_color: permitted_params[:widget_color] + website_name: permitted_params[:website][:website_name], + website_url: permitted_params[:website][:website_url], + widget_color: permitted_params[:website][:widget_color] ) - @inbox = inboxes.create!(name: permitted_params[:website_name], channel: channel) + @inbox = inboxes.create!(name: permitted_params[:website][:website_name], channel: channel) end end + def update + @channel.update!( + widget_color: permitted_params[:website][:widget_color] + ) + end + private def authorize_request @@ -26,7 +34,15 @@ class Api::V1::Widget::InboxesController < Api::BaseController current_account.web_widgets end + def set_web_widget_channel + @channel = web_widgets.find_by(id: permitted_params[:id]) + end + + def set_inbox + @inbox = @channel.inbox + end + def permitted_params - params.fetch(:website).permit(:website_name, :website_url, :widget_color) + params.permit(:id, website: [:website_name, :website_url, :widget_color]) end end diff --git a/app/javascript/dashboard/api/ApiClient.js b/app/javascript/dashboard/api/ApiClient.js index 0bbd13d9c..a3b87dae1 100644 --- a/app/javascript/dashboard/api/ApiClient.js +++ b/app/javascript/dashboard/api/ApiClient.js @@ -4,7 +4,8 @@ const API_VERSION = `/api/v1`; class ApiClient { constructor(url) { - this.url = `${API_VERSION}/${url}`; + this.apiVersion = API_VERSION; + this.url = `${this.apiVersion}/${url}`; } get() { diff --git a/app/javascript/dashboard/api/account.js b/app/javascript/dashboard/api/account.js deleted file mode 100644 index 114cb69ec..000000000 --- a/app/javascript/dashboard/api/account.js +++ /dev/null @@ -1,32 +0,0 @@ -/* global axios */ -import endPoints from './endPoints'; - -export default { - getLabels() { - const urlData = endPoints('fetchLabels'); - return axios.get(urlData.url); - }, - - getInboxes() { - const urlData = endPoints('fetchInboxes'); - return axios.get(urlData.url); - }, - - deleteInbox(id) { - const urlData = endPoints('inbox').delete(id); - return axios.delete(urlData.url); - }, - - listInboxAgents(id) { - const urlData = endPoints('inbox').agents.get(id); - return axios.get(urlData.url); - }, - - updateInboxAgents(inboxId, agentList) { - const urlData = endPoints('inbox').agents.post(); - return axios.post(urlData.url, { - user_ids: agentList, - inbox_id: inboxId, - }); - }, -}; diff --git a/app/javascript/dashboard/api/channel/fbChannel.js b/app/javascript/dashboard/api/channel/fbChannel.js index 215900130..f9781097c 100644 --- a/app/javascript/dashboard/api/channel/fbChannel.js +++ b/app/javascript/dashboard/api/channel/fbChannel.js @@ -19,6 +19,13 @@ class FBChannel extends ApiClient { contact_id: contactId, }); } + + create(params) { + return axios.post( + `${this.apiVersion}/callbacks/register_facebook_page`, + params + ); + } } export default new FBChannel(); diff --git a/app/javascript/dashboard/api/channels.js b/app/javascript/dashboard/api/channels.js index 8a8ac3918..f7db9afbc 100644 --- a/app/javascript/dashboard/api/channels.js +++ b/app/javascript/dashboard/api/channels.js @@ -5,19 +5,6 @@ import endPoints from './endPoints'; export default { - // Get Inbox related to the account - createChannel(channel, channelParams) { - const urlData = endPoints('createChannel')(channel, channelParams); - return axios.post(urlData.url, urlData.params); - }, - - addAgentsToChannel(inboxId, agentsId) { - const urlData = endPoints('addAgentsToChannel'); - urlData.params.inbox_id = inboxId; - urlData.params.user_ids = agentsId; - return axios.post(urlData.url, urlData.params); - }, - fetchFacebookPages(token) { const urlData = endPoints('fetchFacebookPages'); urlData.params.omniauth_token = token; diff --git a/app/javascript/dashboard/api/endPoints.js b/app/javascript/dashboard/api/endPoints.js index 062cbec1f..2898362cc 100644 --- a/app/javascript/dashboard/api/endPoints.js +++ b/app/javascript/dashboard/api/endPoints.js @@ -24,26 +24,6 @@ const endPoints = { params: { inbox_id: null }, }, - fetchLabels: { - url: 'api/v1/labels.json', - }, - - fetchInboxes: { - url: 'api/v1/inboxes.json', - }, - - createChannel(channel, channelParams) { - return { - url: `api/v1/callbacks/register_${channel}_page.json`, - params: channelParams, - }; - }, - - addAgentsToChannel: { - url: 'api/v1/inbox_members.json', - params: { user_ids: [], inbox_id: null }, - }, - fetchFacebookPages: { url: 'api/v1/callbacks/get_facebook_pages.json', params: { omniauth_token: '' }, @@ -69,26 +49,6 @@ const endPoints = { }; }, }, - - inbox: { - delete(id) { - return { - url: `/api/v1/inboxes/${id}`, - }; - }, - agents: { - get(id) { - return { - url: `/api/v1/inbox_members/${id}.json`, - }; - }, - post() { - return { - url: '/api/v1/inbox_members.json', - }; - }, - }, - }, }; export default page => { diff --git a/app/javascript/dashboard/api/inboxMembers.js b/app/javascript/dashboard/api/inboxMembers.js new file mode 100644 index 000000000..2d7001562 --- /dev/null +++ b/app/javascript/dashboard/api/inboxMembers.js @@ -0,0 +1,17 @@ +/* global axios */ +import ApiClient from './ApiClient'; + +class InboxMembers extends ApiClient { + constructor() { + super('inbox_members'); + } + + create({ inboxId, agentList }) { + return axios.post(this.url, { + inbox_id: inboxId, + user_ids: agentList, + }); + } +} + +export default new InboxMembers(); diff --git a/app/javascript/dashboard/api/inboxes.js b/app/javascript/dashboard/api/inboxes.js new file mode 100644 index 000000000..fb3e63dfd --- /dev/null +++ b/app/javascript/dashboard/api/inboxes.js @@ -0,0 +1,9 @@ +import ApiClient from './ApiClient'; + +class Inboxes extends ApiClient { + constructor() { + super('inboxes'); + } +} + +export default new Inboxes(); diff --git a/app/javascript/dashboard/api/specs/inboxes.spec.js b/app/javascript/dashboard/api/specs/inboxes.spec.js new file mode 100644 index 000000000..d1a1d2683 --- /dev/null +++ b/app/javascript/dashboard/api/specs/inboxes.spec.js @@ -0,0 +1,13 @@ +import inboxes from '../inboxes'; +import ApiClient from '../ApiClient'; + +describe('#AgentAPI', () => { + it('creates correct instance', () => { + expect(inboxes).toBeInstanceOf(ApiClient); + expect(inboxes).toHaveProperty('get'); + expect(inboxes).toHaveProperty('show'); + expect(inboxes).toHaveProperty('create'); + expect(inboxes).toHaveProperty('update'); + expect(inboxes).toHaveProperty('delete'); + }); +}); diff --git a/app/javascript/dashboard/assets/scss/views/settings/inbox.scss b/app/javascript/dashboard/assets/scss/views/settings/inbox.scss index 479dbbd8f..b6865213e 100644 --- a/app/javascript/dashboard/assets/scss/views/settings/inbox.scss +++ b/app/javascript/dashboard/assets/scss/views/settings/inbox.scss @@ -1,3 +1,10 @@ +.settings { + overflow: auto; + + .page-top-bar { + @include padding($space-normal $space-two $zero); + } +} // Conversation header - Light BG .settings-header { @include padding($space-small $space-normal); @@ -196,51 +203,23 @@ } } -.settings-modal { - height: 80%; - max-width: 1040px; - width: 100%; +.settings--content { + @include margin($space-small $space-medium); - .delete-wrapper { - position: absolute; - bottom: 0; - width: 100%; - @include flex; - flex-direction: row; - justify-content: space-between; - @include padding($space-normal $space-large); - - a { - margin-left: $space-normal; - } + .title { + font-weight: $font-weight-medium; } - .settings--content { - @include margin($space-medium); + .code { + max-height: $space-mega; + overflow: scroll; + white-space: nowrap; + @include padding($space-one); + background: $color-background; - .title { - font-weight: $font-weight-medium; - } - - .code { - max-height: $space-mega; - overflow: scroll; - white-space: nowrap; - @include padding($space-one); - background: $color-background; - - code { - background: transparent; - border: 0; - } - } - } - - .agent-wrapper { - @include margin($space-medium); - - .title { - font-weight: $font-weight-medium; + code { + background: transparent; + border: 0; } } } diff --git a/app/javascript/dashboard/assets/scss/widgets/_modal.scss b/app/javascript/dashboard/assets/scss/widgets/_modal.scss index 04c92e836..5a2cc7b3d 100644 --- a/app/javascript/dashboard/assets/scss/widgets/_modal.scss +++ b/app/javascript/dashboard/assets/scss/widgets/_modal.scss @@ -27,6 +27,15 @@ } } + +.page-top-bar { + @include padding($zero $space-two); + + img { + max-height: 6rem; + } +} + .modal-container { background-color: $color-white; border-radius: $space-small; @@ -35,13 +44,6 @@ position: relative; width: 60rem; - .page-top-bar { - @include padding($zero $space-two); - - img { - max-height: 6rem; - } - } .content-box { @include padding($zero); diff --git a/app/javascript/dashboard/components/ChatList.vue b/app/javascript/dashboard/components/ChatList.vue index ac3afe4dd..6f69857f7 100644 --- a/app/javascript/dashboard/components/ChatList.vue +++ b/app/javascript/dashboard/components/ChatList.vue @@ -3,7 +3,7 @@

- {{ getInboxName }} + {{ inbox.name || pageTitle }}

@@ -53,6 +53,11 @@ import conversationMixin from '../mixins/conversations'; import wootConstants from '../constants'; export default { + components: { + ChatTypeTabs, + ConversationCard, + ChatFilter, + }, mixins: [timeMixin, conversationMixin], props: ['conversationInbox', 'pageTitle'], data() { @@ -61,25 +66,12 @@ export default { activeStatus: 0, }; }, - mounted() { - this.$watch('$store.state.route', () => { - if (this.$store.state.route.name !== 'inbox_conversation') { - this.$store.dispatch('emptyAllConversations'); - this.fetchData(); - } - }); - - this.$store.dispatch('emptyAllConversations'); - this.fetchData(); - this.$store.dispatch('agents/get'); - }, computed: { ...mapGetters({ chatLists: 'getAllConversations', mineChatsList: 'getMineChats', allChatList: 'getAllStatusChats', unAssignedChatsList: 'getUnAssignedChats', - inboxesList: 'getInboxesList', chatListLoading: 'getChatListLoadingStatus', currentUserID: 'getCurrentUserID', activeInbox: 'getSelectedInbox', @@ -92,12 +84,8 @@ export default { count: this.convStats[item.KEY] || 0, })); }, - getInboxName() { - const inboxId = Number(this.activeInbox); - const [stateInbox] = this.inboxesList.filter( - inbox => inbox.channel_id === inboxId - ); - return !stateInbox ? this.pageTitle : stateInbox.label; + inbox() { + return this.$store.getters['inboxes/getInbox'](this.activeInbox); }, getToggleStatus() { if (this.toggleType) { @@ -106,6 +94,18 @@ export default { return 'Resolved'; }, }, + mounted() { + this.$watch('$store.state.route', () => { + if (this.$store.state.route.name !== 'inbox_conversation') { + this.$store.dispatch('emptyAllConversations'); + this.fetchData(); + } + }); + + this.$store.dispatch('emptyAllConversations'); + this.fetchData(); + this.$store.dispatch('agents/get'); + }, methods: { fetchData() { if (this.chatLists.length === 0) { @@ -149,12 +149,6 @@ export default { return sorted; }, }, - - components: { - ChatTypeTabs, - ConversationCard, - ChatFilter, - }, }; diff --git a/app/javascript/dashboard/components/ModalHeader.vue b/app/javascript/dashboard/components/ModalHeader.vue index 1019640bb..9f930f983 100644 --- a/app/javascript/dashboard/components/ModalHeader.vue +++ b/app/javascript/dashboard/components/ModalHeader.vue @@ -1,11 +1,11 @@ diff --git a/app/javascript/dashboard/components/SettingsFormHeader.vue b/app/javascript/dashboard/components/SettingsFormHeader.vue index ffde8bc11..795751612 100644 --- a/app/javascript/dashboard/components/SettingsFormHeader.vue +++ b/app/javascript/dashboard/components/SettingsFormHeader.vue @@ -10,7 +10,7 @@
diff --git a/app/javascript/dashboard/components/layout/Sidebar.vue b/app/javascript/dashboard/components/layout/Sidebar.vue index 2b711a2b4..6091678c7 100644 --- a/app/javascript/dashboard/components/layout/Sidebar.vue +++ b/app/javascript/dashboard/components/layout/Sidebar.vue @@ -13,6 +13,12 @@ :key="item.toState" :menu-item="item" /> + +
@@ -41,7 +47,7 @@
- +

{{ currentUser.name }} @@ -50,9 +56,8 @@ {{ currentUser.role }}

- + +
@@ -69,12 +74,19 @@ import SidebarItem from './SidebarItem'; import WootStatusBar from '../widgets/StatusBar'; import { frontendURL } from '../../helper/URLHelper'; import Thumbnail from '../widgets/Thumbnail'; +import sidemenuItems from '../../i18n/default-sidebar'; export default { + components: { + SidebarItem, + WootStatusBar, + Thumbnail, + }, mixins: [clickaway, adminMixin], props: { route: { type: String, + default: '', }, }, data() { @@ -82,27 +94,22 @@ export default { showOptionsMenu: false, }; }, - mounted() { - // this.$store.dispatch('fetchLabels'); - this.$store.dispatch('fetchInboxes'); - }, computed: { ...mapGetters({ - sidebarList: 'getMenuItems', daysLeft: 'getTrialLeft', subscriptionData: 'getSubscription', + inboxes: 'inboxes/getInboxes', }), accessibleMenuItems() { - const currentRoute = this.$store.state.route.name; // get all keys in menuGroup - const groupKey = Object.keys(this.sidebarList); + const groupKey = Object.keys(sidemenuItems); let menuItems = []; // Iterate over menuGroup to find the correct group for (let i = 0; i < groupKey.length; i += 1) { - const groupItem = this.sidebarList[groupKey[i]]; + const groupItem = sidemenuItems[groupKey[i]]; // Check if current route is included - const isRouteIncluded = groupItem.routes.includes(currentRoute); + const isRouteIncluded = groupItem.routes.includes(this.currentRoute); if (isRouteIncluded) { menuItems = Object.values(groupItem.menuItems); } @@ -114,6 +121,29 @@ export default { return this.filterMenuItemsByRole(menuItems); }, + currentRoute() { + return this.$store.state.route.name; + }, + shouldShowInboxes() { + return sidemenuItems.common.routes.includes(this.currentRoute); + }, + inboxSection() { + return { + icon: 'ion-folder', + label: 'Inboxes', + hasSubMenu: true, + newLink: true, + key: 'inbox', + cssClass: 'menu-title align-justify', + toState: frontendURL('settings/inboxes'), + toStateName: 'settings_inbox_list', + children: this.inboxes.map(inbox => ({ + id: inbox.id, + label: inbox.name, + toState: frontendURL(`inbox/${inbox.id}`), + })), + }; + }, currentUser() { return Auth.getCurrentUser(); }, @@ -140,7 +170,9 @@ export default { return `${this.daysLeft} ${this.$t('APP_GLOBAL.TRIAL_MESSAGE')}`; }, }, - + mounted() { + this.$store.dispatch('inboxes/get'); + }, methods: { gravatarUrl() { const hash = md5(this.currentUser.email); @@ -165,11 +197,5 @@ export default { this.showOptionsMenu = !this.showOptionsMenu; }, }, - - components: { - SidebarItem, - WootStatusBar, - Thumbnail, - }, }; diff --git a/app/javascript/dashboard/components/widgets/conversation/ConversationCard.vue b/app/javascript/dashboard/components/widgets/conversation/ConversationCard.vue index 324776793..56f783cde 100644 --- a/app/javascript/dashboard/components/widgets/conversation/ConversationCard.vue +++ b/app/javascript/dashboard/components/widgets/conversation/ConversationCard.vue @@ -63,7 +63,7 @@ export default { computed: { ...mapGetters({ currentChat: 'getSelectedChat', - inboxesList: 'getInboxesList', + inboxesList: 'inboxes/getInboxes', activeInbox: 'getSelectedInbox', }), @@ -107,11 +107,10 @@ export default { `; }, getEmojiSVG, + inboxName(inboxId) { - const [stateInbox] = this.inboxesList.filter( - inbox => inbox.channel_id === inboxId - ); - return !stateInbox ? '' : stateInbox.label; + const stateInbox = this.$store.getters['inboxes/getInbox'](inboxId); + return stateInbox.name || ''; }, }, }; diff --git a/app/javascript/dashboard/components/widgets/conversation/EmptyState.vue b/app/javascript/dashboard/components/widgets/conversation/EmptyState.vue index 0944a3702..2f2678954 100644 --- a/app/javascript/dashboard/components/widgets/conversation/EmptyState.vue +++ b/app/javascript/dashboard/components/widgets/conversation/EmptyState.vue @@ -1,11 +1,11 @@