diff --git a/Gemfile b/Gemfile index f7cac3c70..b8e511b1b 100644 --- a/Gemfile +++ b/Gemfile @@ -156,11 +156,6 @@ group :test do end group :development, :test do - # TODO: is this needed ? - # errors thrown by devise password gem - gem 'flay' - gem 'rspec' - # for error thrown by devise password gem gem 'active_record_query_trace' gem 'bundle-audit', require: false gem 'byebug', platform: :mri diff --git a/Gemfile.lock b/Gemfile.lock index b12a2fc5c..92639c0d5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/chatwoot/devise-secure_password - revision: de11e8765654b8242d42101ee9c8ffc8126f7975 + revision: d777b04f12652d576b1272b8f39857e3e0b3fc26 specs: devise-secure_password (2.0.1) devise (>= 4.0.0, < 5.0.0) @@ -182,7 +182,6 @@ GEM regexp_parser (~> 2.2) email_reply_trimmer (0.1.13) erubi (1.10.0) - erubis (2.7.0) et-orbi (1.2.7) tzinfo execjs (2.8.1) @@ -204,11 +203,6 @@ GEM faraday (~> 1) ffi (1.15.5) flag_shih_tzu (0.3.23) - flay (2.12.1) - erubis (~> 2.7.0) - path_expander (~> 1.0) - ruby_parser (~> 3.0) - sexp_processor (~> 4.0) foreman (0.87.2) fugit (1.5.3) et-orbi (~> 1, >= 1.2.7) @@ -393,7 +387,6 @@ GEM parallel (1.21.0) parser (3.1.1.0) ast (~> 2.4.1) - path_expander (1.1.0) pg (1.3.2) procore-sift (0.16.0) rails (> 4.2.0) @@ -468,10 +461,6 @@ GEM netrc (~> 0.8) retriable (3.1.2) rexml (3.2.5) - rspec (3.11.0) - rspec-core (~> 3.11.0) - rspec-expectations (~> 3.11.0) - rspec-mocks (~> 3.11.0) rspec-core (3.11.0) rspec-support (~> 3.11.0) rspec-expectations (3.11.0) @@ -681,7 +670,6 @@ DEPENDENCIES faker fcm flag_shih_tzu - flay foreman geocoder google-cloud-dialogflow @@ -718,7 +706,6 @@ DEPENDENCIES redis-namespace responders rest-client - rspec rspec-rails (~> 5.0.0) rubocop rubocop-performance @@ -755,4 +742,4 @@ RUBY VERSION ruby 3.0.4p208 BUNDLED WITH - 2.3.10 + 2.3.14 diff --git a/app/javascript/dashboard/api/bulkActions.js b/app/javascript/dashboard/api/bulkActions.js new file mode 100644 index 000000000..a606c56ac --- /dev/null +++ b/app/javascript/dashboard/api/bulkActions.js @@ -0,0 +1,9 @@ +import ApiClient from './ApiClient'; + +class BulkActionsAPI extends ApiClient { + constructor() { + super('bulk_actions', { accountScoped: true }); + } +} + +export default new BulkActionsAPI(); diff --git a/app/javascript/dashboard/api/specs/bulkAction.spec.js b/app/javascript/dashboard/api/specs/bulkAction.spec.js new file mode 100644 index 000000000..aec0b1e3e --- /dev/null +++ b/app/javascript/dashboard/api/specs/bulkAction.spec.js @@ -0,0 +1,9 @@ +import bulkActions from '../bulkActions'; +import ApiClient from '../ApiClient'; + +describe('#BulkActionsAPI', () => { + it('creates correct instance', () => { + expect(bulkActions).toBeInstanceOf(ApiClient); + expect(bulkActions).toHaveProperty('create'); + }); +}); diff --git a/app/javascript/dashboard/components/ChatList.vue b/app/javascript/dashboard/components/ChatList.vue index 2887e4a08..4a90bf7a1 100644 --- a/app/javascript/dashboard/components/ChatList.vue +++ b/app/javascript/dashboard/components/ChatList.vue @@ -96,6 +96,9 @@ :chat="chat" :conversation-type="conversationType" :show-assignee="showAssigneeInConversationCard" + :selected="isConversationSelected(chat.id)" + @select-conversation="selectConversation" + @de-select-conversation="deSelectConversation" />
@@ -134,6 +137,16 @@ @applyFilter="onApplyFilter" /> + +
@@ -152,6 +165,8 @@ import advancedFilterTypes from './widgets/conversation/advancedFilterItems'; import filterQueryGenerator from '../helper/filterQueryGenerator.js'; import AddCustomViews from 'dashboard/routes/dashboard/customviews/AddCustomViews'; import DeleteCustomViews from 'dashboard/routes/dashboard/customviews/DeleteCustomViews.vue'; +import ConversationBulkActions from './widgets/conversation/conversationBulkActions/Actions.vue'; +import alertMixin from 'shared/mixins/alertMixin'; import { hasPressedAltAndJKey, @@ -166,8 +181,9 @@ export default { ChatFilter, ConversationAdvancedFilter, DeleteCustomViews, + ConversationBulkActions, }, - mixins: [timeMixin, conversationMixin, eventListenerMixins], + mixins: [timeMixin, conversationMixin, eventListenerMixins, alertMixin], props: { conversationInbox: { type: [String, Number], @@ -202,6 +218,8 @@ export default { foldersQuery: {}, showAddFoldersModal: false, showDeleteFoldersModal: false, + selectedConversations: [], + selectedInboxes: [], }; }, computed: { @@ -217,6 +235,7 @@ export default { conversationStats: 'conversationStats/getStats', appliedFilters: 'getAppliedConversationFilters', folders: 'customViews/getCustomViews', + inboxes: 'inboxes/getInboxes', }), hasAppliedFilters() { return this.appliedFilters.length !== 0; @@ -343,6 +362,15 @@ export default { } return {}; }, + allConversationsSelected() { + return ( + JSON.stringify(this.selectedConversations) === + JSON.stringify(this.conversationList.map(item => item.id)) + ); + }, + uniqueInboxes() { + return [...new Set(this.selectedInboxes)]; + }, }, watch: { activeTeam() { @@ -376,6 +404,7 @@ export default { if (this.$route.name !== 'home') { this.$router.push({ name: 'home' }); } + this.resetBulkActions(); this.foldersQuery = filterQueryGenerator(payload); this.$store.dispatch('conversationPage/reset'); this.$store.dispatch('emptyAllConversations'); @@ -441,6 +470,7 @@ export default { } }, resetAndFetchData() { + this.resetBulkActions(); this.$store.dispatch('conversationPage/reset'); this.$store.dispatch('emptyAllConversations'); this.$store.dispatch('clearConversationFilters'); @@ -491,6 +521,7 @@ export default { }, updateAssigneeTab(selectedTab) { if (this.activeAssigneeTab !== selectedTab) { + this.resetBulkActions(); bus.$emit('clearSearchInput'); this.activeAssigneeTab = selectedTab; if (!this.currentPage) { @@ -498,6 +529,10 @@ export default { } } }, + resetBulkActions() { + this.selectedConversations = []; + this.selectedInboxes = []; + }, updateStatusType(index) { if (this.activeStatus !== index) { this.activeStatus = index; @@ -520,6 +555,59 @@ export default { this.fetchConversations(); } }, + isConversationSelected(id) { + return this.selectedConversations.includes(id); + }, + selectConversation(conversationId, inboxId) { + this.selectedConversations.push(conversationId); + this.selectedInboxes.push(inboxId); + }, + deSelectConversation(conversationId, inboxId) { + this.selectedConversations = this.selectedConversations.filter( + item => item !== conversationId + ); + this.selectedInboxes = this.selectedInboxes.filter( + item => item !== inboxId + ); + }, + selectAllConversations(check) { + if (check) { + this.selectedConversations = this.conversationList.map(item => item.id); + this.selectedInboxes = this.conversationList.map(item => item.inbox_id); + } else { + this.resetBulkActions(); + } + }, + async onAssignAgent(agent) { + try { + await this.$store.dispatch('bulkActions/process', { + type: 'Conversation', + ids: this.selectedConversations, + fields: { + assignee_id: agent.id, + }, + }); + this.selectedConversations = []; + this.showAlert(this.$t('BULK_ACTION.ASSIGN_SUCCESFUL')); + } catch (err) { + this.showAlert(this.$t('BULK_ACTION.ASSIGN_FAILED')); + } + }, + async onResolveConversations() { + try { + await this.$store.dispatch('bulkActions/process', { + type: 'Conversation', + ids: this.selectedConversations, + fields: { + status: 'resolved', + }, + }); + this.selectedConversations = []; + this.showAlert(this.$t('BULK_ACTION.RESOLVE_SUCCESFUL')); + } catch (error) { + this.showAlert(this.$t('BULK_ACTION.RESOLVE_FAILED')); + } + }, }, }; @@ -535,7 +623,7 @@ export default { .conversations-list-wrap { flex-shrink: 0; width: 34rem; - + overflow: hidden; @include breakpoint(large up) { width: 36rem; } diff --git a/app/javascript/dashboard/components/widgets/conversation/ConversationBox.vue b/app/javascript/dashboard/components/widgets/conversation/ConversationBox.vue index 494bd5c82..d2eb8a4bd 100644 --- a/app/javascript/dashboard/components/widgets/conversation/ConversationBox.vue +++ b/app/javascript/dashboard/components/widgets/conversation/ConversationBox.vue @@ -99,7 +99,7 @@ export default { watch: { 'currentChat.inbox_id'(inboxId) { if (inboxId) { - this.$store.dispatch('inboxAssignableAgents/fetch', { inboxId }); + this.$store.dispatch('inboxAssignableAgents/fetch', [inboxId]); } }, 'currentChat.id'() { diff --git a/app/javascript/dashboard/components/widgets/conversation/ConversationCard.vue b/app/javascript/dashboard/components/widgets/conversation/ConversationCard.vue index 7e6c89bac..ee12b989a 100644 --- a/app/javascript/dashboard/components/widgets/conversation/ConversationCard.vue +++ b/app/javascript/dashboard/components/widgets/conversation/ConversationCard.vue @@ -5,11 +5,24 @@ active: isActiveChat, 'unread-chat': hasUnread, 'has-inbox-name': showInboxName, + 'conversation-selected': selected, }" + @mouseenter="onCardHover" + @mouseleave="onCardLeave" @click="cardClick(chat)" > + @@ -272,6 +305,10 @@ export default { } } +.conversation-selected { + background: var(--color-background-light); +} + .has-inbox-name { &::v-deep .user-thumbnail-box { margin-top: var(--space-normal); @@ -320,4 +357,22 @@ export default { margin-top: var(--space-minus-micro); vertical-align: middle; } +.checkbox-wrapper { + height: 40px; + width: 40px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 100%; + margin-top: var(--space-normal); + cursor: pointer; + &:hover { + background-color: var(--w-100); + } + + input[type='checkbox'] { + margin: var(--space-zero); + cursor: pointer; + } +} diff --git a/app/javascript/dashboard/components/widgets/conversation/conversationBulkActions/Actions.vue b/app/javascript/dashboard/components/widgets/conversation/conversationBulkActions/Actions.vue new file mode 100644 index 000000000..5466f559d --- /dev/null +++ b/app/javascript/dashboard/components/widgets/conversation/conversationBulkActions/Actions.vue @@ -0,0 +1,135 @@ + + + + + diff --git a/app/javascript/dashboard/components/widgets/conversation/conversationBulkActions/AgentSelector.vue b/app/javascript/dashboard/components/widgets/conversation/conversationBulkActions/AgentSelector.vue new file mode 100644 index 000000000..1d7fe6ec0 --- /dev/null +++ b/app/javascript/dashboard/components/widgets/conversation/conversationBulkActions/AgentSelector.vue @@ -0,0 +1,249 @@ + + + + + diff --git a/app/javascript/dashboard/i18n/locale/en/bulkActions.json b/app/javascript/dashboard/i18n/locale/en/bulkActions.json new file mode 100644 index 000000000..bfd688bef --- /dev/null +++ b/app/javascript/dashboard/i18n/locale/en/bulkActions.json @@ -0,0 +1,17 @@ +{ + "BULK_ACTION": { + "CONVERSATIONS_SELECTED": "%{conversationCount} conversations selected", + "AGENT_SELECT_LABEL": "Select Agent", + "ASSIGN_CONFIRMATION_LABEL": "Are you sure you want to assign %{conversationCount} %{conversationLabel} to", + "GO_BACK_LABEL": "Go back", + "ASSIGN_LABEL": "Assign", + "ASSIGN_AGENT_TOOLTIP": "Assign Agent", + "RESOLVE_TOOLTIP": "Resolve", + "ASSIGN_SUCCESFUL": "Conversations assigned successfully", + "ASSIGN_FAILED": "Failed to assign conversations, please try again", + "RESOLVE_SUCCESFUL": "Conversations resolved successfully", + "RESOLVE_FAILED": "Failed to resolve conversations, please try again", + "ALL_CONVERSATIONS_SELECTED_ALERT": "Conversations visible on this page are only selected.", + "AGENT_LIST_LOADING": "Loading Agents" + } +} diff --git a/app/javascript/dashboard/i18n/locale/en/index.js b/app/javascript/dashboard/i18n/locale/en/index.js index 5c1449fab..b21009322 100644 --- a/app/javascript/dashboard/i18n/locale/en/index.js +++ b/app/javascript/dashboard/i18n/locale/en/index.js @@ -21,6 +21,7 @@ import { default as _setNewPassword } from './setNewPassword.json'; import { default as _settings } from './settings.json'; import { default as _signup } from './signup.json'; import { default as _teamsSettings } from './teamsSettings.json'; +import { default as _bulkActions } from './bulkActions.json'; export default { ..._advancedFilters, @@ -46,4 +47,5 @@ export default { ..._settings, ..._signup, ..._teamsSettings, + ..._bulkActions, }; diff --git a/app/javascript/dashboard/store/index.js b/app/javascript/dashboard/store/index.js index 5cd1a0112..4957a27cc 100755 --- a/app/javascript/dashboard/store/index.js +++ b/app/javascript/dashboard/store/index.js @@ -6,6 +6,7 @@ import agents from './modules/agents'; import attributes from './modules/attributes'; import auth from './modules/auth'; import automations from './modules/automations'; +import bulkActions from './modules/bulkActions'; import campaigns from './modules/campaigns'; import cannedResponse from './modules/cannedResponse'; import contactConversations from './modules/contactConversations'; @@ -43,6 +44,7 @@ export default new Vuex.Store({ attributes, auth, automations, + bulkActions, campaigns, cannedResponse, contactConversations, diff --git a/app/javascript/dashboard/store/modules/bulkActions.js b/app/javascript/dashboard/store/modules/bulkActions.js new file mode 100644 index 000000000..709f4875a --- /dev/null +++ b/app/javascript/dashboard/store/modules/bulkActions.js @@ -0,0 +1,44 @@ +import types from '../mutation-types'; +import BulkActionsAPI from '../../api/bulkActions'; + +export const state = { + uiFlags: { + isUpdating: false, + }, +}; + +export const getters = { + getUIFlags(_state) { + return _state.uiFlags; + }, +}; + +export const actions = { + process: async function processAction({ commit }, payload) { + commit(types.SET_BULK_ACTIONS_FLAG, { isUpdating: true }); + try { + await BulkActionsAPI.create(payload); + } catch (error) { + throw new Error(error); + } finally { + commit(types.SET_BULK_ACTIONS_FLAG, { isUpdating: false }); + } + }, +}; + +export const mutations = { + [types.SET_BULK_ACTIONS_FLAG](_state, data) { + _state.uiFlags = { + ..._state.uiFlags, + ...data, + }; + }, +}; + +export default { + namespaced: true, + actions, + state, + getters, + mutations, +}; diff --git a/app/javascript/dashboard/store/modules/inboxAssignableAgents.js b/app/javascript/dashboard/store/modules/inboxAssignableAgents.js index 1acb6897a..08baeb536 100644 --- a/app/javascript/dashboard/store/modules/inboxAssignableAgents.js +++ b/app/javascript/dashboard/store/modules/inboxAssignableAgents.js @@ -26,13 +26,16 @@ export const getters = { }; export const actions = { - async fetch({ commit }, { inboxId }) { + async fetch({ commit }, inboxIds) { commit(types.SET_INBOX_ASSIGNABLE_AGENTS_UI_FLAG, { isFetching: true }); try { const { data: { payload }, - } = await AssignableAgentsAPI.get([inboxId]); - commit(types.SET_INBOX_ASSIGNABLE_AGENTS, { inboxId, members: payload }); + } = await AssignableAgentsAPI.get(inboxIds); + commit(types.SET_INBOX_ASSIGNABLE_AGENTS, { + inboxId: inboxIds.join(','), + members: payload, + }); } catch (error) { throw new Error(error); } finally { diff --git a/app/javascript/dashboard/store/modules/specs/bulkActions/actions.spec.js b/app/javascript/dashboard/store/modules/specs/bulkActions/actions.spec.js new file mode 100644 index 000000000..5342c46fd --- /dev/null +++ b/app/javascript/dashboard/store/modules/specs/bulkActions/actions.spec.js @@ -0,0 +1,28 @@ +import axios from 'axios'; +import { actions } from '../../bulkActions'; +import * as types from '../../../mutation-types'; +import payload from './fixtures'; +const commit = jest.fn(); +global.axios = axios; +jest.mock('axios'); + +describe('#actions', () => { + describe('#create', () => { + it('sends correct actions if API is success', async () => { + axios.post.mockResolvedValue({ data: payload }); + await actions.process({ commit }, payload); + expect(commit.mock.calls).toEqual([ + [types.default.SET_BULK_ACTIONS_FLAG, { isUpdating: true }], + [types.default.SET_BULK_ACTIONS_FLAG, { isUpdating: false }], + ]); + }); + it('sends correct actions if API is error', async () => { + axios.post.mockRejectedValue({ message: 'Incorrect header' }); + await expect(actions.process({ commit })).rejects.toThrow(Error); + expect(commit.mock.calls).toEqual([ + [types.default.SET_BULK_ACTIONS_FLAG, { isUpdating: true }], + [types.default.SET_BULK_ACTIONS_FLAG, { isUpdating: false }], + ]); + }); + }); +}); diff --git a/app/javascript/dashboard/store/modules/specs/bulkActions/fixtures.js b/app/javascript/dashboard/store/modules/specs/bulkActions/fixtures.js new file mode 100644 index 000000000..3b3606e36 --- /dev/null +++ b/app/javascript/dashboard/store/modules/specs/bulkActions/fixtures.js @@ -0,0 +1,5 @@ +export default { + type: 'Conversation', + ids: [64, 39], + fields: { assignee_id: 6 }, +}; diff --git a/app/javascript/dashboard/store/modules/specs/bulkActions/getters.spec.js b/app/javascript/dashboard/store/modules/specs/bulkActions/getters.spec.js new file mode 100644 index 000000000..2349e2efb --- /dev/null +++ b/app/javascript/dashboard/store/modules/specs/bulkActions/getters.spec.js @@ -0,0 +1,14 @@ +import { getters } from '../../bulkActions'; + +describe('#getters', () => { + it('getUIFlags', () => { + const state = { + uiFlags: { + isUpdating: false, + }, + }; + expect(getters.getUIFlags(state)).toEqual({ + isUpdating: false, + }); + }); +}); diff --git a/app/javascript/dashboard/store/modules/specs/bulkActions/mutations.spec.js b/app/javascript/dashboard/store/modules/specs/bulkActions/mutations.spec.js new file mode 100644 index 000000000..29278c72e --- /dev/null +++ b/app/javascript/dashboard/store/modules/specs/bulkActions/mutations.spec.js @@ -0,0 +1,12 @@ +import types from '../../../mutation-types'; +import { mutations } from '../../bulkActions'; + +describe('#mutations', () => { + describe('#toggleUiFlag', () => { + it('set update flags', () => { + const state = { uiFlags: { isUpdating: false } }; + mutations[types.SET_BULK_ACTIONS_FLAG](state, { isUpdating: true }); + expect(state.uiFlags.isUpdating).toEqual(true); + }); + }); +}); diff --git a/app/javascript/dashboard/store/modules/specs/inboxAssignableMembers/actions.spec.js b/app/javascript/dashboard/store/modules/specs/inboxAssignableMembers/actions.spec.js index 480ee657f..39b21b3c0 100644 --- a/app/javascript/dashboard/store/modules/specs/inboxAssignableMembers/actions.spec.js +++ b/app/javascript/dashboard/store/modules/specs/inboxAssignableMembers/actions.spec.js @@ -12,12 +12,12 @@ describe('#actions', () => { axios.get.mockResolvedValue({ data: { payload: agentsData }, }); - await actions.fetch({ commit }, { inboxId: 1 }); + await actions.fetch({ commit }, [1]); expect(commit.mock.calls).toEqual([ [types.SET_INBOX_ASSIGNABLE_AGENTS_UI_FLAG, { isFetching: true }], [ types.SET_INBOX_ASSIGNABLE_AGENTS, - { inboxId: 1, members: agentsData }, + { inboxId: '1', members: agentsData }, ], [types.SET_INBOX_ASSIGNABLE_AGENTS_UI_FLAG, { isFetching: false }], ]); diff --git a/app/javascript/dashboard/store/mutation-types.js b/app/javascript/dashboard/store/mutation-types.js index b78e468a1..e20efbf78 100755 --- a/app/javascript/dashboard/store/mutation-types.js +++ b/app/javascript/dashboard/store/mutation-types.js @@ -211,6 +211,9 @@ export default { ADD_CUSTOM_VIEW: 'ADD_CUSTOM_VIEW', DELETE_CUSTOM_VIEW: 'DELETE_CUSTOM_VIEW', + // Bulk Actions + SET_BULK_ACTIONS_FLAG: 'SET_BULK_ACTIONS_FLAG', + // Dashboard Apps SET_DASHBOARD_APPS_UI_FLAG: 'SET_DASHBOARD_APPS_UI_FLAG', SET_DASHBOARD_APPS: 'SET_DASHBOARD_APPS', diff --git a/app/javascript/shared/assets/stylesheets/shadows.scss b/app/javascript/shared/assets/stylesheets/shadows.scss index 0e44e1d26..6b11a427b 100644 --- a/app/javascript/shared/assets/stylesheets/shadows.scss +++ b/app/javascript/shared/assets/stylesheets/shadows.scss @@ -8,4 +8,9 @@ 0 4px 6px -2px rgba(0, 0, 0, 0.05); --shadow-larger: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); + --shadow-dropdown-pane: + 0 0.8rem 1.6rem rgb(50 50 93 / 8%), + 0 0.4rem 1.2rem rgb(0 0 0 / 7%); + --shadow-bulk-action-container: + 6px 3px 22px 9px rgb(181 181 181 / 25%); } diff --git a/app/javascript/shared/components/FluentIcon/dashboard-icons.json b/app/javascript/shared/components/FluentIcon/dashboard-icons.json index b42744990..76e04a538 100644 --- a/app/javascript/shared/components/FluentIcon/dashboard-icons.json +++ b/app/javascript/shared/components/FluentIcon/dashboard-icons.json @@ -99,6 +99,7 @@ "people-outline": "M4 13.999 13 14a2 2 0 0 1 1.995 1.85L15 16v1.5C14.999 21 11.284 22 8.5 22c-2.722 0-6.335-.956-6.495-4.27L2 17.5v-1.501c0-1.054.816-1.918 1.85-1.995L4 14ZM15.22 14H20c1.054 0 1.918.816 1.994 1.85L22 16v1c-.001 3.062-2.858 4-5 4a7.16 7.16 0 0 1-2.14-.322c.336-.386.607-.827.802-1.327A6.19 6.19 0 0 0 17 19.5l.267-.006c.985-.043 3.086-.363 3.226-2.289L20.5 17v-1a.501.501 0 0 0-.41-.492L20 15.5h-4.051a2.957 2.957 0 0 0-.595-1.34L15.22 14H20h-4.78ZM4 15.499l-.1.01a.51.51 0 0 0-.254.136.506.506 0 0 0-.136.253l-.01.101V17.5c0 1.009.45 1.722 1.417 2.242.826.445 2.003.714 3.266.753l.317.005.317-.005c1.263-.039 2.439-.308 3.266-.753.906-.488 1.359-1.145 1.412-2.057l.005-.186V16a.501.501 0 0 0-.41-.492L13 15.5l-9-.001ZM8.5 3a4.5 4.5 0 1 1 0 9 4.5 4.5 0 0 1 0-9Zm9 2a3.5 3.5 0 1 1 0 7 3.5 3.5 0 0 1 0-7Zm-9-.5c-1.654 0-3 1.346-3 3s1.346 3 3 3 3-1.346 3-3-1.346-3-3-3Zm9 2c-1.103 0-2 .897-2 2s.897 2 2 2 2-.897 2-2-.897-2-2-2Z", "people-team-outline": "M14.754 10c.966 0 1.75.784 1.75 1.75v4.749a4.501 4.501 0 0 1-9.002 0V11.75c0-.966.783-1.75 1.75-1.75h5.502Zm0 1.5H9.252a.25.25 0 0 0-.25.25v4.749a3.001 3.001 0 0 0 6.002 0V11.75a.25.25 0 0 0-.25-.25ZM3.75 10h3.381a2.738 2.738 0 0 0-.618 1.5H3.75a.25.25 0 0 0-.25.25v3.249a2.501 2.501 0 0 0 3.082 2.433c.085.504.24.985.453 1.432A4.001 4.001 0 0 1 2 14.999V11.75c0-.966.784-1.75 1.75-1.75Zm13.125 0h3.375c.966 0 1.75.784 1.75 1.75V15a4 4 0 0 1-5.03 3.866c.214-.448.369-.929.455-1.433A2.5 2.5 0 0 0 20.5 15v-3.25a.25.25 0 0 0-.25-.25h-2.757a2.738 2.738 0 0 0-.618-1.5ZM12 3a3 3 0 1 1 0 6 3 3 0 0 1 0-6Zm6.5 1a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5Zm-13 0a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5Zm6.5.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Zm6.5 1a1 1 0 1 0 0 2 1 1 0 0 0 0-2Zm-13 0a1 1 0 1 0 0 2 1 1 0 0 0 0-2Z", "person-add-outline": "M17.5 12a5.5 5.5 0 1 1 0 11a5.5 5.5 0 0 1 0-11zm-5.477 2a6.47 6.47 0 0 0-.709 1.5H4.253a.749.749 0 0 0-.75.75v.577c0 .535.192 1.053.54 1.46c1.253 1.469 3.22 2.214 5.957 2.214c.597 0 1.157-.035 1.68-.106c.246.495.553.954.912 1.367c-.795.16-1.66.24-2.592.24c-3.146 0-5.532-.906-7.098-2.74a3.75 3.75 0 0 1-.898-2.435v-.578A2.249 2.249 0 0 1 4.253 14h7.77zm5.477 0l-.09.008a.5.5 0 0 0-.402.402L17 14.5V17h-2.496l-.09.008a.5.5 0 0 0-.402.402l-.008.09l.008.09a.5.5 0 0 0 .402.402l.09.008H17L17 20.5l.008.09a.5.5 0 0 0 .402.402l.09.008l.09-.008a.5.5 0 0 0 .402-.402L18 20.5V18h2.504l.09-.008a.5.5 0 0 0 .402-.402l.008-.09l-.008-.09a.5.5 0 0 0-.402-.402l-.09-.008H18L18 14.5l-.008-.09a.5.5 0 0 0-.402-.402L17.5 14zM10 2.005a5 5 0 1 1 0 10a5 5 0 0 1 0-10zm0 1.5a3.5 3.5 0 1 0 0 7a3.5 3.5 0 0 0 0-7z", + "person-assign-outline": "M11.313 15.5a6.471 6.471 0 0 1 .709-1.5h-7.77a2.249 2.249 0 0 0-2.249 2.25v.577c0 .892.319 1.756.899 2.435c1.566 1.834 3.952 2.74 7.098 2.74c.931 0 1.796-.08 2.592-.24a6.51 6.51 0 0 1-.913-1.366c-.524.07-1.083.105-1.68.105c-2.737 0-4.703-.745-5.957-2.213a2.25 2.25 0 0 1-.539-1.461v-.578a.75.75 0 0 1 .75-.749h7.06ZM10 2.005a5 5 0 1 1 0 10a5 5 0 0 1 0-10Zm0 1.5a3.5 3.5 0 1 0 0 7a3.5 3.5 0 0 0 0-7ZM23 17.5a5.5 5.5 0 1 1-11 0a5.5 5.5 0 0 1 11 0Zm-4.647-2.853a.5.5 0 0 0-.707.707L19.293 17H15a.5.5 0 1 0 0 1h4.293l-1.647 1.647a.5.5 0 0 0 .707.707l2.5-2.5a.497.497 0 0 0 .147-.345V17.5a.498.498 0 0 0-.15-.357l-2.497-2.496Z", "person-outline": "M17.754 14a2.249 2.249 0 0 1 2.25 2.249v.575c0 .894-.32 1.76-.902 2.438-1.57 1.834-3.957 2.739-7.102 2.739-3.146 0-5.532-.905-7.098-2.74a3.75 3.75 0 0 1-.898-2.435v-.577a2.249 2.249 0 0 1 2.249-2.25h11.501Zm0 1.5H6.253a.749.749 0 0 0-.75.749v.577c0 .536.192 1.054.54 1.461 1.253 1.468 3.219 2.214 5.957 2.214s4.706-.746 5.962-2.214a2.25 2.25 0 0 0 .541-1.463v-.575a.749.749 0 0 0-.749-.75ZM12 2.004a5 5 0 1 1 0 10 5 5 0 0 1 0-10Zm0 1.5a3.5 3.5 0 1 0 0 7 3.5 3.5 0 0 0 0-7Z", "person-account-outline": "M11 15c0-.35.06-.687.17-1H4.253a2.249 2.249 0 0 0-2.249 2.249v.578c0 .892.319 1.756.899 2.435 1.566 1.834 3.952 2.74 7.098 2.74.397 0 .783-.015 1.156-.044A2.998 2.998 0 0 1 11 21v-.535c-.321.024-.655.036-1 .036-2.738 0-4.704-.746-5.958-2.213a2.25 2.25 0 0 1-.539-1.462v-.577c0-.414.336-.75.75-.75H11V15ZM10 2.005a5 5 0 1 1 0 10 5 5 0 0 1 0-10Zm0 1.5a3.5 3.5 0 1 0 0 7 3.5 3.5 0 0 0 0-7ZM12 15a2 2 0 0 1 2-2h7a2 2 0 0 1 2 2v6a2 2 0 0 1-2 2h-7a2 2 0 0 1-2-2v-6Zm2.5 1a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1h-6Zm0 3a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1h-6Z", "power-outline": "M8.204 4.82a.75.75 0 0 1 .634 1.36A7.51 7.51 0 0 0 4.5 12.991c0 4.148 3.358 7.51 7.499 7.51s7.499-3.362 7.499-7.51a7.51 7.51 0 0 0-4.323-6.804.75.75 0 1 1 .637-1.358 9.01 9.01 0 0 1 5.186 8.162c0 4.976-4.029 9.01-9 9.01C7.029 22 3 17.966 3 12.99a9.01 9.01 0 0 1 5.204-8.17ZM12 2.496a.75.75 0 0 1 .743.648l.007.102v7.5a.75.75 0 0 1-1.493.102l-.007-.102v-7.5a.75.75 0 0 1 .75-.75Z", diff --git a/deployment/setup_20.04.sh b/deployment/setup_20.04.sh index 6f9512f39..15f538846 100644 --- a/deployment/setup_20.04.sh +++ b/deployment/setup_20.04.sh @@ -179,7 +179,8 @@ function main() { Chatwoot Installation (latest) *************************************************************************** -For more verbose logs, open up a second terminal and follow along using, `tail -f /var/log/chatwoot`. +For more verbose logs, open up a second terminal and follow along using, +'tail -f /var/log/chatwoot'. EOF @@ -199,7 +200,7 @@ EOF echo "***** Skipping Postgres and Redis installation. ****" fi - echo -en "\n➥ 1/9 Installing dependencies. This takes a while." + echo -en "\n➥ 1/9 Installing dependencies. This takes a while.\n" install_dependencies &>> "${LOG_FILE}" if [ "$install_pg_redis" != "no" ] @@ -273,7 +274,7 @@ The database migrations had not run as Postgres and Redis were not installed as part of the installation process. After modifying the environment variables (in the .env file) with your external database credentials, run the database migrations using the below command. -`RAILS_ENV=production bundle exec rails db:chatwoot_prepare`. +'RAILS_ENV=production bundle exec rails db:chatwoot_prepare'. *************************************************************************** EOF fi