Compare commits
23
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b0ff544f9 | ||
|
|
65ac19718f | ||
|
|
7808634432 | ||
|
|
f429db89b4 | ||
|
|
b5ebc47637 | ||
|
|
f916fb2924 | ||
|
|
dc335e88c9 | ||
|
|
b1120ae7fb | ||
|
|
443214e9a0 | ||
|
|
3ce026e2bc | ||
|
|
f42fddd38e | ||
|
|
22b5e12a53 | ||
|
|
03bde0a8aa | ||
|
|
3a0b5f387d | ||
|
|
9bd658137a | ||
|
|
f73c5ef0b8 | ||
|
|
f9fce5e2df | ||
|
|
c2d8e2ad77 | ||
|
|
03c0a7c62e | ||
|
|
d40a59f7fa | ||
|
|
72c5671e09 | ||
|
|
8c0885e1d2 | ||
|
|
e92f72b318 |
@@ -15,6 +15,10 @@ class Api::V1::Accounts::SearchController < Api::V1::Accounts::BaseController
|
||||
@result = search('Message')
|
||||
end
|
||||
|
||||
def articles
|
||||
@result = search('Article')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def search(search_type)
|
||||
|
||||
@@ -32,22 +32,17 @@ class SuperAdmin::AppConfigsController < SuperAdmin::ApplicationController
|
||||
end
|
||||
|
||||
def allowed_configs
|
||||
@allowed_configs = case @config
|
||||
when 'facebook'
|
||||
%w[FB_APP_ID FB_VERIFY_TOKEN FB_APP_SECRET IG_VERIFY_TOKEN FACEBOOK_API_VERSION ENABLE_MESSENGER_CHANNEL_HUMAN_AGENT]
|
||||
when 'shopify'
|
||||
%w[SHOPIFY_CLIENT_ID SHOPIFY_CLIENT_SECRET]
|
||||
when 'microsoft'
|
||||
%w[AZURE_APP_ID AZURE_APP_SECRET]
|
||||
when 'email'
|
||||
['MAILER_INBOUND_EMAIL_DOMAIN']
|
||||
when 'linear'
|
||||
%w[LINEAR_CLIENT_ID LINEAR_CLIENT_SECRET]
|
||||
when 'instagram'
|
||||
%w[INSTAGRAM_APP_ID INSTAGRAM_APP_SECRET INSTAGRAM_VERIFY_TOKEN INSTAGRAM_API_VERSION ENABLE_INSTAGRAM_CHANNEL_HUMAN_AGENT]
|
||||
else
|
||||
%w[ENABLE_ACCOUNT_SIGNUP FIREBASE_PROJECT_ID FIREBASE_CREDENTIALS]
|
||||
end
|
||||
mapping = {
|
||||
'facebook' => %w[FB_APP_ID FB_VERIFY_TOKEN FB_APP_SECRET IG_VERIFY_TOKEN FACEBOOK_API_VERSION ENABLE_MESSENGER_CHANNEL_HUMAN_AGENT],
|
||||
'shopify' => %w[SHOPIFY_CLIENT_ID SHOPIFY_CLIENT_SECRET],
|
||||
'microsoft' => %w[AZURE_APP_ID AZURE_APP_SECRET],
|
||||
'email' => ['MAILER_INBOUND_EMAIL_DOMAIN'],
|
||||
'linear' => %w[LINEAR_CLIENT_ID LINEAR_CLIENT_SECRET],
|
||||
'slack' => %w[SLACK_CLIENT_ID SLACK_CLIENT_SECRET],
|
||||
'instagram' => %w[INSTAGRAM_APP_ID INSTAGRAM_APP_SECRET INSTAGRAM_VERIFY_TOKEN INSTAGRAM_API_VERSION ENABLE_INSTAGRAM_CHANNEL_HUMAN_AGENT]
|
||||
}
|
||||
|
||||
@allowed_configs = mapping.fetch(@config, %w[ENABLE_ACCOUNT_SIGNUP FIREBASE_PROJECT_ID FIREBASE_CREDENTIALS])
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
/* global axios */
|
||||
import ApiClient from '../ApiClient';
|
||||
|
||||
class CopilotMessages extends ApiClient {
|
||||
constructor() {
|
||||
super('captain/copilot_threads', { accountScoped: true });
|
||||
}
|
||||
|
||||
get(threadId) {
|
||||
return axios.get(`${this.url}/${threadId}/copilot_messages`);
|
||||
}
|
||||
|
||||
create({ threadId, ...rest }) {
|
||||
return axios.post(`${this.url}/${threadId}/copilot_messages`, rest);
|
||||
}
|
||||
}
|
||||
|
||||
export default new CopilotMessages();
|
||||
@@ -0,0 +1,9 @@
|
||||
import ApiClient from '../ApiClient';
|
||||
|
||||
class CopilotThreads extends ApiClient {
|
||||
constructor() {
|
||||
super('captain/copilot_threads', { accountScoped: true });
|
||||
}
|
||||
}
|
||||
|
||||
export default new CopilotThreads();
|
||||
@@ -40,6 +40,15 @@ class SearchAPI extends ApiClient {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
articles({ q, page = 1 }) {
|
||||
return axios.get(`${this.url}/articles`, {
|
||||
params: {
|
||||
q,
|
||||
page: page,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new SearchAPI();
|
||||
|
||||
+23
-1
@@ -42,6 +42,7 @@ const initialState = {
|
||||
conversationFaqs: false,
|
||||
memories: false,
|
||||
},
|
||||
temperature: 1,
|
||||
};
|
||||
|
||||
const state = reactive({ ...initialState });
|
||||
@@ -87,6 +88,7 @@ const updateStateFromAssistant = assistant => {
|
||||
conversationFaqs: config.feature_faq || false,
|
||||
memories: config.feature_memory || false,
|
||||
};
|
||||
state.temperature = config.temperature || 1;
|
||||
};
|
||||
|
||||
const handleBasicInfoUpdate = async () => {
|
||||
@@ -136,6 +138,7 @@ const handleInstructionsUpdate = async () => {
|
||||
const payload = {
|
||||
config: {
|
||||
...props.assistant.config,
|
||||
temperature: state.temperature || 1,
|
||||
instructions: state.instructions,
|
||||
},
|
||||
};
|
||||
@@ -212,7 +215,7 @@ watch(
|
||||
|
||||
<!-- Instructions Section -->
|
||||
<Accordion :title="t('CAPTAIN.ASSISTANTS.FORM.SECTIONS.INSTRUCTIONS')">
|
||||
<div class="flex flex-col gap-4 pt-4">
|
||||
<div class="flex flex-col gap-4">
|
||||
<Editor
|
||||
v-model="state.instructions"
|
||||
:placeholder="t('CAPTAIN.ASSISTANTS.FORM.INSTRUCTIONS.PLACEHOLDER')"
|
||||
@@ -221,6 +224,25 @@ watch(
|
||||
:message-type="formErrors.instructions ? 'error' : 'info'"
|
||||
/>
|
||||
|
||||
<div class="flex flex-col gap-2 mt-4">
|
||||
<label class="text-sm font-medium text-n-slate-12">
|
||||
{{ t('CAPTAIN.ASSISTANTS.FORM.TEMPERATURE.LABEL') }}
|
||||
</label>
|
||||
<div class="flex items-center gap-4">
|
||||
<input
|
||||
v-model="state.temperature"
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.1"
|
||||
class="w-full"
|
||||
/>
|
||||
<span class="text-sm text-n-slate-12">{{ state.temperature }}</span>
|
||||
</div>
|
||||
<p class="text-sm text-n-slate-11 italic">
|
||||
{{ t('CAPTAIN.ASSISTANTS.FORM.TEMPERATURE.DESCRIPTION') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<Button
|
||||
size="small"
|
||||
|
||||
@@ -315,11 +315,7 @@ const componentToRender = computed(() => {
|
||||
});
|
||||
|
||||
const shouldShowContextMenu = computed(() => {
|
||||
return !(
|
||||
props.status === MESSAGE_STATUS.FAILED ||
|
||||
props.status === MESSAGE_STATUS.PROGRESS ||
|
||||
props.contentAttributes?.isUnsupported
|
||||
);
|
||||
return !props.contentAttributes?.isUnsupported;
|
||||
});
|
||||
|
||||
const isBubble = computed(() => {
|
||||
@@ -344,12 +340,23 @@ const contextMenuEnabledOptions = computed(() => {
|
||||
const hasAttachments = !!(props.attachments && props.attachments.length > 0);
|
||||
|
||||
const isOutgoing = props.messageType === MESSAGE_TYPES.OUTGOING;
|
||||
const isFailedOrProcessing =
|
||||
props.status === MESSAGE_STATUS.FAILED ||
|
||||
props.status === MESSAGE_STATUS.PROGRESS;
|
||||
|
||||
return {
|
||||
copy: hasText,
|
||||
delete: hasText || hasAttachments,
|
||||
cannedResponse: isOutgoing && hasText,
|
||||
replyTo: !props.private && props.inboxSupportsReplyTo.outgoing,
|
||||
delete:
|
||||
(hasText || hasAttachments) &&
|
||||
!isFailedOrProcessing &&
|
||||
!isMessageDeleted.value,
|
||||
cannedResponse: isOutgoing && hasText && !isMessageDeleted.value,
|
||||
copyLink: !isFailedOrProcessing,
|
||||
translate: !isFailedOrProcessing && !isMessageDeleted.value && hasText,
|
||||
replyTo:
|
||||
!props.private &&
|
||||
props.inboxSupportsReplyTo.outgoing &&
|
||||
!isFailedOrProcessing,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -499,8 +506,8 @@ provideMessageContext({
|
||||
<div
|
||||
class="[grid-area:bubble] flex"
|
||||
:class="{
|
||||
'ltr:pl-8 rtl:pr-8 justify-end': orientation === ORIENTATION.RIGHT,
|
||||
'ltr:pr-8 rtl:pl-8': orientation === ORIENTATION.LEFT,
|
||||
'ltr:ml-8 rtl:mr-8 justify-end': orientation === ORIENTATION.RIGHT,
|
||||
'ltr:mr-8 rtl:ml-8': orientation === ORIENTATION.LEFT,
|
||||
'min-w-0': variant === MESSAGE_VARIANTS.EMAIL,
|
||||
}"
|
||||
@contextmenu="openContextMenu($event)"
|
||||
@@ -516,7 +523,7 @@ provideMessageContext({
|
||||
</div>
|
||||
<div v-if="shouldShowContextMenu" class="context-menu-wrap">
|
||||
<ContextMenu
|
||||
v-if="isBubble && !isMessageDeleted"
|
||||
v-if="isBubble"
|
||||
:context-menu-position="contextMenuPosition"
|
||||
:is-open="showContextMenu"
|
||||
:enabled-options="contextMenuEnabledOptions"
|
||||
|
||||
@@ -40,7 +40,7 @@ const senderName = computed(() => {
|
||||
<Icon :icon="icon" class="text-white size-4" />
|
||||
</slot>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<div class="space-y-1 overflow-hidden">
|
||||
<div v-if="senderName" class="text-n-slate-12 text-sm truncate">
|
||||
{{
|
||||
t(senderTranslationKey, {
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
computed,
|
||||
watch,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
defineEmits,
|
||||
} from 'vue';
|
||||
import { useStore } from 'vuex';
|
||||
@@ -44,7 +43,7 @@ import {
|
||||
useSnakeCase,
|
||||
} from 'dashboard/composables/useTransformKeys';
|
||||
import { useEmitter } from 'dashboard/composables/emitter';
|
||||
import { useEventListener, useScrollLock } from '@vueuse/core';
|
||||
import { useEventListener } from '@vueuse/core';
|
||||
|
||||
import { emitter } from 'shared/helpers/mitt';
|
||||
|
||||
@@ -87,12 +86,8 @@ const store = useStore();
|
||||
|
||||
const conversationListRef = ref(null);
|
||||
const conversationDynamicScroller = ref(null);
|
||||
const conversationListScrollableElement = computed(
|
||||
() => conversationDynamicScroller.value?.$el
|
||||
);
|
||||
const conversationListScrollLock = useScrollLock(
|
||||
conversationListScrollableElement
|
||||
);
|
||||
|
||||
provide('contextMenuElementTarget', conversationDynamicScroller);
|
||||
|
||||
const activeAssigneeTab = ref(wootConstants.ASSIGNEE_TYPE.ME);
|
||||
const activeStatus = ref(wootConstants.STATUS_TYPE.OPEN);
|
||||
@@ -746,7 +741,6 @@ function allSelectedConversationsStatus(status) {
|
||||
|
||||
function onContextMenuToggle(state) {
|
||||
isContextMenuOpen.value = state;
|
||||
conversationListScrollLock.value = state;
|
||||
}
|
||||
|
||||
function toggleSelectAll(check) {
|
||||
@@ -770,10 +764,6 @@ onMounted(() => {
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
conversationListScrollLock.value = false;
|
||||
});
|
||||
|
||||
provide('selectConversation', selectConversation);
|
||||
provide('deSelectConversation', deSelectConversation);
|
||||
provide('assignAgent', onAssignAgent);
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, nextTick, useTemplateRef } from 'vue';
|
||||
import { useWindowSize, useElementBounding } from '@vueuse/core';
|
||||
import {
|
||||
computed,
|
||||
onMounted,
|
||||
nextTick,
|
||||
onUnmounted,
|
||||
useTemplateRef,
|
||||
inject,
|
||||
} from 'vue';
|
||||
import { useWindowSize, useElementBounding, useScrollLock } from '@vueuse/core';
|
||||
|
||||
import TeleportWithDirection from 'dashboard/components-next/TeleportWithDirection.vue';
|
||||
|
||||
@@ -11,27 +18,34 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(['close']);
|
||||
|
||||
const elementToLock = inject('contextMenuElementTarget', null);
|
||||
|
||||
const menuRef = useTemplateRef('menuRef');
|
||||
|
||||
const scrollLockElement = computed(() => {
|
||||
if (!elementToLock?.value) return null;
|
||||
return elementToLock.value?.$el;
|
||||
});
|
||||
|
||||
const isLocked = useScrollLock(scrollLockElement);
|
||||
|
||||
const { width: windowWidth, height: windowHeight } = useWindowSize();
|
||||
const { width: menuWidth, height: menuHeight } = useElementBounding(menuRef);
|
||||
|
||||
const calculatePosition = (x, y, menuW, menuH, windowW, windowH) => {
|
||||
const PADDING = 16;
|
||||
// Initial position
|
||||
let left = x;
|
||||
let top = y;
|
||||
|
||||
// Boundary checks
|
||||
const isOverflowingRight = left + menuW > windowW;
|
||||
const isOverflowingBottom = top + menuH > windowH;
|
||||
|
||||
const isOverflowingRight = left + menuW > windowW - PADDING;
|
||||
const isOverflowingBottom = top + menuH > windowH - PADDING;
|
||||
// Adjust position if overflowing
|
||||
if (isOverflowingRight) left = windowW - menuW;
|
||||
if (isOverflowingBottom) top = windowH - menuH;
|
||||
|
||||
if (isOverflowingRight) left = windowW - menuW - PADDING;
|
||||
if (isOverflowingBottom) top = windowH - menuH - PADDING;
|
||||
return {
|
||||
left: Math.max(0, left),
|
||||
top: Math.max(0, top),
|
||||
left: Math.max(PADDING, left),
|
||||
top: Math.max(PADDING, top),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -54,8 +68,18 @@ const position = computed(() => {
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
isLocked.value = true;
|
||||
nextTick(() => menuRef.value?.focus());
|
||||
});
|
||||
|
||||
const handleClose = () => {
|
||||
isLocked.value = false;
|
||||
emit('close');
|
||||
};
|
||||
|
||||
onUnmounted(() => {
|
||||
isLocked.value = false;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -65,7 +89,7 @@ onMounted(() => {
|
||||
class="fixed outline-none z-[9999] cursor-pointer"
|
||||
:style="position"
|
||||
tabindex="0"
|
||||
@blur="emit('close')"
|
||||
@blur="handleClose"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
@@ -185,8 +185,17 @@ export default {
|
||||
contextMenuEnabledOptions() {
|
||||
return {
|
||||
copy: this.hasText,
|
||||
delete: this.hasText || this.hasAttachments,
|
||||
cannedResponse: this.isOutgoing && this.hasText,
|
||||
delete:
|
||||
(this.hasText || this.hasAttachments) &&
|
||||
!this.isMessageDeleted &&
|
||||
!this.isFailed,
|
||||
cannedResponse:
|
||||
this.isOutgoing && this.hasText && !this.isMessageDeleted,
|
||||
copyLink: !this.isFailed || !this.isProcessing,
|
||||
translate:
|
||||
(!this.isFailed || !this.isProcessing) &&
|
||||
!this.isMessageDeleted &&
|
||||
this.hasText,
|
||||
replyTo: !this.data.private && this.inboxSupportsReplyTo.outgoing,
|
||||
};
|
||||
},
|
||||
@@ -328,7 +337,7 @@ export default {
|
||||
return !this.sender.type || this.sender.type === 'agent_bot';
|
||||
},
|
||||
shouldShowContextMenu() {
|
||||
return !(this.isFailed || this.isPending || this.isUnsupported);
|
||||
return !this.isUnsupported;
|
||||
},
|
||||
showAvatar() {
|
||||
if (this.isOutgoing || this.isTemplate) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script>
|
||||
import { ref } from 'vue';
|
||||
import { ref, provide } from 'vue';
|
||||
// composable
|
||||
import { useConfig } from 'dashboard/composables/useConfig';
|
||||
import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents';
|
||||
@@ -63,6 +63,7 @@ export default {
|
||||
emits: ['contactPanelToggle'],
|
||||
setup() {
|
||||
const isPopOutReplyBox = ref(false);
|
||||
const conversationPanelRef = ref(null);
|
||||
const { isEnterprise } = useConfig();
|
||||
|
||||
const closePopOutReplyBox = () => {
|
||||
@@ -98,6 +99,8 @@ export default {
|
||||
FEATURE_FLAGS.CHATWOOT_V4
|
||||
);
|
||||
|
||||
provide('contextMenuElementTarget', conversationPanelRef);
|
||||
|
||||
return {
|
||||
isEnterprise,
|
||||
isPopOutReplyBox,
|
||||
@@ -108,6 +111,7 @@ export default {
|
||||
fetchIntegrationsIfRequired,
|
||||
fetchLabelSuggestions,
|
||||
showNextBubbles,
|
||||
conversationPanelRef,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
@@ -541,6 +545,7 @@ export default {
|
||||
</div>
|
||||
<NextMessageList
|
||||
v-if="showNextBubbles"
|
||||
ref="conversationPanelRef"
|
||||
class="conversation-panel"
|
||||
:current-user-id="currentUserId"
|
||||
:first-unread-id="unReadMessages[0]?.id"
|
||||
@@ -572,7 +577,7 @@ export default {
|
||||
/>
|
||||
</template>
|
||||
</NextMessageList>
|
||||
<ul v-else class="conversation-panel">
|
||||
<ul v-else ref="conversationPanelRef" class="conversation-panel">
|
||||
<transition name="slide-up">
|
||||
<!-- eslint-disable-next-line vue/require-toggle-inside-transition -->
|
||||
<li class="min-h-[4rem]">
|
||||
|
||||
@@ -3,6 +3,9 @@ import BaseActionCableConnector from '../../shared/helpers/BaseActionCableConnec
|
||||
import DashboardAudioNotificationHelper from './AudioAlerts/DashboardAudioNotificationHelper';
|
||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||
import { emitter } from 'shared/helpers/mitt';
|
||||
import { useImpersonation } from 'dashboard/composables/useImpersonation';
|
||||
|
||||
const { isImpersonating } = useImpersonation();
|
||||
|
||||
class ActionCableConnector extends BaseActionCableConnector {
|
||||
constructor(app, pubsubToken) {
|
||||
@@ -30,6 +33,7 @@ class ActionCableConnector extends BaseActionCableConnector {
|
||||
'conversation.read': this.onConversationRead,
|
||||
'conversation.updated': this.onConversationUpdated,
|
||||
'account.cache_invalidated': this.onCacheInvalidate,
|
||||
'copilot.message.created': this.onCopilotMessageCreated,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -52,6 +56,7 @@ class ActionCableConnector extends BaseActionCableConnector {
|
||||
};
|
||||
|
||||
onPresenceUpdate = data => {
|
||||
if (isImpersonating.value) return;
|
||||
this.app.$store.dispatch('contacts/updatePresence', data.contacts);
|
||||
this.app.$store.dispatch('agents/updatePresence', data.users);
|
||||
this.app.$store.dispatch('setCurrentUserAvailability', data.users);
|
||||
@@ -185,6 +190,10 @@ class ActionCableConnector extends BaseActionCableConnector {
|
||||
this.app.$store.dispatch('notifications/updateNotification', data);
|
||||
};
|
||||
|
||||
onCopilotMessageCreated = data => {
|
||||
this.app.$store.dispatch('copilotMessages/upsert', data);
|
||||
};
|
||||
|
||||
onCacheInvalidate = data => {
|
||||
const keys = data.cache_keys;
|
||||
this.app.$store.dispatch('labels/revalidate', { newKey: keys.label });
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { describe, it, beforeEach, expect, vi } from 'vitest';
|
||||
import ActionCableConnector from '../actionCable';
|
||||
|
||||
vi.mock('shared/helpers/mitt', () => ({
|
||||
emitter: {
|
||||
emit: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('dashboard/composables/useImpersonation', () => ({
|
||||
useImpersonation: () => ({
|
||||
isImpersonating: { value: false },
|
||||
}),
|
||||
}));
|
||||
|
||||
global.chatwootConfig = {
|
||||
websocketURL: 'wss://test.chatwoot.com',
|
||||
};
|
||||
|
||||
describe('ActionCableConnector - Copilot Tests', () => {
|
||||
let store;
|
||||
let actionCable;
|
||||
let mockDispatch;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockDispatch = vi.fn();
|
||||
store = {
|
||||
$store: {
|
||||
dispatch: mockDispatch,
|
||||
getters: {
|
||||
getCurrentAccountId: 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
actionCable = ActionCableConnector.init(store.$store, 'test-token');
|
||||
});
|
||||
describe('copilot event handlers', () => {
|
||||
it('should register the copilot.message.created event handler', () => {
|
||||
expect(Object.keys(actionCable.events)).toContain(
|
||||
'copilot.message.created'
|
||||
);
|
||||
expect(actionCable.events['copilot.message.created']).toBe(
|
||||
actionCable.onCopilotMessageCreated
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle the copilot.message.created event through the ActionCable system', () => {
|
||||
const copilotData = {
|
||||
id: 2,
|
||||
content: 'This is a copilot message from ActionCable',
|
||||
conversation_id: 456,
|
||||
created_at: '2025-05-27T15:58:04-06:00',
|
||||
account_id: 1,
|
||||
};
|
||||
actionCable.onReceived({
|
||||
event: 'copilot.message.created',
|
||||
data: copilotData,
|
||||
});
|
||||
expect(mockDispatch).toHaveBeenCalledWith(
|
||||
'copilotMessages/upsert',
|
||||
copilotData
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -367,7 +367,6 @@
|
||||
"CANCEL_ANYTIME": "You can change or cancel your plan anytime"
|
||||
},
|
||||
"ENTERPRISE_PAYWALL": {
|
||||
"AVAILABLE_ON": "Captain AI feature is only available in a paid plan.",
|
||||
"UPGRADE_PROMPT": "Upgrade your plan to get access to our assistants, copilot and more.",
|
||||
"ASK_ADMIN": "Please reach out to your administrator for the upgrade."
|
||||
},
|
||||
@@ -410,6 +409,10 @@
|
||||
"PLACEHOLDER": "Enter assistant name",
|
||||
"ERROR": "The name is required"
|
||||
},
|
||||
"TEMPERATURE": {
|
||||
"LABEL": "Response Temperature",
|
||||
"DESCRIPTION": "Adjust how creative or restrictive the assistant's responses should be. Lower values produce more focused and deterministic responses, while higher values allow for more creative and varied outputs."
|
||||
},
|
||||
"DESCRIPTION": {
|
||||
"LABEL": "Description",
|
||||
"PLACEHOLDER": "Enter assistant description",
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
"ALL": "All",
|
||||
"CONTACTS": "Contacts",
|
||||
"CONVERSATIONS": "Conversations",
|
||||
"MESSAGES": "Messages"
|
||||
"MESSAGES": "Messages",
|
||||
"ARTICLES": "Articles"
|
||||
},
|
||||
"SECTION": {
|
||||
"CONTACTS": "Contacts",
|
||||
"CONVERSATIONS": "Conversations",
|
||||
"MESSAGES": "Messages"
|
||||
"MESSAGES": "Messages",
|
||||
"ARTICLES": "Articles"
|
||||
},
|
||||
"VIEW_MORE": "View more",
|
||||
"LOAD_MORE": "Load more",
|
||||
|
||||
@@ -47,6 +47,7 @@ export default {
|
||||
emits: ['open', 'close', 'replyTo'],
|
||||
setup() {
|
||||
const { getPlainText } = useMessageFormatter();
|
||||
|
||||
return {
|
||||
getPlainText,
|
||||
};
|
||||
@@ -167,7 +168,7 @@ export default {
|
||||
</woot-modal>
|
||||
<!-- Confirm Deletion -->
|
||||
<woot-delete-modal
|
||||
v-if="showDeleteModal"
|
||||
v-if="showDeleteModal && enabledOptions['delete']"
|
||||
v-model:show="showDeleteModal"
|
||||
class="context-menu--delete-modal"
|
||||
:on-close="closeDeleteModal"
|
||||
@@ -212,7 +213,7 @@ export default {
|
||||
@click.stop="handleCopy"
|
||||
/>
|
||||
<MenuItem
|
||||
v-if="enabledOptions['copy']"
|
||||
v-if="enabledOptions['translate']"
|
||||
:option="{
|
||||
icon: 'translate',
|
||||
label: $t('CONVERSATION.CONTEXT_MENU.TRANSLATE'),
|
||||
@@ -222,6 +223,7 @@ export default {
|
||||
/>
|
||||
<hr />
|
||||
<MenuItem
|
||||
v-if="enabledOptions['copyLink']"
|
||||
:option="{
|
||||
icon: 'link',
|
||||
label: $t('CONVERSATION.CONTEXT_MENU.COPY_PERMALINK'),
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import Icon from 'next/icon/Icon.vue';
|
||||
import { frontendURL } from 'dashboard/helper/URLHelper';
|
||||
import MessageFormatter from 'shared/helpers/MessageFormatter';
|
||||
|
||||
const props = defineProps({
|
||||
id: { type: [String, Number], default: 0 },
|
||||
title: { type: String, default: '' },
|
||||
description: { type: String, default: '' },
|
||||
category: { type: String, default: '' },
|
||||
locale: { type: String, default: '' },
|
||||
content: { type: String, default: '' },
|
||||
portalSlug: { type: String, required: true },
|
||||
accountId: { type: [String, Number], default: 0 },
|
||||
});
|
||||
|
||||
const MAX_LENGTH = 300;
|
||||
|
||||
const navigateTo = computed(() => {
|
||||
return frontendURL(
|
||||
`accounts/${props.accountId}/portals/${props.portalSlug}/${props.locale}/articles/edit/${props.id}`
|
||||
);
|
||||
});
|
||||
|
||||
const truncatedContent = computed(() => {
|
||||
if (!props.content) return props.description || '';
|
||||
|
||||
// Use MessageFormatter to properly convert markdown to plain text
|
||||
const formatter = new MessageFormatter(props.content);
|
||||
const plainText = formatter.plainText.trim();
|
||||
|
||||
return plainText.length > MAX_LENGTH
|
||||
? `${plainText.substring(0, MAX_LENGTH)}...`
|
||||
: plainText;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<router-link
|
||||
:to="navigateTo"
|
||||
class="flex items-start p-2 rounded-xl cursor-pointer hover:bg-n-slate-2"
|
||||
>
|
||||
<div
|
||||
class="flex items-center justify-center w-6 h-6 mt-0.5 rounded bg-n-slate-3"
|
||||
>
|
||||
<Icon icon="i-lucide-library-big" class="text-n-slate-10" />
|
||||
</div>
|
||||
<div class="ltr:ml-2 rtl:mr-2 min-w-0 flex-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<h5 class="text-sm font-medium truncate min-w-0 text-n-slate-12">
|
||||
{{ title }}
|
||||
</h5>
|
||||
<span
|
||||
v-if="category"
|
||||
class="text-xs font-medium whitespace-nowrap capitalize bg-n-slate-3 px-1 py-0.5 rounded text-n-slate-10"
|
||||
>
|
||||
{{ category }}
|
||||
</span>
|
||||
</div>
|
||||
<p
|
||||
v-if="truncatedContent"
|
||||
class="mt-1 text-sm text-n-slate-11 line-clamp-2"
|
||||
>
|
||||
{{ truncatedContent }}
|
||||
</p>
|
||||
</div>
|
||||
</router-link>
|
||||
</template>
|
||||
@@ -0,0 +1,53 @@
|
||||
<script setup>
|
||||
import { useMapGetter } from 'dashboard/composables/store.js';
|
||||
|
||||
import SearchResultSection from './SearchResultSection.vue';
|
||||
import SearchResultArticleItem from './SearchResultArticleItem.vue';
|
||||
|
||||
defineProps({
|
||||
articles: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
query: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
isFetching: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showTitle: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
|
||||
const accountId = useMapGetter('getCurrentAccountId');
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SearchResultSection
|
||||
:title="$t('SEARCH.SECTION.ARTICLES')"
|
||||
:empty="!articles.length"
|
||||
:query="query"
|
||||
:show-title="showTitle"
|
||||
:is-fetching="isFetching"
|
||||
>
|
||||
<ul v-if="articles.length" class="space-y-1.5 list-none">
|
||||
<li v-for="article in articles" :key="article.id">
|
||||
<SearchResultArticleItem
|
||||
:id="article.id"
|
||||
:title="article.title"
|
||||
:description="article.description"
|
||||
:content="article.content"
|
||||
:portal-slug="article.portal_slug"
|
||||
:locale="article.locale"
|
||||
:account-id="accountId"
|
||||
:category="article.category_name"
|
||||
:status="article.status"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</SearchResultSection>
|
||||
</template>
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
ROLES,
|
||||
CONVERSATION_PERMISSIONS,
|
||||
CONTACT_PERMISSIONS,
|
||||
PORTAL_PERMISSIONS,
|
||||
} from 'dashboard/constants/permissions.js';
|
||||
import {
|
||||
getUserPermissions,
|
||||
@@ -22,6 +23,7 @@ import SearchTabs from './SearchTabs.vue';
|
||||
import SearchResultConversationsList from './SearchResultConversationsList.vue';
|
||||
import SearchResultMessagesList from './SearchResultMessagesList.vue';
|
||||
import SearchResultContactsList from './SearchResultContactsList.vue';
|
||||
import SearchResultArticlesList from './SearchResultArticlesList.vue';
|
||||
|
||||
const router = useRouter();
|
||||
const store = useStore();
|
||||
@@ -34,6 +36,7 @@ const pages = ref({
|
||||
contacts: 1,
|
||||
conversations: 1,
|
||||
messages: 1,
|
||||
articles: 1,
|
||||
});
|
||||
|
||||
const currentUser = useMapGetter('getCurrentUser');
|
||||
@@ -43,6 +46,7 @@ const conversationRecords = useMapGetter(
|
||||
'conversationSearch/getConversationRecords'
|
||||
);
|
||||
const messageRecords = useMapGetter('conversationSearch/getMessageRecords');
|
||||
const articleRecords = useMapGetter('conversationSearch/getArticleRecords');
|
||||
const uiFlags = useMapGetter('conversationSearch/getUIFlags');
|
||||
|
||||
const addTypeToRecords = (records, type) =>
|
||||
@@ -57,6 +61,9 @@ const mappedConversations = computed(() =>
|
||||
const mappedMessages = computed(() =>
|
||||
addTypeToRecords(messageRecords, 'message')
|
||||
);
|
||||
const mappedArticles = computed(() =>
|
||||
addTypeToRecords(articleRecords, 'article')
|
||||
);
|
||||
|
||||
const isSelectedTabAll = computed(() => selectedTab.value === 'all');
|
||||
|
||||
@@ -66,6 +73,7 @@ const sliceRecordsIfAllTab = items =>
|
||||
const contacts = computed(() => sliceRecordsIfAllTab(mappedContacts));
|
||||
const conversations = computed(() => sliceRecordsIfAllTab(mappedConversations));
|
||||
const messages = computed(() => sliceRecordsIfAllTab(mappedMessages));
|
||||
const articles = computed(() => sliceRecordsIfAllTab(mappedArticles));
|
||||
|
||||
const filterByTab = tab =>
|
||||
computed(() => selectedTab.value === tab || isSelectedTabAll.value);
|
||||
@@ -73,6 +81,7 @@ const filterByTab = tab =>
|
||||
const filterContacts = filterByTab('contacts');
|
||||
const filterConversations = filterByTab('conversations');
|
||||
const filterMessages = filterByTab('messages');
|
||||
const filterArticles = filterByTab('articles');
|
||||
|
||||
const userPermissions = computed(() =>
|
||||
getUserPermissions(currentUser.value, currentAccountId.value)
|
||||
@@ -80,7 +89,12 @@ const userPermissions = computed(() =>
|
||||
|
||||
const TABS_CONFIG = {
|
||||
all: {
|
||||
permissions: [CONTACT_PERMISSIONS, ...ROLES, ...CONVERSATION_PERMISSIONS],
|
||||
permissions: [
|
||||
CONTACT_PERMISSIONS,
|
||||
...ROLES,
|
||||
...CONVERSATION_PERMISSIONS,
|
||||
PORTAL_PERMISSIONS,
|
||||
],
|
||||
count: () => null, // No count for all tab
|
||||
},
|
||||
contacts: {
|
||||
@@ -95,6 +109,10 @@ const TABS_CONFIG = {
|
||||
permissions: [...ROLES, ...CONVERSATION_PERMISSIONS],
|
||||
count: () => mappedMessages.value.length,
|
||||
},
|
||||
articles: {
|
||||
permissions: [...ROLES, PORTAL_PERMISSIONS],
|
||||
count: () => mappedArticles.value.length,
|
||||
},
|
||||
};
|
||||
|
||||
const tabs = computed(() => {
|
||||
@@ -123,6 +141,10 @@ const totalSearchResultsCount = computed(() => {
|
||||
permissions: [...ROLES, ...CONVERSATION_PERMISSIONS],
|
||||
count: () => conversations.value.length + messages.value.length,
|
||||
},
|
||||
articles: {
|
||||
permissions: [...ROLES, PORTAL_PERMISSIONS],
|
||||
count: () => articles.value.length,
|
||||
},
|
||||
};
|
||||
return filterItemsByPermission(
|
||||
permissionCounts,
|
||||
@@ -138,12 +160,13 @@ const activeTabIndex = computed(() => {
|
||||
});
|
||||
|
||||
const isFetchingAny = computed(() => {
|
||||
const { contact, message, conversation, isFetching } = uiFlags.value;
|
||||
const { contact, message, conversation, article, isFetching } = uiFlags.value;
|
||||
return (
|
||||
isFetching ||
|
||||
contact.isFetching ||
|
||||
message.isFetching ||
|
||||
conversation.isFetching
|
||||
conversation.isFetching ||
|
||||
article.isFetching
|
||||
);
|
||||
});
|
||||
|
||||
@@ -171,6 +194,7 @@ const showLoadMore = computed(() => {
|
||||
contacts: mappedContacts.value,
|
||||
conversations: mappedConversations.value,
|
||||
messages: mappedMessages.value,
|
||||
articles: mappedArticles.value,
|
||||
}[selectedTab.value];
|
||||
|
||||
return (
|
||||
@@ -185,10 +209,11 @@ const showViewMore = computed(() => ({
|
||||
conversations:
|
||||
mappedConversations.value?.length > 5 && isSelectedTabAll.value,
|
||||
messages: mappedMessages.value?.length > 5 && isSelectedTabAll.value,
|
||||
articles: mappedArticles.value?.length > 5 && isSelectedTabAll.value,
|
||||
}));
|
||||
|
||||
const clearSearchResult = () => {
|
||||
pages.value = { contacts: 1, conversations: 1, messages: 1 };
|
||||
pages.value = { contacts: 1, conversations: 1, messages: 1, articles: 1 };
|
||||
store.dispatch('conversationSearch/clearSearchResults');
|
||||
};
|
||||
|
||||
@@ -214,6 +239,7 @@ const loadMore = () => {
|
||||
contacts: 'conversationSearch/contactSearch',
|
||||
conversations: 'conversationSearch/conversationSearch',
|
||||
messages: 'conversationSearch/messageSearch',
|
||||
articles: 'conversationSearch/articleSearch',
|
||||
};
|
||||
|
||||
if (uiFlags.value.isFetching || selectedTab.value === 'all') return;
|
||||
@@ -328,6 +354,28 @@ onUnmounted(() => {
|
||||
/>
|
||||
</Policy>
|
||||
|
||||
<Policy
|
||||
:permissions="[...ROLES, PORTAL_PERMISSIONS]"
|
||||
class="flex flex-col justify-center"
|
||||
>
|
||||
<SearchResultArticlesList
|
||||
v-if="filterArticles"
|
||||
:is-fetching="uiFlags.article.isFetching"
|
||||
:articles="articles"
|
||||
:query="query"
|
||||
:show-title="isSelectedTabAll"
|
||||
/>
|
||||
<NextButton
|
||||
v-if="showViewMore.articles"
|
||||
:label="t(`SEARCH.VIEW_MORE`)"
|
||||
icon="i-lucide-eye"
|
||||
slate
|
||||
sm
|
||||
outline
|
||||
@click="selectedTab = 'articles'"
|
||||
/>
|
||||
</Policy>
|
||||
|
||||
<div v-if="showLoadMore" class="flex justify-center mt-4 mb-6">
|
||||
<NextButton
|
||||
v-if="!isSelectedTabAll"
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
ROLES,
|
||||
CONVERSATION_PERMISSIONS,
|
||||
CONTACT_PERMISSIONS,
|
||||
PORTAL_PERMISSIONS,
|
||||
} from 'dashboard/constants/permissions.js';
|
||||
|
||||
import SearchView from './components/SearchView.vue';
|
||||
@@ -12,7 +13,12 @@ export const routes = [
|
||||
path: frontendURL('accounts/:accountId/search'),
|
||||
name: 'search',
|
||||
meta: {
|
||||
permissions: [...ROLES, ...CONVERSATION_PERMISSIONS, CONTACT_PERMISSIONS],
|
||||
permissions: [
|
||||
...ROLES,
|
||||
...CONVERSATION_PERMISSIONS,
|
||||
CONTACT_PERMISSIONS,
|
||||
PORTAL_PERMISSIONS,
|
||||
],
|
||||
},
|
||||
component: SearchView,
|
||||
},
|
||||
|
||||
@@ -117,7 +117,7 @@ export default {
|
||||
<div class="flex flex-col h-auto overflow-auto integration-hooks">
|
||||
<woot-modal-header
|
||||
:header-title="integration.name"
|
||||
:header-content="integration.short_description || integration.description"
|
||||
:header-content="integration.short_description"
|
||||
/>
|
||||
<FormKit
|
||||
v-model="values"
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import CopilotMessagesAPI from 'dashboard/api/captain/copilotMessages';
|
||||
import { createStore } from './storeFactory';
|
||||
|
||||
export default createStore({
|
||||
name: 'CopilotMessages',
|
||||
API: CopilotMessagesAPI,
|
||||
getters: {
|
||||
getMessagesByThreadId: state => copilotThreadId => {
|
||||
return state.records.filter(
|
||||
record => record.copilot_thread?.id === Number(copilotThreadId)
|
||||
);
|
||||
},
|
||||
},
|
||||
actions: mutationTypes => ({
|
||||
upsert({ commit }, data) {
|
||||
commit(mutationTypes.UPSERT, data);
|
||||
},
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
import CopilotThreadsAPI from 'dashboard/api/captain/copilotThreads';
|
||||
import { createStore } from './storeFactory';
|
||||
|
||||
export default createStore({
|
||||
name: 'CopilotThreads',
|
||||
API: CopilotThreadsAPI,
|
||||
});
|
||||
@@ -1,5 +1,11 @@
|
||||
import { throwErrorMessage } from 'dashboard/store/utils/api';
|
||||
import * as MutationHelpers from 'shared/helpers/vuex/mutationHelpers';
|
||||
import {
|
||||
createRecord,
|
||||
deleteRecord,
|
||||
getRecords,
|
||||
showRecord,
|
||||
updateRecord,
|
||||
} from './storeFactoryHelper';
|
||||
|
||||
export const generateMutationTypes = name => {
|
||||
const capitalizedName = name.toUpperCase();
|
||||
@@ -10,6 +16,7 @@ export const generateMutationTypes = name => {
|
||||
EDIT: `EDIT_${capitalizedName}`,
|
||||
DELETE: `DELETE_${capitalizedName}`,
|
||||
SET_META: `SET_${capitalizedName}_META`,
|
||||
UPSERT: `UPSERT_${capitalizedName}`,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -33,7 +40,6 @@ export const createGetters = () => ({
|
||||
getMeta: state => state.meta,
|
||||
});
|
||||
|
||||
// store/mutations.js
|
||||
export const createMutations = mutationTypes => ({
|
||||
[mutationTypes.SET_UI_FLAG](state, data) {
|
||||
state.uiFlags = {
|
||||
@@ -51,78 +57,19 @@ export const createMutations = mutationTypes => ({
|
||||
[mutationTypes.ADD]: MutationHelpers.create,
|
||||
[mutationTypes.EDIT]: MutationHelpers.update,
|
||||
[mutationTypes.DELETE]: MutationHelpers.destroy,
|
||||
[mutationTypes.UPSERT]: MutationHelpers.setSingleRecord,
|
||||
});
|
||||
|
||||
// store/actions/crud.js
|
||||
export const createCrudActions = (API, mutationTypes) => ({
|
||||
async get({ commit }, params = {}) {
|
||||
commit(mutationTypes.SET_UI_FLAG, { fetchingList: true });
|
||||
try {
|
||||
const response = await API.get(params);
|
||||
commit(mutationTypes.SET, response.data.payload);
|
||||
commit(mutationTypes.SET_META, response.data.meta);
|
||||
return response.data.payload;
|
||||
} catch (error) {
|
||||
return throwErrorMessage(error);
|
||||
} finally {
|
||||
commit(mutationTypes.SET_UI_FLAG, { fetchingList: false });
|
||||
}
|
||||
},
|
||||
|
||||
async show({ commit }, id) {
|
||||
commit(mutationTypes.SET_UI_FLAG, { fetchingItem: true });
|
||||
try {
|
||||
const response = await API.show(id);
|
||||
commit(mutationTypes.ADD, response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
return throwErrorMessage(error);
|
||||
} finally {
|
||||
commit(mutationTypes.SET_UI_FLAG, { fetchingItem: false });
|
||||
}
|
||||
},
|
||||
|
||||
async create({ commit }, dataObj) {
|
||||
commit(mutationTypes.SET_UI_FLAG, { creatingItem: true });
|
||||
try {
|
||||
const response = await API.create(dataObj);
|
||||
commit(mutationTypes.ADD, response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
return throwErrorMessage(error);
|
||||
} finally {
|
||||
commit(mutationTypes.SET_UI_FLAG, { creatingItem: false });
|
||||
}
|
||||
},
|
||||
|
||||
async update({ commit }, { id, ...updateObj }) {
|
||||
commit(mutationTypes.SET_UI_FLAG, { updatingItem: true });
|
||||
try {
|
||||
const response = await API.update(id, updateObj);
|
||||
commit(mutationTypes.EDIT, response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
return throwErrorMessage(error);
|
||||
} finally {
|
||||
commit(mutationTypes.SET_UI_FLAG, { updatingItem: false });
|
||||
}
|
||||
},
|
||||
|
||||
async delete({ commit }, id) {
|
||||
commit(mutationTypes.SET_UI_FLAG, { deletingItem: true });
|
||||
try {
|
||||
await API.delete(id);
|
||||
commit(mutationTypes.DELETE, id);
|
||||
return id;
|
||||
} catch (error) {
|
||||
return throwErrorMessage(error);
|
||||
} finally {
|
||||
commit(mutationTypes.SET_UI_FLAG, { deletingItem: false });
|
||||
}
|
||||
},
|
||||
get: getRecords(mutationTypes, API),
|
||||
show: showRecord(mutationTypes, API),
|
||||
create: createRecord(mutationTypes, API),
|
||||
update: updateRecord(mutationTypes, API),
|
||||
delete: deleteRecord(mutationTypes, API),
|
||||
});
|
||||
|
||||
export const createStore = options => {
|
||||
const { name, API, actions } = options;
|
||||
const { name, API, actions, getters } = options;
|
||||
const mutationTypes = generateMutationTypes(name);
|
||||
|
||||
const customActions = actions ? actions(mutationTypes) : {};
|
||||
@@ -130,7 +77,10 @@ export const createStore = options => {
|
||||
return {
|
||||
namespaced: true,
|
||||
state: createInitialState(),
|
||||
getters: createGetters(),
|
||||
getters: {
|
||||
...createGetters(),
|
||||
...(getters || {}),
|
||||
},
|
||||
mutations: createMutations(mutationTypes),
|
||||
actions: {
|
||||
...createCrudActions(API, mutationTypes),
|
||||
|
||||
@@ -0,0 +1,380 @@
|
||||
import { throwErrorMessage } from 'dashboard/store/utils/api';
|
||||
import * as MutationHelpers from 'shared/helpers/vuex/mutationHelpers';
|
||||
import {
|
||||
generateMutationTypes,
|
||||
createInitialState,
|
||||
createGetters,
|
||||
createMutations,
|
||||
createCrudActions,
|
||||
createStore,
|
||||
} from './storeFactory';
|
||||
|
||||
vi.mock('dashboard/store/utils/api', () => ({
|
||||
throwErrorMessage: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('shared/helpers/vuex/mutationHelpers', () => ({
|
||||
set: vi.fn(),
|
||||
create: vi.fn(),
|
||||
update: vi.fn(),
|
||||
destroy: vi.fn(),
|
||||
setSingleRecord: vi.fn(),
|
||||
}));
|
||||
|
||||
describe('storeFactory', () => {
|
||||
describe('generateMutationTypes', () => {
|
||||
it('generates correct mutation types with capitalized name', () => {
|
||||
const result = generateMutationTypes('test');
|
||||
expect(result).toEqual({
|
||||
SET_UI_FLAG: 'SET_TEST_UI_FLAG',
|
||||
SET: 'SET_TEST',
|
||||
ADD: 'ADD_TEST',
|
||||
EDIT: 'EDIT_TEST',
|
||||
DELETE: 'DELETE_TEST',
|
||||
SET_META: 'SET_TEST_META',
|
||||
UPSERT: 'UPSERT_TEST',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('createInitialState', () => {
|
||||
it('returns the correct initial state structure', () => {
|
||||
const result = createInitialState();
|
||||
expect(result).toEqual({
|
||||
records: [],
|
||||
meta: {},
|
||||
uiFlags: {
|
||||
fetchingList: false,
|
||||
fetchingItem: false,
|
||||
creatingItem: false,
|
||||
updatingItem: false,
|
||||
deletingItem: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('createGetters', () => {
|
||||
it('returns getters with correct implementations', () => {
|
||||
const getters = createGetters();
|
||||
|
||||
const state = {
|
||||
records: [{ id: 2 }, { id: 1 }, { id: 3 }],
|
||||
uiFlags: { fetchingList: true },
|
||||
meta: { totalCount: 10, page: 1 },
|
||||
};
|
||||
expect(getters.getRecords(state)).toEqual([
|
||||
{ id: 3 },
|
||||
{ id: 2 },
|
||||
{ id: 1 },
|
||||
]);
|
||||
|
||||
expect(getters.getRecord(state)(2)).toEqual({ id: 2 });
|
||||
expect(getters.getRecord(state)(4)).toEqual({});
|
||||
|
||||
expect(getters.getUIFlags(state)).toEqual({
|
||||
fetchingList: true,
|
||||
});
|
||||
|
||||
expect(getters.getMeta(state)).toEqual({
|
||||
totalCount: 10,
|
||||
page: 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('createMutations', () => {
|
||||
it('creates mutations with correct implementations', () => {
|
||||
const mutationTypes = generateMutationTypes('test');
|
||||
const mutations = createMutations(mutationTypes);
|
||||
|
||||
const state = { uiFlags: { fetchingList: false } };
|
||||
mutations[mutationTypes.SET_UI_FLAG](state, { fetchingList: true });
|
||||
expect(state.uiFlags).toEqual({ fetchingList: true });
|
||||
|
||||
const metaState = { meta: {} };
|
||||
mutations[mutationTypes.SET_META](metaState, {
|
||||
total_count: '10',
|
||||
page: '2',
|
||||
});
|
||||
expect(metaState.meta).toEqual({ totalCount: 10, page: 2 });
|
||||
|
||||
expect(mutations[mutationTypes.SET]).toBe(MutationHelpers.set);
|
||||
expect(mutations[mutationTypes.ADD]).toBe(MutationHelpers.create);
|
||||
expect(mutations[mutationTypes.EDIT]).toBe(MutationHelpers.update);
|
||||
expect(mutations[mutationTypes.DELETE]).toBe(MutationHelpers.destroy);
|
||||
expect(mutations[mutationTypes.UPSERT]).toBe(
|
||||
MutationHelpers.setSingleRecord
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createCrudActions', () => {
|
||||
let API;
|
||||
let commit;
|
||||
let mutationTypes;
|
||||
let actions;
|
||||
|
||||
beforeEach(() => {
|
||||
API = {
|
||||
get: vi.fn(),
|
||||
show: vi.fn(),
|
||||
create: vi.fn(),
|
||||
update: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
};
|
||||
commit = vi.fn();
|
||||
mutationTypes = generateMutationTypes('test');
|
||||
actions = createCrudActions(API, mutationTypes);
|
||||
});
|
||||
|
||||
describe('get action', () => {
|
||||
it('handles successful API response', async () => {
|
||||
const payload = [{ id: 1 }];
|
||||
const meta = { total_count: 10, page: 1 };
|
||||
API.get.mockResolvedValue({ data: { payload, meta } });
|
||||
|
||||
const result = await actions.get({ commit });
|
||||
|
||||
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||
fetchingList: true,
|
||||
});
|
||||
expect(commit).toHaveBeenCalledWith(mutationTypes.SET, payload);
|
||||
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_META, meta);
|
||||
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||
fetchingList: false,
|
||||
});
|
||||
expect(result).toEqual(payload);
|
||||
});
|
||||
|
||||
it('handles API error', async () => {
|
||||
const error = new Error('API Error');
|
||||
API.get.mockRejectedValue(error);
|
||||
throwErrorMessage.mockReturnValue('Error thrown');
|
||||
|
||||
const result = await actions.get({ commit });
|
||||
|
||||
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||
fetchingList: true,
|
||||
});
|
||||
expect(throwErrorMessage).toHaveBeenCalledWith(error);
|
||||
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||
fetchingList: false,
|
||||
});
|
||||
expect(result).toEqual('Error thrown');
|
||||
});
|
||||
});
|
||||
|
||||
describe('show action', () => {
|
||||
it('handles successful API response', async () => {
|
||||
const data = { id: 1, name: 'Test' };
|
||||
API.show.mockResolvedValue({ data });
|
||||
|
||||
const result = await actions.show({ commit }, 1);
|
||||
|
||||
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||
fetchingItem: true,
|
||||
});
|
||||
expect(commit).toHaveBeenCalledWith(mutationTypes.ADD, data);
|
||||
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||
fetchingItem: false,
|
||||
});
|
||||
expect(result).toEqual(data);
|
||||
});
|
||||
|
||||
it('handles API error', async () => {
|
||||
const error = new Error('API Error');
|
||||
API.show.mockRejectedValue(error);
|
||||
throwErrorMessage.mockReturnValue('Error thrown');
|
||||
|
||||
const result = await actions.show({ commit }, 1);
|
||||
|
||||
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||
fetchingItem: true,
|
||||
});
|
||||
expect(throwErrorMessage).toHaveBeenCalledWith(error);
|
||||
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||
fetchingItem: false,
|
||||
});
|
||||
expect(result).toEqual('Error thrown');
|
||||
});
|
||||
});
|
||||
|
||||
describe('create action', () => {
|
||||
it('handles successful API response', async () => {
|
||||
const data = { id: 1, name: 'Test' };
|
||||
API.create.mockResolvedValue({ data });
|
||||
|
||||
const result = await actions.create({ commit }, { name: 'Test' });
|
||||
|
||||
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||
creatingItem: true,
|
||||
});
|
||||
expect(commit).toHaveBeenCalledWith(mutationTypes.UPSERT, data);
|
||||
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||
creatingItem: false,
|
||||
});
|
||||
expect(result).toEqual(data);
|
||||
});
|
||||
|
||||
it('handles API error', async () => {
|
||||
const error = new Error('API Error');
|
||||
API.create.mockRejectedValue(error);
|
||||
throwErrorMessage.mockReturnValue('Error thrown');
|
||||
|
||||
const result = await actions.create({ commit }, { name: 'Test' });
|
||||
|
||||
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||
creatingItem: true,
|
||||
});
|
||||
expect(throwErrorMessage).toHaveBeenCalledWith(error);
|
||||
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||
creatingItem: false,
|
||||
});
|
||||
expect(result).toEqual('Error thrown');
|
||||
});
|
||||
});
|
||||
|
||||
describe('update action', () => {
|
||||
it('handles successful API response', async () => {
|
||||
const data = { id: 1, name: 'Updated' };
|
||||
API.update.mockResolvedValue({ data });
|
||||
|
||||
const result = await actions.update(
|
||||
{ commit },
|
||||
{ id: 1, name: 'Updated' }
|
||||
);
|
||||
|
||||
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||
updatingItem: true,
|
||||
});
|
||||
expect(API.update).toHaveBeenCalledWith(1, { name: 'Updated' });
|
||||
expect(commit).toHaveBeenCalledWith(mutationTypes.EDIT, data);
|
||||
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||
updatingItem: false,
|
||||
});
|
||||
expect(result).toEqual(data);
|
||||
});
|
||||
|
||||
it('handles API error', async () => {
|
||||
const error = new Error('API Error');
|
||||
API.update.mockRejectedValue(error);
|
||||
throwErrorMessage.mockReturnValue('Error thrown');
|
||||
|
||||
const result = await actions.update(
|
||||
{ commit },
|
||||
{ id: 1, name: 'Updated' }
|
||||
);
|
||||
|
||||
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||
updatingItem: true,
|
||||
});
|
||||
expect(throwErrorMessage).toHaveBeenCalledWith(error);
|
||||
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||
updatingItem: false,
|
||||
});
|
||||
expect(result).toEqual('Error thrown');
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete action', () => {
|
||||
it('handles successful API response', async () => {
|
||||
API.delete.mockResolvedValue({});
|
||||
|
||||
const result = await actions.delete({ commit }, 1);
|
||||
|
||||
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||
deletingItem: true,
|
||||
});
|
||||
expect(API.delete).toHaveBeenCalledWith(1);
|
||||
expect(commit).toHaveBeenCalledWith(mutationTypes.DELETE, 1);
|
||||
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||
deletingItem: false,
|
||||
});
|
||||
expect(result).toEqual(1);
|
||||
});
|
||||
|
||||
it('handles API error', async () => {
|
||||
const error = new Error('API Error');
|
||||
API.delete.mockRejectedValue(error);
|
||||
throwErrorMessage.mockReturnValue('Error thrown');
|
||||
|
||||
const result = await actions.delete({ commit }, 1);
|
||||
|
||||
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||
deletingItem: true,
|
||||
});
|
||||
expect(throwErrorMessage).toHaveBeenCalledWith(error);
|
||||
expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, {
|
||||
deletingItem: false,
|
||||
});
|
||||
expect(result).toEqual('Error thrown');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('createStore', () => {
|
||||
it('creates a complete store with default options', () => {
|
||||
const API = {};
|
||||
const store = createStore({ name: 'test', API });
|
||||
|
||||
expect(store.namespaced).toBe(true);
|
||||
expect(store.state).toEqual(createInitialState());
|
||||
expect(Object.keys(store.getters)).toEqual([
|
||||
'getRecords',
|
||||
'getRecord',
|
||||
'getUIFlags',
|
||||
'getMeta',
|
||||
]);
|
||||
expect(Object.keys(store.mutations)).toEqual([
|
||||
'SET_TEST_UI_FLAG',
|
||||
'SET_TEST_META',
|
||||
'SET_TEST',
|
||||
'ADD_TEST',
|
||||
'EDIT_TEST',
|
||||
'DELETE_TEST',
|
||||
'UPSERT_TEST',
|
||||
]);
|
||||
expect(Object.keys(store.actions)).toEqual([
|
||||
'get',
|
||||
'show',
|
||||
'create',
|
||||
'update',
|
||||
'delete',
|
||||
]);
|
||||
});
|
||||
|
||||
it('creates a store with custom actions and getters', () => {
|
||||
const API = {};
|
||||
const customGetters = { customGetter: () => 'custom' };
|
||||
const customActions = () => ({
|
||||
customAction: () => 'custom',
|
||||
});
|
||||
|
||||
const store = createStore({
|
||||
name: 'test',
|
||||
API,
|
||||
getters: customGetters,
|
||||
actions: customActions,
|
||||
});
|
||||
|
||||
expect(store.getters).toHaveProperty('customGetter');
|
||||
expect(store.actions).toHaveProperty('customAction');
|
||||
expect(Object.keys(store.getters)).toEqual([
|
||||
'getRecords',
|
||||
'getRecord',
|
||||
'getUIFlags',
|
||||
'getMeta',
|
||||
'customGetter',
|
||||
]);
|
||||
expect(Object.keys(store.actions)).toEqual([
|
||||
'get',
|
||||
'show',
|
||||
'create',
|
||||
'update',
|
||||
'delete',
|
||||
'customAction',
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,77 @@
|
||||
import { throwErrorMessage } from 'dashboard/store/utils/api';
|
||||
|
||||
export const getRecords =
|
||||
(mutationTypes, API) =>
|
||||
async ({ commit }, params = {}) => {
|
||||
commit(mutationTypes.SET_UI_FLAG, { fetchingList: true });
|
||||
try {
|
||||
const response = await API.get(params);
|
||||
commit(mutationTypes.SET, response.data.payload);
|
||||
commit(mutationTypes.SET_META, response.data.meta);
|
||||
return response.data.payload;
|
||||
} catch (error) {
|
||||
return throwErrorMessage(error);
|
||||
} finally {
|
||||
commit(mutationTypes.SET_UI_FLAG, { fetchingList: false });
|
||||
}
|
||||
};
|
||||
|
||||
export const showRecord =
|
||||
(mutationTypes, API) =>
|
||||
async ({ commit }, id) => {
|
||||
commit(mutationTypes.SET_UI_FLAG, { fetchingItem: true });
|
||||
try {
|
||||
const response = await API.show(id);
|
||||
commit(mutationTypes.ADD, response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
return throwErrorMessage(error);
|
||||
} finally {
|
||||
commit(mutationTypes.SET_UI_FLAG, { fetchingItem: false });
|
||||
}
|
||||
};
|
||||
|
||||
export const createRecord =
|
||||
(mutationTypes, API) =>
|
||||
async ({ commit }, dataObj) => {
|
||||
commit(mutationTypes.SET_UI_FLAG, { creatingItem: true });
|
||||
try {
|
||||
const response = await API.create(dataObj);
|
||||
commit(mutationTypes.UPSERT, response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
return throwErrorMessage(error);
|
||||
} finally {
|
||||
commit(mutationTypes.SET_UI_FLAG, { creatingItem: false });
|
||||
}
|
||||
};
|
||||
|
||||
export const updateRecord =
|
||||
(mutationTypes, API) =>
|
||||
async ({ commit }, { id, ...updateObj }) => {
|
||||
commit(mutationTypes.SET_UI_FLAG, { updatingItem: true });
|
||||
try {
|
||||
const response = await API.update(id, updateObj);
|
||||
commit(mutationTypes.EDIT, response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
return throwErrorMessage(error);
|
||||
} finally {
|
||||
commit(mutationTypes.SET_UI_FLAG, { updatingItem: false });
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteRecord =
|
||||
(mutationTypes, API) =>
|
||||
async ({ commit }, id) => {
|
||||
commit(mutationTypes.SET_UI_FLAG, { deletingItem: true });
|
||||
try {
|
||||
await API.delete(id);
|
||||
commit(mutationTypes.DELETE, id);
|
||||
return id;
|
||||
} catch (error) {
|
||||
return throwErrorMessage(error);
|
||||
} finally {
|
||||
commit(mutationTypes.SET_UI_FLAG, { deletingItem: false });
|
||||
}
|
||||
};
|
||||
@@ -51,6 +51,9 @@ import captainDocuments from './captain/document';
|
||||
import captainResponses from './captain/response';
|
||||
import captainInboxes from './captain/inboxes';
|
||||
import captainBulkActions from './captain/bulkActions';
|
||||
import copilotThreads from './captain/copilotThreads';
|
||||
import copilotMessages from './captain/copilotMessages';
|
||||
|
||||
const plugins = [];
|
||||
|
||||
export default createStore({
|
||||
@@ -106,6 +109,8 @@ export default createStore({
|
||||
captainResponses,
|
||||
captainInboxes,
|
||||
captainBulkActions,
|
||||
copilotThreads,
|
||||
copilotMessages,
|
||||
},
|
||||
plugins,
|
||||
});
|
||||
|
||||
@@ -5,12 +5,14 @@ export const initialState = {
|
||||
contactRecords: [],
|
||||
conversationRecords: [],
|
||||
messageRecords: [],
|
||||
articleRecords: [],
|
||||
uiFlags: {
|
||||
isFetching: false,
|
||||
isSearchCompleted: false,
|
||||
contact: { isFetching: false },
|
||||
conversation: { isFetching: false },
|
||||
message: { isFetching: false },
|
||||
article: { isFetching: false },
|
||||
},
|
||||
};
|
||||
|
||||
@@ -27,6 +29,9 @@ export const getters = {
|
||||
getMessageRecords(state) {
|
||||
return state.messageRecords;
|
||||
},
|
||||
getArticleRecords(state) {
|
||||
return state.articleRecords;
|
||||
},
|
||||
getUIFlags(state) {
|
||||
return state.uiFlags;
|
||||
},
|
||||
@@ -65,6 +70,7 @@ export const actions = {
|
||||
dispatch('contactSearch', { q }),
|
||||
dispatch('conversationSearch', { q }),
|
||||
dispatch('messageSearch', { q }),
|
||||
dispatch('articleSearch', { q }),
|
||||
]);
|
||||
} catch (error) {
|
||||
// Ignore error
|
||||
@@ -108,6 +114,17 @@ export const actions = {
|
||||
commit(types.MESSAGE_SEARCH_SET_UI_FLAG, { isFetching: false });
|
||||
}
|
||||
},
|
||||
async articleSearch({ commit }, { q, page = 1 }) {
|
||||
commit(types.ARTICLE_SEARCH_SET_UI_FLAG, { isFetching: true });
|
||||
try {
|
||||
const { data } = await SearchAPI.articles({ q, page });
|
||||
commit(types.ARTICLE_SEARCH_SET, data.payload.articles);
|
||||
} catch (error) {
|
||||
// Ignore error
|
||||
} finally {
|
||||
commit(types.ARTICLE_SEARCH_SET_UI_FLAG, { isFetching: false });
|
||||
}
|
||||
},
|
||||
async clearSearchResults({ commit }) {
|
||||
commit(types.CLEAR_SEARCH_RESULTS);
|
||||
},
|
||||
@@ -126,6 +143,9 @@ export const mutations = {
|
||||
[types.MESSAGE_SEARCH_SET](state, records) {
|
||||
state.messageRecords = [...state.messageRecords, ...records];
|
||||
},
|
||||
[types.ARTICLE_SEARCH_SET](state, records) {
|
||||
state.articleRecords = [...state.articleRecords, ...records];
|
||||
},
|
||||
[types.SEARCH_CONVERSATIONS_SET_UI_FLAG](state, uiFlags) {
|
||||
state.uiFlags = { ...state.uiFlags, ...uiFlags };
|
||||
},
|
||||
@@ -141,10 +161,14 @@ export const mutations = {
|
||||
[types.MESSAGE_SEARCH_SET_UI_FLAG](state, uiFlags) {
|
||||
state.uiFlags.message = { ...state.uiFlags.message, ...uiFlags };
|
||||
},
|
||||
[types.ARTICLE_SEARCH_SET_UI_FLAG](state, uiFlags) {
|
||||
state.uiFlags.article = { ...state.uiFlags.article, ...uiFlags };
|
||||
},
|
||||
[types.CLEAR_SEARCH_RESULTS](state) {
|
||||
state.contactRecords = [];
|
||||
state.conversationRecords = [];
|
||||
state.messageRecords = [];
|
||||
state.articleRecords = [];
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -75,6 +75,7 @@ describe('#actions', () => {
|
||||
q: 'test',
|
||||
});
|
||||
expect(dispatch).toHaveBeenCalledWith('messageSearch', { q: 'test' });
|
||||
expect(dispatch).toHaveBeenCalledWith('articleSearch', { q: 'test' });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -150,6 +151,30 @@ describe('#actions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#articleSearch', () => {
|
||||
it('should handle successful article search', async () => {
|
||||
axios.get.mockResolvedValue({
|
||||
data: { payload: { articles: [{ id: 1 }] } },
|
||||
});
|
||||
|
||||
await actions.articleSearch({ commit }, { q: 'test', page: 1 });
|
||||
expect(commit.mock.calls).toEqual([
|
||||
[types.ARTICLE_SEARCH_SET_UI_FLAG, { isFetching: true }],
|
||||
[types.ARTICLE_SEARCH_SET, [{ id: 1 }]],
|
||||
[types.ARTICLE_SEARCH_SET_UI_FLAG, { isFetching: false }],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle failed article search', async () => {
|
||||
axios.get.mockRejectedValue({});
|
||||
await actions.articleSearch({ commit }, { q: 'test' });
|
||||
expect(commit.mock.calls).toEqual([
|
||||
[types.ARTICLE_SEARCH_SET_UI_FLAG, { isFetching: true }],
|
||||
[types.ARTICLE_SEARCH_SET_UI_FLAG, { isFetching: false }],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#clearSearchResults', () => {
|
||||
it('should commit clear search results mutation', () => {
|
||||
actions.clearSearchResults({ commit });
|
||||
|
||||
@@ -37,6 +37,15 @@ describe('#getters', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('getArticleRecords', () => {
|
||||
const state = {
|
||||
articleRecords: [{ id: 1, title: 'Article 1' }],
|
||||
};
|
||||
expect(getters.getArticleRecords(state)).toEqual([
|
||||
{ id: 1, title: 'Article 1' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('getUIFlags', () => {
|
||||
const state = {
|
||||
uiFlags: {
|
||||
@@ -45,6 +54,7 @@ describe('#getters', () => {
|
||||
contact: { isFetching: true },
|
||||
message: { isFetching: false },
|
||||
conversation: { isFetching: false },
|
||||
article: { isFetching: false },
|
||||
},
|
||||
};
|
||||
expect(getters.getUIFlags(state)).toEqual({
|
||||
@@ -53,6 +63,7 @@ describe('#getters', () => {
|
||||
contact: { isFetching: true },
|
||||
message: { isFetching: false },
|
||||
conversation: { isFetching: false },
|
||||
article: { isFetching: false },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -101,17 +101,39 @@ describe('#mutations', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#ARTICLE_SEARCH_SET', () => {
|
||||
it('should append new article records to existing ones', () => {
|
||||
const state = { articleRecords: [{ id: 1 }] };
|
||||
mutations[types.ARTICLE_SEARCH_SET](state, [{ id: 2 }]);
|
||||
expect(state.articleRecords).toEqual([{ id: 1 }, { id: 2 }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#ARTICLE_SEARCH_SET_UI_FLAG', () => {
|
||||
it('set article search UI flags correctly', () => {
|
||||
const state = {
|
||||
uiFlags: {
|
||||
article: { isFetching: true },
|
||||
},
|
||||
};
|
||||
mutations[types.ARTICLE_SEARCH_SET_UI_FLAG](state, { isFetching: false });
|
||||
expect(state.uiFlags.article).toEqual({ isFetching: false });
|
||||
});
|
||||
});
|
||||
|
||||
describe('#CLEAR_SEARCH_RESULTS', () => {
|
||||
it('should clear all search records', () => {
|
||||
const state = {
|
||||
contactRecords: [{ id: 1 }],
|
||||
conversationRecords: [{ id: 1 }],
|
||||
messageRecords: [{ id: 1 }],
|
||||
articleRecords: [{ id: 1 }],
|
||||
};
|
||||
mutations[types.CLEAR_SEARCH_RESULTS](state);
|
||||
expect(state.contactRecords).toEqual([]);
|
||||
expect(state.conversationRecords).toEqual([]);
|
||||
expect(state.messageRecords).toEqual([]);
|
||||
expect(state.articleRecords).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -317,8 +317,10 @@ export default {
|
||||
CONVERSATION_SEARCH_SET: 'CONVERSATION_SEARCH_SET',
|
||||
CONVERSATION_SEARCH_SET_UI_FLAG: 'CONVERSATION_SEARCH_SET_UI_FLAG',
|
||||
MESSAGE_SEARCH_SET: 'MESSAGE_SEARCH_SET',
|
||||
ARTICLE_SEARCH_SET: 'ARTICLE_SEARCH_SET',
|
||||
CLEAR_SEARCH_RESULTS: 'CLEAR_SEARCH_RESULTS',
|
||||
MESSAGE_SEARCH_SET_UI_FLAG: 'MESSAGE_SEARCH_SET_UI_FLAG',
|
||||
ARTICLE_SEARCH_SET_UI_FLAG: 'ARTICLE_SEARCH_SET_UI_FLAG',
|
||||
FULL_SEARCH_SET_UI_FLAG: 'FULL_SEARCH_SET_UI_FLAG',
|
||||
SET_CONVERSATION_PARTICIPANTS_UI_FLAG:
|
||||
'SET_CONVERSATION_PARTICIPANTS_UI_FLAG',
|
||||
|
||||
@@ -25,33 +25,71 @@ export const getHeadingsfromTheArticle = () => {
|
||||
return rows;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts various input formats to URL objects.
|
||||
* Handles URL objects, domain strings, relative paths, and full URLs.
|
||||
* @param {string|URL} input - Input to convert to URL object
|
||||
* @returns {URL|null} URL object or null if input is invalid
|
||||
*/
|
||||
const toURL = input => {
|
||||
if (!input) return null;
|
||||
if (input instanceof URL) return input;
|
||||
|
||||
if (
|
||||
typeof input === 'string' &&
|
||||
!input.includes('://') &&
|
||||
!input.startsWith('/')
|
||||
) {
|
||||
return new URL(`https://${input}`);
|
||||
}
|
||||
|
||||
if (typeof input === 'string' && input.startsWith('/')) {
|
||||
return new URL(input, window.location.origin);
|
||||
}
|
||||
|
||||
return new URL(input);
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines if two URLs belong to the same host by comparing their normalized URL objects.
|
||||
* Handles various input formats including URL objects, domain strings, relative paths, and full URLs.
|
||||
* Returns false if either URL cannot be parsed or normalized.
|
||||
* @param {string|URL} url1 - First URL to compare
|
||||
* @param {string|URL} url2 - Second URL to compare
|
||||
* @returns {boolean} True if both URLs have the same host, false otherwise
|
||||
*/
|
||||
const isSameHost = (url1, url2) => {
|
||||
try {
|
||||
const urlObj1 = toURL(url1);
|
||||
const urlObj2 = toURL(url2);
|
||||
|
||||
if (!urlObj1 || !urlObj2) return false;
|
||||
|
||||
return urlObj1.hostname === urlObj2.hostname;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const openExternalLinksInNewTab = () => {
|
||||
const { customDomain, hostURL } = window.portalConfig;
|
||||
const isSameHost =
|
||||
window.location.href.includes(customDomain) ||
|
||||
window.location.href.includes(hostURL);
|
||||
|
||||
// Modify external links only on articles page
|
||||
const isOnArticlePage =
|
||||
isSameHost && document.querySelector('#cw-article-content') !== null;
|
||||
document.querySelector('#cw-article-content') !== null;
|
||||
|
||||
document.addEventListener('click', event => {
|
||||
if (!isOnArticlePage) return;
|
||||
|
||||
// Some of the links come wrapped in strong tag through prosemirror
|
||||
const link = event.target.closest('a');
|
||||
|
||||
const isTagAnchor = event.target.tagName === 'A';
|
||||
const isParentTagAnchor =
|
||||
event.target.tagName === 'STRONG' &&
|
||||
event.target.parentNode.tagName === 'A';
|
||||
|
||||
if (isTagAnchor || isParentTagAnchor) {
|
||||
const link = isTagAnchor ? event.target : event.target.parentNode;
|
||||
if (link) {
|
||||
const currentLocation = window.location.href;
|
||||
const linkHref = link.href;
|
||||
|
||||
// Check against current location and custom domains
|
||||
const isInternalLink =
|
||||
link.hostname === window.location.hostname ||
|
||||
link.href.includes(customDomain) ||
|
||||
link.href.includes(hostURL);
|
||||
isSameHost(linkHref, currentLocation) ||
|
||||
(customDomain && isSameHost(linkHref, customDomain)) ||
|
||||
(hostURL && isSameHost(linkHref, hostURL));
|
||||
|
||||
if (!isInternalLink) {
|
||||
link.target = '_blank';
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import { InitializationHelpers } from '../portalHelpers';
|
||||
import {
|
||||
InitializationHelpers,
|
||||
openExternalLinksInNewTab,
|
||||
} from '../portalHelpers';
|
||||
|
||||
describe('InitializationHelpers.navigateToLocalePage', () => {
|
||||
let dom;
|
||||
@@ -44,3 +47,139 @@ describe('InitializationHelpers.navigateToLocalePage', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('openExternalLinksInNewTab', () => {
|
||||
let dom;
|
||||
let document;
|
||||
let window;
|
||||
|
||||
beforeEach(() => {
|
||||
dom = new JSDOM(
|
||||
`<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<div id="cw-article-content">
|
||||
<a href="https://external.com" id="external">External</a>
|
||||
<a href="https://app.chatwoot.com/page" id="internal">Internal</a>
|
||||
<a href="https://custom.domain.com/page" id="custom">Custom</a>
|
||||
<a href="https://example.com" id="nested"><code>Code</code><strong>Bold</strong></a>
|
||||
<ul>
|
||||
<li>Visit the preferences centre here > <a href="https://external.com" id="list-link"><strong>https://external.com</strong></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>`,
|
||||
{ url: 'https://app.chatwoot.com/hc/article' }
|
||||
);
|
||||
|
||||
document = dom.window.document;
|
||||
window = dom.window;
|
||||
|
||||
window.portalConfig = {
|
||||
customDomain: 'custom.domain.com',
|
||||
hostURL: 'app.chatwoot.com',
|
||||
};
|
||||
|
||||
global.document = document;
|
||||
global.window = window;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
dom = null;
|
||||
document = null;
|
||||
window = null;
|
||||
delete global.document;
|
||||
delete global.window;
|
||||
});
|
||||
|
||||
const simulateClick = selector => {
|
||||
const element = document.querySelector(selector);
|
||||
const event = new window.MouseEvent('click', { bubbles: true });
|
||||
element.dispatchEvent(event);
|
||||
return element.closest('a') || element;
|
||||
};
|
||||
|
||||
it('opens external links in new tab', () => {
|
||||
openExternalLinksInNewTab();
|
||||
|
||||
const link = simulateClick('#external');
|
||||
expect(link.target).toBe('_blank');
|
||||
expect(link.rel).toBe('noopener noreferrer');
|
||||
});
|
||||
|
||||
it('preserves internal links', () => {
|
||||
openExternalLinksInNewTab();
|
||||
|
||||
const internal = simulateClick('#internal');
|
||||
const custom = simulateClick('#custom');
|
||||
|
||||
expect(internal.target).not.toBe('_blank');
|
||||
expect(custom.target).not.toBe('_blank');
|
||||
});
|
||||
|
||||
it('handles clicks on nested elements', () => {
|
||||
openExternalLinksInNewTab();
|
||||
|
||||
simulateClick('#nested code');
|
||||
simulateClick('#nested strong');
|
||||
|
||||
const link = document.getElementById('nested');
|
||||
expect(link.target).toBe('_blank');
|
||||
expect(link.rel).toBe('noopener noreferrer');
|
||||
});
|
||||
|
||||
it('handles links inside list items with strong tags', () => {
|
||||
openExternalLinksInNewTab();
|
||||
|
||||
// Click on the strong element inside the link in the list
|
||||
simulateClick('#list-link strong');
|
||||
|
||||
const link = document.getElementById('list-link');
|
||||
expect(link.target).toBe('_blank');
|
||||
expect(link.rel).toBe('noopener noreferrer');
|
||||
});
|
||||
|
||||
it('opens external links in a new tab even if customDomain is empty', () => {
|
||||
window = dom.window;
|
||||
window.portalConfig = {
|
||||
hostURL: 'app.chatwoot.com',
|
||||
};
|
||||
|
||||
global.window = window;
|
||||
|
||||
openExternalLinksInNewTab();
|
||||
|
||||
const link = simulateClick('#external');
|
||||
const internal = simulateClick('#internal');
|
||||
const custom = simulateClick('#custom');
|
||||
|
||||
expect(link.target).toBe('_blank');
|
||||
expect(link.rel).toBe('noopener noreferrer');
|
||||
|
||||
expect(internal.target).not.toBe('_blank');
|
||||
// this will be blank since the configs customDomain is empty
|
||||
// which is a fair expectation
|
||||
expect(custom.target).toBe('_blank');
|
||||
});
|
||||
|
||||
it('opens external links in a new tab even if hostURL is empty', () => {
|
||||
window = dom.window;
|
||||
window.portalConfig = {
|
||||
customDomain: 'custom.domain.com',
|
||||
};
|
||||
|
||||
global.window = window;
|
||||
|
||||
openExternalLinksInNewTab();
|
||||
|
||||
const link = simulateClick('#external');
|
||||
const internal = simulateClick('#internal');
|
||||
const custom = simulateClick('#custom');
|
||||
|
||||
expect(link.target).toBe('_blank');
|
||||
expect(link.rel).toBe('noopener noreferrer');
|
||||
|
||||
expect(internal.target).not.toBe('_blank');
|
||||
expect(custom.target).not.toBe('_blank');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
class Internal::DeleteAccountsJob < ApplicationJob
|
||||
queue_as :scheduled_jobs
|
||||
|
||||
def perform
|
||||
delete_expired_accounts
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def delete_expired_accounts
|
||||
accounts_pending_deletion.each do |account|
|
||||
AccountDeletionService.new(account: account).perform
|
||||
end
|
||||
end
|
||||
|
||||
def accounts_pending_deletion
|
||||
Account.where("custom_attributes->>'marked_for_deletion_at' IS NOT NULL")
|
||||
.select { |account| deletion_period_expired?(account) }
|
||||
end
|
||||
|
||||
def deletion_period_expired?(account)
|
||||
deletion_time = account.custom_attributes['marked_for_deletion_at']
|
||||
return false if deletion_time.blank?
|
||||
|
||||
DateTime.parse(deletion_time) <= Time.current
|
||||
end
|
||||
end
|
||||
@@ -204,3 +204,5 @@ class ActionCableListener < BaseListener
|
||||
::ActionCableBroadcastJob.perform_later(tokens.uniq, event_name, payload)
|
||||
end
|
||||
end
|
||||
|
||||
ActionCableListener.prepend_mod_with('ActionCableListener')
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
class AdministratorNotifications::AccountComplianceMailer < AdministratorNotifications::BaseMailer
|
||||
def account_deleted(account)
|
||||
return if instance_admin_email.blank?
|
||||
|
||||
subject = subject_for(account)
|
||||
meta = build_meta(account)
|
||||
|
||||
send_notification(subject, to: instance_admin_email, meta: meta)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_meta(account)
|
||||
deleted_users = params[:soft_deleted_users] || []
|
||||
|
||||
user_info_list = deleted_users.map do |user|
|
||||
{
|
||||
'user_id' => user[:id].to_s,
|
||||
'user_email' => user[:original_email].to_s
|
||||
}
|
||||
end
|
||||
|
||||
{
|
||||
'instance_url' => instance_url,
|
||||
'account_id' => account.id,
|
||||
'account_name' => account.name,
|
||||
'deleted_at' => format_time(Time.current.iso8601),
|
||||
'deletion_reason' => account.custom_attributes['marked_for_deletion_reason'] || 'not specified',
|
||||
'marked_for_deletion_at' => format_time(account.custom_attributes['marked_for_deletion_at']),
|
||||
'soft_deleted_users' => user_info_list,
|
||||
'deleted_user_count' => user_info_list.size
|
||||
}
|
||||
end
|
||||
|
||||
def format_time(time_string)
|
||||
return 'not specified' if time_string.blank?
|
||||
|
||||
Time.zone.parse(time_string).strftime('%B %d, %Y %H:%M:%S %Z')
|
||||
end
|
||||
|
||||
def subject_for(account)
|
||||
"Account Deletion Notice for #{account.id} - #{account.name}"
|
||||
end
|
||||
|
||||
def instance_admin_email
|
||||
GlobalConfig.get('CHATWOOT_INSTANCE_ADMIN_EMAIL')['CHATWOOT_INSTANCE_ADMIN_EMAIL']
|
||||
end
|
||||
|
||||
def instance_url
|
||||
ENV.fetch('FRONTEND_URL', 'not available')
|
||||
end
|
||||
end
|
||||
@@ -5,6 +5,7 @@ module ActivityMessageHandler
|
||||
include LabelActivityMessageHandler
|
||||
include SlaActivityMessageHandler
|
||||
include TeamActivityMessageHandler
|
||||
include CsatActivityMessageHandler
|
||||
|
||||
private
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
module CsatActivityMessageHandler
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def create_csat_not_sent_activity_message
|
||||
content = I18n.t('conversations.activity.csat.not_sent_due_to_messaging_window')
|
||||
::Conversations::ActivityMessageJob.perform_later(self, activity_message_params(content)) if content
|
||||
end
|
||||
end
|
||||
@@ -39,7 +39,8 @@ class Integrations::App
|
||||
def action
|
||||
case params[:id]
|
||||
when 'slack'
|
||||
"#{params[:action]}&client_id=#{ENV.fetch('SLACK_CLIENT_ID', nil)}&redirect_uri=#{self.class.slack_integration_url}"
|
||||
client_id = GlobalConfigService.load('SLACK_CLIENT_ID', nil)
|
||||
"#{params[:action]}&client_id=#{client_id}&redirect_uri=#{self.class.slack_integration_url}"
|
||||
when 'linear'
|
||||
build_linear_action
|
||||
else
|
||||
@@ -50,7 +51,7 @@ class Integrations::App
|
||||
def active?(account)
|
||||
case params[:id]
|
||||
when 'slack'
|
||||
ENV['SLACK_CLIENT_SECRET'].present?
|
||||
GlobalConfigService.load('SLACK_CLIENT_SECRET', nil).present?
|
||||
when 'linear'
|
||||
GlobalConfigService.load('LINEAR_CLIENT_ID', nil).present?
|
||||
when 'shopify'
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
class AccountDeletionService
|
||||
attr_reader :account, :soft_deleted_users
|
||||
|
||||
def initialize(account:)
|
||||
@account = account
|
||||
@soft_deleted_users = []
|
||||
end
|
||||
|
||||
def perform
|
||||
Rails.logger.info("Deleting account #{account.id} - #{account.name} that was marked for deletion")
|
||||
|
||||
soft_delete_orphaned_users
|
||||
send_compliance_notification
|
||||
DeleteObjectJob.perform_later(account)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def send_compliance_notification
|
||||
AdministratorNotifications::AccountComplianceMailer.with(
|
||||
account: account,
|
||||
soft_deleted_users: soft_deleted_users
|
||||
).account_deleted(account).deliver_later
|
||||
end
|
||||
|
||||
def soft_delete_orphaned_users
|
||||
account.users.each do |user|
|
||||
# Find all account_users for this user excluding the current account
|
||||
other_accounts = user.account_users.where.not(account_id: account.id).count
|
||||
|
||||
# If user has no other accounts, soft delete them
|
||||
next unless other_accounts.zero?
|
||||
|
||||
# Soft delete user by appending -deleted.com to email
|
||||
original_email = user.email
|
||||
user.email = "#{original_email}-deleted.com"
|
||||
user.skip_reconfirmation!
|
||||
user.save!
|
||||
|
||||
user_info = {
|
||||
id: user.id.to_s,
|
||||
original_email: original_email
|
||||
}
|
||||
|
||||
soft_deleted_users << user_info
|
||||
|
||||
Rails.logger.info("Soft deleted user #{user.id} with email #{original_email}")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -6,17 +6,18 @@ class Crm::Leadsquared::Mappers::ConversationMapper
|
||||
# so this limits it
|
||||
ACTIVITY_NOTE_MAX_SIZE = 1800
|
||||
|
||||
def self.map_conversation_activity(conversation)
|
||||
new(conversation).conversation_activity
|
||||
def self.map_conversation_activity(hook, conversation)
|
||||
new(hook, conversation).conversation_activity
|
||||
end
|
||||
|
||||
def self.map_transcript_activity(conversation, messages = nil)
|
||||
new(conversation, messages).transcript_activity
|
||||
def self.map_transcript_activity(hook, conversation)
|
||||
new(hook, conversation).transcript_activity
|
||||
end
|
||||
|
||||
def initialize(conversation, messages = nil)
|
||||
def initialize(hook, conversation)
|
||||
@hook = hook
|
||||
@timezone = Time.find_zone(hook.settings['timezone']) || Time.zone
|
||||
@conversation = conversation
|
||||
@messages = messages
|
||||
end
|
||||
|
||||
def conversation_activity
|
||||
@@ -41,14 +42,14 @@ class Crm::Leadsquared::Mappers::ConversationMapper
|
||||
|
||||
private
|
||||
|
||||
attr_reader :conversation, :messages
|
||||
attr_reader :conversation
|
||||
|
||||
def formatted_creation_time
|
||||
conversation.created_at.strftime('%Y-%m-%d %H:%M:%S')
|
||||
conversation.created_at.in_time_zone(@timezone).strftime('%Y-%m-%d %H:%M:%S')
|
||||
end
|
||||
|
||||
def transcript_messages
|
||||
@transcript_messages ||= messages || conversation.messages.chat.select(&:conversation_transcriptable?)
|
||||
@transcript_messages ||= conversation.messages.chat.select(&:conversation_transcriptable?)
|
||||
end
|
||||
|
||||
def format_messages
|
||||
@@ -77,8 +78,7 @@ class Crm::Leadsquared::Mappers::ConversationMapper
|
||||
end
|
||||
|
||||
def message_time(message)
|
||||
# TODO: Figure out what timezone to send the time in
|
||||
message.created_at.strftime('%Y-%m-%d %H:%M')
|
||||
message.created_at.in_time_zone(@timezone).strftime('%Y-%m-%d %H:%M')
|
||||
end
|
||||
|
||||
def sender_name(message)
|
||||
|
||||
@@ -37,7 +37,7 @@ class Crm::Leadsquared::ProcessorService < Crm::BaseProcessorService
|
||||
activity_type: 'conversation',
|
||||
activity_code_key: 'conversation_activity_code',
|
||||
metadata_key: 'created_activity_id',
|
||||
activity_note: Crm::Leadsquared::Mappers::ConversationMapper.map_conversation_activity(conversation)
|
||||
activity_note: Crm::Leadsquared::Mappers::ConversationMapper.map_conversation_activity(@hook, conversation)
|
||||
)
|
||||
end
|
||||
|
||||
@@ -50,7 +50,7 @@ class Crm::Leadsquared::ProcessorService < Crm::BaseProcessorService
|
||||
activity_type: 'transcript',
|
||||
activity_code_key: 'transcript_activity_code',
|
||||
metadata_key: 'transcript_activity_id',
|
||||
activity_note: Crm::Leadsquared::Mappers::ConversationMapper.map_transcript_activity(conversation)
|
||||
activity_note: Crm::Leadsquared::Mappers::ConversationMapper.map_transcript_activity(@hook, conversation)
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
@@ -25,11 +25,12 @@ class Crm::Leadsquared::SetupService
|
||||
response = @client.get('Authentication.svc/UserByAccessKey.Get')
|
||||
endpoint_host = response['LSQCommonServiceURLs']['api']
|
||||
app_host = response['LSQCommonServiceURLs']['app']
|
||||
timezone = response['TimeZone']
|
||||
|
||||
endpoint_url = "https://#{endpoint_host}/v2/"
|
||||
app_url = "https://#{app_host}/"
|
||||
|
||||
update_hook_settings({ :endpoint_url => endpoint_url, :app_url => app_url })
|
||||
update_hook_settings({ :endpoint_url => endpoint_url, :app_url => app_url, :timezone => timezone })
|
||||
|
||||
# replace the clients
|
||||
@client = Crm::Leadsquared::Api::BaseClient.new(@access_key, @secret_key, endpoint_url)
|
||||
|
||||
@@ -17,7 +17,7 @@ class MessageTemplates::HookExecutionService
|
||||
::MessageTemplates::Template::OutOfOffice.new(conversation: conversation).perform if should_send_out_of_office_message?
|
||||
::MessageTemplates::Template::Greeting.new(conversation: conversation).perform if should_send_greeting?
|
||||
::MessageTemplates::Template::EmailCollect.new(conversation: conversation).perform if inbox.enable_email_collect && should_send_email_collect?
|
||||
::MessageTemplates::Template::CsatSurvey.new(conversation: conversation).perform if should_send_csat_survey?
|
||||
handle_csat_survey
|
||||
end
|
||||
|
||||
def should_send_out_of_office_message?
|
||||
@@ -65,13 +65,26 @@ class MessageTemplates::HookExecutionService
|
||||
true
|
||||
end
|
||||
|
||||
def should_send_csat_survey?
|
||||
def handle_csat_survey
|
||||
return unless csat_enabled_conversation?
|
||||
|
||||
# only send CSAT once in a conversation
|
||||
return if conversation.messages.where(content_type: :input_csat).present?
|
||||
return if csat_already_sent?
|
||||
|
||||
true
|
||||
# Only send CSAT if agent can still reply by checking the messaging window restriction
|
||||
# https://www.chatwoot.com/docs/self-hosted/supported-features#outgoing-message-restriction
|
||||
if within_messaging_window?
|
||||
::MessageTemplates::Template::CsatSurvey.new(conversation: conversation).perform
|
||||
else
|
||||
conversation.create_csat_not_sent_activity_message
|
||||
end
|
||||
end
|
||||
|
||||
def csat_already_sent?
|
||||
conversation.messages.where(content_type: :input_csat).present?
|
||||
end
|
||||
|
||||
def within_messaging_window?
|
||||
conversation.can_reply?
|
||||
end
|
||||
end
|
||||
MessageTemplates::HookExecutionService.prepend_mod_with('MessageTemplates::HookExecutionService')
|
||||
|
||||
@@ -9,8 +9,10 @@ class SearchService
|
||||
{ conversations: filter_conversations }
|
||||
when 'Contact'
|
||||
{ contacts: filter_contacts }
|
||||
when 'Article'
|
||||
{ articles: filter_articles }
|
||||
else
|
||||
{ contacts: filter_contacts, messages: filter_messages, conversations: filter_conversations }
|
||||
{ contacts: filter_contacts, messages: filter_messages, conversations: filter_conversations, articles: filter_articles }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -90,4 +92,12 @@ class SearchService
|
||||
ILIKE :search OR identifier ILIKE :search", search: "%#{search_query}%"
|
||||
).resolved_contacts.order_on_last_activity_at('desc').page(params[:page]).per(15)
|
||||
end
|
||||
|
||||
def filter_articles
|
||||
@articles = current_account.articles
|
||||
.text_search(search_query)
|
||||
.reorder('updated_at DESC')
|
||||
.page(params[:page])
|
||||
.per(15)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
json.id article.id
|
||||
json.title article.title
|
||||
json.locale article.locale
|
||||
json.content article.content
|
||||
json.slug article.slug
|
||||
json.portal_slug article.portal.slug
|
||||
json.account_id article.account_id
|
||||
json.category_name article.category&.name
|
||||
@@ -0,0 +1,15 @@
|
||||
json.id conversation.display_id
|
||||
json.account_id conversation.account_id
|
||||
json.created_at conversation.created_at.to_i
|
||||
json.message do
|
||||
json.partial! 'message', formats: [:json], message: conversation.messages.try(:first)
|
||||
end
|
||||
json.contact do
|
||||
json.partial! 'contact', formats: [:json], contact: conversation.contact if conversation.try(:contact).present?
|
||||
end
|
||||
json.inbox do
|
||||
json.partial! 'inbox', formats: [:json], inbox: conversation.inbox if conversation.try(:inbox).present?
|
||||
end
|
||||
json.agent do
|
||||
json.partial! 'agent', formats: [:json], agent: conversation.assignee if conversation.try(:assignee).present?
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
json.payload do
|
||||
json.articles do
|
||||
json.array! @result[:articles] do |article|
|
||||
json.partial! 'article', formats: [:json], article: article
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,21 +1,7 @@
|
||||
json.payload do
|
||||
json.conversations do
|
||||
json.array! @result[:conversations] do |conversation|
|
||||
json.id conversation.display_id
|
||||
json.account_id conversation.account_id
|
||||
json.created_at conversation.created_at.to_i
|
||||
json.message do
|
||||
json.partial! 'message', formats: [:json], message: conversation.messages.try(:first)
|
||||
end
|
||||
json.contact do
|
||||
json.partial! 'contact', formats: [:json], contact: conversation.contact if conversation.try(:contact).present?
|
||||
end
|
||||
json.inbox do
|
||||
json.partial! 'inbox', formats: [:json], inbox: conversation.inbox if conversation.try(:inbox).present?
|
||||
end
|
||||
json.agent do
|
||||
json.partial! 'agent', formats: [:json], agent: conversation.assignee if conversation.try(:assignee).present?
|
||||
end
|
||||
json.partial! 'conversation_search_result', formats: [:json], conversation: conversation
|
||||
end
|
||||
end
|
||||
json.contacts do
|
||||
@@ -23,10 +9,14 @@ json.payload do
|
||||
json.partial! 'contact', formats: [:json], contact: contact
|
||||
end
|
||||
end
|
||||
|
||||
json.messages do
|
||||
json.array! @result[:messages] do |message|
|
||||
json.partial! 'message', formats: [:json], message: message
|
||||
end
|
||||
end
|
||||
json.articles do
|
||||
json.array! @result[:articles] do |article|
|
||||
json.partial! 'article', formats: [:json], article: article
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
<p>Hello,</p>
|
||||
|
||||
<p>This is a notification to inform you that an account has been permanently deleted from your Chatwoot instance.</p>
|
||||
|
||||
<p>
|
||||
<strong>Chatwoot Installation:</strong> {{ meta.instance_url }}<br>
|
||||
<strong>Account ID:</strong> {{ meta.account_id }}<br>
|
||||
<strong>Account Name:</strong> {{ meta.account_name }}<br>
|
||||
<strong>Deleted At:</strong> {{ meta.deleted_at }}<br>
|
||||
<strong>Marked for Deletion at:</strong> {{ meta.marked_for_deletion_at }}<br>
|
||||
<strong>Deletion Reason:</strong> {{ meta.deletion_reason }}
|
||||
</p>
|
||||
|
||||
{% if meta.deleted_user_count > 0 %}
|
||||
<p>
|
||||
<strong>Deleted Users ({{ meta.deleted_user_count }}):</strong><br>
|
||||
{% for user in meta.soft_deleted_users %}
|
||||
User ID: {{ user.user_id }}, Email: {{ user.user_email }}{% unless forloop.last %}<br>{% endunless %}
|
||||
{% endfor %}
|
||||
</p>
|
||||
{% else %}
|
||||
<p>
|
||||
<strong>Deleted Users:</strong> None
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<p>This email serves as a record for compliance purposes.</p>
|
||||
|
||||
<p>Thank you,<br>
|
||||
Chatwoot System</p>
|
||||
@@ -159,4 +159,7 @@
|
||||
<symbol id="icon-shopify" viewBox="0 0 32 32">
|
||||
<path fill="currentColor" d="m20.448 31.974l9.625-2.083s-3.474-23.484-3.5-23.641s-.156-.255-.281-.255c-.13 0-2.573-.182-2.573-.182s-1.703-1.698-1.922-1.88a.4.4 0 0 0-.161-.099l-1.219 28.141zm-4.833-16.901s-1.083-.563-2.365-.563c-1.932 0-2.005 1.203-2.005 1.521c0 1.641 4.318 2.286 4.318 6.172c0 3.057-1.922 5.01-4.542 5.01c-3.141 0-4.719-1.953-4.719-1.953l.859-2.781s1.661 1.422 3.042 1.422c.901 0 1.302-.724 1.302-1.245c0-2.156-3.542-2.255-3.542-5.807c-.047-2.984 2.094-5.891 6.438-5.891c1.677 0 2.5.479 2.5.479l-1.26 3.625zm-.719-13.969c.177 0 .359.052.536.182c-1.313.62-2.75 2.188-3.344 5.323a76 76 0 0 1-2.516.771c.688-2.38 2.359-6.26 5.323-6.26zm1.646 3.932v.182c-1.005.307-2.115.646-3.193.979c.62-2.37 1.776-3.526 2.781-3.958c.255.667.411 1.568.411 2.797zm.718-2.973c.922.094 1.521 1.151 1.901 2.339c-.464.151-.979.307-1.542.484v-.333c0-1.005-.13-1.828-.359-2.495zm3.99 1.718c-.031 0-.083.026-.104.026c-.026 0-.385.099-.953.281C19.63 2.442 18.625.927 16.849.927h-.156C16.183.281 15.558 0 15.021 0c-4.141 0-6.12 5.172-6.74 7.797c-1.594.484-2.75.844-2.88.896c-.901.286-.927.313-1.031 1.161c-.099.615-2.438 18.75-2.438 18.75L20.01 32z"/>
|
||||
</symbol>
|
||||
</svg>
|
||||
<symbol id="icon-slack" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M6.527 14.514A1.973 1.973 0 0 1 4.56 16.48a1.973 1.973 0 0 1-1.968-1.967c0-1.083.885-1.968 1.968-1.968h1.967zm.992 0c0-1.083.885-1.968 1.968-1.968s1.967.885 1.967 1.968v4.927a1.973 1.973 0 0 1-1.967 1.968a1.973 1.973 0 0 1-1.968-1.968zm1.968-7.987A1.973 1.973 0 0 1 7.519 4.56c0-1.083.885-1.967 1.968-1.967s1.967.884 1.967 1.967v1.968zm0 .992c1.083 0 1.967.884 1.967 1.967a1.973 1.973 0 0 1-1.967 1.968H4.56a1.973 1.973 0 0 1-1.968-1.968c0-1.083.885-1.967 1.968-1.967zm7.986 1.967c0-1.083.885-1.967 1.968-1.967s1.968.884 1.968 1.967a1.973 1.973 0 0 1-1.968 1.968h-1.968zm-.991 0a1.973 1.973 0 0 1-1.968 1.968a1.973 1.973 0 0 1-1.968-1.968V4.56c0-1.083.885-1.967 1.968-1.967s1.968.884 1.968 1.967zm-1.968 7.987c1.083 0 1.968.885 1.968 1.968a1.973 1.973 0 0 1-1.968 1.968a1.973 1.973 0 0 1-1.968-1.968v-1.968zm0-.992a1.973 1.973 0 0 1-1.968-1.967c0-1.083.885-1.968 1.968-1.968h4.927c1.083 0 1.968.885 1.968 1.968a1.973 1.973 0 0 1-1.968 1.967z"/>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 41 KiB |
@@ -0,0 +1,264 @@
|
||||
---
|
||||
title: Environment Variables
|
||||
description: Complete reference for Chatwoot environment variables and configuration options
|
||||
sidebarTitle: Environment Variables
|
||||
---
|
||||
|
||||
## The .env File
|
||||
|
||||
We use the `dotenv-rails` gem to manage the environment variables. There is a file called `env.example` in the root directory of this project with all the environment variables set to empty values. You can set the correct values as per the following options. Once you set the values, you should rename the file to `.env` before you start the server.
|
||||
|
||||
## Configure frontend URL (domain)
|
||||
|
||||
Provide your chatwoot domain as frontend URL.
|
||||
|
||||
```bash
|
||||
FRONTEND_URL='https://your-chatwoot-domain.tld'
|
||||
```
|
||||
|
||||
## Rails production variables
|
||||
|
||||
For production deployment, you have to set the following variables
|
||||
|
||||
```bash
|
||||
RAILS_ENV=production
|
||||
SECRET_KEY_BASE=replace_with_your_own_secret_string
|
||||
```
|
||||
|
||||
You can generate `SECRET_KEY_BASE` using `rake secret` command from the project root folder. If you dont have rails installed, use `head /dev/urandom | tr -dc A-Za-z0-9 | head -c 63 ; echo ''`.
|
||||
|
||||
<Note>
|
||||
SECRET_KEY_BASE should be alphanumeric. Avoid special characters or symbols.
|
||||
</Note>
|
||||
|
||||
## Database configuration
|
||||
|
||||
Postgres can be configured in two ways: via `DATABASE_URL` or setting up independent Postgres variables.
|
||||
|
||||
### Configure Postgres
|
||||
|
||||
Set the `DATABASE_URL` variable with value as Postgres connection URI to connect to the database.
|
||||
|
||||
The URI is of the format
|
||||
|
||||
```bash
|
||||
postgresql://[user[:password]@][netloc][:port][,...][/dbname][?param1=value1&...]
|
||||
```
|
||||
|
||||
Or you can set the following environment variables to configure Postgres. Replace the values here with yours. Skip this
|
||||
if you have configured `DATABASE_URL`.
|
||||
|
||||
```bash
|
||||
POSTGRES_HOST=localhost
|
||||
POSTGRES_PORT=5432
|
||||
POSTGRES_DATABASE=chatwoot_production
|
||||
POSTGRES_USERNAME=admin
|
||||
POSTGRES_PASSWORD=password
|
||||
```
|
||||
|
||||
### Configure Redis
|
||||
|
||||
For development, you can use the following URL to connect to Redis. For production, configure your Redis URL.
|
||||
|
||||
```bash
|
||||
REDIS_URL='redis://127.0.0.1:6379'
|
||||
```
|
||||
|
||||
To authenticate Redis connections made by the app server and sidekick, if it's protected by a password, use the following environment variable to set the password.
|
||||
|
||||
```bash
|
||||
REDIS_PASSWORD=
|
||||
```
|
||||
|
||||
## Configure emails
|
||||
|
||||
For development, you don't need an email provider. Chatwoot uses the [letter-opener](https://github.com/ryanb/letter_opener) gem to test emails locally
|
||||
|
||||
For production use, please configure the following variables.
|
||||
|
||||
```bash
|
||||
# could user either `email@yourdomain.com` or `BrandName <email@yourdomain.com>`
|
||||
MAILER_SENDER_EMAIL=
|
||||
```
|
||||
|
||||
and based on your SMTP server the following variables
|
||||
|
||||
```bash
|
||||
SMTP_ADDRESS=
|
||||
SMTP_USERNAME=
|
||||
SMTP_PASSWORD=
|
||||
SMTP_TLS=
|
||||
SMTP_SSL=
|
||||
```
|
||||
|
||||
### Postfix
|
||||
|
||||
Follow these steps if you want to use a selfhosted mail server with Chatwoot. This is the default behavior starting from `v2.12.0` and relies on `SMTP_ADDRESS` environment variable not being set.
|
||||
|
||||
```
|
||||
sudo apt install -y postfix
|
||||
```
|
||||
|
||||
Choose internet-site when prompted and enter the domain name you used with Chatwoot setup for `System mail name`.
|
||||
|
||||
<Note>
|
||||
By default, all major cloud provider have blocked port 25 used for sending emails as part of their spam combat effects. Please raise a support ticket with your cloud provider to enable outbound access on port 25 for this to work. Refer [AWS](https://aws.amazon.com/premiumsupport/knowledge-center/ec2-port-25-throttle), [GCP](https://cloud.google.com/compute/docs/tutorials/sending-mail), [Azure](https://learn.microsoft.com/en-us/azure/virtual-network/troubleshoot-outbound-smtp-connectivity) and [DigitalOcean](https://www.digitalocean.com/blog/smtp-restricted-by-default) for more details.
|
||||
</Note>
|
||||
|
||||
Also please add MX and PTR records for your domain. If your emails are being flagged by `Gmail` and `Outlook`, setup [SPF and DKIM records](https://www.linuxbabe.com/mail-server/setting-up-dkim-and-spf) for your domain as well. This should improve your email reputation.
|
||||
|
||||
### Amazon SES
|
||||
|
||||
```bash
|
||||
SMTP_ADDRESS=email-smtp.<region>.amazonaws.com
|
||||
SMTP_AUTHENTICATION=plain
|
||||
SMTP_ENABLE_STARTTLS_AUTO=true
|
||||
SMTP_USERNAME=<Your SMTP username>
|
||||
SMTP_PASSWORD=<Your SMTP password>
|
||||
```
|
||||
|
||||
### SendGrid
|
||||
|
||||
<Info>
|
||||
For clarification, the `SMTP_USERNAME` should be set to the literal text apikey—this is not the actual API key. SendGrid uses 'apikey' as the standard username for its services.
|
||||
</Info>
|
||||
|
||||
```bash
|
||||
SMTP_ADDRESS=smtp.sendgrid.net
|
||||
SMTP_AUTHENTICATION=plain
|
||||
SMTP_DOMAIN=<your verified domain>
|
||||
SMTP_ENABLE_STARTTLS_AUTO=true
|
||||
SMTP_PORT=587
|
||||
SMTP_USERNAME=apikey
|
||||
SMTP_PASSWORD=<your Sendgrid API key>
|
||||
```
|
||||
|
||||
### MailGun
|
||||
|
||||
```bash
|
||||
SMTP_ADDRESS=smtp.mailgun.org
|
||||
SMTP_AUTHENTICATION=plain
|
||||
SMTP_DOMAIN=<Your domain, this has to be verified in Mailgun>
|
||||
SMTP_ENABLE_STARTTLS_AUTO=true
|
||||
SMTP_PORT=587
|
||||
SMTP_USERNAME=<Your SMTP username, view under Domains tab>
|
||||
SMTP_PASSWORD=<Your SMTP password, view under Domains tab>
|
||||
```
|
||||
|
||||
### Mandrill
|
||||
|
||||
If you would like to use Mailchimp to send your emails, use the following environment variables:
|
||||
|
||||
<Note>
|
||||
Mandrill is the transactional email service for Mailchimp. You need to enable transactional email and login to mandrillapp.com.
|
||||
</Note>
|
||||
|
||||
```bash
|
||||
SMTP_ADDRESS=smtp.mandrillapp.com
|
||||
SMTP_AUTHENTICATION=plain
|
||||
SMTP_DOMAIN=<Your verified domain in Mailchimp>
|
||||
SMTP_ENABLE_STARTTLS_AUTO=true
|
||||
SMTP_PORT=587
|
||||
SMTP_USERNAME=<Your SMTP username displayed under Settings -> SMTP & API info>
|
||||
SMTP_PASSWORD=<Any valid API key, create an API key under Settings -> SMTP & API Info>
|
||||
```
|
||||
|
||||
## Configure default language
|
||||
|
||||
```bash
|
||||
DEFAULT_LOCALE='en'
|
||||
```
|
||||
|
||||
## Configure storage
|
||||
|
||||
Chatwoot uses [active storage](https://edgeguides.rubyonrails.org/active_storage_overview.html) for storing attachments. The default storage option is the local storage on your server.
|
||||
|
||||
But you can change it to use any of the cloud providers like amazon s3, microsoft azure, google gcs etc. Refer [configuring cloud storage](/docs/self-hosted/deployment/storage/supported-providers) for additional environment variables required.
|
||||
|
||||
```bash
|
||||
ACTIVE_STORAGE_SERVICE=local
|
||||
```
|
||||
|
||||
When `local` storage is used the files are stored under `/storage` directory in the chatwoot root folder.
|
||||
|
||||
<Warning>
|
||||
It is recommended to use a cloud provider for your chatwoot storage to ensure proper backup of the stored attachments and prevent data loss.
|
||||
</Warning>
|
||||
|
||||
## Rails Logging Variables
|
||||
|
||||
By default, Chatwoot will capture `info` level logs in production. Ref [rails docs](https://guides.rubyonrails.org/debugging_rails_applications.html#log-levels) for the additional log-level options.
|
||||
We will also retain 1 GB of your recent logs and your last shifted log file.
|
||||
You can fine-tune these settings using the following environment variables
|
||||
|
||||
```bash
|
||||
# possible values: 'debug', 'info', 'warn', 'error', 'fatal' and 'unknown'
|
||||
LOG_LEVEL=
|
||||
# value in megabytes
|
||||
LOG_SIZE= 1024
|
||||
```
|
||||
|
||||
## Configure FB Channel
|
||||
|
||||
To use FB Channel, you have to create a Facebook app in the developer portal. You can find more details about creating FB channels [here](https://developers.facebook.com/docs/apps/#register)
|
||||
|
||||
```bash
|
||||
FB_VERIFY_TOKEN=
|
||||
FB_APP_SECRET=
|
||||
FB_APP_ID=
|
||||
```
|
||||
|
||||
## Using CDN for asset delivery
|
||||
|
||||
With the release v1.8.0, we are enabling CDN support for Chatwoot. If you have a high traffic website, we recommend to setup a CDN for your asset delivery. Read setting up [CloudFront as your CDN](/docs/self-hosted/deployment/performance/cloudfront-cdn) guide.
|
||||
|
||||
## Enable new account signup
|
||||
|
||||
By default, Chatwoot will not allow users to create an account[multi-tenancy] from the login page. However, if you are setting up a public server, you can enable signup using:
|
||||
|
||||
```bash
|
||||
ENABLE_ACCOUNT_SIGNUP=true
|
||||
```
|
||||
|
||||
## Enable direct upload to storage cloud
|
||||
|
||||
By default, Chatwoot will upload the files to the application server and then it will push them to the cloud storage. We have introduced the direct upload functionality so that we can upload the file directly to the cloud storage. This has been built according to rails new direct upload functionality documented [here](https://edgeguides.rubyonrails.org/active_storage_overview.html#direct-uploads). Set below environment variable to true to use the direct upload feature.
|
||||
|
||||
Make sure to follow [this guide](https://edgeguides.rubyonrails.org/active_storage_overview.html#cross-origin-resource-sharing-cors-configuration) and set the appropriate CORS configuration on your cloud storage after setting `DIRECT_UPLOADS_ENABLED` to true.
|
||||
|
||||
```bash
|
||||
DIRECT_UPLOADS_ENABLED=true
|
||||
```
|
||||
|
||||
## Google OAuth
|
||||
|
||||
To enable Google OAuth in Chatwoot, you need to provide the client ID, client secret, and callback URL. You can find the instructions to generate the details [here](https://support.google.com/cloud/answer/6158849).
|
||||
|
||||
Set the `GOOGLE_OAUTH_CLIENT_ID` and `GOOGLE_OAUTH_CLIENT_SECRET` environment variables in your Chatwoot installation using the values you copied from the Google API Console. Set the `GOOGLE_OAUTH_CALLBACK_URL` environment variable to the callback URL you used in the Google API Console. Here's an example of the same
|
||||
|
||||
```bash
|
||||
GOOGLE_OAUTH_CLIENT_ID=369777777777-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com
|
||||
GOOGLE_OAUTH_CLIENT_SECRET=ABCDEF-GHijklmnoPqrstuvwX-yz1234567
|
||||
GOOGLE_OAUTH_CALLBACK_URL=https://<your-server-domain>/omniauth/google_oauth2/callback
|
||||
```
|
||||
|
||||
<Warning>
|
||||
The callback URL should comply with the format in the example above. This endpoint cannot be changed at the moment.
|
||||
</Warning>
|
||||
|
||||
After setting these environment variables, restart your Chatwoot server to apply the changes. Now, users will be able to sign in using their Google accounts.
|
||||
|
||||
## LogRocket
|
||||
|
||||
To enable LogRocket in Chatwoot, you need to provide the project ID from LogRocket. Here are the steps to follow:
|
||||
|
||||
1. Open the LogRocket [website](https://logrocket.com/) and create an account or sign in to your existing account.
|
||||
2. After signing in, create a new project in LogRocket by clicking on "Create new project".
|
||||
3. Enter a name for your project, and save the project ID.
|
||||
4. Set the `LOG_ROCKET_PROJECT_ID` environment variable in your `.env` file with the project ID you copied from LogRocket.
|
||||
|
||||
```bash
|
||||
LOG_ROCKET_PROJECT_ID=abcd12/pineapple-on-pizza
|
||||
```
|
||||
|
||||
After setting this environment variable, restart your Chatwoot server to apply the changes. Now, LogRocket will start capturing user sessions on your Chatwoot installation.
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
title: Help Center
|
||||
description: Set up a public-facing help center portal with custom domain and SSL certificate
|
||||
sidebarTitle: Help Center
|
||||
---
|
||||
|
||||
Help center allows you to create a portal and add articles from the chatwoot app dashboard. You can point to these help center portal articles from your main site and display them as your public-facing help center.
|
||||
|
||||
## How to get SSL certificate for your custom domain
|
||||
|
||||
### Create a Portal in Chatwoot's dashboard
|
||||
|
||||
Follow these step to create your Portal. Refer to [this guide.](https://www.chatwoot.com/hc/user-guide/articles/1677861202-how-to-setup-a-help-center)
|
||||
|
||||
### Point your custom domain to your Chatwoot domain
|
||||
|
||||
1. Go to your DNS provider and add a new CNAME record.
|
||||
- For the above example, add docs as a CNAME record and point it to the your selfhosted chatwoot domain(FRONTEND_URL).
|
||||
|
||||
2. This will ensure that your CNAME record points to the selfhosted Chatwoot installation. For your custom domain, we have your portal information. In this case, `docs.example.com`
|
||||
|
||||
### Setting up SSL
|
||||
|
||||
1. Use certbot to generate SSL certificates for your custom domain.
|
||||
|
||||
```bash
|
||||
certbot certonly --agree-tos --nginx -d "docs.example.com"
|
||||
```
|
||||
|
||||
2. Create a new nginx config to route requests to this domain to Chatwoot. Make a copy of `/etc/nginx/sites-available/nginx_chatwoot.conf` and make necessary changes for the new domain.
|
||||
|
||||
3. Restart nginx server.
|
||||
|
||||
```bash
|
||||
sudo systemctl restart nginx
|
||||
```
|
||||
|
||||
Voila!
|
||||
|
||||
`docs.yourdomain.com` is live with a secure connection, and your portal data is visible.
|
||||
|
||||
### How does this work?
|
||||
|
||||
These are the engineering details to understand `How does docs.yourdomain.com` gets the portal data with SSL certificate.
|
||||
|
||||
1. `docs.yourdomain.com` resolves by customers nameserver and redirects to your Chatwoot domain.
|
||||
2. Chatwoot check for the portal record with custom-domain `docs.yourdomain.com`
|
||||
3. Redirects to the portal records for the domain `docs.yourdomain.com`
|
||||
|
||||
Yaay!!
|
||||
|
||||
Now you can have your own help-center, product-documentation related portal saved at Chatwoot dashboard and served at your domain with SSL certificate.
|
||||
@@ -235,6 +235,14 @@
|
||||
description: Used to notify Chatwoot about account abuses, potential threads (Should be a Discord Webhook URL)
|
||||
# ------- End of Chatwoot Internal Config for Self Hosted ----#
|
||||
|
||||
# ------- Compliance Related Config ----#
|
||||
- name: CHATWOOT_INSTANCE_ADMIN_EMAIL
|
||||
display_title: 'Instance Admin Email'
|
||||
value:
|
||||
description: 'The email of the instance administrator to receive compliance-related notifications'
|
||||
locked: false
|
||||
# ------- End of Compliance Related Config ----#
|
||||
|
||||
## ------ Configs added for enterprise clients ------ ##
|
||||
- name: API_CHANNEL_NAME
|
||||
value:
|
||||
@@ -280,6 +288,20 @@
|
||||
type: secret
|
||||
## ------ End of Configs added for Linear ------ ##
|
||||
|
||||
## ------ Configs added for Slack ------ ##
|
||||
- name: SLACK_CLIENT_ID
|
||||
display_title: 'Slack Client ID'
|
||||
value:
|
||||
locked: false
|
||||
description: 'Slack client ID'
|
||||
- name: SLACK_CLIENT_SECRET
|
||||
display_title: 'Slack Client Secret'
|
||||
value:
|
||||
locked: false
|
||||
description: 'Slack client secret'
|
||||
type: secret
|
||||
## ------ End of Configs added for Slack ------ ##
|
||||
|
||||
# ------- Shopify Integration Config ------- #
|
||||
- name: SHOPIFY_CLIENT_ID
|
||||
display_title: 'Shopify Client ID'
|
||||
|
||||
@@ -205,6 +205,7 @@ leadsquared:
|
||||
'secret_key': { 'type': 'string' },
|
||||
'endpoint_url': { 'type': 'string' },
|
||||
'app_url': { 'type': 'string' },
|
||||
'timezone': { 'type': 'string' },
|
||||
'enable_conversation_activity': { 'type': 'boolean' },
|
||||
'enable_transcript_activity': { 'type': 'boolean' },
|
||||
'conversation_activity_score': { 'type': 'string' },
|
||||
|
||||
@@ -185,6 +185,8 @@ en:
|
||||
removed: '%{user_name} removed %{labels}'
|
||||
sla:
|
||||
added: '%{user_name} added SLA policy %{sla_name}'
|
||||
csat:
|
||||
not_sent_due_to_messaging_window: 'CSAT survey not sent due to outgoing message restrictions'
|
||||
removed: '%{user_name} removed SLA policy %{sla_name}'
|
||||
muted: '%{user_name} has muted the conversation'
|
||||
unmuted: '%{user_name} has unmuted the conversation'
|
||||
@@ -213,33 +215,41 @@ en:
|
||||
online:
|
||||
delete: '%{contact_name} is Online, please try again later'
|
||||
integration_apps:
|
||||
# Note: webhooks and dashboard_apps don't need short_description as they use different modal components
|
||||
dashboard_apps:
|
||||
name: 'Dashboard Apps'
|
||||
description: 'Dashboard Apps allow you to create and embed applications that display user information, orders, or payment history, providing more context to your customer support agents.'
|
||||
dyte:
|
||||
name: 'Dyte'
|
||||
short_description: 'Start video/voice calls with customers directly from Chatwoot.'
|
||||
description: 'Dyte is a product that integrates audio and video functionalities into your application. With this integration, your agents can start video/voice calls with your customers directly from Chatwoot.'
|
||||
meeting_name: '%{agent_name} has started a meeting'
|
||||
slack:
|
||||
name: 'Slack'
|
||||
short_description: 'Receive notifications and respond to conversations directly in Slack.'
|
||||
description: "Integrate Chatwoot with Slack to keep your team in sync. This integration allows you to receive notifications for new conversations and respond to them directly within Slack's interface."
|
||||
webhooks:
|
||||
name: 'Webhooks'
|
||||
description: 'Webhook events provide real-time updates about activities in your Chatwoot account. You can subscribe to your preferred events, and Chatwoot will send you HTTP callbacks with the updates.'
|
||||
dialogflow:
|
||||
name: 'Dialogflow'
|
||||
short_description: 'Build chatbots to handle initial queries before transferring to agents.'
|
||||
description: 'Build chatbots with Dialogflow and easily integrate them into your inbox. These bots can handle initial queries before transferring them to a customer service agent.'
|
||||
google_translate:
|
||||
name: 'Google Translate'
|
||||
short_description: 'Automatically translate customer messages for agents.'
|
||||
description: "Integrate Google Translate to help agents easily translate customer messages. This integration automatically detects the language and converts it to the agent's or admin's preferred language."
|
||||
openai:
|
||||
name: 'OpenAI'
|
||||
short_description: 'AI-powered reply suggestions, summarization, and message enhancement.'
|
||||
description: 'Leverage the power of large language models from OpenAI with the features such as reply suggestions, summarization, message rephrasing, spell-checking, and label classification.'
|
||||
linear:
|
||||
name: 'Linear'
|
||||
short_description: 'Create and link Linear issues directly from conversations.'
|
||||
description: 'Create issues in Linear directly from your conversation window. Alternatively, link existing Linear issues for a more streamlined and efficient issue tracking process.'
|
||||
shopify:
|
||||
name: 'Shopify'
|
||||
short_description: 'Access order details and customer data from your Shopify store.'
|
||||
description: 'Connect your Shopify store to access order details, customer information, and product data directly within your conversations and helps your support team provide faster, more contextual assistance to your customers.'
|
||||
leadsquared:
|
||||
name: 'LeadSquared'
|
||||
|
||||
+3
-2
@@ -60,8 +60,8 @@ Rails.application.routes.draw do
|
||||
end
|
||||
resources :assistant_responses
|
||||
resources :bulk_actions, only: [:create]
|
||||
resources :copilot_threads, only: [:index] do
|
||||
resources :copilot_messages, only: [:index]
|
||||
resources :copilot_threads, only: [:index, :create] do
|
||||
resources :copilot_messages, only: [:index, :create]
|
||||
end
|
||||
resources :documents, only: [:index, :show, :create, :destroy]
|
||||
end
|
||||
@@ -137,6 +137,7 @@ Rails.application.routes.draw do
|
||||
get :conversations
|
||||
get :messages
|
||||
get :contacts
|
||||
get :articles
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -39,3 +39,10 @@ process_stale_contacts_job:
|
||||
cron: '30 04 * * *'
|
||||
class: 'Internal::ProcessStaleContactsJob'
|
||||
queue: housekeeping
|
||||
|
||||
# executed daily at 0100 UTC
|
||||
# to delete accounts marked for deletion
|
||||
delete_accounts_job:
|
||||
cron: '0 1 * * *'
|
||||
class: 'Internal::DeleteAccountsJob'
|
||||
queue: scheduled_jobs
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
class RemoveUuidFromCopilotThreads < ActiveRecord::Migration[7.1]
|
||||
def change
|
||||
remove_column :copilot_threads, :uuid, :string
|
||||
|
||||
add_column :copilot_threads, :assistant_id, :integer
|
||||
add_index :copilot_threads, :assistant_id
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
class RemoveUserIdFromCopilotMessages < ActiveRecord::Migration[7.1]
|
||||
def change
|
||||
remove_reference :copilot_messages, :user, index: true
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,11 @@
|
||||
class ChangeMessageTypeToIntegerInCopilotMessages < ActiveRecord::Migration[7.1]
|
||||
def up
|
||||
remove_column :copilot_messages, :message_type
|
||||
add_column :copilot_messages, :message_type, :integer, default: 0
|
||||
end
|
||||
|
||||
def down
|
||||
remove_column :copilot_messages, :message_type
|
||||
add_column :copilot_messages, :message_type, :string, default: 'user'
|
||||
end
|
||||
end
|
||||
+4
-6
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.0].define(version: 2025_05_14_045638) do
|
||||
ActiveRecord::Schema[7.1].define(version: 2025_05_23_031839) do
|
||||
# These extensions should be enabled to support this database
|
||||
enable_extension "pg_stat_statements"
|
||||
enable_extension "pg_trgm"
|
||||
@@ -577,27 +577,25 @@ ActiveRecord::Schema[7.0].define(version: 2025_05_14_045638) do
|
||||
|
||||
create_table "copilot_messages", force: :cascade do |t|
|
||||
t.bigint "copilot_thread_id", null: false
|
||||
t.bigint "user_id", null: false
|
||||
t.bigint "account_id", null: false
|
||||
t.string "message_type", null: false
|
||||
t.jsonb "message", default: {}, null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "message_type", default: 0
|
||||
t.index ["account_id"], name: "index_copilot_messages_on_account_id"
|
||||
t.index ["copilot_thread_id"], name: "index_copilot_messages_on_copilot_thread_id"
|
||||
t.index ["user_id"], name: "index_copilot_messages_on_user_id"
|
||||
end
|
||||
|
||||
create_table "copilot_threads", force: :cascade do |t|
|
||||
t.string "title", null: false
|
||||
t.bigint "user_id", null: false
|
||||
t.bigint "account_id", null: false
|
||||
t.uuid "uuid", default: -> { "gen_random_uuid()" }, null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "assistant_id"
|
||||
t.index ["account_id"], name: "index_copilot_threads_on_account_id"
|
||||
t.index ["assistant_id"], name: "index_copilot_threads_on_assistant_id"
|
||||
t.index ["user_id"], name: "index_copilot_threads_on_user_id"
|
||||
t.index ["uuid"], name: "index_copilot_threads_on_uuid", unique: true
|
||||
end
|
||||
|
||||
create_table "csat_survey_responses", force: :cascade do |t|
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
title: Introduction to Chatwoot APIs
|
||||
description: Learn how to use Chatwoot APIs to build integrations, customize chat experiences, and manage your installation.
|
||||
sidebarTitle: Introduction
|
||||
---
|
||||
|
||||
Welcome to the Chatwoot API documentation. Whether you're building custom workflows for your support team, integrating Chatwoot into your product, or managing users across installations, our APIs provide the flexibility and power to help you do more with Chatwoot.
|
||||
|
||||
Chatwoot provides three categories of APIs, each designed with a specific use case in mind:
|
||||
|
||||
- **Application APIs** – For account-level automation and agent-facing integrations.
|
||||
- **Client APIs** – For building custom chat interfaces for end-users
|
||||
- **Platform APIs** – For managing and administering installations at scale
|
||||
|
||||
---
|
||||
|
||||
## Application APIs
|
||||
|
||||
Application APIs are designed for interacting with a Chatwoot account from an agent/admin perspective. Use them to build internal tools, automate workflows, or perform bulk operations like data import/export.
|
||||
|
||||
- **Authentication**: Requires a user `access_token`, which can be generated from **Profile Settings** after logging into your Chatwoot account.
|
||||
- **Availability**: Supported on both **Cloud** and **Self-hosted** Chatwoot installations.
|
||||
- **Example**: [Google Cloud Functions Demo](https://github.com/chatwoot/google-cloud-functions-demo)
|
||||
|
||||
---
|
||||
|
||||
## Client APIs
|
||||
|
||||
Client APIs are intended for building custom messaging experiences over Chatwoot. If you're not using the native website widget or want to embed chat in your mobile app, these APIs are the way to go.
|
||||
|
||||
- **Authentication**: Uses `inbox_identifier` (from **Settings → Configuration** in API inboxes) and `contact_identifier` (returned when creating a contact).
|
||||
- **Availability**: Supported on both **Cloud** and **Self-hosted** Chatwoot installations.
|
||||
- **Examples**:
|
||||
|
||||
- [Client API Demo](https://github.com/chatwoot/client-api-demo)
|
||||
- [Flutter SDK](https://github.com/chatwoot/chatwoot-flutter-sdk)
|
||||
|
||||
---
|
||||
|
||||
## Platform APIs
|
||||
|
||||
Platform APIs are used to manage Chatwoot installations at the admin level. These APIs allow you to control users, roles, and accounts, or sync data from external authentication systems.
|
||||
|
||||
- **Authentication**: Requires an `access_token` generated by a **Platform App**, which can be created in the **Super Admin Console**.
|
||||
- **Availability**: Available on **Self-hosted** / **Managed Hosting** Chatwoot installations only.
|
||||
|
||||
---
|
||||
|
||||
Use the right API for your use case, and you'll be able to extend, customize, and integrate Chatwoot into your stack with ease.
|
||||
@@ -0,0 +1,89 @@
|
||||
---
|
||||
title: Contributor Covenant Code of Conduct
|
||||
description: Code of conduct for Chatwoot community members and contributors
|
||||
sidebarTitle: Code of Conduct
|
||||
---
|
||||
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at **hello@chatwoot.com**.
|
||||
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 2.0, available at [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html](https://www.contributor-covenant.org/version/2/0/code_of_conduct.html).
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq). Translations are available at [https://www.contributor-covenant.org/translations](https://www.contributor-covenant.org/translations).
|
||||
|
||||
---
|
||||
|
||||
By participating in the Chatwoot community, you agree to abide by this Code of Conduct. Thank you for helping us create a welcoming and inclusive environment for everyone.
|
||||
@@ -0,0 +1,205 @@
|
||||
---
|
||||
title: Chatwoot Community Guidelines
|
||||
description: Guidelines for participating in the Chatwoot community across all platforms
|
||||
sidebarTitle: Community Guidelines
|
||||
---
|
||||
|
||||
# Chatwoot Community Guidelines
|
||||
|
||||
Welcome to the Chatwoot community! These guidelines help ensure our community remains welcoming, productive, and inclusive for everyone.
|
||||
|
||||
## General Principles
|
||||
|
||||
### 1. Respect and Inclusivity
|
||||
Everyone is welcome here. Treat all members with respect, regardless of their background, identity, or level of experience.
|
||||
|
||||
### 2. Collaboration Over Conflict
|
||||
Always approach discussions and feedback with a constructive attitude. We're all here to learn and grow together.
|
||||
|
||||
### 3. No Spam or Self-Promotion
|
||||
Unsolicited advertisements, promotions, or spammy links are not allowed. Share valuable content that benefits the community.
|
||||
|
||||
## Platform-Specific Guidelines
|
||||
|
||||
### Discord-Specific Guidelines
|
||||
|
||||
Our Discord server is where the community gathers for real-time discussions, support, and collaboration.
|
||||
|
||||
#### Channel Etiquette
|
||||
|
||||
1. **Stay On Topic**: Each channel has its purpose. Ensure your discussions are relevant to the channel topic.
|
||||
2. **Use Thread Responses**: For lengthy discussions, use thread replies to keep channels organized.
|
||||
3. **Search Before Asking**: Check pinned messages and recent discussions before asking questions.
|
||||
|
||||
#### Content Guidelines
|
||||
|
||||
1. **No NSFW Content**: This is a professional community. Do not share or promote any NSFW content.
|
||||
2. **Quality Over Quantity**: Focus on helpful, meaningful contributions rather than frequent low-value messages.
|
||||
3. **Appropriate Language**: Keep language professional and appropriate for a business environment.
|
||||
|
||||
#### Voice Channels
|
||||
|
||||
1. **Microphone Etiquette**: Ensure your microphone is muted when not speaking.
|
||||
2. **Respect Speaking Time**: Avoid interrupting others and allow everyone to participate.
|
||||
3. **Background Noise**: Use push-to-talk if you're in a noisy environment.
|
||||
|
||||
#### Reporting and Support
|
||||
|
||||
1. **Report Violations**: If you see someone violating these guidelines, don't engage. Instead, report it to the moderators.
|
||||
2. **Use Private Messages**: For sensitive issues, reach out to moderators via private message.
|
||||
|
||||
### GitHub-Specific Guidelines
|
||||
|
||||
GitHub is our primary platform for code collaboration, issue tracking, and project management.
|
||||
|
||||
#### Issue Reporting
|
||||
|
||||
1. **Search First**: Before reporting a bug or requesting a feature, search the issues to ensure it hasn't been addressed already.
|
||||
2. **Use Templates**: Follow the provided issue templates for bug reports and feature requests.
|
||||
3. **Provide Details**: Include clear steps to reproduce, expected behavior, and actual behavior.
|
||||
4. **Stay Updated**: Monitor your issues for questions from maintainers and respond promptly.
|
||||
|
||||
#### Pull Requests
|
||||
|
||||
1. **Clear Descriptions**: Ensure your PRs are concise, have a clear title, and are linked to relevant issues.
|
||||
2. **Follow Style Guide**: Adhere to the existing coding style and conventions.
|
||||
3. **Test Thoroughly**: Test your changes in multiple scenarios before submitting.
|
||||
4. **Respond to Reviews**: Address feedback constructively and make requested changes promptly.
|
||||
|
||||
#### Code Review Guidelines
|
||||
|
||||
1. **Be Constructive**: Provide specific, actionable feedback rather than general criticism.
|
||||
2. **Explain Reasoning**: When suggesting changes, explain why the change would improve the code.
|
||||
3. **Appreciate Good Work**: Acknowledge good code and clever solutions.
|
||||
4. **Stay Professional**: Keep discussions focused on the code, not the person.
|
||||
|
||||
#### Documentation
|
||||
|
||||
1. **Update Documentation**: If your PR introduces a new feature, ensure that it's documented.
|
||||
2. **Fix What You See**: If you spot outdated or missing documentation, consider updating it or raising an issue.
|
||||
3. **Clear Examples**: Provide clear, working examples in documentation.
|
||||
|
||||
## Community Support
|
||||
|
||||
### Helping Others
|
||||
|
||||
#### In Discord
|
||||
|
||||
- **Be Patient**: Remember that people have different experience levels
|
||||
- **Provide Context**: When helping, explain not just what to do, but why
|
||||
- **Share Resources**: Link to relevant documentation or previous discussions
|
||||
- **Follow Up**: Check if your help resolved the issue
|
||||
|
||||
#### In GitHub
|
||||
|
||||
- **Helpful Comments**: Provide constructive feedback on issues and PRs
|
||||
- **Share Knowledge**: Contribute to discussions with your expertise
|
||||
- **Test Solutions**: Help test proposed fixes when possible
|
||||
|
||||
### Getting Help
|
||||
|
||||
#### Before Asking
|
||||
|
||||
1. **Check Documentation**: Review the official docs first
|
||||
2. **Search History**: Look through previous discussions and issues
|
||||
3. **Try Solutions**: Attempt basic troubleshooting steps
|
||||
|
||||
#### When Asking
|
||||
|
||||
1. **Be Specific**: Provide clear details about your issue
|
||||
2. **Include Context**: Share relevant environment information
|
||||
3. **Show Effort**: Explain what you've already tried
|
||||
4. **Be Patient**: Allow time for community members to respond
|
||||
|
||||
## Consequences for Violating Guidelines
|
||||
|
||||
We enforce these guidelines to maintain a positive community environment.
|
||||
|
||||
### Warning System
|
||||
|
||||
1. **First Violation**: For most violations, you will receive a warning from the moderators.
|
||||
2. **Repeated Minor Violations**: Additional warnings may lead to temporary restrictions.
|
||||
|
||||
### Immediate Actions
|
||||
|
||||
For serious violations, immediate actions may be taken:
|
||||
|
||||
- **Spam or Self-Promotion**: Immediate ban or removal
|
||||
- **Harassment or Abuse**: Immediate removal from the community
|
||||
- **Sharing Inappropriate Content**: Content removal and potential ban
|
||||
|
||||
### Appeal Process
|
||||
|
||||
If you believe you've been unfairly moderated:
|
||||
|
||||
1. **Contact Moderators**: Reach out via private message
|
||||
2. **Provide Context**: Explain your perspective respectfully
|
||||
3. **Accept Decisions**: Respect final moderation decisions
|
||||
|
||||
Refer to [enforcement guidelines](/contributing/code-of-conduct#enforcement-guidelines) for more details.
|
||||
|
||||
## Moderator and Admin Privileges
|
||||
|
||||
### Selection Process
|
||||
|
||||
Active contributors to the Chatwoot community, those who consistently offer valuable insights, help, and engagement, may be handpicked as moderators or granted specific privileges.
|
||||
|
||||
### Criteria for Moderators
|
||||
|
||||
- **Consistent Contribution**: Regular, helpful participation in the community
|
||||
- **Good Judgment**: Demonstrated ability to handle conflicts constructively
|
||||
- **Technical Knowledge**: Understanding of Chatwoot and related technologies
|
||||
- **Time Commitment**: Ability to dedicate time to moderation duties
|
||||
|
||||
### Responsibilities
|
||||
|
||||
Moderators are expected to:
|
||||
|
||||
- **Enforce Guidelines**: Apply community guidelines fairly and consistently
|
||||
- **Facilitate Discussions**: Help keep conversations productive and on-topic
|
||||
- **Support Users**: Provide assistance and guidance to community members
|
||||
- **Report Issues**: Escalate serious violations to administrators
|
||||
|
||||
### Discretionary Decisions
|
||||
|
||||
- **Appointment Process**: The appointment of moderators and the granting of additional privileges are at the sole discretion of the Chatwoot team.
|
||||
- **Review Process**: Moderator performance is reviewed regularly to ensure community standards are maintained.
|
||||
|
||||
### Admin Privileges
|
||||
|
||||
For compliance and security reasons, admin privileges in all Chatwoot communities are reserved exclusively for Chatwoot employees.
|
||||
|
||||
## Building a Positive Community
|
||||
|
||||
### Encouraging Participation
|
||||
|
||||
- **Welcome Newcomers**: Help new members feel included and valued
|
||||
- **Celebrate Contributions**: Acknowledge helpful contributions and achievements
|
||||
- **Share Knowledge**: Contribute your expertise to help others learn
|
||||
- **Provide Feedback**: Offer constructive feedback on ideas and solutions
|
||||
|
||||
### Creating Inclusive Environment
|
||||
|
||||
- **Use Inclusive Language**: Choose words that welcome all community members
|
||||
- **Respect Differences**: Value diverse perspectives and experiences
|
||||
- **Avoid Assumptions**: Don't assume others' backgrounds or knowledge levels
|
||||
- **Learn Together**: Approach discussions as learning opportunities
|
||||
|
||||
## Resources for Community Members
|
||||
|
||||
### Getting Started
|
||||
|
||||
- **Community Onboarding**: [Discord community](https://discord.com/invite/cJXdrwS)
|
||||
- **Contributor Guide**: [Contributing documentation](/contributing/introduction)
|
||||
- **Code of Conduct**: [Detailed guidelines](/contributing/code-of-conduct)
|
||||
|
||||
### Stay Connected
|
||||
|
||||
- **Discord Server**: Real-time community discussions
|
||||
- **GitHub Discussions**: Long-form technical discussions
|
||||
- **Twitter**: [@chatwootapp](https://twitter.com/chatwootapp) for updates
|
||||
- **Blog**: [Chatwoot Blog](https://www.chatwoot.com/blog) for articles and insights
|
||||
|
||||
---
|
||||
|
||||
Together, we can build an amazing community that supports Chatwoot users and contributors worldwide. Thank you for being part of our journey! 🚀
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
title: Contributors
|
||||
description: Meet the amazing people who contribute to Chatwoot
|
||||
sidebarTitle: Contributors
|
||||
---
|
||||
|
||||
# Contributors
|
||||
|
||||
Chatwoot is made possible by the amazing community of developers, designers, translators, and supporters who contribute their time and expertise to make it better every day.
|
||||
|
||||
## Our Contributors
|
||||
|
||||
You can find the full list of contributors at [https://contributors.chatwoot.com](https://contributors.chatwoot.com)
|
||||
|
||||
<a href="https://github.com/chatwoot/chatwoot/graphs/contributors"><img src="https://contributors.chatwoot.com/api/graph.svg?columns=30&size=32" /></a>
|
||||
|
||||
## Join Our Contributors
|
||||
|
||||
Ready to make your mark on Chatwoot? Here's how to get started:
|
||||
|
||||
### Quick Start
|
||||
|
||||
1. **Star the Repository**: [Star Chatwoot on GitHub](https://github.com/chatwoot/chatwoot)
|
||||
2. **Join Discord**: [Join our Discord community](https://discord.com/invite/cJXdrwS)
|
||||
3. **Pick an Issue**: Find a [good first issue](https://github.com/chatwoot/chatwoot/labels/good%20first%20issue)
|
||||
4. **Make Your First Contribution**: Follow our [contribution guide](/contributing/introduction)
|
||||
|
||||
### Stay Connected
|
||||
|
||||
- **GitHub**: [github.com/chatwoot/chatwoot](https://github.com/chatwoot/chatwoot)
|
||||
- **Discord**: [discord.com/invite/cJXdrwS](https://discord.com/invite/cJXdrwS)
|
||||
- **Twitter**: [@chatwootapp](https://twitter.com/chatwootapp)
|
||||
- **LinkedIn**: [Chatwoot Company Page](https://www.linkedin.com/company/chatwoot)
|
||||
|
||||
---
|
||||
|
||||
Every contribution, no matter how small, makes Chatwoot better for thousands of users worldwide. Thank you for considering joining our contributor community! 🙏
|
||||
@@ -0,0 +1,127 @@
|
||||
---
|
||||
title: End-to-end testing with Cypress
|
||||
description: Guide to running Cypress end-to-end tests for Chatwoot
|
||||
sidebarTitle: Cypress Testing
|
||||
---
|
||||
|
||||
# End-to-end testing with Cypress
|
||||
|
||||
Chatwoot uses [Cypress](https://www.cypress.io/) for end-to-end testing. Use the following steps to run the tests on your local machine.
|
||||
|
||||
## Prepare the Test Server
|
||||
|
||||
Choose any of the given methods to run your Chatwoot test server.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Local Installation">
|
||||
|
||||
### Using Local Chatwoot Installation
|
||||
|
||||
<Note>
|
||||
You have to install the necessary dependencies as described in [setup guide](/contributing/project-setup/setup-guide) for this method to work.
|
||||
</Note>
|
||||
|
||||
Navigate to Chatwoot codebase in your local machine and execute the following steps:
|
||||
|
||||
#### Create a fresh test database
|
||||
|
||||
```bash
|
||||
RAILS_ENV=test bin/rake db:drop
|
||||
RAILS_ENV=test bin/rake db:create
|
||||
RAILS_ENV=test bin/rake db:schema:load
|
||||
```
|
||||
|
||||
#### Start Chatwoot in the test environment
|
||||
|
||||
```bash
|
||||
RAILS_ENV=test foreman start -f Procfile.test
|
||||
```
|
||||
|
||||
Load the URL in the browser and wait for it to start up:
|
||||
```
|
||||
http://localhost:5050/app/login
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab title="Docker Setup">
|
||||
|
||||
### Using Docker
|
||||
|
||||
Follow the [docker setup guide](/contributing/project-setup/docker-setup) until you build the images.
|
||||
|
||||
#### Change the Rails Environment
|
||||
|
||||
Open `docker-compose.yaml` and update all the `RAILS_ENV` values from `development` to `test`:
|
||||
|
||||
```yaml
|
||||
# In docker-compose.yaml
|
||||
environment:
|
||||
- RAILS_ENV=test # Change from development to test
|
||||
```
|
||||
|
||||
#### Update the Port
|
||||
|
||||
Under rails section in your `docker-compose.yaml` update the port value as given below:
|
||||
|
||||
```yaml
|
||||
# In docker-compose.yaml
|
||||
ports:
|
||||
- 5050:3000 # Change from 3000:3000 to 5050:3000
|
||||
```
|
||||
|
||||
#### Reset the Database
|
||||
|
||||
```bash
|
||||
docker-compose run --rm rails bundle exec rails db:reset
|
||||
```
|
||||
|
||||
#### Start Chatwoot Docker in the test environment
|
||||
|
||||
```bash
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
Load the URL in the browser and wait for it to start up:
|
||||
```
|
||||
http://localhost:5050/app/login
|
||||
```
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Run Cypress
|
||||
|
||||
Load `localhost:5050` on your browser and ensure that the Chatwoot server is running.
|
||||
|
||||
Navigate to your Chatwoot local directory and execute the following command to run the Cypress tests:
|
||||
|
||||
```bash
|
||||
pnpm cypress open --project ./spec
|
||||
```
|
||||
|
||||
This will open the Cypress Test Runner where you can:
|
||||
|
||||
1. **Choose a browser** for running tests
|
||||
2. **Select test files** to run individual or all tests
|
||||
3. **Watch tests run** in real-time with step-by-step execution
|
||||
4. **Debug failed tests** with detailed error information
|
||||
|
||||
|
||||
## Getting Help
|
||||
|
||||
If you encounter issues with Cypress testing:
|
||||
|
||||
- **Cypress Documentation**: [Official Cypress Docs](https://docs.cypress.io/)
|
||||
- **Cypress Best Practices**: [Testing Guide](https://docs.cypress.io/guides/references/best-practices)
|
||||
- **Common Errors**: See [Common Errors](/contributing/project-setup/common-errors)
|
||||
- **Community Support**: [Discord](https://discord.com/invite/cJXdrwS)
|
||||
|
||||
## Useful Resources
|
||||
|
||||
- **Cypress API Reference**: [https://docs.cypress.io/api/table-of-contents](https://docs.cypress.io/api/table-of-contents)
|
||||
- **Testing Library**: [Testing utilities for better element selection](https://testing-library.com/)
|
||||
- **Cypress Examples**: [Real-world examples](https://github.com/cypress-io/cypress-example-recipes)
|
||||
|
||||
---
|
||||
|
||||
Your Cypress testing environment is now ready for comprehensive end-to-end testing! 🧪
|
||||
@@ -0,0 +1,588 @@
|
||||
---
|
||||
title: Local Development Setup
|
||||
description: Set up Chatwoot for local development on your machine
|
||||
sidebarTitle: Local Development
|
||||
---
|
||||
|
||||
# Local Development Setup
|
||||
|
||||
This guide will help you set up Chatwoot for local development on your machine. Follow these steps to get a complete development environment running.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before setting up Chatwoot locally, ensure you have the following installed:
|
||||
|
||||
### Required Software
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Ruby" icon="gem">
|
||||
Ruby 3.3.3 (managed with rbenv or RVM)
|
||||
</Card>
|
||||
<Card title="Node.js" icon="node-js">
|
||||
Node.js 20+ with pnpm package manager
|
||||
</Card>
|
||||
<Card title="PostgreSQL" icon="database">
|
||||
PostgreSQL 13+ for the database
|
||||
</Card>
|
||||
<Card title="Redis" icon="redis">
|
||||
Redis 6+ for caching and background jobs
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
### System Dependencies
|
||||
|
||||
<Tabs>
|
||||
<Tab title="macOS">
|
||||
```bash
|
||||
# Install Homebrew if not already installed
|
||||
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||
|
||||
# Install dependencies
|
||||
brew install postgresql@15 redis imagemagick git
|
||||
|
||||
# Install rbenv for Ruby version management
|
||||
brew install rbenv ruby-build
|
||||
|
||||
# Install Node.js and pnpm
|
||||
brew install node
|
||||
npm install -g pnpm
|
||||
|
||||
# Start services
|
||||
brew services start postgresql@15
|
||||
brew services start redis
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="Ubuntu/Debian">
|
||||
```bash
|
||||
# Update package list
|
||||
sudo apt update
|
||||
|
||||
# Install dependencies
|
||||
sudo apt install -y curl git build-essential libssl-dev libreadline-dev \
|
||||
zlib1g-dev libpq-dev imagemagick libmagickwand-dev libffi-dev \
|
||||
postgresql postgresql-contrib redis-server
|
||||
|
||||
# Install rbenv
|
||||
curl -fsSL https://github.com/rbenv/rbenv-installer/raw/HEAD/bin/rbenv-installer | bash
|
||||
|
||||
# Install Node.js (using NodeSource)
|
||||
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
|
||||
sudo apt install -y nodejs
|
||||
|
||||
# Install pnpm
|
||||
npm install -g pnpm
|
||||
|
||||
# Start services
|
||||
sudo systemctl start postgresql
|
||||
sudo systemctl start redis-server
|
||||
sudo systemctl enable postgresql
|
||||
sudo systemctl enable redis-server
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="CentOS/RHEL">
|
||||
```bash
|
||||
# Install EPEL repository
|
||||
sudo yum install -y epel-release
|
||||
|
||||
# Install dependencies
|
||||
sudo yum groupinstall -y "Development Tools"
|
||||
sudo yum install -y curl git openssl-devel readline-devel zlib-devel \
|
||||
postgresql-devel ImageMagick-devel libffi-devel postgresql-server \
|
||||
postgresql-contrib redis
|
||||
|
||||
# Initialize PostgreSQL
|
||||
sudo postgresql-setup initdb
|
||||
|
||||
# Install rbenv
|
||||
curl -fsSL https://github.com/rbenv/rbenv-installer/raw/HEAD/bin/rbenv-installer | bash
|
||||
|
||||
# Install Node.js
|
||||
curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash -
|
||||
sudo yum install -y nodejs
|
||||
|
||||
# Install pnpm
|
||||
npm install -g pnpm
|
||||
|
||||
# Start services
|
||||
sudo systemctl start postgresql
|
||||
sudo systemctl start redis
|
||||
sudo systemctl enable postgresql
|
||||
sudo systemctl enable redis
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Ruby Setup
|
||||
|
||||
### Install Ruby with rbenv
|
||||
|
||||
```bash
|
||||
# Add rbenv to your shell profile
|
||||
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
|
||||
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
|
||||
source ~/.bashrc
|
||||
|
||||
# Install Ruby 3.3.3
|
||||
rbenv install 3.3.3
|
||||
rbenv global 3.3.3
|
||||
|
||||
# Verify installation
|
||||
ruby --version
|
||||
# Should output: ruby 3.3.3
|
||||
|
||||
# Install bundler
|
||||
gem install bundler
|
||||
```
|
||||
|
||||
### Alternative: Using RVM
|
||||
|
||||
```bash
|
||||
# Install RVM
|
||||
curl -sSL https://get.rvm.io | bash -s stable
|
||||
source ~/.rvm/scripts/rvm
|
||||
|
||||
# Install Ruby 3.3.3
|
||||
rvm install 3.3.3
|
||||
rvm use 3.3.3 --default
|
||||
|
||||
# Verify installation
|
||||
ruby --version
|
||||
gem install bundler
|
||||
```
|
||||
|
||||
## Database Setup
|
||||
|
||||
### PostgreSQL Configuration
|
||||
|
||||
```bash
|
||||
# Create PostgreSQL user (macOS with Homebrew)
|
||||
createuser -s chatwoot
|
||||
|
||||
# Create PostgreSQL user (Linux)
|
||||
sudo -u postgres createuser -s chatwoot
|
||||
|
||||
# Set password for the user
|
||||
sudo -u postgres psql
|
||||
postgres=# ALTER USER chatwoot PASSWORD 'password';
|
||||
postgres=# \q
|
||||
|
||||
# Create databases
|
||||
createdb chatwoot_development
|
||||
createdb chatwoot_test
|
||||
```
|
||||
|
||||
### PostgreSQL Authentication Setup
|
||||
|
||||
Edit PostgreSQL configuration to allow local connections:
|
||||
|
||||
```bash
|
||||
# Find pg_hba.conf location
|
||||
sudo -u postgres psql -c "SHOW hba_file;"
|
||||
|
||||
# Edit the file (example path)
|
||||
sudo nano /etc/postgresql/15/main/pg_hba.conf
|
||||
|
||||
# Add or modify these lines:
|
||||
local all chatwoot md5
|
||||
host all chatwoot 127.0.0.1/32 md5
|
||||
host all chatwoot ::1/128 md5
|
||||
|
||||
# Restart PostgreSQL
|
||||
sudo systemctl restart postgresql
|
||||
```
|
||||
|
||||
## Project Setup
|
||||
|
||||
### Clone the Repository
|
||||
|
||||
```bash
|
||||
# Fork the repository on GitHub first, then clone your fork
|
||||
git clone https://github.com/YOUR_USERNAME/chatwoot.git
|
||||
cd chatwoot
|
||||
|
||||
# Add upstream remote
|
||||
git remote add upstream https://github.com/chatwoot/chatwoot.git
|
||||
|
||||
# Verify remotes
|
||||
git remote -v
|
||||
```
|
||||
|
||||
### Install Dependencies
|
||||
|
||||
```bash
|
||||
# Install Ruby dependencies
|
||||
bundle install
|
||||
|
||||
# Install Node.js dependencies
|
||||
pnpm install
|
||||
|
||||
# Install Playwright for E2E tests (optional)
|
||||
pnpm exec playwright install
|
||||
```
|
||||
|
||||
### Environment Configuration
|
||||
|
||||
```bash
|
||||
# Copy environment file
|
||||
cp .env.example .env
|
||||
|
||||
# Edit the environment file
|
||||
nano .env
|
||||
```
|
||||
|
||||
Update the `.env` file with your local configuration:
|
||||
|
||||
```bash
|
||||
# Database configuration
|
||||
DATABASE_URL=postgresql://chatwoot:password@localhost:5432/chatwoot_development
|
||||
REDIS_URL=redis://localhost:6379/0
|
||||
|
||||
# Application settings
|
||||
FRONTEND_URL=http://localhost:3000
|
||||
FORCE_SSL=false
|
||||
RAILS_ENV=development
|
||||
NODE_ENV=development
|
||||
|
||||
# Email configuration (for development)
|
||||
MAILER_SENDER_EMAIL=dev@chatwoot.local
|
||||
SMTP_ADDRESS=localhost
|
||||
SMTP_PORT=1025
|
||||
|
||||
# File storage (local)
|
||||
ACTIVE_STORAGE_SERVICE=local
|
||||
|
||||
# Development features
|
||||
ENABLE_DEVELOPMENT_FEATURES=true
|
||||
LOG_LEVEL=debug
|
||||
```
|
||||
|
||||
### Database Initialization
|
||||
|
||||
```bash
|
||||
# Create and migrate the database
|
||||
bundle exec rails db:create
|
||||
bundle exec rails db:migrate
|
||||
|
||||
# Seed the database with sample data
|
||||
bundle exec rails db:seed
|
||||
|
||||
# Prepare the test database
|
||||
RAILS_ENV=test bundle exec rails db:create
|
||||
RAILS_ENV=test bundle exec rails db:migrate
|
||||
```
|
||||
|
||||
## Running the Application
|
||||
|
||||
### Start Development Servers
|
||||
|
||||
You'll need to run multiple processes for full development:
|
||||
|
||||
#### Option 1: Using Foreman (Recommended)
|
||||
|
||||
```bash
|
||||
# Install foreman
|
||||
gem install foreman
|
||||
|
||||
# Start all services
|
||||
foreman start -f Procfile.dev
|
||||
```
|
||||
|
||||
#### Option 2: Manual Process Management
|
||||
|
||||
Open multiple terminal windows/tabs:
|
||||
|
||||
```bash
|
||||
# Terminal 1: Rails server
|
||||
bundle exec rails server -p 3000
|
||||
|
||||
# Terminal 2: Webpack dev server
|
||||
pnpm run dev
|
||||
|
||||
# Terminal 3: Sidekiq worker
|
||||
bundle exec sidekiq
|
||||
|
||||
# Terminal 4: MailHog (for email testing)
|
||||
mailhog
|
||||
```
|
||||
|
||||
### Access the Application
|
||||
|
||||
Once all services are running:
|
||||
|
||||
- **Web Application**: http://localhost:3000
|
||||
- **API Documentation**: http://localhost:3000/swagger
|
||||
- **Sidekiq Web UI**: http://localhost:3000/sidekiq
|
||||
- **MailHog (Email)**: http://localhost:8025
|
||||
|
||||
### Default Login Credentials
|
||||
|
||||
After seeding the database, you can log in with:
|
||||
|
||||
- **Email**: john@acme.inc
|
||||
- **Password**: Password1!
|
||||
|
||||
## Development Tools
|
||||
|
||||
### Code Quality Tools
|
||||
|
||||
```bash
|
||||
# Install development gems
|
||||
bundle install --with development test
|
||||
|
||||
# Run RuboCop (Ruby linter)
|
||||
bundle exec rubocop
|
||||
|
||||
# Run RuboCop with auto-fix
|
||||
bundle exec rubocop -a
|
||||
|
||||
# Run ESLint (JavaScript linter)
|
||||
pnpm run lint
|
||||
|
||||
# Run ESLint with auto-fix
|
||||
pnpm run lint:fix
|
||||
|
||||
# Run Prettier (code formatter)
|
||||
pnpm run format
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
# Run Ruby tests
|
||||
bundle exec rspec
|
||||
|
||||
# Run specific test file
|
||||
bundle exec rspec spec/models/user_spec.rb
|
||||
|
||||
# Run JavaScript tests
|
||||
pnpm run test
|
||||
|
||||
# Run E2E tests
|
||||
pnpm run test:e2e
|
||||
|
||||
# Run tests with coverage
|
||||
COVERAGE=true bundle exec rspec
|
||||
```
|
||||
|
||||
### Database Operations
|
||||
|
||||
```bash
|
||||
# Reset database
|
||||
bundle exec rails db:drop db:create db:migrate db:seed
|
||||
|
||||
# Generate migration
|
||||
bundle exec rails generate migration AddColumnToTable column:type
|
||||
|
||||
# Run migrations
|
||||
bundle exec rails db:migrate
|
||||
|
||||
# Rollback migration
|
||||
bundle exec rails db:rollback
|
||||
|
||||
# Check migration status
|
||||
bundle exec rails db:migrate:status
|
||||
```
|
||||
|
||||
## IDE and Editor Setup
|
||||
|
||||
### VS Code Configuration
|
||||
|
||||
Create `.vscode/settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"ruby.intellisense": "rubyLocate",
|
||||
"ruby.codeCompletion": "rcodetools",
|
||||
"ruby.format": "rubocop",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.rulers": [120],
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"files.insertFinalNewline": true,
|
||||
"eslint.autoFixOnSave": true,
|
||||
"prettier.requireConfig": true
|
||||
}
|
||||
```
|
||||
|
||||
### Recommended VS Code Extensions
|
||||
|
||||
```json
|
||||
{
|
||||
"recommendations": [
|
||||
"rebornix.ruby",
|
||||
"wingrunr21.vscode-ruby",
|
||||
"bradlc.vscode-tailwindcss",
|
||||
"esbenp.prettier-vscode",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"ms-vscode.vscode-typescript-next",
|
||||
"bradlc.vscode-tailwindcss"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### RubyMine Configuration
|
||||
|
||||
1. Open the project in RubyMine
|
||||
2. Configure Ruby SDK: File → Project Structure → SDKs
|
||||
3. Set up database connection in Database tool window
|
||||
4. Configure code style: File → Settings → Editor → Code Style
|
||||
|
||||
## Debugging
|
||||
|
||||
### Rails Debugging
|
||||
|
||||
```ruby
|
||||
# Add to your code for debugging
|
||||
binding.pry
|
||||
|
||||
# Or use the built-in debugger
|
||||
debugger
|
||||
```
|
||||
|
||||
### JavaScript Debugging
|
||||
|
||||
```javascript
|
||||
// Add to your code
|
||||
console.log('Debug info:', variable);
|
||||
debugger;
|
||||
```
|
||||
|
||||
### Database Debugging
|
||||
|
||||
```bash
|
||||
# Rails console
|
||||
bundle exec rails console
|
||||
|
||||
# Database console
|
||||
bundle exec rails dbconsole
|
||||
|
||||
# Check database queries in logs
|
||||
tail -f log/development.log | grep SQL
|
||||
```
|
||||
|
||||
## Common Issues and Solutions
|
||||
|
||||
### Bundle Install Issues
|
||||
|
||||
<Accordion title="pg gem installation fails">
|
||||
```bash
|
||||
# macOS
|
||||
brew install postgresql
|
||||
bundle config build.pg --with-pg-config=/usr/local/bin/pg_config
|
||||
|
||||
# Ubuntu/Debian
|
||||
sudo apt-get install libpq-dev
|
||||
bundle install
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="ImageMagick issues">
|
||||
```bash
|
||||
# macOS
|
||||
brew install imagemagick pkg-config
|
||||
|
||||
# Ubuntu/Debian
|
||||
sudo apt-get install libmagickwand-dev
|
||||
|
||||
# Then reinstall the gem
|
||||
bundle pristine rmagick
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
### Node.js Issues
|
||||
|
||||
<Accordion title="pnpm install fails">
|
||||
```bash
|
||||
# Clear cache and reinstall
|
||||
pnpm store prune
|
||||
rm -rf node_modules
|
||||
pnpm install
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Webpack compilation errors">
|
||||
```bash
|
||||
# Clear webpack cache
|
||||
rm -rf tmp/cache/webpacker
|
||||
pnpm run dev
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
### Database Issues
|
||||
|
||||
<Accordion title="Database connection refused">
|
||||
```bash
|
||||
# Check if PostgreSQL is running
|
||||
sudo systemctl status postgresql
|
||||
|
||||
# Start PostgreSQL if not running
|
||||
sudo systemctl start postgresql
|
||||
|
||||
# Check connection
|
||||
psql -U chatwoot -d chatwoot_development -h localhost
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Permission denied for database">
|
||||
```bash
|
||||
# Reset PostgreSQL user password
|
||||
sudo -u postgres psql
|
||||
postgres=# ALTER USER chatwoot PASSWORD 'password';
|
||||
postgres=# \q
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### Development Performance Tips
|
||||
|
||||
```bash
|
||||
# Use spring for faster Rails commands
|
||||
bundle exec spring binstub --all
|
||||
|
||||
# Use bootsnap for faster boot times (already included)
|
||||
# Ensure tmp/cache directory exists
|
||||
mkdir -p tmp/cache
|
||||
|
||||
# Use parallel testing
|
||||
bundle exec rspec --parallel
|
||||
|
||||
# Optimize database queries
|
||||
# Add to config/environments/development.rb
|
||||
config.active_record.verbose_query_logs = true
|
||||
```
|
||||
|
||||
### Memory Usage Optimization
|
||||
|
||||
```bash
|
||||
# Monitor memory usage
|
||||
ps aux | grep ruby
|
||||
ps aux | grep node
|
||||
|
||||
# Use jemalloc for better memory management
|
||||
export MALLOC_ARENA_MAX=2
|
||||
bundle exec rails server
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
Once you have your development environment set up:
|
||||
|
||||
1. **Read the Contributing Guidelines**: Check out the [contributing guide](../introduction) for code standards and workflow
|
||||
2. **Explore the Codebase**: Familiarize yourself with the project structure
|
||||
3. **Pick an Issue**: Look for "good first issue" labels on GitHub
|
||||
4. **Join the Community**: Connect with other contributors on Discord or GitHub Discussions
|
||||
|
||||
## Getting Help
|
||||
|
||||
If you encounter issues during setup:
|
||||
|
||||
- **GitHub Issues**: Search existing issues or create a new one
|
||||
- **Discord Community**: Join the Chatwoot Discord server
|
||||
- **Documentation**: Check the official documentation
|
||||
- **Stack Overflow**: Search for Chatwoot-related questions
|
||||
|
||||
---
|
||||
|
||||
You're now ready to start contributing to Chatwoot! The development environment should be fully functional and ready for coding.
|
||||
@@ -0,0 +1,196 @@
|
||||
---
|
||||
title: Contributing to Chatwoot
|
||||
description: Complete guide to contributing to Chatwoot - from setting up your development environment to submitting pull requests.
|
||||
sidebarTitle: Introduction
|
||||
---
|
||||
|
||||
# Contributing Guide
|
||||
|
||||
Thank you for taking an interest in contributing to Chatwoot! This guide will help you get started with contributing to our open-source customer support platform. Before submitting your contribution, please make sure to take a moment and read through the following guidelines.
|
||||
|
||||
## Getting Started
|
||||
|
||||
<Warning>
|
||||
Before starting your work, ensure an issue exists for it. If not, feel free to create one. You can also take a look into the issues tagged [Good first issues](https://github.com/chatwoot/chatwoot/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22).
|
||||
</Warning>
|
||||
|
||||
### Initial Steps
|
||||
|
||||
1. **Check for Existing Issues**: Browse the [GitHub issues](https://github.com/chatwoot/chatwoot/issues) to see if someone is already working on what you want to contribute.
|
||||
|
||||
2. **Comment on the Issue**: Add a comment on the issue and wait for the issue to be assigned before you start working on it.
|
||||
- This helps to avoid multiple people working on similar issues.
|
||||
|
||||
3. **Propose Complex Solutions**: If the solution is complex, propose the solution on the issue and wait for one of the core contributors to approve before going into the implementation.
|
||||
- This helps in shorter turn around times in merging PRs.
|
||||
|
||||
4. **Justify New Features**: For new feature requests, provide a convincing reason to add this feature. Real-life business use-cases will be super helpful.
|
||||
|
||||
5. **Join the Community**: Feel free to join our [Discord community](https://discord.com/invite/cJXdrwS) if you need further discussions with the core team.
|
||||
|
||||
## Pull Request Guidelines
|
||||
|
||||
<Info>
|
||||
We use git-flow branching model. The base branch is `develop`. Please raise your PRs against the `develop` branch.
|
||||
</Info>
|
||||
|
||||
### Before Submitting
|
||||
|
||||
- Please make sure that you have read the [issue triage guidelines](https://www.chatwoot.com/hc/handbook/articles/issue-triage-29) before you make a contribution.
|
||||
- It's okay and encouraged to have multiple small commits as you work on the PR - we will squash the commits before merging.
|
||||
- For other guidelines, see [PR Guidelines](https://www.chatwoot.com/hc/handbook/articles/pull-request-guidelines-32)
|
||||
- Ensure that all the text copies that you add into the product are i18n translatable. You are only required to add the `English` version of the strings. We pull in other language translations from our contributors on crowdin. See [Translation guidelines](https://www.chatwoot.com/docs/contributing-guide/translation-guidelines) to learn more.
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Developing a New Feature
|
||||
|
||||
```bash
|
||||
# Create a branch in the following format:
|
||||
feature/<issue-id>-<issue-name>
|
||||
|
||||
# Example:
|
||||
feature/235-contact-panel
|
||||
```
|
||||
|
||||
**Requirements:**
|
||||
- Add accompanying test cases
|
||||
- Follow our coding standards
|
||||
- Include proper documentation
|
||||
|
||||
### Bug Fixes or Chores
|
||||
|
||||
```bash
|
||||
# Branch naming for bug fixes:
|
||||
fix/<issue-id>-<issue-name>
|
||||
|
||||
# Branch naming for chores:
|
||||
chore/<description>
|
||||
```
|
||||
|
||||
**Requirements:**
|
||||
- If you are resolving a particular issue, add `fix: Fixes xxxx` (#xxxx is the issue) in your PR title
|
||||
- Provide a detailed description of the bug in the PR
|
||||
- Add appropriate test coverage if applicable
|
||||
|
||||
## Environment Setup
|
||||
|
||||
Choose the guide that matches your operating system:
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card
|
||||
title="macOS Setup"
|
||||
icon="apple"
|
||||
href="/contributing/project-setup/macos-setup"
|
||||
>
|
||||
Complete setup guide for macOS developers
|
||||
</Card>
|
||||
<Card
|
||||
title="Ubuntu Setup"
|
||||
icon="ubuntu"
|
||||
href="/contributing/project-setup/ubuntu-setup"
|
||||
>
|
||||
Step-by-step Ubuntu installation guide
|
||||
</Card>
|
||||
<Card
|
||||
title="Windows Setup"
|
||||
icon="windows"
|
||||
href="/contributing/project-setup/windows-setup"
|
||||
>
|
||||
Windows 10/11 development environment setup
|
||||
</Card>
|
||||
<Card
|
||||
title="Docker Setup"
|
||||
icon="docker"
|
||||
href="/contributing/project-setup/docker-setup"
|
||||
>
|
||||
Quick setup using Docker containers
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
### Speed Up Development
|
||||
|
||||
Use our [Make commands](/contributing/project-setup/make-setup) to speed up your local development workflow.
|
||||
|
||||
## Project Setup
|
||||
|
||||
Once you have set up the environment, follow these guides to get Chatwoot running locally:
|
||||
|
||||
1. **[Quick Setup Guide](/contributing/project-setup/setup-guide)** - Step-by-step setup instructions
|
||||
2. **[Environment Variables](/contributing/project-setup/environment-variables)** - Configuration options
|
||||
3. **[Common Errors](/contributing/project-setup/common-errors)** - Troubleshooting guide
|
||||
|
||||
### Special App Integrations
|
||||
|
||||
If you're working on specific integrations:
|
||||
- **[Telegram App Setup](/contributing/project-setup/telegram-app)**
|
||||
- **[Line App Setup](/contributing/project-setup/line-app)**
|
||||
- **[Mobile App Development](/contributing/project-setup/mobile-app)**
|
||||
|
||||
## Testing Your Contributions
|
||||
|
||||
We use comprehensive testing to ensure code quality:
|
||||
|
||||
### Test Types
|
||||
- **Unit Tests**: Test individual components and functions
|
||||
- **Integration Tests**: Test component interactions
|
||||
- **End-to-End Tests**: Test complete user workflows with [Cypress](/contributing/testing)
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
bundle exec rspec
|
||||
|
||||
# Run specific test file
|
||||
bundle exec rspec spec/models/user_spec.rb
|
||||
|
||||
# Run Cypress tests
|
||||
npm run cypress:open
|
||||
```
|
||||
|
||||
## Documentation and Translation
|
||||
|
||||
### Documentation Guidelines
|
||||
- Keep documentation clear and concise
|
||||
- Include code examples where helpful
|
||||
- Update documentation when changing functionality
|
||||
- Follow our [translation guidelines](https://www.chatwoot.com/docs/contributing-guide/other/translation-guidelines)
|
||||
|
||||
### Internationalization
|
||||
- All user-facing text must be translatable
|
||||
- Only add English strings - other languages are handled via [Crowdin](https://translate.chatwoot.com/)
|
||||
- Use proper i18n keys and formatting
|
||||
|
||||
## Community Guidelines
|
||||
|
||||
We strive to maintain a welcoming and inclusive community:
|
||||
|
||||
- **[Code of Conduct](https://www.chatwoot.com/docs/contributing-guide/other/code-of-conduct)** - Our community standards
|
||||
- **[Community Guidelines](https://www.chatwoot.com/docs/contributing-guide/other/community-guidelines)** - How we interact
|
||||
- **[Security Reports](https://www.chatwoot.com/docs/contributing-guide/other/security-reports)** - Reporting security issues
|
||||
|
||||
## API Development
|
||||
|
||||
If you're working on API-related features:
|
||||
|
||||
- **[Chatwoot APIs](https://www.chatwoot.com/docs/contributing-guide/other/chatwoot-apis)** - API development guide
|
||||
- **[API Documentation](https://www.chatwoot.com/docs/contributing-guide/other/api-documentation)** - Documenting APIs
|
||||
- **[Platform APIs](https://www.chatwoot.com/docs/contributing-guide/other/chatwoot-platform-apis)** - Platform-level APIs
|
||||
|
||||
## Recognition
|
||||
|
||||
We value all contributions to Chatwoot. Check out our [Contributors page](https://www.chatwoot.com/docs/contributing-guide/other/contributors) to see the amazing people who have helped make Chatwoot better.
|
||||
|
||||
## Getting Help
|
||||
|
||||
Need assistance? Here are your options:
|
||||
|
||||
- **GitHub Issues**: For bug reports and feature requests
|
||||
- **Discord Community**: For real-time discussions with the core team
|
||||
- **Documentation**: Comprehensive guides and API references
|
||||
- **Community Forums**: Connect with other contributors
|
||||
|
||||
---
|
||||
|
||||
Ready to start contributing? Pick an issue that interests you and follow our guidelines above. Every contribution, no matter how small, helps make Chatwoot better for everyone! 🚀
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,283 @@
|
||||
---
|
||||
title: Docker Development Setup
|
||||
description: Complete guide to setting up Chatwoot development environment using Docker and Docker Compose.
|
||||
sidebarTitle: Docker Setup
|
||||
---
|
||||
|
||||
# Docker Development Setup
|
||||
|
||||
This guide will help you set up a complete Chatwoot development environment using Docker and Docker Compose.
|
||||
|
||||
## Pre-requisites
|
||||
|
||||
Before proceeding, make sure you have the latest version of `docker` and `docker-compose` installed.
|
||||
|
||||
As of now, we recommend a version equal to or higher than the following:
|
||||
|
||||
```bash
|
||||
$ docker --version
|
||||
Docker version 25.0.4, build 1a576c5
|
||||
$ docker compose --version
|
||||
docker-compose version 2.24.7
|
||||
```
|
||||
|
||||
### Install Docker
|
||||
|
||||
#### Windows
|
||||
|
||||
1. **Download Docker Desktop** from [https://www.docker.com/products/docker-desktop/](https://www.docker.com/products/docker-desktop/)
|
||||
2. **Run the installer** and follow setup instructions
|
||||
3. **Enable WSL2 backend** (recommended)
|
||||
4. **Restart your computer** when prompted
|
||||
|
||||
#### macOS
|
||||
|
||||
```bash
|
||||
# Option 1: Download from website
|
||||
# Go to https://www.docker.com/products/docker-desktop/
|
||||
|
||||
# Option 2: Using Homebrew
|
||||
brew install --cask docker
|
||||
```
|
||||
|
||||
#### Linux (Ubuntu/Debian)
|
||||
|
||||
```bash
|
||||
# Update package index
|
||||
sudo apt update
|
||||
|
||||
# Install dependencies
|
||||
sudo apt install -y apt-transport-https ca-certificates curl gnupg lsb-release
|
||||
|
||||
# Add Docker's official GPG key
|
||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
||||
|
||||
# Add Docker repository
|
||||
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
|
||||
# Install Docker
|
||||
sudo apt update
|
||||
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
|
||||
|
||||
# Add user to docker group
|
||||
sudo usermod -aG docker $USER
|
||||
|
||||
# Start Docker service
|
||||
sudo systemctl start docker
|
||||
sudo systemctl enable docker
|
||||
```
|
||||
|
||||
<Warning>
|
||||
After adding yourself to the docker group on Linux, log out and log back in for the changes to take effect.
|
||||
</Warning>
|
||||
|
||||
## Development Environment
|
||||
|
||||
1. **Clone the repository.**
|
||||
|
||||
```bash
|
||||
git clone https://github.com/chatwoot/chatwoot.git
|
||||
```
|
||||
|
||||
2. **Make a copy of the example environment file and modify it as required.**
|
||||
|
||||
```bash
|
||||
# Navigate to Chatwoot
|
||||
cd chatwoot
|
||||
cp .env.example .env
|
||||
# Update redis and postgres passwords
|
||||
nano .env
|
||||
# Update docker-compose.yaml with the same postgres password
|
||||
nano docker-compose.yaml
|
||||
```
|
||||
|
||||
3. **Build the images.**
|
||||
|
||||
```bash
|
||||
# Build base image first
|
||||
docker compose build base
|
||||
|
||||
# Build the server and worker
|
||||
docker compose build
|
||||
```
|
||||
|
||||
4. **After building the image or destroying the stack, you would have to reset the database using the following command.**
|
||||
|
||||
```bash
|
||||
docker compose run --rm rails bundle exec rails db:chatwoot_prepare
|
||||
```
|
||||
|
||||
5. **To run the app:**
|
||||
|
||||
```bash
|
||||
docker compose up
|
||||
```
|
||||
|
||||
* Access the rails app frontend by visiting `http://0.0.0.0:3000/`
|
||||
* Access Mailhog inbox by visiting `http://0.0.0.0:8025/` (You will receive all emails going out of the application here)
|
||||
|
||||
#### Login with credentials
|
||||
```
|
||||
url: http://localhost:3000
|
||||
user_name: john@acme.inc
|
||||
password: Password1!
|
||||
```
|
||||
|
||||
6. **To stop the app:**
|
||||
|
||||
```bash
|
||||
docker compose down
|
||||
```
|
||||
|
||||
## Running RSpec Tests
|
||||
|
||||
For running the complete RSpec tests:
|
||||
|
||||
```bash
|
||||
docker compose run --rm rails bundle exec rspec
|
||||
```
|
||||
|
||||
For running specific test:
|
||||
|
||||
```bash
|
||||
docker compose run --rm rails bundle exec rspec spec/<path-to-file>:<line-number>
|
||||
```
|
||||
|
||||
## Production Environment
|
||||
|
||||
To debug the production build locally, set `SECRET_KEY_BASE` environment variable in your `.env` file and then run the below commands:
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.production.yaml build
|
||||
docker compose -f docker-compose.production.yaml up
|
||||
```
|
||||
|
||||
## Debugging Mode
|
||||
|
||||
To use debuggers like `byebug` or `binding.pry`, use the following command to bring up the app instead of `docker compose up`:
|
||||
|
||||
```bash
|
||||
docker compose run --rm --service-port rails
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Daily Development Commands
|
||||
|
||||
```bash
|
||||
# Start development environment
|
||||
docker compose up
|
||||
|
||||
# View logs
|
||||
docker compose logs -f rails
|
||||
|
||||
# Access Rails console
|
||||
docker compose exec rails bundle exec rails console
|
||||
|
||||
# Run migrations
|
||||
docker compose exec rails bundle exec rails db:migrate
|
||||
|
||||
# Install new gems
|
||||
docker compose exec rails bundle install
|
||||
|
||||
# Restart a service
|
||||
docker compose restart rails
|
||||
|
||||
# Stop all services
|
||||
docker compose down
|
||||
|
||||
# Stop and remove volumes (reset database)
|
||||
docker compose down -v
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If there is an update to any of the following:
|
||||
- `dockerfile`
|
||||
- `gemfile`
|
||||
- `package.json`
|
||||
- schema change
|
||||
|
||||
Make sure to rebuild the containers and run `db:reset`.
|
||||
|
||||
```bash
|
||||
docker compose down
|
||||
docker compose build
|
||||
docker compose run --rm rails bundle exec rails db:reset
|
||||
docker compose up
|
||||
```
|
||||
|
||||
### Common Issues
|
||||
|
||||
<Accordion title="Container fails to start">
|
||||
**Solution**: Check service dependencies and logs:
|
||||
```bash
|
||||
# Check service status
|
||||
docker compose ps
|
||||
|
||||
# Check logs for specific service
|
||||
docker compose logs rails
|
||||
|
||||
# Restart problematic service
|
||||
docker compose restart rails
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Database connection refused">
|
||||
**Solution**: Ensure PostgreSQL container is healthy:
|
||||
```bash
|
||||
# Check postgres health
|
||||
docker compose exec postgres pg_isready
|
||||
|
||||
# Restart postgres if needed
|
||||
docker compose restart postgres
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Port already in use">
|
||||
**Solution**: Stop other services using the same ports:
|
||||
```bash
|
||||
# Check what's using port 3000
|
||||
lsof -i :3000
|
||||
|
||||
# Or change ports in docker-compose.yaml
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Out of disk space">
|
||||
**Solution**: Clean up Docker resources:
|
||||
```bash
|
||||
# Remove unused containers, networks, images
|
||||
docker system prune -f
|
||||
|
||||
# Remove volumes (WARNING: This deletes data)
|
||||
docker volume prune -f
|
||||
|
||||
# Remove everything (nuclear option)
|
||||
docker system prune -a --volumes
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Build fails">
|
||||
**Solution**: Clear Docker cache and rebuild:
|
||||
```bash
|
||||
# Clear build cache
|
||||
docker builder prune
|
||||
|
||||
# Rebuild without cache
|
||||
docker compose build --no-cache
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
## Getting Help
|
||||
|
||||
If you encounter Docker-specific issues:
|
||||
|
||||
- **Docker Documentation**: [https://docs.docker.com/](https://docs.docker.com/)
|
||||
- **Docker Compose Reference**: [https://docs.docker.com/compose/](https://docs.docker.com/compose/)
|
||||
- **Chatwoot Issues**: [GitHub Issues](https://github.com/chatwoot/chatwoot/issues)
|
||||
- **Community Support**: [Discord](https://discord.com/invite/cJXdrwS)
|
||||
|
||||
---
|
||||
|
||||
Your Docker development environment is now ready for Chatwoot development! 🐳
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
title: Environment Variables for Development
|
||||
description: Complete guide to environment variables for Chatwoot development and testing
|
||||
sidebarTitle: Environment Variables
|
||||
---
|
||||
|
||||
# Environment Variables for Development
|
||||
|
||||
This guide covers environment variables specifically for development and testing environments. For production environment variables, see the [Self-hosted Environment Variables](../../self-hosted/configuration/environment-variables) guide.
|
||||
|
||||
### Use letter opener instead of mailhog/SMTP
|
||||
|
||||
Set the following variable to open emails in letter opener instead of SMTP
|
||||
|
||||
```bash
|
||||
LETTER_OPENER=true
|
||||
```
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
@@ -0,0 +1,198 @@
|
||||
---
|
||||
title: Line App Integration Setup
|
||||
description: Setup Line app integration on your local machine for development
|
||||
sidebarTitle: Line Setup
|
||||
---
|
||||
|
||||
# Setup Line app integration on your local machine
|
||||
|
||||
Please follow the steps if you are trying to work with the Line integration on your local machine.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Line Developer Account
|
||||
- Access to [Line Developer Console](https://developers.line.biz/console)
|
||||
- Ngrok or similar tunneling service
|
||||
- Running Chatwoot development environment
|
||||
|
||||
## Setup Steps
|
||||
|
||||
### 1. Start Ngrok Server
|
||||
|
||||
Start a Ngrok server listening at port `3000` or the port you will be running the Chatwoot installation:
|
||||
|
||||
```bash
|
||||
# Install ngrok if you haven't already
|
||||
# Download from https://ngrok.com/download
|
||||
|
||||
# Start ngrok tunnel
|
||||
ngrok http 3000
|
||||
```
|
||||
|
||||
### 2. Update Environment Variables
|
||||
|
||||
Update the `.env` variable `FRONTEND_URL` in Chatwoot with the `https` version of the Ngrok URL:
|
||||
|
||||
```bash
|
||||
# In your .env file
|
||||
FRONTEND_URL=https://your-ngrok-subdomain.ngrok.io
|
||||
```
|
||||
|
||||
### 3. Configure Line Developer Console
|
||||
|
||||
1. **Access Line Developer Console**: Go to [Line Developer Console](https://developers.line.biz/console)
|
||||
2. **Create a Provider** (if you don't have one)
|
||||
3. **Create a New Channel** and select "Messaging API"
|
||||
4. **Configure Basic Settings**:
|
||||
- Channel name
|
||||
- Channel description
|
||||
- Category
|
||||
- Subcategory
|
||||
|
||||
### 4. Get Required Credentials
|
||||
|
||||
From the Line Developer Console under the "Messaging API" channel, collect the following values:
|
||||
|
||||
1. **Channel Name**
|
||||
2. **LINE Channel ID**
|
||||
3. **LINE Channel Secret**
|
||||
4. **LINE Channel Token**
|
||||
|
||||
### 5. Start Chatwoot Server
|
||||
|
||||
Start the Chatwoot server and create a new Line channel with the values obtained from Line Developer Console:
|
||||
|
||||
```bash
|
||||
# Start the development server
|
||||
make run
|
||||
# or
|
||||
foreman start -f Procfile.dev
|
||||
```
|
||||
|
||||
### 6. Create Line Channel in Chatwoot
|
||||
|
||||
1. **Access Chatwoot**: Go to your Chatwoot instance (http://localhost:3000)
|
||||
2. **Navigate to Settings** → **Inboxes** → **Add Inbox**
|
||||
3. **Select Line** as the channel type
|
||||
4. **Enter Line Credentials**:
|
||||
- Channel Name
|
||||
- LINE Channel ID
|
||||
- LINE Channel Secret
|
||||
- LINE Channel Token
|
||||
5. **Save Configuration**
|
||||
|
||||
## Configure Webhook in Line Developer Console
|
||||
|
||||
After creating the channel, Chatwoot will provide a webhook URL for the channel. You need to configure this webhook URL in the Line Developer Console:
|
||||
|
||||
### Steps to Configure Webhook
|
||||
|
||||
1. **Go to Line Developer Console** → Your Channel → **Messaging API**
|
||||
2. **Find Webhook Settings**
|
||||
3. **Set Webhook URL**: Use the URL provided by Chatwoot
|
||||
```
|
||||
https://your-ngrok-subdomain.ngrok.io/webhooks/line/your-channel-id
|
||||
```
|
||||
4. **Enable Webhook**: Toggle the webhook to "Enabled"
|
||||
5. **Verify Webhook**: Use the "Verify" button to test the connection
|
||||
|
||||
### Additional Line Settings
|
||||
|
||||
Configure these settings in the Line Developer Console:
|
||||
|
||||
- **Auto-reply messages**: Disable (so Chatwoot can handle responses)
|
||||
- **Greeting messages**: Optional
|
||||
- **Webhook redelivery**: Enable for reliability
|
||||
|
||||
## Testing the Integration
|
||||
|
||||
If the webhook is registered correctly with Line, your Ngrok server should receive events for new Line messages, and new conversations will be created in Chatwoot.
|
||||
|
||||
### Test Steps
|
||||
|
||||
1. **Add your Line bot as a friend** using the QR code or bot ID
|
||||
2. **Send a message** to your Line bot
|
||||
3. **Check Ngrok logs** to see if the webhook request is received
|
||||
4. **Check Chatwoot** to see if a new conversation is created
|
||||
5. **Reply from Chatwoot** to test bidirectional communication
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
<Accordion title="Webhook verification fails">
|
||||
**Problem**: Line webhook verification fails in Developer Console
|
||||
|
||||
**Solution**:
|
||||
- Ensure your Ngrok URL is accessible publicly
|
||||
- Check that `FRONTEND_URL` is set correctly in your `.env` file
|
||||
- Verify the webhook URL format is correct
|
||||
- Restart Chatwoot after updating environment variables
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Messages not appearing in Chatwoot">
|
||||
**Problem**: Line messages don't create conversations in Chatwoot
|
||||
|
||||
**Solution**:
|
||||
- Check Ngrok logs for incoming webhook requests
|
||||
- Verify webhook is enabled in Line Developer Console
|
||||
- Check Chatwoot logs for any error messages
|
||||
- Ensure all Line credentials are entered correctly
|
||||
- Verify the channel is enabled in Chatwoot
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="SSL/TLS errors">
|
||||
**Problem**: SSL certificate issues with webhook
|
||||
|
||||
**Solution**:
|
||||
- Use the `https` version of your Ngrok URL
|
||||
- Ensure Ngrok is running properly
|
||||
- Line requires HTTPS for webhook URLs
|
||||
- Try restarting Ngrok and updating the webhook
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Authentication errors">
|
||||
**Problem**: Line API authentication failures
|
||||
|
||||
**Solution**:
|
||||
- Verify Channel ID, Channel Secret, and Channel Token are correct
|
||||
- Check that the channel is published and not in development mode
|
||||
- Ensure the Messaging API is enabled for your channel
|
||||
- Regenerate Channel Token if necessary
|
||||
</Accordion>
|
||||
|
||||
## Line API Features
|
||||
|
||||
Line offers various features you can integrate:
|
||||
|
||||
- **Rich Messages**: Cards, carousels, quick replies
|
||||
- **Flex Messages**: Custom layouts
|
||||
- **LIFF (Line Frontend Framework)**: Web apps within Line
|
||||
- **Line Login**: User authentication
|
||||
|
||||
## Next Steps
|
||||
|
||||
After successful setup:
|
||||
|
||||
1. **Test message flow** between Line and Chatwoot
|
||||
2. **Configure agent assignments** for Line conversations
|
||||
3. **Set up automated responses** if needed
|
||||
4. **Explore rich message features** for enhanced user experience
|
||||
5. **Review webhook logs** for debugging
|
||||
|
||||
## Getting Help
|
||||
|
||||
If you encounter issues:
|
||||
|
||||
- **Check Logs**: Review both Chatwoot and Ngrok logs
|
||||
- **Line Developers Documentation**: [Official Line API Docs](https://developers.line.biz/en/docs/)
|
||||
- **Common Errors**: See [Common Errors](/contributing/project-setup/common-errors)
|
||||
- **Community Support**: [Discord](https://discord.com/invite/cJXdrwS)
|
||||
|
||||
## Useful Resources
|
||||
|
||||
- **Line Messaging API Documentation**: [https://developers.line.biz/en/docs/messaging-api/](https://developers.line.biz/en/docs/messaging-api/)
|
||||
- **Line Developer Console**: [https://developers.line.biz/console](https://developers.line.biz/console)
|
||||
- **Webhook Test Tool**: Available in Line Developer Console
|
||||
|
||||
---
|
||||
|
||||
Your Line integration is now ready for development and testing! 💬
|
||||
@@ -0,0 +1,314 @@
|
||||
---
|
||||
title: macOS Development Setup
|
||||
description: Complete guide to setting up your macOS development environment for Chatwoot contribution.
|
||||
sidebarTitle: macOS Setup
|
||||
---
|
||||
|
||||
# macOS Development Setup
|
||||
|
||||
This guide will help you set up your macOS development environment for contributing to Chatwoot. Open Terminal app and run the following commands.
|
||||
|
||||
## Installing the Standalone Command Line Tools
|
||||
|
||||
Open Terminal app and run:
|
||||
|
||||
```bash
|
||||
xcode-select --install
|
||||
```
|
||||
|
||||
This installs essential development tools including Git, GCC, and other command line utilities.
|
||||
|
||||
## Install Homebrew
|
||||
|
||||
Homebrew is the missing package manager for macOS:
|
||||
|
||||
```bash
|
||||
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
|
||||
```
|
||||
|
||||
After installation, add Homebrew to your PATH (if not automatically added):
|
||||
|
||||
```bash
|
||||
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
|
||||
eval "$(/opt/homebrew/bin/brew shellenv)"
|
||||
```
|
||||
|
||||
## Install Git
|
||||
|
||||
```bash
|
||||
brew update
|
||||
brew install git
|
||||
```
|
||||
|
||||
Configure Git with your information:
|
||||
|
||||
```bash
|
||||
git config --global user.name "Your Name"
|
||||
git config --global user.email "your.email@example.com"
|
||||
```
|
||||
|
||||
## Install Ruby Version Manager
|
||||
|
||||
Choose between RVM or rbenv for managing Ruby versions.
|
||||
|
||||
### Option 1: Install RVM (Recommended)
|
||||
|
||||
```bash
|
||||
curl -L https://get.rvm.io | bash -s stable
|
||||
source ~/.rvm/scripts/rvm
|
||||
```
|
||||
|
||||
### Option 2: Install rbenv (Alternative)
|
||||
|
||||
```bash
|
||||
brew install rbenv ruby-build
|
||||
echo 'eval "$(rbenv init -)"' >> ~/.zshrc
|
||||
source ~/.zshrc
|
||||
```
|
||||
|
||||
## Install Ruby
|
||||
|
||||
Chatwoot APIs are built on Ruby on Rails. You need to install Ruby 3.2.2.
|
||||
|
||||
### If using RVM:
|
||||
|
||||
```bash
|
||||
rvm install ruby-3.2.2
|
||||
rvm use 3.2.2 --default
|
||||
source ~/.rvm/scripts/rvm
|
||||
```
|
||||
|
||||
### If using rbenv:
|
||||
|
||||
```bash
|
||||
rbenv install 3.2.2
|
||||
rbenv global 3.2.2
|
||||
```
|
||||
|
||||
<Info>
|
||||
rbenv identifies the ruby version from `.ruby-version` file on the root of the project and loads it automatically.
|
||||
</Info>
|
||||
|
||||
Verify Ruby installation:
|
||||
|
||||
```bash
|
||||
ruby --version
|
||||
# Should output: ruby 3.2.2
|
||||
```
|
||||
|
||||
## Install Node.js
|
||||
|
||||
Chatwoot requires Node.js version 20:
|
||||
|
||||
```bash
|
||||
brew install node@20
|
||||
```
|
||||
|
||||
If you need to link Node.js 20:
|
||||
|
||||
```bash
|
||||
brew link node@20
|
||||
echo 'export PATH="/opt/homebrew/opt/node@20/bin:$PATH"' >> ~/.zshrc
|
||||
source ~/.zshrc
|
||||
```
|
||||
|
||||
Verify Node.js installation:
|
||||
|
||||
```bash
|
||||
node --version
|
||||
# Should output: v20.x.x
|
||||
```
|
||||
|
||||
## Install pnpm
|
||||
|
||||
We use `pnpm` as our package manager for better performance:
|
||||
|
||||
```bash
|
||||
brew install pnpm
|
||||
```
|
||||
|
||||
Verify pnpm installation:
|
||||
|
||||
```bash
|
||||
pnpm --version
|
||||
```
|
||||
|
||||
## Install PostgreSQL
|
||||
|
||||
The database used in Chatwoot is PostgreSQL.
|
||||
|
||||
### Option 1: PostgresApp (Recommended)
|
||||
|
||||
1. Download and install PostgresApp from [https://postgresapp.com](https://postgresapp.com)
|
||||
2. This is the easiest way to get started with PostgreSQL on macOS
|
||||
3. Follow the setup instructions on their website
|
||||
|
||||
### Option 2: Homebrew Installation
|
||||
|
||||
```bash
|
||||
brew install postgresql@14
|
||||
```
|
||||
|
||||
Start PostgreSQL service:
|
||||
|
||||
```bash
|
||||
brew services start postgresql@14
|
||||
```
|
||||
|
||||
Create a PostgreSQL user:
|
||||
|
||||
```bash
|
||||
createuser -s postgres
|
||||
```
|
||||
|
||||
Connect to PostgreSQL to verify installation:
|
||||
|
||||
```bash
|
||||
psql postgres
|
||||
# Type \q to exit
|
||||
```
|
||||
|
||||
## Install Redis Server
|
||||
|
||||
Chatwoot uses Redis server for agent assignments and reporting:
|
||||
|
||||
```bash
|
||||
brew install redis
|
||||
```
|
||||
|
||||
Start the Redis service:
|
||||
|
||||
```bash
|
||||
brew services start redis
|
||||
```
|
||||
|
||||
Verify Redis installation:
|
||||
|
||||
```bash
|
||||
redis-cli ping
|
||||
# Should output: PONG
|
||||
```
|
||||
|
||||
## Install ImageMagick
|
||||
|
||||
Chatwoot uses ImageMagick library to resize images for previews and thumbnails:
|
||||
|
||||
```bash
|
||||
brew install imagemagick
|
||||
```
|
||||
|
||||
Verify ImageMagick installation:
|
||||
|
||||
```bash
|
||||
convert --version
|
||||
```
|
||||
|
||||
## Install Additional Dependencies
|
||||
|
||||
Install other useful development tools:
|
||||
|
||||
```bash
|
||||
# Install Yarn (alternative to pnpm if needed)
|
||||
brew install yarn
|
||||
|
||||
# Install SQLite (for testing)
|
||||
brew install sqlite
|
||||
|
||||
# Install libvips (for image processing)
|
||||
brew install libvips
|
||||
```
|
||||
|
||||
## Install Docker (Optional)
|
||||
|
||||
For development and testing with containers:
|
||||
|
||||
```bash
|
||||
# Install Docker Desktop
|
||||
brew install --cask docker
|
||||
```
|
||||
|
||||
Or download Docker Desktop from [https://www.docker.com/products/docker-desktop/](https://www.docker.com/products/docker-desktop/).
|
||||
|
||||
## Environment Verification
|
||||
|
||||
Verify all installations are working:
|
||||
|
||||
```bash
|
||||
# Check versions
|
||||
ruby --version # Should be 3.2.2
|
||||
node --version # Should be v20.x.x
|
||||
pnpm --version # Should show pnpm version
|
||||
psql --version # Should show PostgreSQL version
|
||||
redis-cli --version # Should show Redis version
|
||||
convert --version # Should show ImageMagick version
|
||||
git --version # Should show Git version
|
||||
```
|
||||
|
||||
## Configure Shell Environment
|
||||
|
||||
Add useful aliases to your shell configuration file (`~/.zshrc` for Zsh):
|
||||
|
||||
```bash
|
||||
# Add to ~/.zshrc
|
||||
echo '# Chatwoot Development Aliases' >> ~/.zshrc
|
||||
echo 'alias cw-server="bundle exec rails server"' >> ~/.zshrc
|
||||
echo 'alias cw-console="bundle exec rails console"' >> ~/.zshrc
|
||||
echo 'alias cw-test="bundle exec rspec"' >> ~/.zshrc
|
||||
echo 'alias cw-migrate="bundle exec rails db:migrate"' >> ~/.zshrc
|
||||
|
||||
# Reload shell configuration
|
||||
source ~/.zshrc
|
||||
```
|
||||
## Troubleshooting Common Issues
|
||||
|
||||
<Accordion title="Command line tools installation fails">
|
||||
**Solution**: Update macOS to the latest version and try again. You can also download Xcode from the App Store.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Homebrew installation permission errors">
|
||||
**Solution**:
|
||||
```bash
|
||||
sudo chown -R $(whoami) /opt/homebrew
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Ruby installation fails with RVM">
|
||||
**Solution**:
|
||||
```bash
|
||||
# Install missing dependencies
|
||||
brew install openssl readline libyaml
|
||||
rvm reinstall 3.2.2 --with-openssl-dir=$(brew --prefix openssl)
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="PostgreSQL connection refused">
|
||||
**Solution**:
|
||||
```bash
|
||||
# Restart PostgreSQL
|
||||
brew services restart postgresql@14
|
||||
|
||||
# Check if it's running
|
||||
brew services list | grep postgresql
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="ImageMagick installation issues">
|
||||
**Solution**:
|
||||
```bash
|
||||
# If you encounter issues, try:
|
||||
brew uninstall imagemagick
|
||||
brew install imagemagick
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
## Getting Help
|
||||
|
||||
If you encounter issues:
|
||||
|
||||
- **Common Errors**: Check [Common Errors](/contributing/project-setup/common-errors)
|
||||
- **Discord Community**: Join our [Discord](https://discord.com/invite/cJXdrwS)
|
||||
- **GitHub Issues**: [Create an issue](https://github.com/chatwoot/chatwoot/issues)
|
||||
|
||||
---
|
||||
|
||||
Your macOS development environment is now ready for Chatwoot development! 🚀
|
||||
@@ -0,0 +1,115 @@
|
||||
---
|
||||
title: Make Commands Setup
|
||||
description: Speed up your local development workflow with Make commands for Chatwoot.
|
||||
sidebarTitle: Make Setup
|
||||
---
|
||||
|
||||
# Speed up your local development with Make
|
||||
|
||||
Speed up your local development workflow with make commands for Chatwoot.
|
||||
|
||||
## Clone the repo and cd to the Chatwoot directory
|
||||
|
||||
Clone the repository and navigate to the Chatwoot directory:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/chatwoot/chatwoot.git
|
||||
cd chatwoot
|
||||
```
|
||||
|
||||
## Install Ruby & JavaScript dependencies
|
||||
|
||||
Install Ruby and JavaScript dependencies using the following command. This command runs Bundler and pnpm:
|
||||
|
||||
```bash
|
||||
make burn
|
||||
```
|
||||
|
||||
## Run database migrations
|
||||
|
||||
Apply necessary database schema changes to your development environment by running the following command:
|
||||
|
||||
```bash
|
||||
make db
|
||||
```
|
||||
|
||||
## Run database seed
|
||||
|
||||
Load some seed data to your development environment for testing by running the following command:
|
||||
|
||||
```bash
|
||||
make db_seed
|
||||
```
|
||||
|
||||
## Run dev server using Overmind
|
||||
|
||||
Start the development server using Overmind, a process manager that can run multiple processes concurrently:
|
||||
|
||||
```bash
|
||||
make run
|
||||
```
|
||||
|
||||
## Force run if ./.overmind.sock file exists
|
||||
|
||||
If the `make run` command fails due to the existence of a `./.overmind.sock` file, you can try using the following command:
|
||||
|
||||
```bash
|
||||
make force_run
|
||||
```
|
||||
|
||||
## Debug - Attach to backend via Overmind tmux session
|
||||
|
||||
For debugging purposes, you can attach to the backend via the Overmind tmux session using the following command:
|
||||
|
||||
```bash
|
||||
make debug
|
||||
```
|
||||
|
||||
## Debug worker
|
||||
|
||||
To debug the worker, use the following command:
|
||||
|
||||
```bash
|
||||
make debug_worker
|
||||
```
|
||||
|
||||
## Get Rails console
|
||||
|
||||
Access the Rails console, which provides an interactive environment for interacting with the Chatwoot application:
|
||||
|
||||
```bash
|
||||
make console
|
||||
```
|
||||
|
||||
## Build Docker image
|
||||
|
||||
Build the Docker image for the Chatwoot project:
|
||||
|
||||
```bash
|
||||
make docker
|
||||
```
|
||||
|
||||
## Workflow after pulling in the latest changes from `develop`
|
||||
|
||||
To update your development environment after pulling the latest changes from the `develop` branch, follow these steps:
|
||||
|
||||
```bash
|
||||
make burn # Install dependencies
|
||||
|
||||
make db # Run migrations
|
||||
|
||||
make run # Start the server
|
||||
```
|
||||
|
||||
## Getting Help
|
||||
|
||||
If you encounter issues with Make commands:
|
||||
|
||||
- **Makefile Documentation**: Check the project's `Makefile` for available commands
|
||||
- **Overmind Documentation**: [https://github.com/DarthSim/overmind](https://github.com/DarthSim/overmind)
|
||||
- **Chatwoot Issues**: [GitHub Issues](https://github.com/chatwoot/chatwoot/issues)
|
||||
- **Community Support**: [Discord](https://discord.com/invite/cJXdrwS)
|
||||
|
||||
---
|
||||
|
||||
Your Make-based development workflow is now ready for efficient Chatwoot development! 🚀
|
||||
@@ -0,0 +1,242 @@
|
||||
---
|
||||
title: Mobile App Development Setup
|
||||
description: Setup guide for Chatwoot mobile app development
|
||||
sidebarTitle: Mobile App Setup
|
||||
---
|
||||
|
||||
# Setup guide for mobile app
|
||||
|
||||
Complete guide to setting up the Chatwoot mobile app for development and contribution.
|
||||
|
||||
## Installation and setup
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Before starting, ensure you have the following installed:
|
||||
|
||||
- [Node.js](https://nodejs.org/en/download/) (Latest LTS version)
|
||||
- [React Native CLI](https://reactnative.dev/docs/environment-setup)
|
||||
- [Expo CLI](https://docs.expo.dev/get-started/installation/)
|
||||
- [Expo Account](https://expo.dev/signup)
|
||||
|
||||
<Note>
|
||||
To learn more about the most up-to-date instructions, please refer to the guide available [here](https://docs.expo.dev/get-started/set-up-your-environment/).
|
||||
</Note>
|
||||
|
||||
### Clone the repository
|
||||
|
||||
```bash
|
||||
git clone git@github.com:chatwoot/chatwoot-mobile-app.git
|
||||
cd chatwoot-mobile-app
|
||||
```
|
||||
|
||||
### Install dependencies
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### Install Expo CLI
|
||||
|
||||
```bash
|
||||
pnpm install -g expo-cli
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Create your environment configuration file:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Configure the following environment variables:
|
||||
|
||||
| Name | Description | Default Value | Required |
|
||||
| ---------------------------------------- | ------------------------------------------- | ------------------------ | -------- |
|
||||
| EXPO_PUBLIC_CHATWOOT_WEBSITE_TOKEN | Web widget token for in-app support | - | No |
|
||||
| EXPO_PUBLIC_CHATWOOT_BASE_URL | Self-hosted installation URL | https://app.chatwoot.com | Yes |
|
||||
| EXPO_PUBLIC_JUNE_SDK_KEY | June analytics SDK key | - | No |
|
||||
| EXPO_PUBLIC_MINIMUM_CHATWOOT_VERSION | Minimum supported Chatwoot version | - | Yes |
|
||||
| EXPO_PUBLIC_SENTRY_DSN | Sentry DSN URL for error reporting | - | No |
|
||||
| EXPO_PUBLIC_PROJECT_ID | Expo project identifier | - | Yes |
|
||||
| EXPO_PUBLIC_APP_SLUG | Application slug for Expo | - | Yes |
|
||||
| EXPO_PUBLIC_SENTRY_PROJECT_NAME | Project name in Sentry | - | No |
|
||||
| EXPO_PUBLIC_SENTRY_ORG_NAME | Organization name in Sentry | - | No |
|
||||
| EXPO_PUBLIC_IOS_GOOGLE_SERVICES_FILE | Path to iOS Google Services config file | - | No |
|
||||
| EXPO_PUBLIC_ANDROID_GOOGLE_SERVICES_FILE | Path to Android Google Services config file | - | No |
|
||||
| EXPO_APPLE_ID | Apple Developer account ID | - | No |
|
||||
| EXPO_APPLE_TEAM_ID | Apple Developer team ID | - | No |
|
||||
| EXPO_STORYBOOK_ENABLED | Enable/disable Storybook | false | No |
|
||||
|
||||
## Generate the native code
|
||||
|
||||
```bash
|
||||
pnpm generate
|
||||
```
|
||||
|
||||
This command generates native Android and iOS directories using [Prebuild](https://docs.expo.dev/workflow/continuous-native-generation/).
|
||||
|
||||
<Warning>
|
||||
You need to run pre-build if you add a new native dependency to your project or change the project configuration in Expo app config (app.config.ts).
|
||||
</Warning>
|
||||
|
||||
## How to run the app
|
||||
|
||||
Connect your iPhone/Android device and run the following command to install the app on your device.
|
||||
|
||||
### iOS Development
|
||||
|
||||
```bash
|
||||
pnpm run:ios
|
||||
```
|
||||
|
||||
### Android Development
|
||||
|
||||
```bash
|
||||
pnpm run:android
|
||||
```
|
||||
|
||||
## Package Installation
|
||||
|
||||
<Warning>
|
||||
Please always install packages using the command `npx expo install package-name` instead of `pnpm install package-name`.
|
||||
</Warning>
|
||||
|
||||
This is crucial for native dependencies because Expo will automatically install the correct compatible version, while pnpm/yarn/npm may install the latest version, which may not be compatible.
|
||||
|
||||
```bash
|
||||
# Correct way to install packages
|
||||
npx expo install package-name
|
||||
|
||||
# Incorrect way (may cause compatibility issues)
|
||||
pnpm install package-name
|
||||
```
|
||||
|
||||
## Push notification
|
||||
|
||||
If you are using the community edition of Chatwoot, you can now use the [official mobile app](https://www.chatwoot.com/mobile-apps) with push notifications without any additional configuration.
|
||||
|
||||
For more details, please refer to the [push notification documentation](https://www.chatwoot.com/hc/handbook/articles/1687935909-push-notification).
|
||||
|
||||
## Build & Submit using EAS
|
||||
|
||||
We use Expo Application Services (EAS) for building, deploying, and submitting the app to app stores. EAS Build and Submit is available to anyone with an Expo account, regardless of whether you pay for EAS or use our Free plan.
|
||||
|
||||
You can sign up at [Expo EAS](https://expo.dev/eas).
|
||||
|
||||
### Build the app
|
||||
|
||||
#### iOS Build
|
||||
|
||||
```bash
|
||||
pnpm run build:ios:local
|
||||
```
|
||||
|
||||
#### Android Build
|
||||
|
||||
```bash
|
||||
pnpm run build:android:local
|
||||
```
|
||||
|
||||
### Submit the app
|
||||
|
||||
#### iOS Submission
|
||||
|
||||
```bash
|
||||
pnpm submit:ios
|
||||
```
|
||||
|
||||
#### Android Submission
|
||||
|
||||
```bash
|
||||
pnpm submit:android
|
||||
```
|
||||
|
||||
When you run the above command, you will be prompted to provide a path to a local app binary file. Please select the file that you built in the previous step:
|
||||
|
||||
- **iOS**: `.ipa` file
|
||||
- **Android**: `.aab` file
|
||||
|
||||
<Note>
|
||||
It may take a while to complete the submission process. You will see the status of the submission on your terminal.
|
||||
</Note>
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
<Accordion title="Metro bundler issues">
|
||||
**Problem**: Metro bundler fails to start or bundle
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
# Clear cache and restart
|
||||
pnpm clear
|
||||
pnpm start --reset-cache
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="iOS build fails">
|
||||
**Problem**: iOS build or simulator issues
|
||||
|
||||
**Solution**:
|
||||
- Ensure Xcode is properly installed
|
||||
- Check iOS simulator version compatibility
|
||||
- Clear derived data in Xcode
|
||||
- Restart Metro bundler
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Android build fails">
|
||||
**Problem**: Android build or emulator issues
|
||||
|
||||
**Solution**:
|
||||
- Verify Android Studio setup
|
||||
- Check SDK versions and build tools
|
||||
- Ensure emulator is running
|
||||
- Clear Gradle cache
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Expo CLI issues">
|
||||
**Problem**: Expo commands fail
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
# Update Expo CLI
|
||||
npm install -g @expo/cli@latest
|
||||
|
||||
# Login to Expo
|
||||
expo login
|
||||
|
||||
# Clear Expo cache
|
||||
expo r -c
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
## Contributing Guidelines
|
||||
|
||||
When contributing to the mobile app:
|
||||
|
||||
1. **Follow coding standards**: Use ESLint and Prettier configurations
|
||||
2. **Write tests**: Include unit tests for new features
|
||||
3. **Test on both platforms**: Ensure iOS and Android compatibility
|
||||
4. **Update documentation**: Document new features and changes
|
||||
5. **Check performance**: Monitor app performance impact
|
||||
|
||||
## Getting Help
|
||||
|
||||
If you encounter issues:
|
||||
|
||||
- **Expo Documentation**: [Official Expo Docs](https://docs.expo.dev/)
|
||||
- **React Native Documentation**: [React Native Docs](https://reactnative.dev/docs/getting-started)
|
||||
- **GitHub Issues**: [Mobile App Issues](https://github.com/chatwoot/chatwoot-mobile-app/issues)
|
||||
- **Community Support**: [Discord](https://discord.com/invite/cJXdrwS)
|
||||
|
||||
## Useful Resources
|
||||
|
||||
- **Expo Development**: [https://docs.expo.dev/](https://docs.expo.dev/)
|
||||
- **React Native**: [https://reactnative.dev/](https://reactnative.dev/)
|
||||
- **EAS Build**: [https://docs.expo.dev/build/introduction/](https://docs.expo.dev/build/introduction/)
|
||||
- **EAS Submit**: [https://docs.expo.dev/submit/introduction/](https://docs.expo.dev/submit/introduction/)
|
||||
|
||||
---
|
||||
|
||||
Your Chatwoot mobile app development environment is now ready! 📱
|
||||
@@ -0,0 +1,181 @@
|
||||
---
|
||||
title: Project Setup Guide
|
||||
description: Complete guide to setting up and running Chatwoot in development mode
|
||||
sidebarTitle: Setup Guide
|
||||
---
|
||||
|
||||
# Project Setup
|
||||
|
||||
This guide will help you to setup and run Chatwoot in development mode. Please make sure you have completed the environment setup.
|
||||
|
||||
## Clone the repo
|
||||
|
||||
```bash
|
||||
# change location to the path you want chatwoot to be installed
|
||||
cd ~
|
||||
|
||||
# clone the repo and cd to chatwoot dir
|
||||
git clone https://github.com/chatwoot/chatwoot.git
|
||||
cd chatwoot
|
||||
```
|
||||
|
||||
## Install Ruby & Javascript dependencies
|
||||
|
||||
Use the following command to run `bundle && pnpm install` to install ruby and Javascript dependencies.
|
||||
|
||||
```bash
|
||||
make burn
|
||||
```
|
||||
|
||||
This would install all required dependencies for Chatwoot application.
|
||||
|
||||
<Warning>
|
||||
If you face issue with pg gem, please refer to [Common Errors](/contributing/project-setup/common-errors#pg-gem-installation-error)
|
||||
</Warning>
|
||||
|
||||
## Setup environment variables
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Please refer to [environment-variables](/contributing/project-setup/environment-variables) to read on setting environment variables.
|
||||
|
||||
## Setup rails server
|
||||
|
||||
```bash
|
||||
# run db migrations
|
||||
make db
|
||||
# fireup the server
|
||||
foreman start -f Procfile.dev
|
||||
```
|
||||
|
||||
<Note>
|
||||
If you have overmind installed, use `make run` to run the server.
|
||||
</Note>
|
||||
|
||||
## Login with credentials
|
||||
|
||||
```bash
|
||||
http://localhost:3000
|
||||
user name: john@acme.inc
|
||||
password: Password1!
|
||||
```
|
||||
|
||||
## Testing chat widget in your local environment
|
||||
|
||||
When running Chatwoot in development environment, the chat widget can be accessed under the following URL.
|
||||
|
||||
```
|
||||
http://localhost:3000/widget_tests
|
||||
```
|
||||
|
||||
You can also test the `setUser` method by using
|
||||
|
||||
```
|
||||
http://localhost:3000/widget_tests?setUser=true
|
||||
```
|
||||
|
||||
## Docker for development
|
||||
|
||||
<Note>
|
||||
Follow this section only if you are trying to setup Chatwoot via docker. Else skip this.
|
||||
</Note>
|
||||
|
||||
The first time you start your development environment run the following two commands:
|
||||
|
||||
```bash
|
||||
# build base image first
|
||||
docker compose build base
|
||||
|
||||
# build the server and worker
|
||||
docker compose build
|
||||
|
||||
# prepare the database
|
||||
docker compose exec rails bundle exec rails db:chatwoot_prepare
|
||||
|
||||
# docker compose up
|
||||
```
|
||||
|
||||
Then browse http://localhost:3000
|
||||
|
||||
```bash
|
||||
# To stop your environment use Control+C (on Mac) CTRL+C (on Win) or
|
||||
docker compose down
|
||||
# start the services
|
||||
docker compose up
|
||||
```
|
||||
|
||||
When you change the service's Dockerfile or the contents of the build directory, run stop then build. (For example after modifying package.json or Gemfile)
|
||||
|
||||
```bash
|
||||
docker compose stop
|
||||
docker compose build
|
||||
```
|
||||
|
||||
The docker-compose environment consists of:
|
||||
- chatwoot server
|
||||
- postgres
|
||||
- redis
|
||||
- webpacker-dev-server
|
||||
|
||||
If in case you encounter a seeding issue or you want reset the database you can do it using the following command:
|
||||
|
||||
```bash
|
||||
docker compose run --rm rails bundle exec rake db:reset
|
||||
```
|
||||
|
||||
This command essentially runs postgres and redis containers and then run the rake command inside the chatwoot server container.
|
||||
|
||||
## Running Cypress Tests
|
||||
|
||||
Refer the docs to learn how to write cypress specs:
|
||||
- https://github.com/shakacode/cypress-on-rails
|
||||
- https://docs.cypress.io/guides/overview/why-cypress.html
|
||||
|
||||
```bash
|
||||
# in terminal tab1
|
||||
overmind start -f Procfile.test
|
||||
# in terminal tab2
|
||||
pnpm cypress open --project ./test
|
||||
```
|
||||
|
||||
## Debugging Docker for production
|
||||
|
||||
You can use our official Docker image from [https://hub.docker.com/r/chatwoot/chatwoot](https://hub.docker.com/r/chatwoot/chatwoot)
|
||||
|
||||
```bash
|
||||
docker pull chatwoot/chatwoot
|
||||
```
|
||||
|
||||
You can create an image yourselves by running the following command on the root directory.
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.production.yaml build
|
||||
```
|
||||
|
||||
This will build the image which you can deploy in Kubernetes (GCP, Openshift, AWS, Azure or anywhere), Amazon ECS or Docker Swarm. You can tag this image and push this image to docker registry of your choice.
|
||||
|
||||
Remember to make the required environment variables available during the deployment.
|
||||
|
||||
## Next Steps
|
||||
|
||||
After completing this setup:
|
||||
|
||||
1. **Verify Installation**: Access http://localhost:3000 and log in with the provided credentials
|
||||
2. **Explore the Code**: Start making changes and see them reflected in your development environment
|
||||
3. **Run Tests**: Execute the test suite to ensure everything works correctly
|
||||
4. **Check Troubleshooting**: If you encounter issues, refer to [Common Errors](/contributing/project-setup/common-errors)
|
||||
|
||||
## Getting Help
|
||||
|
||||
If you encounter issues during setup:
|
||||
|
||||
- **Common Errors**: Check [Common Errors](/contributing/project-setup/common-errors)
|
||||
- **Environment Variables**: See [Environment Variables](/contributing/project-setup/environment-variables)
|
||||
- **Discord Community**: Join our [Discord](https://discord.com/invite/cJXdrwS)
|
||||
- **GitHub Issues**: [Create an issue](https://github.com/chatwoot/chatwoot/issues)
|
||||
|
||||
---
|
||||
|
||||
Your Chatwoot development environment is now ready for contribution! 🚀
|
||||
@@ -0,0 +1,129 @@
|
||||
---
|
||||
title: Telegram App Integration Setup
|
||||
description: Setup Telegram app integration on your local machine for development
|
||||
sidebarTitle: Telegram Setup
|
||||
---
|
||||
|
||||
# Setup Telegram app integration on your local machine
|
||||
|
||||
Please follow the steps if you are trying to work with the Telegram integration on your local machine.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Telegram Bot Token from [BotFather](https://t.me/botfather)
|
||||
- Ngrok or similar tunneling service
|
||||
- Running Chatwoot development environment
|
||||
|
||||
## Setup Steps
|
||||
|
||||
### 1. Start Ngrok Server
|
||||
|
||||
Start a Ngrok server listening at port `3000` or the port you will be running the Chatwoot installation:
|
||||
|
||||
```bash
|
||||
# Install ngrok if you haven't already
|
||||
# Download from https://ngrok.com/download
|
||||
|
||||
# Start ngrok tunnel
|
||||
ngrok http 3000
|
||||
```
|
||||
|
||||
### 2. Update Environment Variables
|
||||
|
||||
Update the `.env` variable `FRONTEND_URL` in Chatwoot with the `https` version of the Ngrok URL:
|
||||
|
||||
```bash
|
||||
# In your .env file
|
||||
FRONTEND_URL=https://your-ngrok-subdomain.ngrok.io
|
||||
```
|
||||
|
||||
### 3. Start Chatwoot Server
|
||||
|
||||
Start the Chatwoot server and create a new Telegram channel with the token obtained from Telegram BotFather.
|
||||
|
||||
```bash
|
||||
# Start the development server
|
||||
make run
|
||||
# or
|
||||
foreman start -f Procfile.dev
|
||||
```
|
||||
|
||||
### 4. Create Telegram Channel
|
||||
|
||||
1. **Access Chatwoot**: Go to your Chatwoot instance (http://localhost:3000)
|
||||
2. **Navigate to Settings** → **Inboxes** → **Add Inbox**
|
||||
3. **Select Telegram** as the channel type
|
||||
4. **Enter Bot Token**: Paste the token you received from BotFather
|
||||
5. **Configure Channel**: Set up the channel name and other settings
|
||||
|
||||
## Verify Webhook Registration
|
||||
|
||||
While creating the channel, Chatwoot should have registered a webhook callback URL in Telegram for your Bot. You can verify whether this URL registration was done successfully by calling the Telegram API:
|
||||
|
||||
```bash
|
||||
GET https://api.telegram.org/bot{your_bot_token}/getWebhookInfo
|
||||
```
|
||||
|
||||
## Testing the Integration
|
||||
|
||||
If the webhook is registered correctly with Telegram, your Ngrok server should receive events for new Telegram messages, and new conversations will be created in Chatwoot.
|
||||
|
||||
### Test Steps
|
||||
|
||||
1. **Send a message** to your Telegram bot
|
||||
2. **Check Ngrok logs** to see if the webhook request is received
|
||||
3. **Check Chatwoot** to see if a new conversation is created
|
||||
4. **Reply from Chatwoot** to test bidirectional communication
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
<Accordion title="Webhook not registered">
|
||||
**Problem**: Telegram webhook registration fails
|
||||
|
||||
**Solution**:
|
||||
- Ensure your Ngrok URL is accessible publicly
|
||||
- Check that `FRONTEND_URL` is set correctly in your `.env` file
|
||||
- Verify the bot token is correct
|
||||
- Restart Chatwoot after updating environment variables
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Messages not appearing in Chatwoot">
|
||||
**Problem**: Telegram messages don't create conversations in Chatwoot
|
||||
|
||||
**Solution**:
|
||||
- Check Ngrok logs for incoming webhook requests
|
||||
- Verify the webhook URL in Telegram using the API call above
|
||||
- Check Chatwoot logs for any error messages
|
||||
- Ensure the channel is properly configured and enabled
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="SSL/TLS errors">
|
||||
**Problem**: SSL certificate issues with webhook
|
||||
|
||||
**Solution**:
|
||||
- Use the `https` version of your Ngrok URL
|
||||
- Ensure Ngrok is running properly
|
||||
- Try restarting Ngrok and updating the webhook
|
||||
</Accordion>
|
||||
|
||||
## Next Steps
|
||||
|
||||
After successful setup:
|
||||
|
||||
1. **Test message flow** between Telegram and Chatwoot
|
||||
2. **Configure agent assignments** for Telegram conversations
|
||||
3. **Set up automated responses** if needed
|
||||
4. **Review webhook logs** for debugging
|
||||
|
||||
## Getting Help
|
||||
|
||||
If you encounter issues:
|
||||
|
||||
- **Check Logs**: Review both Chatwoot and Ngrok logs
|
||||
- **Telegram Bot API**: [Official Documentation](https://core.telegram.org/bots/api)
|
||||
- **Common Errors**: See [Common Errors](/contributing/project-setup/common-errors)
|
||||
- **Community Support**: [Discord](https://discord.com/invite/cJXdrwS)
|
||||
|
||||
---
|
||||
|
||||
Your Telegram integration is now ready for development and testing! 📱
|
||||
@@ -0,0 +1,246 @@
|
||||
---
|
||||
title: Ubuntu Development Setup
|
||||
description: Complete guide to setting up your Ubuntu development environment for Chatwoot contribution.
|
||||
sidebarTitle: Ubuntu Setup
|
||||
---
|
||||
|
||||
# Ubuntu Development Setup
|
||||
|
||||
This guide will help you set up your Ubuntu development environment for contributing to Chatwoot. These instructions work for Ubuntu 20.04, 22.04, and newer versions.
|
||||
|
||||
## Update System Packages
|
||||
|
||||
First, update your system packages to ensure you have the latest security updates:
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt upgrade -y
|
||||
```
|
||||
|
||||
## Install Essential Build Tools
|
||||
|
||||
Install fundamental development tools and dependencies:
|
||||
|
||||
```bash
|
||||
sudo apt install -y curl wget gnupg2 software-properties-common apt-transport-https ca-certificates build-essential libssl-dev libreadline-dev zlib1g-dev libyaml-dev libxml2-dev libxslt-dev
|
||||
```
|
||||
|
||||
## Install Git
|
||||
|
||||
Install Git for version control:
|
||||
|
||||
```bash
|
||||
sudo apt install -y git
|
||||
```
|
||||
|
||||
Configure Git with your information:
|
||||
|
||||
```bash
|
||||
git config --global user.name "Your Name"
|
||||
git config --global user.email "your.email@example.com"
|
||||
```
|
||||
|
||||
Verify Git installation:
|
||||
|
||||
```bash
|
||||
git --version
|
||||
```
|
||||
|
||||
## Install Ruby Version Manager (RVM)
|
||||
|
||||
Install RVM to manage Ruby versions:
|
||||
|
||||
```bash
|
||||
# Install GPG keys
|
||||
curl -sSL https://rvm.io/mpapis.asc | gpg --import -
|
||||
curl -sSL https://rvm.io/pkuczynski.asc | gpg --import -
|
||||
|
||||
# Install RVM
|
||||
curl -L https://get.rvm.io | bash -s stable
|
||||
|
||||
# Load RVM into current shell
|
||||
source ~/.rvm/scripts/rvm
|
||||
```
|
||||
|
||||
Add RVM to your shell profile:
|
||||
|
||||
```bash
|
||||
echo 'source ~/.rvm/scripts/rvm' >> ~/.bashrc
|
||||
source ~/.bashrc
|
||||
```
|
||||
|
||||
## Install Ruby
|
||||
|
||||
Install Ruby 3.2.2 using RVM:
|
||||
|
||||
```bash
|
||||
# Install Ruby 3.2.2
|
||||
rvm install ruby-3.2.2
|
||||
|
||||
# Set as default Ruby version
|
||||
rvm use 3.2.2 --default
|
||||
|
||||
# Verify installation
|
||||
ruby --version
|
||||
# Should output: ruby 3.2.2
|
||||
```
|
||||
|
||||
## Install Node.js
|
||||
|
||||
Install Node.js 20 using NodeSource repository:
|
||||
|
||||
```bash
|
||||
# Add NodeSource repository
|
||||
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
|
||||
|
||||
# Install Node.js
|
||||
sudo apt install -y nodejs
|
||||
|
||||
# Verify installation
|
||||
node --version
|
||||
# Should output: v20.x.x
|
||||
|
||||
npm --version
|
||||
```
|
||||
|
||||
## Install pnpm
|
||||
|
||||
Install pnpm package manager:
|
||||
|
||||
```bash
|
||||
# Install pnpm globally
|
||||
npm install -g pnpm
|
||||
|
||||
# Verify installation
|
||||
pnpm --version
|
||||
```
|
||||
|
||||
## Install PostgreSQL
|
||||
|
||||
Install PostgreSQL database server:
|
||||
|
||||
```bash
|
||||
# Install PostgreSQL
|
||||
sudo apt install -y postgresql postgresql-contrib libpq-dev
|
||||
|
||||
# Start and enable PostgreSQL service
|
||||
sudo systemctl start postgresql
|
||||
sudo systemctl enable postgresql
|
||||
```
|
||||
|
||||
Configure PostgreSQL:
|
||||
|
||||
```bash
|
||||
# Switch to postgres user and create a superuser
|
||||
sudo -u postgres createuser --superuser $USER
|
||||
|
||||
# Set password for your user
|
||||
sudo -u postgres psql -c "ALTER USER $USER PASSWORD 'password';"
|
||||
|
||||
# Create a database for your user
|
||||
sudo -u postgres createdb $USER
|
||||
```
|
||||
|
||||
Verify PostgreSQL installation:
|
||||
|
||||
```bash
|
||||
psql --version
|
||||
psql -c "SELECT version();"
|
||||
```
|
||||
|
||||
## Install Redis
|
||||
|
||||
Install Redis server for background job processing:
|
||||
|
||||
```bash
|
||||
# Install Redis
|
||||
sudo apt install -y redis-server
|
||||
|
||||
# Start and enable Redis service
|
||||
sudo systemctl start redis-server
|
||||
sudo systemctl enable redis-server
|
||||
```
|
||||
|
||||
Verify Redis installation:
|
||||
|
||||
```bash
|
||||
redis-cli ping
|
||||
# Should output: PONG
|
||||
```
|
||||
|
||||
## Install ImageMagick
|
||||
|
||||
Install ImageMagick for image processing:
|
||||
|
||||
```bash
|
||||
sudo apt install -y imagemagick libmagickwand-dev
|
||||
```
|
||||
|
||||
Verify ImageMagick installation:
|
||||
|
||||
```bash
|
||||
convert --version
|
||||
```
|
||||
|
||||
## Troubleshooting Common Issues
|
||||
|
||||
<Accordion title="Ruby installation fails">
|
||||
**Solution**: Install missing dependencies:
|
||||
```bash
|
||||
sudo apt install -y autoconf bison build-essential libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev libgdbm6 libgdbm-dev libdb-dev
|
||||
rvm reinstall 3.2.2
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="PostgreSQL authentication fails">
|
||||
**Solution**: Configure peer authentication:
|
||||
```bash
|
||||
sudo -u postgres psql
|
||||
ALTER USER postgres PASSWORD 'your_password';
|
||||
\q
|
||||
|
||||
# Edit pg_hba.conf
|
||||
sudo nano /etc/postgresql/*/main/pg_hba.conf
|
||||
# Change 'peer' to 'md5' for local connections
|
||||
sudo systemctl restart postgresql
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Permission denied for /usr/local">
|
||||
**Solution**: Fix ownership:
|
||||
```bash
|
||||
sudo chown -R $USER:$USER /usr/local
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Node.js installation issues">
|
||||
**Solution**: Use Node Version Manager (nvm):
|
||||
```bash
|
||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
|
||||
source ~/.bashrc
|
||||
nvm install 20
|
||||
nvm use 20
|
||||
nvm alias default 20
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="ImageMagick policy errors">
|
||||
**Solution**: Update ImageMagick policy:
|
||||
```bash
|
||||
sudo nano /etc/ImageMagick-6/policy.xml
|
||||
# Comment out or modify restrictive policies
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
## Getting Help
|
||||
|
||||
If you encounter issues:
|
||||
|
||||
- **Common Errors**: Check [Common Errors](/contributing/project-setup/common-errors)
|
||||
- **Discord Community**: Join our [Discord](https://discord.com/invite/cJXdrwS)
|
||||
- **GitHub Issues**: [Create an issue](https://github.com/chatwoot/chatwoot/issues)
|
||||
- **Ubuntu Community**: [Ubuntu Forums](https://ubuntuforums.org/)
|
||||
|
||||
---
|
||||
|
||||
Your Ubuntu development environment is now ready for Chatwoot development! 🐧
|
||||
@@ -0,0 +1,320 @@
|
||||
---
|
||||
title: Windows Development Setup
|
||||
description: Complete guide to setting up your Windows development environment for Chatwoot contribution using WSL2.
|
||||
sidebarTitle: Windows Setup
|
||||
---
|
||||
|
||||
# Windows Development Setup
|
||||
|
||||
This guide will walk you through setting up your Windows development environment for contributing to Chatwoot. We'll use Windows Subsystem for Linux 2 (WSL2) which provides the best development experience on Windows.
|
||||
|
||||
## Requirements
|
||||
|
||||
You need to install the Windows Subsystem for Linux 2 (WSL2) on your Windows machine.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Windows 10 version 2004 and higher (Build 19041 and higher) or Windows 11
|
||||
- Administrator privileges on your Windows machine
|
||||
|
||||
## Step 1: Enable Developer Mode
|
||||
|
||||
The first step is to enable "Developer mode" in Windows. You can do this by opening up Settings and navigating to "Update & Security". In there, choose the tab on the left that reads "For Developers". Turn the "Developer mode" toggle on to enable it.
|
||||
|
||||
<img src="/contributing/project-setup/img/developer-mode.jpg" width="500" alt="Enable Developer Mode" />
|
||||
|
||||
## Step 2: Enable Windows Subsystem for Linux
|
||||
|
||||
Next you have to enable the Windows Subsystem for Linux. Open the "Control Panel" and go to "Programs and Features". Click on the link on the left "Turn Windows features on or off". Look for the "Windows Subsystem for Linux" option and select the checkbox next to it.
|
||||
|
||||
<img src="/contributing/project-setup/img/enable-wsl.jpg" width="500" alt="Enable WSL" />
|
||||
|
||||
You'll also need to enable "Virtual Machine Platform" for WSL2. Make sure both checkboxes are selected:
|
||||
- ✅ Windows Subsystem for Linux
|
||||
- ✅ Virtual Machine Platform
|
||||
|
||||
After enabling these features, restart your computer.
|
||||
|
||||
## Step 3: Install WSL2 and Ubuntu
|
||||
|
||||
### Option 1: Using Microsoft Store (Recommended)
|
||||
|
||||
1. **Open Microsoft Store** and search for "Ubuntu"
|
||||
2. **Install Ubuntu 22.04 LTS** (or latest LTS version)
|
||||
3. **Launch Ubuntu** from the Start Menu
|
||||
|
||||
### Option 2: Using Command Line
|
||||
|
||||
Open PowerShell as Administrator and run:
|
||||
|
||||
```powershell
|
||||
# Install WSL2 with Ubuntu
|
||||
wsl --install -d Ubuntu-22.04
|
||||
|
||||
# Set WSL2 as default version
|
||||
wsl --set-default-version 2
|
||||
```
|
||||
|
||||
## Step 4: Initial Ubuntu Setup
|
||||
|
||||
When you first launch Ubuntu, you'll be prompted to create a user account:
|
||||
|
||||
```bash
|
||||
# Create a username and password when prompted
|
||||
# This will be your Linux user account
|
||||
```
|
||||
|
||||
Update the system packages:
|
||||
|
||||
```bash
|
||||
sudo apt update && sudo apt upgrade -y
|
||||
```
|
||||
|
||||
## Step 5: Install Core Dependencies
|
||||
|
||||
You need core Linux dependencies installed in order to install Ruby and other tools.
|
||||
|
||||
```bash
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y git-core curl zlib1g-dev build-essential libssl-dev libreadline-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt1-dev libcurl4-openssl-dev software-properties-common libffi-dev
|
||||
```
|
||||
|
||||
## Installing RVM & Ruby
|
||||
|
||||
Install additional dependencies required for RVM:
|
||||
|
||||
```bash
|
||||
sudo apt-get install -y libgdbm-dev libncurses5-dev automake libtool bison libffi-dev
|
||||
```
|
||||
|
||||
Install RVM & Ruby version 3.2.2:
|
||||
|
||||
```bash
|
||||
# Add RVM GPG keys
|
||||
gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
|
||||
|
||||
# Install RVM
|
||||
curl -sSL https://get.rvm.io | bash -s stable
|
||||
|
||||
# Load RVM into current session
|
||||
source ~/.rvm/scripts/rvm
|
||||
|
||||
# Install Ruby 3.2.2
|
||||
rvm install 3.2.2
|
||||
rvm use 3.2.2 --default
|
||||
|
||||
# Verify installation
|
||||
ruby -v
|
||||
```
|
||||
|
||||
## Install Node.js
|
||||
|
||||
Chatwoot requires Node.js version 20. Install Node.js from NodeSource using the following commands:
|
||||
|
||||
```bash
|
||||
curl -sL https://deb.nodesource.com/setup_20.x | sudo -E bash -
|
||||
sudo apt-get install -y nodejs
|
||||
```
|
||||
|
||||
Verify Node.js installation:
|
||||
|
||||
```bash
|
||||
node --version
|
||||
# Should output: v20.x.x
|
||||
```
|
||||
|
||||
## Install pnpm
|
||||
|
||||
We use `pnpm` as the package manager for better performance:
|
||||
|
||||
```bash
|
||||
# Install pnpm globally
|
||||
npm install -g pnpm
|
||||
|
||||
# Verify installation
|
||||
pnpm --version
|
||||
```
|
||||
|
||||
## Install PostgreSQL
|
||||
|
||||
The database used in Chatwoot is PostgreSQL. Use the following commands to install PostgreSQL:
|
||||
|
||||
```bash
|
||||
sudo apt install -y postgresql postgresql-contrib
|
||||
```
|
||||
|
||||
The installation procedure created a user account called postgres that is associated with the default Postgres role. In order to use PostgreSQL, you can log into that account:
|
||||
|
||||
```bash
|
||||
sudo -u postgres psql
|
||||
```
|
||||
|
||||
Install `libpq-dev` dependencies for Ubuntu:
|
||||
|
||||
```bash
|
||||
sudo apt-get install -y libpq-dev
|
||||
```
|
||||
|
||||
Start PostgreSQL service:
|
||||
|
||||
```bash
|
||||
sudo service postgresql start
|
||||
```
|
||||
|
||||
Configure PostgreSQL to start automatically:
|
||||
|
||||
```bash
|
||||
echo 'sudo service postgresql start' >> ~/.bashrc
|
||||
```
|
||||
|
||||
Create a database user:
|
||||
|
||||
```bash
|
||||
# Switch to postgres user and create a superuser
|
||||
sudo -u postgres createuser --superuser $USER
|
||||
|
||||
# Set password for your user
|
||||
sudo -u postgres psql -c "ALTER USER $USER PASSWORD 'password';"
|
||||
```
|
||||
|
||||
## Install Redis Server
|
||||
|
||||
Chatwoot uses Redis server for agent assignments and reporting. To install `redis-server`:
|
||||
|
||||
```bash
|
||||
sudo apt-get install -y redis-server
|
||||
```
|
||||
|
||||
Start Redis service:
|
||||
|
||||
```bash
|
||||
sudo service redis-server start
|
||||
```
|
||||
|
||||
Configure Redis to start automatically:
|
||||
|
||||
```bash
|
||||
echo 'sudo service redis-server start' >> ~/.bashrc
|
||||
```
|
||||
|
||||
Enable Redis to start on system boot:
|
||||
|
||||
```bash
|
||||
sudo systemctl enable redis-server.service
|
||||
```
|
||||
|
||||
## Install ImageMagick
|
||||
|
||||
Chatwoot uses ImageMagick for image processing:
|
||||
|
||||
```bash
|
||||
sudo apt-get install -y imagemagick libmagickwand-dev
|
||||
```
|
||||
|
||||
## Configure Git
|
||||
|
||||
Set up Git with your information:
|
||||
|
||||
```bash
|
||||
git config --global user.name "Your Name"
|
||||
git config --global user.email "your.email@example.com"
|
||||
```
|
||||
|
||||
## Windows-Specific Configuration
|
||||
|
||||
### Install VS Code with WSL Extension
|
||||
|
||||
1. **Install Visual Studio Code** on Windows from [https://code.visualstudio.com/](https://code.visualstudio.com/)
|
||||
2. **Install Remote - WSL extension** from the Extensions marketplace
|
||||
3. **Open your project in WSL** by running `code .` from your WSL terminal
|
||||
|
||||
### Configure File Permissions
|
||||
|
||||
WSL2 may have file permission issues. Fix them:
|
||||
|
||||
```bash
|
||||
# Add to ~/.bashrc for better file permissions
|
||||
echo 'umask 022' >> ~/.bashrc
|
||||
|
||||
# Configure Git to ignore file mode changes
|
||||
git config --global core.filemode false
|
||||
```
|
||||
|
||||
## Environment Verification
|
||||
|
||||
Verify all installations are working correctly:
|
||||
|
||||
```bash
|
||||
# Check all versions
|
||||
ruby --version # Should be 3.2.2
|
||||
node --version # Should be v20.x.x
|
||||
pnpm --version # Should show pnpm version
|
||||
psql --version # Should show PostgreSQL version
|
||||
redis-cli ping # Should output: PONG
|
||||
convert --version # Should show ImageMagick version
|
||||
git --version # Should show Git version
|
||||
```
|
||||
|
||||
## Troubleshooting Common Issues
|
||||
|
||||
<Accordion title="WSL installation fails">
|
||||
**Solution**: Ensure virtualization is enabled in BIOS and Windows features are properly enabled:
|
||||
1. Restart computer and enter BIOS settings
|
||||
2. Enable Intel VT-x or AMD-V virtualization
|
||||
3. Enable Hyper-V in Windows Features
|
||||
4. Restart and try installation again
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Ubuntu terminal won't open">
|
||||
**Solution**: Reset WSL or reinstall Ubuntu:
|
||||
```powershell
|
||||
# Reset Ubuntu (will delete all data)
|
||||
wsl --unregister Ubuntu-22.04
|
||||
wsl --install -d Ubuntu-22.04
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="PostgreSQL fails to start">
|
||||
**Solution**: Check if Windows PostgreSQL service is conflicting:
|
||||
```bash
|
||||
# Stop Windows PostgreSQL service first (run in Windows Command Prompt as Admin)
|
||||
net stop postgresql-x64-14
|
||||
|
||||
# Then start WSL2 PostgreSQL
|
||||
sudo service postgresql start
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Permission denied errors">
|
||||
**Solution**: Fix file permissions:
|
||||
```bash
|
||||
# For the entire project
|
||||
find . -type f -exec chmod 644 {} \;
|
||||
find . -type d -exec chmod 755 {} \;
|
||||
|
||||
# For executable files
|
||||
chmod +x bin/*
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Slow performance">
|
||||
**Solution**: Ensure code is stored in WSL2 filesystem:
|
||||
```bash
|
||||
# Good: Store code here (fast)
|
||||
/home/username/projects/chatwoot
|
||||
|
||||
# Avoid: Storing code here (slow)
|
||||
/mnt/c/Users/Username/projects/chatwoot
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
If you encounter issues during setup:
|
||||
|
||||
- **Common Errors**: Check [Common Errors](/contributing/project-setup/common-errors)
|
||||
- **WSL2 Documentation**: [Microsoft WSL Documentation](https://docs.microsoft.com/en-us/windows/wsl/)
|
||||
- **Discord Community**: Join our [Discord](https://discord.com/invite/cJXdrwS)
|
||||
- **GitHub Issues**: [Create an issue](https://github.com/chatwoot/chatwoot/issues)
|
||||
|
||||
---
|
||||
|
||||
Your Windows development environment with WSL2 is now ready for Chatwoot development! 🪟🐧
|
||||
@@ -0,0 +1,127 @@
|
||||
---
|
||||
title: Reporting Security Issues
|
||||
description: How to report security vulnerabilities in Chatwoot
|
||||
sidebarTitle: Security Reports
|
||||
---
|
||||
|
||||
# Reporting Security Issues
|
||||
|
||||
Chatwoot is looking forward to working with security researchers worldwide to keep Chatwoot and our users safe. If you have found an issue in our systems/applications, please reach out to us.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
We use GitHub for security issues that affect our project. If you believe you have found a vulnerability, please disclose it via this [form](https://github.com/chatwoot/chatwoot/security/advisories/new).
|
||||
|
||||
This will enable us to review the vulnerability, fix it promptly, and reward you for your efforts.
|
||||
|
||||
If you have any questions about the process, contact **security@chatwoot.com**.
|
||||
|
||||
Please try your best to describe a clear and realistic impact for your report, and please don't open any public issues on GitHub or social media; we're doing our best to respond through GitHub as quickly as possible.
|
||||
|
||||
<Note>
|
||||
Please use the email for questions related to the process. Disclosures should be done via [GitHub](https://github.com/chatwoot/chatwoot/security/advisories/new).
|
||||
</Note>
|
||||
|
||||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
| -------- | --------- |
|
||||
| latest | ️✅ |
|
||||
| < latest | ❌ |
|
||||
|
||||
## Vulnerabilities We Care About 🫣
|
||||
|
||||
<Warning>
|
||||
Please do not perform testing against Chatwoot production services. Use a `self-hosted instance` to perform tests.
|
||||
</Warning>
|
||||
|
||||
We consider the following vulnerabilities as high priority:
|
||||
|
||||
- Remote command execution
|
||||
- SQL Injection
|
||||
- Authentication bypass
|
||||
- Privilege Escalation
|
||||
- Cross-site scripting (XSS)
|
||||
- Performing limited admin actions without authorization
|
||||
- CSRF
|
||||
|
||||
## Non-Qualifying Vulnerabilities
|
||||
|
||||
We consider the following out of scope, though there may be exceptions:
|
||||
|
||||
- Missing HTTP security headers
|
||||
- Incomplete/Missing SPF/DKIM
|
||||
- Reports from automated tools or scanners
|
||||
- Theoretical attacks without proof of exploitability
|
||||
- Social engineering
|
||||
- Reflected file download
|
||||
- Physical attacks
|
||||
- Weak SSL/TLS/SSH algorithms or protocols
|
||||
- Attacks involving physical access to a user's device or a device or network that's already seriously compromised (e.g., man-in-the-middle)
|
||||
- The user attacks themselves
|
||||
- Incomplete/Missing SPF/DKIM
|
||||
- Denial of Service attacks
|
||||
- Brute force attacks
|
||||
- DNSSEC
|
||||
|
||||
If you are unsure about the scope, please create a [report](https://github.com/chatwoot/chatwoot/security/advisories/new).
|
||||
|
||||
## Triaging Process
|
||||
|
||||
Chatwoot team triages the issues in GitHub weekly. We're doing our best to respond through GitHub as quickly as we can, so please don't open any public issues on GitHub or social media and avoid duplicate reports over emails.
|
||||
|
||||
- Based on reviewing the report, the team will assign a priority to the issue and move it into the internal backlog to prioritize a fix.
|
||||
- In cases where the team needs more information or disagreements of severity, the team will communicate the same over GitHub before completing the triaging.
|
||||
|
||||
After triage, the team will start working on the issue based on the following severity and timelines:
|
||||
|
||||
## Response Timeline
|
||||
|
||||
| Severity | Timeline |
|
||||
| ------------- | --------- |
|
||||
| Critical (P0) | ️ 7 Days |
|
||||
| High | 30 Days |
|
||||
| Medium | 60 Days |
|
||||
| Low | 90 Days |
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
### For Researchers
|
||||
|
||||
- **Test Responsibly**: Only test on your own self-hosted instances
|
||||
- **Provide Clear Details**: Include steps to reproduce, impact assessment, and suggested fixes
|
||||
- **Be Patient**: Allow time for our team to investigate and respond
|
||||
- **Follow Responsible Disclosure**: Don't publish vulnerabilities publicly until they're fixed
|
||||
|
||||
### For Users
|
||||
|
||||
- **Keep Updated**: Always use the latest version of Chatwoot
|
||||
- **Secure Configuration**: Follow security best practices for your deployment
|
||||
- **Monitor Logs**: Regularly check logs for suspicious activity
|
||||
- **Report Issues**: If you notice anything unusual, report it through proper channels
|
||||
|
||||
## Bounty Program
|
||||
|
||||
While we don't currently have a formal bug bounty program, we do recognize and appreciate security researchers who help us improve Chatwoot's security:
|
||||
|
||||
- **Hall of Fame**: Recognition on our security acknowledgments page
|
||||
- **Direct Communication**: Work directly with our security team
|
||||
- **Early Access**: Get early access to security updates and patches
|
||||
|
||||
## Getting Help
|
||||
|
||||
If you need assistance with security reporting:
|
||||
|
||||
- **Process Questions**: Contact security@chatwoot.com
|
||||
- **Technical Issues**: Use our [Discord community](https://discord.com/invite/cJXdrwS)
|
||||
- **General Support**: Check our [documentation](/contributing/project-setup/common-errors)
|
||||
|
||||
## Thanks
|
||||
|
||||
Thank you for keeping Chatwoot and our users safe. 🙇
|
||||
|
||||
Your efforts help us maintain a secure platform for thousands of businesses worldwide. We appreciate the time and expertise you contribute to making Chatwoot better for everyone.
|
||||
|
||||
---
|
||||
|
||||
Remember: Security is a shared responsibility. Together, we can make Chatwoot safer for everyone.
|
||||
@@ -0,0 +1,151 @@
|
||||
---
|
||||
title: Translate Chatwoot to Your Language
|
||||
description: Guide to translating Chatwoot using Crowdin translation platform
|
||||
sidebarTitle: Translation Guidelines
|
||||
---
|
||||
|
||||
# Translate Chatwoot to Your Language
|
||||
|
||||
Chatwoot uses American English by default. Each and every string available in Chatwoot can be translated to the language of your choice. Chatwoot uses Crowdin to manage the translation process. The updates from Crowdin are also included along with every release.
|
||||
|
||||
## How do I see the strings that need to be translated?
|
||||
|
||||
In the codebase, the strings are placed in the following locations:
|
||||
|
||||
- `app/javascript/dashboard/i18n` - The strings related to the agent dashboard
|
||||
- `app/javascript/widget/i18n` - The strings related to the web widget
|
||||
- `app/javascript/survey/i18n` - The strings related to CSAT surveys
|
||||
- `config/locales` - The strings used in backend messages or API responses
|
||||
|
||||
You can login to **Crowdin** ([https://translate.chatwoot.com](https://translate.chatwoot.com)) and create an account to view the strings that need to be translated.
|
||||
|
||||
## How to contribute?
|
||||
|
||||
If you don't find your language on Crowdin, please create an issue on [GitHub](https://github.com/chatwoot/chatwoot/issues) to add the language.
|
||||
|
||||
### Translate Strings
|
||||
|
||||
The translation process for Chatwoot web and mobile app is managed at [https://translate.chatwoot.com](https://translate.chatwoot.com) using Crowdin. You will have to create an account at Crowdin before you can select a language and contribute.
|
||||
|
||||
<Note>
|
||||
New to Crowdin? Check out their [getting started guide](https://support.crowdin.com/crowdin-intro/) to learn the basics of translation management.
|
||||
</Note>
|
||||
|
||||
### Translation Guidelines
|
||||
|
||||
#### Formal vs Informal Context
|
||||
|
||||
At Chatwoot, we prefer to use formal form of language wherever possible. For instance in German there are two forms of "you" where one is rather used in formal contexts ("Sie") and the other one is used among friends ("Du"). "Sie" is preferred over "Du" in translating Chatwoot.
|
||||
|
||||
#### Consistency Guidelines
|
||||
|
||||
- **Maintain consistency** across similar contexts and features
|
||||
- **Use standard terminology** for technical terms when available in your language
|
||||
- **Keep placeholders intact** - Don't translate variables like `{name}` or `%{count}`
|
||||
- **Preserve formatting** - Maintain HTML tags, markdown, and line breaks
|
||||
- **Consider context** - UI strings may need to be shorter than descriptive text
|
||||
|
||||
#### Brand and Product Names
|
||||
|
||||
- **Chatwoot** - Always keep as "Chatwoot" (don't translate)
|
||||
- **Feature names** - Translate feature names but maintain consistency
|
||||
- **Third-party services** - Keep original names (GitHub, Slack, etc.)
|
||||
|
||||
### Proofreading
|
||||
|
||||
Proofreading helps ensure the accuracy and consistency of translations. Right now, the translations are being accepted without a proofreading step. This would be changed in the future as and when there are more contributors for each language.
|
||||
|
||||
<Warning>
|
||||
If you are the only person contributing to a language, make sure that you inform any of the Chatwoot members to gain access to manage the language.
|
||||
</Warning>
|
||||
|
||||
### Releasing a New Language
|
||||
|
||||
All the translated strings would be included in the next release. If a language has **60% or more translated strings** in Crowdin, we would enable the language in Chatwoot app during the next release.
|
||||
|
||||
#### Steps to Raise a Pull Request for New Language
|
||||
|
||||
Please use this [pull request](https://github.com/chatwoot/chatwoot/pull/7905) as a reference for enabling a new language into Chatwoot.
|
||||
|
||||
- Ensure language is added and enabled in `config/initializers/languages.rb`
|
||||
- Include the language in `i18n/index.js` for all the packs → `dashboard, widget, survey`
|
||||
- Select the language from Chatwoot settings UI and confirm the PR to be working
|
||||
|
||||
## Translation Progress and Metrics
|
||||
|
||||
### Current Status
|
||||
|
||||
You can check the translation progress for different languages on our [Crowdin project page](https://translate.chatwoot.com). This shows:
|
||||
|
||||
- **Overall completion percentage** for each language
|
||||
- **Component-wise progress** (Dashboard, Widget, Survey, API)
|
||||
- **Recent activity** and contributor statistics
|
||||
|
||||
### Quality Metrics
|
||||
|
||||
We track several quality indicators:
|
||||
|
||||
- **Translation coverage** - Percentage of strings translated
|
||||
- **Review coverage** - Percentage of translations reviewed
|
||||
- **Consistency score** - How consistent terminology is across the platform
|
||||
- **Community engagement** - Number of active translators
|
||||
|
||||
## Best Practices for Translators
|
||||
|
||||
### Before You Start
|
||||
|
||||
1. **Review existing translations** in your language for consistency
|
||||
2. **Understand the context** - Test the feature in Chatwoot if possible
|
||||
3. **Check for existing glossaries** or style guides for your language
|
||||
4. **Join the community** discussions for your language
|
||||
|
||||
### During Translation
|
||||
|
||||
1. **Focus on user experience** - How will end users understand this?
|
||||
2. **Maintain professional tone** appropriate for business communication
|
||||
3. **Ask questions** if context is unclear
|
||||
4. **Suggest improvements** for source text if needed
|
||||
|
||||
### After Translation
|
||||
|
||||
1. **Test your translations** in a live Chatwoot instance
|
||||
2. **Report issues** if translations don't fit in the UI
|
||||
3. **Help review** other contributors' work
|
||||
4. **Stay updated** with new strings added
|
||||
|
||||
## Getting Help and Support
|
||||
|
||||
### Community Resources
|
||||
|
||||
- **GitHub Discussions**: [Translation category](https://github.com/chatwoot/chatwoot/discussions/categories/translations)
|
||||
- **Discord**: Join our [Discord community](https://discord.gg/uPtCrFfb9B) (#translations channel)
|
||||
- **Crowdin Comments**: Use comments feature in Crowdin for context-specific questions
|
||||
|
||||
### Technical Support
|
||||
|
||||
For technical issues with translations:
|
||||
|
||||
- **Missing context**: Create an issue on GitHub
|
||||
- **UI layout problems**: Report in Discord with screenshots
|
||||
- **Crowdin access issues**: Contact the maintainers
|
||||
|
||||
### Recognition
|
||||
|
||||
We recognize and appreciate our translation contributors:
|
||||
|
||||
- **Contributors page**: Featured on our contributors page
|
||||
- **Release notes**: Mentioned in release announcements
|
||||
- **Community highlights**: Featured in community updates
|
||||
|
||||
## Multilingual Support Features
|
||||
|
||||
Chatwoot's internationalization supports:
|
||||
|
||||
- **Right-to-left (RTL) languages** - Arabic, Hebrew, etc.
|
||||
- **Pluralization rules** - Correct plural forms for different languages
|
||||
- **Date and time formatting** - Localized date/time display
|
||||
- **Number formatting** - Currency and number format localization
|
||||
|
||||
---
|
||||
|
||||
Ready to help make Chatwoot accessible to users worldwide? [Start translating today](https://translate.chatwoot.com)! 🌍
|
||||
+253
-51
@@ -28,67 +28,269 @@
|
||||
},
|
||||
"theme": "maple",
|
||||
"navigation": {
|
||||
"groups": [
|
||||
"anchors": [
|
||||
{
|
||||
"group": "API Reference",
|
||||
"anchor": "Introduction",
|
||||
"icon": "house",
|
||||
"pages": [
|
||||
"introduction"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Application API",
|
||||
"description": "APIs for managing application aspects of Chatwoot",
|
||||
"includeTags": [
|
||||
"Agents",
|
||||
"Automation Rule",
|
||||
"Account AgentBots",
|
||||
"Canned Responses",
|
||||
"Contact Labels",
|
||||
"Contacts",
|
||||
"Conversation Assignments",
|
||||
"Conversation Labels",
|
||||
"Conversations",
|
||||
"Custom Attributes",
|
||||
"Custom Filters",
|
||||
"Help Center",
|
||||
"Inboxes",
|
||||
"Integrations",
|
||||
"Messages",
|
||||
"Profile",
|
||||
"Reports",
|
||||
"Teams",
|
||||
"Webhooks"
|
||||
],
|
||||
"openapi": "https://raw.githubusercontent.com/chatwoot/chatwoot/develop/swagger/tag_groups/application_swagger.json"
|
||||
"anchor": "Installation & Setup",
|
||||
"icon": "server",
|
||||
"pages": [
|
||||
{
|
||||
"group": "Getting Started",
|
||||
"pages": [
|
||||
"self-hosted/introduction",
|
||||
"self-hosted/architecture",
|
||||
"self-hosted/requirements"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Deployment Methods",
|
||||
"pages": [
|
||||
{
|
||||
"group": "Linux",
|
||||
"pages": [
|
||||
"self-hosted/deployment/linux-vm",
|
||||
"self-hosted/deployment/docker"
|
||||
]
|
||||
},
|
||||
"self-hosted/deployment/kubernetes",
|
||||
"self-hosted/deployment/chatwoot-ctl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Cloud Providers",
|
||||
"pages": [
|
||||
{
|
||||
"group": "AWS",
|
||||
"pages": [
|
||||
"self-hosted/cloud/aws",
|
||||
"self-hosted/cloud/aws-marketplace"
|
||||
]
|
||||
},
|
||||
"self-hosted/cloud/azure",
|
||||
"self-hosted/cloud/digitalocean",
|
||||
"self-hosted/cloud/gcp",
|
||||
"self-hosted/cloud/heroku",
|
||||
{
|
||||
"group": "Community Contributed",
|
||||
"pages": [
|
||||
"self-hosted/cloud/caprover",
|
||||
"self-hosted/cloud/clevercloud",
|
||||
"self-hosted/cloud/cloudron",
|
||||
"self-hosted/cloud/restack",
|
||||
"self-hosted/cloud/easypanel",
|
||||
"self-hosted/cloud/elestio"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Configuration",
|
||||
"pages": [
|
||||
"self-hosted/configuration/environment-variables",
|
||||
{
|
||||
"group": "Performance",
|
||||
"pages": [
|
||||
"self-hosted/configuration/performance/optimizing-configurations",
|
||||
"self-hosted/configuration/performance/cloudfront-cdn"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Monitoring",
|
||||
"pages": [
|
||||
"self-hosted/configuration/monitoring/super-admin-console",
|
||||
"self-hosted/configuration/monitoring/apm-and-tracing",
|
||||
"self-hosted/configuration/monitoring/rate-limiting"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Storage",
|
||||
"pages": [
|
||||
"self-hosted/configuration/storage/supported-providers",
|
||||
"self-hosted/configuration/storage/s3-bucket",
|
||||
"self-hosted/configuration/storage/gcs-bucket"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Email Channel",
|
||||
"pages": [
|
||||
"self-hosted/configuration/features/email-channel/conversation-continuity",
|
||||
"self-hosted/configuration/features/email-channel/conversation-continuity-using-sendgrid",
|
||||
"self-hosted/configuration/features/email-channel/email-channel-setup",
|
||||
"self-hosted/configuration/features/email-channel/azure-app-setup",
|
||||
"self-hosted/configuration/features/email-channel/google-workspace-setup"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Help Center",
|
||||
"pages": [
|
||||
"self-hosted/configuration/help-center"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Integrations",
|
||||
"pages": [
|
||||
"self-hosted/configuration/integrations/facebook-channel-setup",
|
||||
"self-hosted/configuration/integrations/instagram-channel-setup",
|
||||
"self-hosted/configuration/integrations/instagram-via-instagram-business-login",
|
||||
"self-hosted/configuration/integrations/slack-integration-setup",
|
||||
"self-hosted/configuration/integrations/linear-integration-setup",
|
||||
"self-hosted/configuration/integrations/shopify-integration-setup"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Maintenance",
|
||||
"pages": [
|
||||
"self-hosted/maintenance/upgrade",
|
||||
"self-hosted/maintenance/backup"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Runbooks",
|
||||
"pages": [
|
||||
"self-hosted/runbook/migrate-chatwoot-database",
|
||||
"self-hosted/runbook/upgrade-to-chatwoot-v4",
|
||||
"self-hosted/runbook/enable-ip-logging",
|
||||
"self-hosted/runbook/email-notifications"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Others",
|
||||
"pages": [
|
||||
"self-hosted/others/telemetry",
|
||||
"self-hosted/others/enterprise-edition",
|
||||
"self-hosted/others/supported-features",
|
||||
"self-hosted/others/restricted-instances",
|
||||
"self-hosted/others/instagram-app-review",
|
||||
"self-hosted/others/faq"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Platform API",
|
||||
"description": "APIs for managing platform aspects of Chatwoot",
|
||||
"includeTags": [
|
||||
"Accounts",
|
||||
"Account Users",
|
||||
"AgentBots",
|
||||
"Users"
|
||||
],
|
||||
"openapi": "https://raw.githubusercontent.com/chatwoot/chatwoot/develop/swagger/tag_groups/platform_swagger.json"
|
||||
"anchor": "Contributing Guide",
|
||||
"icon": "code",
|
||||
"pages": [
|
||||
{
|
||||
"group": "Getting Started",
|
||||
"pages": [
|
||||
"contributing/introduction"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Environment Setup",
|
||||
"pages": [
|
||||
"contributing/project-setup/macos-setup",
|
||||
"contributing/project-setup/ubuntu-setup",
|
||||
"contributing/project-setup/windows-setup",
|
||||
"contributing/project-setup/docker-setup",
|
||||
"contributing/project-setup/make-setup"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Project Setup",
|
||||
"pages": [
|
||||
"contributing/project-setup/setup-guide",
|
||||
"contributing/project-setup/environment-variables",
|
||||
"contributing/project-setup/common-errors",
|
||||
"contributing/project-setup/telegram-channel-setup",
|
||||
"contributing/project-setup/line-channel-setup",
|
||||
"contributing/project-setup/mobile-app-setup"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Testing",
|
||||
"pages": [
|
||||
"contributing/cypress"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Others",
|
||||
"pages": [
|
||||
"contributing/code-of-conduct",
|
||||
"contributing/security-reports",
|
||||
"contributing/translation-guidelines",
|
||||
"contributing/community-guidelines",
|
||||
"contributing/contributors"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Client API",
|
||||
"description": "APIs for client applications",
|
||||
"includeTags": [
|
||||
"Contacts API",
|
||||
"Conversations API",
|
||||
"Messages API"
|
||||
],
|
||||
"openapi": "https://raw.githubusercontent.com/chatwoot/chatwoot/develop/swagger/tag_groups/client_swagger.json"
|
||||
},
|
||||
{
|
||||
"group": "Other APIs",
|
||||
"description": "Other Chatwoot APIs",
|
||||
"includeTags": [
|
||||
"CSAT Survey Page"
|
||||
],
|
||||
"openapi": "https://raw.githubusercontent.com/chatwoot/chatwoot/develop/swagger/tag_groups/other_swagger.json"
|
||||
"anchor": "API Reference",
|
||||
"icon": "square-terminal",
|
||||
"pages": [],
|
||||
"groups": [
|
||||
{
|
||||
"group": "Getting Started",
|
||||
"description": "Learn about Chatwoot APIs and how to use them",
|
||||
"pages": [
|
||||
"api-reference/introduction"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Application APIs",
|
||||
"description": "APIs for managing application aspects of Chatwoot",
|
||||
"includeTags": [
|
||||
"Agents",
|
||||
"Automation Rule",
|
||||
"Account AgentBots",
|
||||
"Canned Responses",
|
||||
"Contact Labels",
|
||||
"Contacts",
|
||||
"Conversation Assignments",
|
||||
"Conversation Labels",
|
||||
"Conversations",
|
||||
"Custom Attributes",
|
||||
"Custom Filters",
|
||||
"Help Center",
|
||||
"Inboxes",
|
||||
"Integrations",
|
||||
"Messages",
|
||||
"Profile",
|
||||
"Reports",
|
||||
"Teams",
|
||||
"Webhooks"
|
||||
],
|
||||
"openapi": "https://raw.githubusercontent.com/chatwoot/chatwoot/develop/swagger/tag_groups/application_swagger.json"
|
||||
},
|
||||
{
|
||||
"group": "Platform APIs",
|
||||
"description": "APIs for managing platform aspects of Chatwoot",
|
||||
"includeTags": [
|
||||
"Accounts",
|
||||
"Account Users",
|
||||
"AgentBots",
|
||||
"Users"
|
||||
],
|
||||
"openapi": "https://raw.githubusercontent.com/chatwoot/chatwoot/develop/swagger/tag_groups/platform_swagger.json"
|
||||
},
|
||||
{
|
||||
"group": "Client APIs",
|
||||
"description": "APIs for client applications",
|
||||
"includeTags": [
|
||||
"Contacts API",
|
||||
"Conversations API",
|
||||
"Messages API"
|
||||
],
|
||||
"openapi": "https://raw.githubusercontent.com/chatwoot/chatwoot/develop/swagger/tag_groups/client_swagger.json"
|
||||
},
|
||||
{
|
||||
"group": "Other APIs",
|
||||
"description": "Additional Chatwoot APIs",
|
||||
"includeTags": [
|
||||
"CSAT Survey Page"
|
||||
],
|
||||
"openapi": "https://raw.githubusercontent.com/chatwoot/chatwoot/develop/swagger/tag_groups/other_swagger.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -1,49 +1,96 @@
|
||||
---
|
||||
title: Introduction to Chatwoot APIs
|
||||
description: Learn how to use Chatwoot APIs to build integrations, customize chat experiences, and manage your installation.
|
||||
title: Welcome to Chatwoot Developer Docs
|
||||
description: Your comprehensive guide to installing, configuring, developing with, and integrating Chatwoot - the open-source customer support platform.
|
||||
sidebarTitle: Introduction
|
||||
---
|
||||
|
||||
Welcome to the Chatwoot API documentation. Whether you're building custom workflows for your support team, integrating Chatwoot into your product, or managing users across installations, our APIs provide the flexibility and power to help you do more with Chatwoot.
|
||||
# Welcome to Chatwoot Developer Documentation
|
||||
|
||||
Chatwoot provides three categories of APIs, each designed with a specific use case in mind:
|
||||
Welcome to the complete developer documentation for Chatwoot, the open-source customer support platform trusted by thousands of businesses worldwide. Whether you're setting up your own instance, contributing to the project, or building integrations, this documentation will guide you through every step.
|
||||
|
||||
- **Application APIs** – For account-level automation and agent-facing integrations.
|
||||
- **Client APIs** – For building custom chat interfaces for end-users
|
||||
- **Platform APIs** – For managing and administering installations at scale
|
||||
## What You'll Find Here
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card
|
||||
title="Installation & Setup"
|
||||
icon="server"
|
||||
href="/self-hosted/introduction"
|
||||
>
|
||||
Deploy Chatwoot on your infrastructure with Docker, Kubernetes, or cloud providers
|
||||
</Card>
|
||||
<Card
|
||||
title="Development Guide"
|
||||
icon="code"
|
||||
href="/contributing/introduction"
|
||||
>
|
||||
Contribute to Chatwoot with our development setup, testing guidelines, and best practices
|
||||
</Card>
|
||||
<Card
|
||||
title="API Reference"
|
||||
icon="square-terminal"
|
||||
href="/api-reference/introduction"
|
||||
>
|
||||
Build powerful integrations with our comprehensive REST APIs
|
||||
</Card>
|
||||
<Card
|
||||
title="Architecture"
|
||||
icon="sitemap"
|
||||
href="/self-hosted/architecture"
|
||||
>
|
||||
Understand Chatwoot's system architecture and components
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Getting Started Paths
|
||||
|
||||
Choose your path based on what you want to accomplish:
|
||||
|
||||
### 🚀 **I want to install Chatwoot**
|
||||
Perfect! Head to our [Installation Guide](/self-hosted/introduction) to deploy Chatwoot on your preferred platform. We support:
|
||||
- Docker containers for quick setup
|
||||
- Kubernetes for scalable deployments
|
||||
- Major cloud providers (AWS, GCP, Azure)
|
||||
- Traditional Linux VMs
|
||||
|
||||
### 🛠️ **I want to contribute to Chatwoot**
|
||||
Amazing! Check out our [Contributing Guide](/contributing/introduction) to:
|
||||
- Set up your development environment
|
||||
- Understand our coding standards
|
||||
- Learn our testing practices
|
||||
- Submit your first pull request
|
||||
|
||||
### 🔌 **I want to build integrations**
|
||||
Great! Explore our [API Reference](/api-reference/introduction) with three categories of APIs:
|
||||
- **Application APIs** - Manage accounts, agents, and conversations
|
||||
- **Platform APIs** - Administrative control for installations
|
||||
- **Client APIs** - Build custom chat interfaces
|
||||
|
||||
### 📚 **I want to understand the architecture**
|
||||
Excellent! Our [Architecture Guide](/self-hosted/architecture) covers:
|
||||
- System components and their interactions
|
||||
- Database schemas and relationships
|
||||
- Scalability considerations
|
||||
- Security best practices
|
||||
|
||||
## Why Chatwoot?
|
||||
|
||||
Chatwoot is built with modern technologies and follows industry best practices:
|
||||
|
||||
- **Open Source**: Full transparency and community-driven development
|
||||
- **Multi-channel**: Support customers across web, mobile, email, and social platforms
|
||||
- **Scalable**: From small teams to enterprise deployments
|
||||
- **Extensible**: Rich APIs and webhook system for custom integrations
|
||||
- **Modern Stack**: Ruby on Rails backend, Vue.js frontend, PostgreSQL database
|
||||
|
||||
## Community & Support
|
||||
|
||||
Join our thriving community of developers and users:
|
||||
|
||||
- **GitHub**: [github.com/chatwoot/chatwoot](https://github.com/chatwoot/chatwoot)
|
||||
- **Community Slack**: [chatwoot.com/slack](https://chatwoot.com/slack)
|
||||
- **Discussions**: [GitHub Discussions](https://github.com/chatwoot/chatwoot/discussions)
|
||||
- **Twitter**: [@chatwootapp](https://twitter.com/chatwootapp)
|
||||
|
||||
---
|
||||
|
||||
## Application APIs
|
||||
|
||||
Application APIs are designed for interacting with a Chatwoot account from an agent/admin perspective. Use them to build internal tools, automate workflows, or perform bulk operations like data import/export.
|
||||
|
||||
- **Authentication**: Requires a user `access_token`, which can be generated from **Profile Settings** after logging into your Chatwoot account.
|
||||
- **Availability**: Supported on both **Cloud** and **Self-hosted** Chatwoot installations.
|
||||
- **Example**: [Google Cloud Functions Demo](https://github.com/chatwoot/google-cloud-functions-demo)
|
||||
|
||||
---
|
||||
|
||||
## Client APIs
|
||||
|
||||
Client APIs are intended for building custom messaging experiences over Chatwoot. If you're not using the native website widget or want to embed chat in your mobile app, these APIs are the way to go.
|
||||
|
||||
- **Authentication**: Uses `inbox_identifier` (from **Settings → Configuration** in API inboxes) and `contact_identifier` (returned when creating a contact).
|
||||
- **Availability**: Supported on both **Cloud** and **Self-hosted** Chatwoot installations.
|
||||
- **Examples**:
|
||||
|
||||
- [Client API Demo](https://github.com/chatwoot/client-api-demo)
|
||||
- [Flutter SDK](https://github.com/chatwoot/chatwoot-flutter-sdk)
|
||||
|
||||
---
|
||||
|
||||
## Platform APIs
|
||||
|
||||
Platform APIs are used to manage Chatwoot installations at the admin level. These APIs allow you to control users, roles, and accounts, or sync data from external authentication systems.
|
||||
|
||||
- **Authentication**: Requires an `access_token` generated by a **Platform App**, which can be created in the **Super Admin Console**.
|
||||
- **Availability**: Available on **Self-hosted** / **Managed Hosting** Chatwoot installations only.
|
||||
|
||||
---
|
||||
|
||||
Use the right API for your use case, and you'll be able to extend, customize, and integrate Chatwoot into your stack with ease.
|
||||
Ready to dive in? Choose your path above and let's build something amazing together! 🚀
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
title: Chatwoot Production Deployment Guide
|
||||
description: Understanding Chatwoot's production architecture and deployment requirements
|
||||
sidebarTitle: Architecture
|
||||
---
|
||||
|
||||
This guide will help you to deploy Chatwoot to production!
|
||||
|
||||
## Architecture
|
||||
|
||||
Running Chatwoot in production requires the following set of services.
|
||||
|
||||
* Chatwoot web servers
|
||||
* Chatwoot workers
|
||||
* PostgreSQL Database
|
||||
* Redis Database
|
||||
* Email service (SMTP servers / SendGrid / Mailgun etc)
|
||||
* Object Storage (S3, Azure Storage, GCS, etc)
|
||||
|
||||

|
||||
|
||||
## Updating your Chatwoot installation
|
||||
|
||||
A new version of Chatwoot is released around the first Monday of every month. We also release minor versions when there is a need for hotfixes or security updates.
|
||||
|
||||
You can stay tuned to our [Roadmap](https://github.com/chatwoot/chatwoot/milestones) and [releases](https://github.com/chatwoot/chatwoot/releases) on GitHub. We recommend you to stay up to date with our releases to enjoy the latest features and security updates.
|
||||
|
||||
The deployment process for a newer version involves updating your app servers and workers with the latest code. Most updates would involve database migrations as well which can be executed through the following Rails command.
|
||||
|
||||
```bash
|
||||
bundle exec rails db:migrate
|
||||
```
|
||||
|
||||
The detailed instructions can be found in respective deployment guides.
|
||||
|
||||
## Available deployment options
|
||||
|
||||
If you want to self host Chatwoot, the recommended approach is to use one of the recommended one click installation options from the below list. If you are comfortable with Ruby on Rails applications, you can also make use of the other deployment options mentioned below.
|
||||
|
||||
* **[Heroku](/self-hosted/deployment/heroku)** (recommended)
|
||||
* **[Docker](/self-hosted/deployment/docker)** (recommended)
|
||||
* **[Linux](/self-hosted/deployment/linux-vm)**
|
||||
* **[Kubernetes](/self-hosted/deployment/kubernetes)**
|
||||
* **[Chatwoot CTL](/self-hosted/deployment/chatwoot-ctl)**
|
||||
|
||||
### Cloud Providers
|
||||
|
||||
* **[AWS](/self-hosted/cloud/aws)**
|
||||
* **[Azure](/self-hosted/cloud/azure)**
|
||||
* **[DigitalOcean](/self-hosted/cloud/digitalocean)**
|
||||
* **[Google Cloud](/self-hosted/cloud/gcp)**
|
||||
* **[Heroku](/self-hosted/cloud/heroku)**
|
||||
@@ -0,0 +1,170 @@
|
||||
---
|
||||
title: AWS Marketplace AMI Deployment
|
||||
description: Deploy Chatwoot on AWS using the marketplace AMI listing
|
||||
sidebarTitle: AWS Marketplace
|
||||
---
|
||||
|
||||
# AWS Chatwoot Deployment Guide
|
||||
|
||||
The following is the guide for deploying Chatwoot on AWS using the marketplace listing. Use our helm charts with AWS Elastic Kubernetes Service(EKS) for a cloud-native deployment.
|
||||
|
||||
## Prerequisites
|
||||
- AWS account
|
||||
|
||||
## Install Chatwoot via AWS Marketplace AMI
|
||||
|
||||
### Step 1: Subscribe to Chatwoot
|
||||
|
||||
1. Go to [Chatwoot AWS marketplace listing](https://aws.amazon.com/marketplace/pp/prodview-tolblk4kmdqd4) and click on **Subscribe**.
|
||||
|
||||

|
||||
|
||||
### Step 2: Sign In
|
||||
|
||||
2. Sign in with your AWS account.
|
||||
|
||||

|
||||
|
||||
### Step 3: Continue to Configuration
|
||||
|
||||
3. Click on **Continue to Configuration**.
|
||||
|
||||

|
||||
|
||||
### Step 4: Configure Software
|
||||
|
||||
4. Select the latest version in **Software Version** and pick your AWS **region**. Click **Continue to Launch**.
|
||||
|
||||

|
||||
|
||||
### Step 5: Launch Configuration
|
||||
|
||||
5. Review the launch configuration. Leave the **Choose Action** field with the default value **Launch from Website**. Choose a VPC and subnet as per your AWS region preference.
|
||||
|
||||

|
||||
|
||||
### Step 6: Create Security Group
|
||||
|
||||
6. Scroll down to the **Security Group** section and click **Create New Based On Seller Settings**.
|
||||
|
||||

|
||||
|
||||
### Step 7: Save Security Group
|
||||
|
||||
7. Save the new security group and choose it after creation.
|
||||
|
||||

|
||||
|
||||
### Step 8: Configure Key Pair
|
||||
|
||||
8. Pick a key pair you already have or create a new one in the region you are deploying. Click **Launch**.
|
||||
|
||||

|
||||
|
||||
### Step 9: Launch Confirmation
|
||||
|
||||
9. AWS should now display a congratulations screen confirming that Chatwoot instance is launched successfully. Click on the **EC2 Console** link.
|
||||
|
||||

|
||||
|
||||
### Step 10: Wait for Instance
|
||||
|
||||
10. Wait for a few minutes to let the instance come up.
|
||||
|
||||

|
||||
|
||||
### Step 11: Get Public IP
|
||||
|
||||
11. Select the instance and copy the public IP.
|
||||
|
||||

|
||||
|
||||
### Step 12: Access Chatwoot
|
||||
|
||||
12. Visit `http://<your-public-ip>:3000`. This should bring up the Chatwoot UI. Congratulations. Woot! Woot!!
|
||||
|
||||

|
||||
|
||||
## Configuring Chatwoot
|
||||
|
||||
To configure Chatwoot, we need to SSH into the instance. We will use **AWS Console Connect** for this.
|
||||
|
||||
### Step 1: Connect to Instance
|
||||
|
||||
1. Select the instance and click on **Connect**.
|
||||
|
||||

|
||||
|
||||
### Step 2: Use Ubuntu User
|
||||
|
||||
2. Change the username from `root` to `ubuntu` and click **Connect**.
|
||||
|
||||

|
||||
|
||||
### Step 3: Configure Environment Variables
|
||||
|
||||
3. Switch to the `chatwoot` user and configure the necessary environment variables. Refer to [Environment variables](/self-hosted/configuration/environment-variables) document for the complete list.
|
||||
|
||||
```bash
|
||||
sudo -i -u chatwoot
|
||||
cd chatwoot
|
||||
vi .env
|
||||
```
|
||||
|
||||
<Note>
|
||||
It is recommended to configure a proxy server like Nginx and set up SSL. Make sure to modify the security group created in step 6 accordingly.
|
||||
</Note>
|
||||
|
||||
## Updating the Instance
|
||||
|
||||
Please follow the Chatwoot update process in the standard [Linux VM setup](/self-hosted/deployment/linux-vm).
|
||||
|
||||
## Security Recommendations
|
||||
|
||||
### SSL Configuration
|
||||
- Set up SSL certificates using Let's Encrypt or AWS Certificate Manager
|
||||
- Configure Nginx as a reverse proxy
|
||||
- Update security group rules to allow HTTPS traffic (port 443)
|
||||
|
||||
### Access Control
|
||||
- Restrict SSH access to specific IP addresses
|
||||
- Use IAM roles for EC2 instances where possible
|
||||
- Enable AWS CloudTrail for audit logging
|
||||
|
||||
### Backup Strategy
|
||||
- Set up automated EBS snapshots
|
||||
- Configure database backups
|
||||
- Store backups in S3 with appropriate lifecycle policies
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
<Accordion title="Instance not accessible">
|
||||
**Problem**: Cannot access Chatwoot on port 3000
|
||||
|
||||
**Solutions**:
|
||||
- Check security group allows inbound traffic on port 3000
|
||||
- Verify instance is running and healthy
|
||||
- Check if Chatwoot service is running: `sudo systemctl status chatwoot`
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Application not starting">
|
||||
**Problem**: Chatwoot service fails to start
|
||||
|
||||
**Solutions**:
|
||||
- Check logs: `sudo journalctl -u chatwoot -f`
|
||||
- Verify environment variables are correctly set
|
||||
- Ensure database connection is working
|
||||
- Check disk space and memory usage
|
||||
</Accordion>
|
||||
|
||||
### Support Resources
|
||||
|
||||
- [AWS Support](https://aws.amazon.com/support/)
|
||||
- [Chatwoot Community Discord](https://discord.com/invite/cJXdrwS)
|
||||
- [GitHub Issues](https://github.com/chatwoot/chatwoot/issues)
|
||||
|
||||
---
|
||||
|
||||
The AWS Marketplace AMI provides a quick way to deploy Chatwoot with pre-configured settings. For production use, ensure you implement proper security measures and backup strategies.
|
||||
@@ -0,0 +1,277 @@
|
||||
---
|
||||
title: AWS Chatwoot deployment guide
|
||||
description: Deploy Chatwoot on AWS with a reference HA architecture
|
||||
sidebarTitle: Manual Install
|
||||
---
|
||||
|
||||
The following is a reference HA architecture guide for deploying Chatwoot on AWS. For a cloud-native deployment, use our [helm charts](https://github.com/chatwoot/charts) with AWS Elastic Kubernetes Service(EKS).
|
||||
|
||||
## Introduction
|
||||
|
||||
We will use the Linux installation script to get a chatwoot instance running. Also instead of
|
||||
relying on Redis, Postgres and Nginx installed in the same ec2; we will proceed to make use
|
||||
of managed AWS services for the same viz Elasticache, RDS, and ALB.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
1. AWS account
|
||||
2. Domain to use with Chatwoot
|
||||
|
||||
### Architecture
|
||||
|
||||
This guide will follow a standard 3-tier architecture on AWS.
|
||||
|
||||

|
||||
|
||||
## Network
|
||||
|
||||
### Create VPC
|
||||
|
||||
1. Sign in to the AWS console and pick the region you will deploy.
|
||||
2. Navigate to the VPC console and create a new vpc for chatwoot. At the `name` tag, enter
|
||||
`chatwoot-vpc` and use the CIDR block `10.0.0.0/16`.
|
||||
3. Leave the rest of the options as default and click on `Create VPC`.
|
||||
|
||||

|
||||
|
||||
### Subnets
|
||||
Create two public and private subnets in the vpc we created. Make sure to have them in different AZ's and have non-overlapping CIDR ranges.
|
||||
|
||||
1. Navigate to VPC > Subnets.
|
||||
2. Click on `Create Subnet`. Select the `chatwoot-vpc` we created before, name it as `chatwoot-public-1`, select an availability zone (for example, ap-south-1a), and the CIDR block as
|
||||
`10.0.0.0/24`.
|
||||
|
||||

|
||||
|
||||
3. Follow the same to create the remaining subnets.
|
||||
|
||||
| Name | Type | Availability Zone | CIDR Block |
|
||||
| ------------------- | -------- | ------------------ | ------------- |
|
||||
| `chatwoot-public-1` | public | `ap-south-1a` | `10.0.0.0/24` |
|
||||
| `chatwoot-public-2` | public | `ap-south-1b` | `10.0.1.0/24` |
|
||||
| `chatwoot-private-1` | private | `ap-south-1a` | `10.0.2.0/24` |
|
||||
| `chatwoot-private-2` | private | `ap-south-1b` | `10.0.3.0/24` |
|
||||
|
||||
4. After creating all subnets, enable `auto assign public ipv4 address` for public subnets under `Actions` > `Subnet Settings`.
|
||||
|
||||
### Internet Gateway
|
||||
|
||||
1. Select `Create Internet Gateway` , name it `chatwoot-igw`, and click create.
|
||||
2. Select it from the internet gateways list, choose actions and then select `Attach to VPC`.
|
||||
3. Choose `chatwoot-vpc` and click attach.
|
||||
|
||||

|
||||
|
||||
### NAT Gateway
|
||||
|
||||
Chatwoot app servers need to be deployed in the private subnet. For them to access the internet, we need to add NAT gateways to our public subnet and add a route from the private subnets.
|
||||
|
||||
1. Navigate the VPC dashboard and select `NAT gateways`.
|
||||
2. Click `Create NAT Gateway`.
|
||||
|
||||
1. Name it `chatwoot-nat-1`.
|
||||
2. Select the `chatwoot-public-1` subnet.
|
||||
3. Click on `Allocate Elastic IP`.
|
||||
4. Add additional tags as per your need.
|
||||
5. Click `Create NAT gateway`.
|
||||
|
||||

|
||||
|
||||
3. Follow the same to create a second NAT gateway (`chatwoot-nat-2`) and choose the `chatwoot-public-2` subnet.
|
||||
|
||||
### Route tables
|
||||
|
||||
The route table controls the inbound and outbound access for a subnet.
|
||||
|
||||
#### Public Route table
|
||||
|
||||
We will create route tables so that our public subnets can reach the internet via the Internet gateway.
|
||||
|
||||
Navigate to the VPC dashboard and select `Route Tables`.
|
||||
1. Click `Create route table`.
|
||||
2. Use the name `chatwoot-public-rt` and choose `chatwoot-vpc` under VPC.
|
||||
3. Click `Create`.
|
||||
|
||||

|
||||
|
||||
Next, we need to add a route to the internet gateway we created earlier(`chatwoot-igw`).
|
||||
|
||||
1. Select the `chatwoot-public-rt` route table from the list and click on `Edit routes` > `Add Route`.
|
||||
2. Set the destination as `0.0.0.0/0` and choose the target as `chatwoot-igw`. Click on `Save Changes`.
|
||||
|
||||
Also,
|
||||
|
||||
1. Select the `chatwoot-public-rt` route table from the list and click on `Subnet Associations` > `Edit subnet associations`.
|
||||
2. Select both the public subnets(`chatwoot-public-1`,`chatwoot-public-2`) and click `save`.
|
||||
|
||||
#### Private Route table
|
||||
|
||||
We will also create private route tables so that our private subnets can reach the internet via the NAT gateways.
|
||||
|
||||
1. Follow the above guide and create two private route tables, namely, `chatwoot-private-a` and `chatwoot-private-b`.
|
||||
2. Select the route tables and add a route to the NAT gateway in their respective availability zone.
|
||||
1. For `chatwoot-private-a`, add a route to `0.0.0.0/0` and target as `chatwoot-nat-1`.
|
||||
2. For `chatwoot-private-b`, add a route to `0.0.0.0/0` and target as `chatwoot-nat-2`.
|
||||
|
||||
Also,
|
||||
|
||||
1. Associate the private route tables with corresponding private subnets.
|
||||
1. For `chatwoot-private-a`, associate `chatwoot-private-1` subnet.
|
||||
2. For `chatwoot-private-b`, associate `chatwoot-private-2` subnet.
|
||||
|
||||
## Application Load Balancer (ALB)
|
||||
|
||||
Create an application load balancer to receive traffic on port 80 and 443 and distribute it across Chatwoot instances.
|
||||
|
||||
1. Navigate to the EC2 section and choose the Load Balancer section.
|
||||
2. Click `Create Load Balancer`.
|
||||
1. Choose `Application Load Balancer`.
|
||||
2. For the load balancer name, use `chatwoot-loadbalancer`.
|
||||
3. Select the scheme as `internet-facing` and IP address type as `IPv4`.
|
||||
4. For the network mapping section,
|
||||
1. Select `chatwoot-vpc`.
|
||||
2. Select the public subnets `chatwoot-public-1` and `chatwoot-public-2` under the mapping section.
|
||||
5. For the Security group section,
|
||||
1. Create a new security group, `chatwoot-loadbalancer-sg`.
|
||||
2. Add rules to allow HTTP and HTTPS traffic from anywhere(`0.0.0.0/0`, `::/0`).
|
||||
3. Also, add a rule to allow TCP on port 3000. This rule allows the load balancer health checks to pass since Chatwoot runs on port 3000.
|
||||
4. Add a rule to allow SSH traffic from the bastion security group we will create at the latter stage of the guide. Revisit this section after completing the bastion step.
|
||||
6. For the Listeners and routing section, create two listeners for 80 and 443. Set the forwarding rule on listener 80 to redirect `http` to `https`.
|
||||
1. Also, create a target group, `chatwoot-tg`, that will forward the requests to port `3000`(Chatwoot listens on this port).
|
||||
2. Add a health check to the endpoint `/api`. This endpoint is not authenticated and should return the application version.
|
||||
```
|
||||
{
|
||||
"version": "1.22.1",
|
||||
"timestamp": "2021-12-06 16:07:39"
|
||||
}
|
||||
```
|
||||
7. Add any necessary tags and click create.
|
||||
|
||||
Also, add if you have your domain on Route53 and use ACM to generate a certificate to use with ALB.
|
||||
|
||||
## Postgresql using AWS RDS
|
||||
|
||||
Chatwoot uses Postgres as a DB layer, and we will use Amazon RDS with a multi-AZ option for reliability.
|
||||
|
||||
### RDS security group
|
||||
|
||||
1. Navigate to EC2 > Security groups and create a new sg.
|
||||
2. Name it `chatwoot-rds-sg`.
|
||||
3. Select the `chatwoot-vpc` and add an inbound rule for postgres port with source `chatwoot-loadbalancer-sg`.
|
||||
|
||||
### RDS subnet group
|
||||
|
||||
1. Navigate to the RDS section and select subnet groups.
|
||||
2. Create `chatwoot-rds-group` and choose `chatwoot-vpc`.
|
||||
3. Select both az's and the private subnets.
|
||||
|
||||
### RDS
|
||||
|
||||
1. Select create a database.
|
||||
2. Use standard create and choose the Postgres engine.
|
||||
3. Use the production template, and create a Postgres master username and password.
|
||||
4. Enable Multi-AZ deployment.
|
||||
5. Select `chatwoot-vpc` and select the rds security group we created earlier.
|
||||
6. Enable password authentication.
|
||||
7. Click create.
|
||||
8. After completing the creation, note down the hostname, username, and password. We will need this to configure Chatwoot.
|
||||
|
||||
## Redis using AWS Elasticache
|
||||
|
||||
1. Follow similar steps like the rds to create Redis security and subnet groups.
|
||||
2. Create the Redis cluster with a multi-AZ option.
|
||||
|
||||
## Creating Bastion servers
|
||||
|
||||
Create bastion servers in both public subnets. These servers will be used to ssh into Chatwoot servers in private subnets.
|
||||
|
||||
1. Navigate to the EC2 dashboard and click launch instance.
|
||||
2. Use an `Ubuntu 20.04 image` with a `t3.micro` type.
|
||||
3. Choose `chatwoot-vpc` and subnet `chatwoot-public-1`.
|
||||
4. Name it `chatwoot-bastion-a`.
|
||||
5. Add a new sg, `chatwoot-bastion-sg` and enable ssh access from anywhere.
|
||||
6. Leave the rest as defaults and click launch.
|
||||
7. Once the instance is up, try to SSH into the instance.
|
||||
|
||||
Repeat and create another bastion, `chatwoot-bastion-b` in the other AZ.
|
||||
|
||||
## Install Chatwoot
|
||||
|
||||
1. Navigate to the EC2 section, and click on launch instance.
|
||||
2. Use an `Ubuntu 20.04 image` with a `c5.xlarge` instance type.
|
||||
3. Choose the chatwoot-vpc and select the private subnet `chatwoot-private-1`.
|
||||
4. Disable auto-assign public IP and increase the storage of root volume to 60 GB.
|
||||
5. Add necessary tags. Set the `Name` tag to `chatwoot`.
|
||||
6. Select the load balancer security group and click launch.
|
||||
7. SSH into the bastion server and, from there, ssh to the chatwoot instance we created.
|
||||
8. Switch to the `root` user.
|
||||
9. Download the chatwoot Linux installation script.
|
||||
|
||||
```bash
|
||||
wget https://get.chatwoot.app/linux/install.sh
|
||||
chmod 755 install.sh
|
||||
```
|
||||
|
||||
10. Run the script.
|
||||
|
||||
```bash
|
||||
./install.sh --install
|
||||
```
|
||||
|
||||
## Configure Chatwoot
|
||||
|
||||
11. Once the installation is complete, switch to the chatwoot user and navigate to the chatwoot folder. Edit the .env file and replace the Postgres and Redis credentials with RDS and elasticache values.
|
||||
|
||||
```bash
|
||||
sudo -i -u chatwoot
|
||||
cd chatwoot
|
||||
vi .env
|
||||
```
|
||||
|
||||
12. Run the db migration.
|
||||
|
||||
```bash
|
||||
RAILS_ENV=production bundle exec rake db:prepare
|
||||
```
|
||||
|
||||
13. Also modify the other necessary environment variable for your chatwoot setup. Refer to https://www.chatwoot.com/docs/self-hosted/deployment/linux-vm#configure-the-required-environment-variables
|
||||
|
||||
14. Restart the `chatwoot` service.
|
||||
|
||||
```bash
|
||||
sudo cwctl --restart
|
||||
```
|
||||
|
||||
## Verify login
|
||||
|
||||
15. Add this instance to the target group attached to the alb.
|
||||
16. Navigate to your chatwoot domain to see if everything is working.
|
||||
|
||||
## Create a custom AMI
|
||||
|
||||
1. If you are getting the onboarding page, complete the signup and verify the installation.
|
||||
2. Voila !! Your chatwoot instance is up.
|
||||
3. If everything looks good, proceed to create an ami from this instance and name it `chatwoot-base-ami`.
|
||||
|
||||
## Auto Scaling Groups (ASG)
|
||||
|
||||
1. Create a launch configuration using the above base image.
|
||||
2. Proceed to create an auto-scaling group from this launch config.
|
||||
3. Set the minimum and desired capacity to 2 and the maximum to 4. Modify this as per your requirement.
|
||||
4. Create a scaling policy based on CPU utilization.
|
||||
5. At this point, we are good to terminate the instance we created earlier.
|
||||
6. Check the load balancer or target group to verify if two new chatwoot instances have come up.
|
||||
7. That's it.
|
||||
|
||||
## Monitoring
|
||||
|
||||
1. Refer to https://www.chatwoot.com/docs/self-hosted/monitoring/apm-and-error-monitoring
|
||||
|
||||
## Updating Chatwoot
|
||||
|
||||
1. log in to one of the application servers and complete the update instructions. Run migrations if needed. Refer to https://www.chatwoot.com/docs/self-hosted/deployment/linux-vm#upgrading-to-a-newer-version-of-chatwoot
|
||||
2. Create a new ami and update the launch config.
|
||||
|
||||
## Conclusion
|
||||
|
||||
This document is a reference guideline for an HA chatwoot architecture on AWS. Modify or build upon this to suit your requirements.
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
title: Azure Chatwoot deployment guide
|
||||
description: Deploy Chatwoot on a single VM in Azure
|
||||
sidebarTitle: Azure
|
||||
---
|
||||
|
||||
This guide will deploy chatwoot on a single VM in Azure. For a cloud native deployment, use our [helm charts](https://github.com/chatwoot/charts) with Azure Kubernetes Service(AKS).
|
||||
|
||||
<Note>
|
||||
This guide is a work in progress and your mileage may vary.
|
||||
</Note>
|
||||
|
||||
## Create a Virtual Machine
|
||||
|
||||
1. Login to the Azure portal and choose Virtual Machines.
|
||||
2. Select create a VM from scratch.
|
||||
3. In the Basics tab, create a subscription and a new resource group.
|
||||
4. Name the virtual machine as `chatwoot` and select your preferred region.
|
||||
5. Select `Ubuntu 20.04 LTS - Gen2` as the image.
|
||||
6. For instance size, we recommend the type `Standard_D4s_v3`(4vCPU, 16GB RAM).
|
||||
7. Under authentication, leave the defaults and create a new key pair if needed.
|
||||
8. Allow HTTP, HTTPS and SSH under inbound port rules.
|
||||
9. Click next and leave the defaults for Disks, Networking, Management, Advanced and Tags section.
|
||||
10. Select `Review + create` to spin up the VM.
|
||||
|
||||

|
||||
|
||||
## Install Chatwoot
|
||||
|
||||
1. SSH into the instance created from your local machine or create a bastion in azure to ssh via the browser.
|
||||
2. Follow the linux VM instructions at https://www.chatwoot.com/docs/self-hosted/deployment/linux-vm.
|
||||
3. Woot! Woot! Your Chatwoot Instance is ready and can be accessed at `http://<your-instance-ip>:3000`. Or if you completed the domain setup during the installation, chatwoot should be available at `https://<your-domain>`.
|
||||
|
||||
<Note>
|
||||
Browser access via port 3000 will only work if enabled under inbound rules.
|
||||
</Note>
|
||||
|
||||
## Configure Chatwoot
|
||||
|
||||
1. Follow the Chatwoot docs to configure your domain, email and other parameters you need.
|
||||
https://www.chatwoot.com/docs/self-hosted/deployment/linux-vm#configure-the-required-environment-variables
|
||||
@@ -0,0 +1,106 @@
|
||||
---
|
||||
title: Caprover Chatwoot Production deployment guide
|
||||
description: Deploy Chatwoot using Caprover's one-click application management
|
||||
sidebarTitle: Caprover
|
||||
---
|
||||
|
||||
## Caprover Overview
|
||||
|
||||
Caprover is an extremely easy to use application server management tool. It is blazing fast and uses Docker under the hood. Chatwoot has been made available as a one-click app in Caprover, and the deployment process is straightforward.
|
||||
|
||||
<Note>
|
||||
This is a community contributed installation setup. This will only have community support for any issues in future.
|
||||
</Note>
|
||||
|
||||
## Setup Chatwoot Using Caprover
|
||||
|
||||
### 1. Install Caprover on your VM
|
||||
|
||||
Finish your Caprover installation by referring to [Getting started guide](https://caprover.com/docs/get-started.html).
|
||||
|
||||
### 2. Install Chatwoot
|
||||
|
||||
Chatwoot is available in the one-click apps option in Caprover. Search for Chatwoot in the list of one-click apps. Replace the default `version` with the latest `version` of chatwoot. Use appropriate values for the Postgres and Redis passwords and click install. It should only take a few minutes.
|
||||
|
||||
### 3. Finish the setup
|
||||
|
||||
Head over to the `web` service in the Caprover applications and enable `Websocket Support` in the HTTP settings to true. You could also enable `https` for the application.
|
||||
|
||||

|
||||
|
||||
### 4. Configure environment variables
|
||||
|
||||
Caprover will take care of Postgres and Redis installation, along with the app and worker servers. We would advise you to replace the Database/Redis services with managed/standalone servers once you start scaling.
|
||||
|
||||
Also, ensure to set the appropriate environment variables for email, Object Store service etc. using our [Environment variables guide](/docs/self-hosted/configuration/environment-variables)
|
||||
|
||||
<Note>
|
||||
Chatwoot requires websocket support. Do enable it from `chatwoot-web` settings page in Caprover.
|
||||
</Note>
|
||||
|
||||
## Upgrading Chatwoot installation
|
||||
|
||||
To update your chatwoot installation to the latest version in Caprover, run the following command in the deployment tab for web and worker in `method 5: deploy captain-definition`. Make sure to replace `[DESIRED VERSION HERE]` with the current latest stable version. Check [here](https://www.chatwoot.com/changelog/) and [here](https://hub.docker.com/r/chatwoot/chatwoot/tags) for possible version numbers first.
|
||||
|
||||
### web
|
||||
|
||||
```json
|
||||
{
|
||||
"schemaVersion": 2,
|
||||
"dockerfileLines": [
|
||||
"FROM chatwoot/chatwoot:[DESIRED VERSION HERE]",
|
||||
"RUN chmod +x docker/entrypoints/rails.sh",
|
||||
"ENTRYPOINT [\"docker/entrypoints/rails.sh\"]",
|
||||
"CMD bundle exec rake db:chatwoot_prepare; bundle exec rails s -b 0.0.0.0 -p 3000"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### worker
|
||||
|
||||
```json
|
||||
{
|
||||
"schemaVersion": 2,
|
||||
"dockerfileLines": [
|
||||
"FROM chatwoot/chatwoot:[DESIRED VERSION HERE]",
|
||||
"RUN chmod +x docker/entrypoints/rails.sh",
|
||||
"ENTRYPOINT [\"docker/entrypoints/rails.sh\"]",
|
||||
"CMD bundle exec sidekiq -C config/sidekiq.yml"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Accessing Rails Console
|
||||
|
||||
Login to the server where you have caprover installed and execute the following commands.
|
||||
|
||||
```bash
|
||||
# access the shell inside the container
|
||||
docker exec -it $(docker ps --filter name=srv-captain--chatwoot-web -q) /bin/sh
|
||||
# start rails console
|
||||
RAILS_ENV=production bundle exec rails c
|
||||
```
|
||||
|
||||
## Common Errors
|
||||
|
||||
### API requests failing with "You need to sign in or sign up before continuing."
|
||||
|
||||
Nginx by default strip of headers with `_` . Head over to the Nginx configuration option in caprover under the Chatwoot web and add the following directive.
|
||||
|
||||
Access the Caprover `web dashboard` > `Apps` > `Apps Edit` > `Edit Default Nginx Configurations`. Refer https://caprover.com/docs/nginx-customization.html for more details.
|
||||
|
||||
```nginx
|
||||
# Nginx strips out underscore in headers by default
|
||||
# Chatwoot relies on underscore in headers for API
|
||||
# Make sure that the config is set to on.
|
||||
underscores_in_headers on;
|
||||
```
|
||||
|
||||
### Issues related to storage persistance
|
||||
|
||||
Please setup a cloud storage like s3 or gcs bucket or any s3 api compatible service as the active storage service.
|
||||
Caprover installation needs this for storage persistance. Refer the [storage guide](/docs/self-hosted/deployment/storage/supported-providers).
|
||||
|
||||
## Further references
|
||||
|
||||
- https://isotropic.co/how-to-install-chatwoot-to-a-digitalocean-droplet/
|
||||
@@ -0,0 +1,75 @@
|
||||
---
|
||||
title: Deploy Chatwoot to Clever Cloud
|
||||
description: Deploy Chatwoot on Clever Cloud PaaS platform
|
||||
sidebarTitle: Clever Cloud
|
||||
---
|
||||
|
||||
Clever Cloud is a PaaS platform where you can deploy your applications with ease. To setup Chatwoot on Clever Cloud, you can follow the steps described below.
|
||||
|
||||
<Note>
|
||||
This is a community contributed installation setup. This will only have community support for any issues in future.
|
||||
</Note>
|
||||
|
||||
## 1. Create CleverCloud application
|
||||
|
||||
- Login to Clever Cloud dashboard
|
||||
- Click on create an application
|
||||
- Select your deployment type (> 2GB recommended)
|
||||
- Provide an app name and select the zone
|
||||
|
||||
## 2. Select addons
|
||||
|
||||
Chatwoot requires PostgreSQL and Redis to function properly. Select Postgres and Redis from CleverCloud addons.
|
||||
|
||||
- Copy connection URI from Postgres Addon and set `DATABASE_URL` environment variable
|
||||
- Make sure you have set REDIS_URL
|
||||
|
||||
## 3. Setup Clever cloud origin
|
||||
|
||||
- Clone Chatwoot project from Github
|
||||
|
||||
```bash
|
||||
git clone git@github.com:chatwoot/chatwoot.git
|
||||
```
|
||||
|
||||
- Set Clever Cloud origin
|
||||
|
||||
```bash
|
||||
git remote add clever git+ssh://git@<id>.clever-cloud.com/<app-name>.git
|
||||
```
|
||||
|
||||
## 4. Setup build hooks
|
||||
|
||||
To install the dependencies, you have to setup builds hooks. Set the following in the environment variables of the application.
|
||||
|
||||
```bash
|
||||
CC_POST_BUILD_HOOK="RAILS_ENV=production rails assets:precompile"
|
||||
CC_PRE_BUILD_HOOK="pnpm install"
|
||||
CC_PRE_RUN_HOOK="rake db:chatwoot_prepare"
|
||||
```
|
||||
|
||||
## 5. Push the latest changes
|
||||
|
||||
Push the latest code from your local machine to Clever Cloud.
|
||||
|
||||
```bash
|
||||
git push clever master
|
||||
```
|
||||
|
||||
Voila! After the deployment, you would be able to access the application.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Make sure you have the following environment variables configured in the application.
|
||||
|
||||
```bash
|
||||
CC_POST_BUILD_HOOK="RAILS_ENV=production rails assets:precompile"
|
||||
CC_PRE_BUILD_HOOK="pnpm install"
|
||||
CC_PRE_RUN_HOOK="rake db:chatwoot_prepare"
|
||||
DATABASE_URL="<postgres-addon-url>"
|
||||
FRONTEND_URL="<clever-cloud-app-url>"
|
||||
PORT="8080"
|
||||
RAILS_ENV="production"
|
||||
REDIS_URL="<redis-addon-url>"
|
||||
SECRET_KEY_BASE="<long-secret-key>"
|
||||
```
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
title: Cloudron Chatwoot deployment guide
|
||||
description: Deploy Chatwoot using Cloudron's 1-click app platform
|
||||
sidebarTitle: Cloudron
|
||||
---
|
||||
|
||||
## Cloudron Overview
|
||||
|
||||
[Cloudron](https://cloudron.io) is a platform that makes it easy to install, manage and secure web apps on your server. Chatwoot is now available as a 1-click app in the Cloudron app store and the installation is blazing fast.
|
||||
|
||||
<Note>
|
||||
This is a community contributed installation setup. This will only have community support for any issues in future.
|
||||
</Note>
|
||||
|
||||
## Setup Chatwoot using Cloudron
|
||||
|
||||
### 1. Install Cloudron on your server
|
||||
|
||||
Finish your Cloudron installation by following the instructions at [Get Cloudron](https://www.cloudron.io/get.html).
|
||||
|
||||
### 2. Install Chatwoot
|
||||
|
||||
Once Cloudron installation is complete, login to your cloudron web portal and click on the appstore icon. Search for `Chatwoot` and click install. That's it. Chatwoot should be up and running in a minute or two.
|
||||
|
||||
Direct link to the cloudron app store listing --> https://www.cloudron.io/store/com.chatwoot.cloudronapp.html
|
||||
|
||||
### 3. Finish the setup
|
||||
|
||||
Navigate to your chatwoot domain and complete the onboarding setup.
|
||||
|
||||
### 4. Configure environment variables
|
||||
|
||||
Cloudron will take care of Postgres and Redis installation, along with the app and worker processes. We would advise you to replace the Database/Redis services with managed/standalone servers once you start scaling.
|
||||
|
||||
Also, ensure to set the appropriate environment variables for email, Object Store service etc. using our [Environment variables guide](/docs/self-hosted/configuration/environment-variables).
|
||||
|
||||
Custom environment variables can be set in `/app/data/env` using the Cloudron File manager. Be sure to reboot the app after making any changes.
|
||||
|
||||
## Upgrading Chatwoot installation
|
||||
|
||||
Chatwoot follows a monthly release pattern with a new release every 15th of the month. Use the built in [Cloudron app updates](https://docs.cloudron.io/updates/) to stay on the latest version. Read about the changelog [here](https://www.chatwoot.com/changelog/).
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
title: DigitalOcean Chatwoot deployment guide
|
||||
description: Deploy Chatwoot on a single droplet in DigitalOcean
|
||||
sidebarTitle: DigitalOcean
|
||||
---
|
||||
|
||||
This guide will deploy chatwoot on a single droplet in DigitalOcean. For a cloud native deployment, go with
|
||||
our [1-click k8s app on DigitalOcean Marketplace](https://marketplace.digitalocean.com/apps/chatwoot).
|
||||
|
||||
## Create a Droplet (VM)
|
||||
|
||||
1. Login to DigitalOcean console and choose create droplet.
|
||||
2. Choose `Ubuntu 20.04` image.
|
||||
3. Create an instance with a minimum of 4vCPU and 8GB RAM.
|
||||
4. Make sure to choose the datacenter region you want to deploy.
|
||||
5. Under authentication, choose your SSH key or create a new one. This is important as you will need
|
||||
this key to complete the next section.
|
||||
6. Click create.
|
||||
|
||||

|
||||
|
||||
## Install Chatwoot
|
||||
|
||||
1. SSH into the droplet created above.
|
||||
2. Follow the linux VM instructions at https://www.chatwoot.com/docs/self-hosted/deployment/linux-vm.
|
||||
3. Woot! Woot! Your Chatwoot Instance is ready and can be accessed at `http://<your-droplet-ip>:3000`. Or if you completed the domain setup during the installation, chatwoot should be available at `https://<your-domain>`
|
||||
|
||||
## Configure Chatwoot
|
||||
|
||||
1. Follow the Chatwoot docs to configure your domain, email and other parameters you need.
|
||||
https://www.chatwoot.com/docs/self-hosted/deployment/linux-vm#configure-the-required-environment-variables
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
title: Deploying Chatwoot on Easypanel
|
||||
description: Deploy Chatwoot using Easypanel's modern server control panel
|
||||
sidebarTitle: Easypanel
|
||||
---
|
||||
|
||||
[Easypanel](https://easypanel.io) it's a modern server control panel. You can use it to deploy Chatwoot on your own server.
|
||||
|
||||
[](https://easypanel.io/docs/templates/chatwoot)
|
||||
|
||||
## Instructions
|
||||
|
||||
1. Create a VM that runs Ubuntu on your cloud provider.
|
||||
2. Install Easypanel using the instructions from the website.
|
||||
3. Create a new project.
|
||||
4. Install Chatwoot using the dedicated template.
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
title: Elestio Chatwoot fully managed deployment guide
|
||||
description: Deploy Chatwoot with Elestio's fully managed platform
|
||||
sidebarTitle: Elestio
|
||||
---
|
||||
|
||||
## Deploy to Elestio with one-click
|
||||
|
||||
[](https://elest.io/open-source/chatwoot)
|
||||
|
||||
## Select the providers
|
||||
|
||||
- Select cloud service provider of your choice.
|
||||
- Choose region of your choice
|
||||
- Select service plan. The smallest one offers 1 CPU, 2 GB RAM etc.
|
||||
- Confirm the details and hit "Next"
|
||||
|
||||

|
||||
|
||||
## Configure
|
||||
|
||||
- Select the support level
|
||||
- Name your application
|
||||
- Add admin email (You can add email you want to access your application from)
|
||||
- Click "Create Service"
|
||||
- Here you also get option to copy your terraform config (Optional)
|
||||
|
||||
## Use Chatwoot
|
||||
|
||||
- Click on "Display Admin UI"
|
||||
- Go to Admin Ui link provided
|
||||
- Add username and password provided on dashboard.
|
||||
|
||||

|
||||
|
||||
## Update Chatwoot
|
||||
|
||||
- Go to Overview section in your Chatwoot service
|
||||
- Click "Change version" inside Software section
|
||||
- Choose the latest version or the version of your choice.
|
||||
- Additionally update the configs or restart the instance with single clink under same section
|
||||
|
||||

|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
title: GCP Chatwoot deployment guide
|
||||
description: Deploy Chatwoot on a single VM in GCP
|
||||
sidebarTitle: GCP
|
||||
---
|
||||
|
||||
This guide will deploy chatwoot on a single VM in GCP. For a cloud native deployment, use our [helm charts](https://github.com/chatwoot/charts) with Google Kubernetes Engine(GKE).
|
||||
|
||||
<Note>
|
||||
This guide is a work in progress and your mileage may vary.
|
||||
</Note>
|
||||
|
||||
## Create Compute Engine (VM)
|
||||
|
||||
1. Navigate to VM > Compute Engine window.
|
||||
2. Create an instance with a minimum of 4vCPU and 8GB RAM.(N2 General-Purpose)
|
||||
3. Make sure to select the correct region you want to deploy.
|
||||
4. Choose `Ubuntu 20.04` as your OS with a 120GB disk.
|
||||
5. Click create.
|
||||
|
||||

|
||||
|
||||
## Install Chatwoot
|
||||
|
||||
1. SSH into the instance created.
|
||||
2. Follow the linux VM instructions at https://www.chatwoot.com/docs/self-hosted/deployment/linux-vm.
|
||||
3. Woot! Woot! Your Chatwoot Instance is ready and can be accessed at `http://<your-instance-ip>:3000`. Or if you completed the domain setup during the installation, chatwoot should be available at `https://<your-domain>`
|
||||
|
||||
## Configure Chatwoot
|
||||
|
||||
1. Follow the Chatwoot docs to configure your domain, email and other parameters you need.
|
||||
https://www.chatwoot.com/docs/self-hosted/deployment/linux-vm#configure-the-required-environment-variables
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
title: Heroku Chatwoot Production Deployment Guide
|
||||
description: Deploy Chatwoot on Heroku with one-click deployment
|
||||
sidebarTitle: Heroku
|
||||
---
|
||||
|
||||
# Deploying on Heroku
|
||||
|
||||
<Note>
|
||||
Heroku has discontinued free dynos, postgres and redis. [Chatwoot will use basic/mini plans](https://blog.heroku.com/new-low-cost-plans) for all new Heroku deployments going forward.
|
||||
</Note>
|
||||
|
||||
Deploy Chatwoot on Heroku through the following steps:
|
||||
|
||||
1. Click on the [one click deploy button](https://heroku.com/deploy?template=https://github.com/chatwoot/chatwoot/tree/master) and deploy your app.
|
||||
2. Go to the **Resources** tab in the Heroku app dashboard and ensure the worker dynos is turned on.
|
||||
3. Head over to **Settings** tab in Heroku app dashboard and click **Reveal Config Vars**.
|
||||
4. Configure the environment variables for [mailer](/self-hosted/configuration/environment-variables#configure-emails) and [storage](/self-hosted/deployment/storage/supported-providers) as per the [documentation](/self-hosted/configuration/environment-variables).
|
||||
5. Head over to `yourapp.herokuapp.com` and enjoy using Chatwoot.
|
||||
|
||||
<iframe
|
||||
frameBorder="0"
|
||||
scrolling="no"
|
||||
marginHeight="0"
|
||||
marginWidth="0"
|
||||
width="100%"
|
||||
height="443"
|
||||
type="text/html"
|
||||
src="https://www.youtube-nocookie.com/embed/iN2Dl0QkvEg?autoplay=0&fs=0&iv_load_policy=3&showinfo=1&rel=0&cc_load_policy=0&start=0&end=0&origin=https://youtubeembedcode.com">
|
||||
</iframe>
|
||||
|
||||
## Updating the deployment on Heroku
|
||||
|
||||
Whenever a new version is out for Chatwoot, you update your Heroku deployment through following steps:
|
||||
|
||||
1. In the **Deploy** tab, choose `GitHub` as the deployment option.
|
||||
2. Connect Chatwoot repo to the app.
|
||||
3. Head over to the manual deploy option, choose `master` branch and hit **Deploy**.
|
||||
|
||||
## Known Limitations
|
||||
|
||||
1. **Dyno Sleep**: If you are on a free tier and you don't access the application for a while Heroku will put your dynos to sleep. You can fix this by upgrading the dynos to paid tier.
|
||||
|
||||
2. **Ephemeral Storage**: Heroku has an "ephemeral" hard disk. The files uploaded to Chatwoot would not persist after the application is restarted. By default, Chatwoot uses local disk as the upload destination. To overcome this problem, you will have to [configure a cloud storage](/self-hosted/deployment/storage/supported-providers).
|
||||
|
||||
3. **Build Version**: If the build version is shown as unknown on the settings page, enable the runtime dyno metadata feature. To enable, use:
|
||||
```bash
|
||||
heroku labs:enable runtime-dyno-metadata -a <app-name>
|
||||
```
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user