Compare commits

...
Author SHA1 Message Date
Muhsin KelothandGitHub 141595977a Merge branch 'feat/notion-oauth' into feat/notion-api 2025-06-24 15:52:41 +05:30
Muhsin KelothandGitHub 8a39c4957a Merge branch 'develop' into feat/notion-oauth 2025-06-24 13:32:40 +05:30
Sivin VargheseandGitHub 9edfb1e902 fix: Disable push notifications (#11786)
# Pull Request Template

## Description

Fixes
[CW-4512](https://linear.app/chatwoot/issue/CW-4512/cant-turn-off-push-notification-toggle)
https://github.com/chatwoot/chatwoot/issues/11760

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)


## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
2025-06-24 13:31:39 +05:30
Sivin VargheseandGitHub be6bc88f80 fix: Translation issue in reports table headers on reload (#11793)
# Pull Request Template

## Description

This PR fixes the translation inconsistency in the reports pages, where
table-column headers reverted to English after a page reload.

**Cause**
The components defined the columns array statically, so header labels
were translated only once during component creation. On reload, the
table showed the default system language (English) until the user’s
locale finished loading.

**Solution**
Replaced the static columns array with a computed property and passed it
to `Tanstack useVueTable` via a getter. This makes the headers reactive,
ensuring they automatically update whenever the locale changes and
remain translated after every reload.

Fixes
https://linear.app/chatwoot/issue/CW-4539/translation-issue-in-reports-page-table-header-on-reload

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)


## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
2025-06-24 12:38:05 +05:30
Shivam MishraandGitHub 2f1f734b26 Merge branch 'develop' into feat/notion-oauth 2025-06-23 20:58:00 +05:30
Shivam Mishra 2f1b8f1580 chore: update copy 2025-06-23 20:56:05 +05:30
Shivam Mishra b58f462b88 feat: notion feature flag 2025-06-23 20:48:39 +05:30
Shivam Mishra afc5a9539c feat: add notion controller 2025-06-23 20:46:43 +05:30
PranavandGitHub ea4477ccde fix: Update ActiveStorage::FileNotFoundError error and fix the captain condition in audio transcription (#11779)
Update the error to `ActiveStorage::FileNotFoundError`. Fix the
condition to enable audio transcription and added a spec for it.
2025-06-20 13:20:55 -07:00
Tanmay Deep SharmaandGitHub a2857cac38 feat: Expose custom attributes in conversation to Captain (#11769)
# Pull Request Template

## Linear Link

https://linear.app/chatwoot/issue/CW-4480/expose-custom-attributes-in-conversation-to-captain-so-that-it-can

## Description

Expose custom attributes in conversation to Captain so that it can
provide more information

## Type of change

- [ ] New feature (non-breaking change which adds functionality)

## How Has This Been Tested?

![Screenshot 2025-06-19 at 9 50
45 AM](https://github.com/user-attachments/assets/5216e116-bd89-4d0c-b6a6-416b082638f7)
![Screenshot 2025-06-19 at 9 50
40 AM](https://github.com/user-attachments/assets/a81cb4ad-973b-405c-b188-295d1acce814)



## Checklist:

- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
2025-06-20 10:58:00 -07:00
Vishnu NarayananandGitHub 293a29ec98 chore: refactor account deletion email (#11772)
- Refactor `Marked for deletion` to be `Deletion due at` in the compliance email
2025-06-20 13:05:14 +05:30
Muhsin KelothandGitHub ddada56753 fix: Handle Instagram user consent error for first-time message recipients (#11773)
Fixes https://linear.app/chatwoot/issue/CW-4510/koalafacebookclienterror-type-oauthexception-code-9010-message-9010-no
2025-06-20 09:17:56 +05:30
Muhsin KelothandGitHub 578e2ee8db fix: Respect messaging window constraints for auto-resolve messages (#11757) 2025-06-19 21:30:49 +05:30
Sivin VargheseandGitHub b683973e79 fix: Resolve styling issues in multiselect (#11728) 2025-06-19 21:28:12 +05:30
Sivin VargheseandGitHub 2cfca6008b fix: Incorrect conversation count shown for filters/folders after idle period (#11770) 2025-06-19 14:05:18 +05:30
d2f5311400 fix: Disable custom context menu on img tags (#11762)
# Pull Request Template

## Description

Fixes #11761. See the issue for the details.

## Type of change

Please delete options that are not relevant.

- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality not to work as expected)
- [ ] This change requires a documentation update

## How Has This Been Tested?

I deployed a modified version of Chatwoot with this patch and tested.

## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [x] I have made corresponding changes to the documentation (not
applicable)
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works (not sure how to do this)
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream
modules

---------

Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
2025-06-18 14:51:23 -07:00
Shivam Mishra 04b180b572 chore: run migration 2025-06-18 21:40:38 +05:30
Shivam Mishra c25b927d59 feat: add max crawl depth 2025-06-18 21:39:47 +05:30
Shivam Mishra 5e4fd23cd5 feat: add notion crawling
This crawling is only one level deep
2025-06-18 21:30:45 +05:30
Shivam Mishra 1c19ef0940 feat: include URL in presenter 2025-06-18 21:30:27 +05:30
Shivam Mishra 7207fda7b8 feat: update document doc 2025-06-18 21:11:43 +05:30
Shivam Mishra 5bf4eb0b48 feat: update processor service 2025-06-18 19:59:38 +05:30
Shivam Mishra c5e27e008d test: notion presenter 2025-06-18 19:59:26 +05:30
Shivam Mishra 3bd06fd2c0 refactor: separate presenter 2025-06-18 19:52:52 +05:30
Shivam Mishra 443f68bff6 feat: extract child pages 2025-06-18 19:50:08 +05:30
Shivam Mishra 7a5a450577 feat: format response 2025-06-18 19:44:21 +05:30
Shivam Mishra aa94b17e69 feat: add notion controller 2025-06-18 19:44:16 +05:30
Shivam Mishra 5604e59059 test: processor service 2025-06-18 19:25:57 +05:30
Shivam Mishra 1bdd045417 style: rubocop fixes 2025-06-18 19:25:57 +05:30
Shivam Mishra 3f515d103d feat: notion processor service 2025-06-18 19:25:57 +05:30
Shivam Mishra 94453c8e8b test: page blocks end point 2025-06-18 19:25:57 +05:30
Shivam Mishra 62dbf65f99 feat: allow converting a page to markdown 2025-06-18 19:25:57 +05:30
Shivam Mishra 7d66691844 feat: add page blocks endpoint 2025-06-18 19:25:57 +05:30
Shivam Mishra 6b5e4641ba feat: update auth 2025-06-18 19:25:56 +05:30
Shivam Mishra a5a4b15e5b feat: add notion API 2025-06-18 19:25:56 +05:30
Shivam Mishra a9224a6c67 feat: add Notion version 2025-06-18 19:25:56 +05:30
Shivam Mishra 5a457305f7 feat: add notion to allowed config 2025-06-18 19:25:56 +05:30
Shivam Mishra 2ec1f4645e feat: notion settings 2025-06-18 19:25:56 +05:30
Shivam Mishra bc67705982 feat: add notion page 2025-06-18 19:25:56 +05:30
Shivam Mishra af5edcf4db feat: add notion oauth flow 2025-06-18 19:25:56 +05:30
Shivam Mishra a3333eae9c feat: add notion concern 2025-06-18 19:25:56 +05:30
Shivam Mishra f4b6ad6c90 feat: add notion integration app 2025-06-18 19:25:56 +05:30
f6dbbf0d90 refactor: use state-based authentication (#11690)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2025-06-18 17:39:06 +05:30
Sojan 768fa9ab1b Merge branch 'release/4.3.0' into develop 2025-06-17 17:29:35 -07:00
Sojan 89bcef4623 Bump version to 4.3.0 2025-06-17 17:28:31 -07:00
Chatwoot BotandGitHub 25a0c9ed91 chore: Update translations (#11724) 2025-06-17 17:13:31 -07:00
PranavandGitHub dc77b5bb2b feat: Enable audio transcriptions for self hosted instances (#11755)
- Enable audio transcriptions feature for self hosted instances
2025-06-17 16:54:43 -07:00
5487d4c615 fix: Include private channels in Slack integration pagination (#11751)
Fixes
https://linear.app/chatwoot/issue/CW-4507/slack-integration-not-showing-private-channels
  ## Problem

When the Slack workspace has many channels (requiring multiple API
requests to fetch all of them), our system was only looking for private
channels in the first batch of results. All subsequent batches were
missing the instruction to include private channels, so they only
returned public channels.

  ## Root Cause
- Initial API call correctly specified `types:
'public_channel,private_channel'`
- Pagination loop only passed `cursor` parameter, omitting `types` and
`exclude_archived`
  - Subsequent pages defaulted to public channels only

  ## Changes
- Fixed parameter formatting in `types` (removed space:
`'public_channel, private_channel'` →
`'public_channel,private_channel'`)
- Added missing `types` and `exclude_archived` parameters to paginated
`conversations_list` calls

---------

Co-authored-by: Sojan Jose <sojan@pepalo.com>
2025-06-17 16:44:56 -07:00
Sojan JoseandGitHub b4b2b0bdc2 feat: Hide installation identifier (#11722)
<img width="1779" alt="Screenshot 2025-06-12 at 7 21 59 PM"
src="https://github.com/user-attachments/assets/a4c54d7f-4f5f-4d72-a81b-be4d88066549"
/>




## Summary
- Hides installation identifier by default so that users don't
accidentally expose it in screenshots.
2025-06-17 15:45:40 -07:00
Sojan JoseandGitHub 16dbcabaac feat: Enable super admin deletion of agent bots (#11748)
## Summary
- Allow super admins to remove agent bots
2025-06-17 15:41:29 -07:00
ruslanandGitHub b87b7972c1 feat(channel): add support for Telegram Business bots (#10181) (#11663)
Added support for Telegram Business bots. Telegram webhooks from such bots include the business_message field, which we transform into a standard message for Chatwoot. This PR also modifies how we handle replies, attachments, and image uploads when working with Telegram Business bots.

demo: https://drive.google.com/file/d/1Yz82wXBVRtb-mxjXogkUju4hlJbt3qyh/view?usp=sharing&t=4

Fixes #10181
2025-06-16 20:35:23 -07:00
Sivin VargheseandGitHub 149dab239a chore: Prevent count flicker on loading more conversations (#11726) 2025-06-16 15:38:27 +05:30
Muhsin KelothandGitHub f4381e3b5d feat: Add activity messages for linear actions (#11654) 2025-06-13 11:57:11 +05:30
Sivin VargheseandGitHub 58380c6d01 fix: Hide Copilot launcher on inbox view message screen (#11723)
# Pull Request Template

## Description

This PR will hide Copilot launcher button on inbox view message screen.

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## How Has This Been Tested?

### Screenshots

**Before**
<img width="1425" alt="image"
src="https://github.com/user-attachments/assets/319a010c-d6e0-4ac0-b52f-7a4263e62e7f"
/>

**After**
<img width="1442" alt="image"
src="https://github.com/user-attachments/assets/73ed796e-97ab-4d34-bf65-43a439d7e66b"
/>
<img width="1442" alt="image"
src="https://github.com/user-attachments/assets/98043441-7c8a-492a-abc3-330856d44c19"
/>



## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
2025-06-12 17:29:28 -04:00
Sivin VargheseandGitHub e54e80a936 fix: Display "To" in email meta header on outgoing messages (#11717) 2025-06-12 18:01:12 +05:30
Shivam MishraandGitHub ac3bce3932 fix: missing metrics and labels from label summary (#11718) 2025-06-12 17:58:56 +05:30
Sivin VargheseandGitHub 6baca40597 chore: Display divider only when multiple portals are available (#11709) 2025-06-12 17:01:41 +05:30
Sivin VargheseandGitHub f28bb70d67 fix: Prevent count flicker on loading more conversations (#11706) 2025-06-12 14:38:45 +05:30
161 changed files with 3398 additions and 247 deletions
+12 -1
View File
@@ -10,7 +10,8 @@ function toggleSecretField(e) {
if (!textElement) return;
if (textElement.dataset.secretMasked === 'false') {
textElement.textContent = '•'.repeat(10);
const maskedLength = secretField.dataset.secretText?.length || 10;
textElement.textContent = '•'.repeat(maskedLength);
textElement.dataset.secretMasked = 'true';
toggler.querySelector('svg use').setAttribute('xlink:href', '#eye-show');
@@ -32,3 +33,13 @@ function copySecretField(e) {
navigator.clipboard.writeText(secretField.dataset.secretText);
}
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.cell-data__secret-field').forEach(field => {
const span = field.querySelector('[data-secret-masked]');
if (span && span.dataset.secretMasked === 'true') {
const len = field.dataset.secretText?.length || 10;
span.textContent = '•'.repeat(len);
}
});
});
@@ -46,17 +46,25 @@
.cell-data__secret-field {
align-items: center;
color: $hint-grey;
display: flex;
span {
flex: 1;
flex: 0 0 auto;
}
button {
margin-left: 5px;
[data-secret-toggler],
[data-secret-copier] {
background: transparent;
border: 0;
color: inherit;
margin-left: 0.5rem;
padding: 0;
svg {
fill: currentColor;
height: 1.25rem;
width: 1.25rem;
}
}
}
@@ -31,7 +31,7 @@ class V2::Reports::LabelSummaryBuilder < V2::Reports::BaseSummaryBuilder
resolved_counts: fetch_resolved_counts(conversation_filter),
resolution_metrics: fetch_metrics(conversation_filter, 'conversation_resolved', use_business_hours),
first_response_metrics: fetch_metrics(conversation_filter, 'first_response', use_business_hours),
reply_metrics: fetch_metrics(conversation_filter, 'reply', use_business_hours)
reply_metrics: fetch_metrics(conversation_filter, 'reply_time', use_business_hours)
}
end
@@ -63,7 +63,9 @@ class V2::Reports::LabelSummaryBuilder < V2::Reports::BaseSummaryBuilder
end
def fetch_resolved_counts(conversation_filter)
fetch_counts(conversation_filter.merge(status: :resolved))
# since the base query is ActsAsTaggableOn,
# the status :resolved won't automatically be converted to integer status
fetch_counts(conversation_filter.merge(status: Conversation.statuses[:resolved]))
end
def fetch_counts(conversation_filter)
@@ -1,32 +1,23 @@
class Api::V1::Accounts::Google::AuthorizationsController < Api::V1::Accounts::BaseController
class Api::V1::Accounts::Google::AuthorizationsController < Api::V1::Accounts::OauthAuthorizationController
include GoogleConcern
before_action :check_authorization
def create
email = params[:authorization][:email]
redirect_url = google_client.auth_code.authorize_url(
{
redirect_uri: "#{base_url}/google/callback",
scope: 'email profile https://mail.google.com/',
scope: scope,
response_type: 'code',
prompt: 'consent', # the oauth flow does not return a refresh token, this is supposed to fix it
access_type: 'offline', # the default is 'online'
state: state,
client_id: GlobalConfigService.load('GOOGLE_OAUTH_CLIENT_ID', nil)
}
)
if redirect_url
cache_key = "google::#{email.downcase}"
::Redis::Alfred.setex(cache_key, Current.account.id, 5.minutes)
render json: { success: true, url: redirect_url }
else
render json: { success: false }, status: :unprocessable_entity
end
end
private
def check_authorization
raise Pundit::NotAuthorizedError unless Current.account_user.administrator?
end
end
@@ -1,7 +1,6 @@
class Api::V1::Accounts::Instagram::AuthorizationsController < Api::V1::Accounts::BaseController
class Api::V1::Accounts::Instagram::AuthorizationsController < Api::V1::Accounts::OauthAuthorizationController
include InstagramConcern
include Instagram::IntegrationHelper
before_action :check_authorization
def create
# https://developers.facebook.com/docs/instagram-platform/instagram-api-with-instagram-login/business-login#step-1--get-authorization
@@ -21,10 +20,4 @@ class Api::V1::Accounts::Instagram::AuthorizationsController < Api::V1::Accounts
render json: { success: false }, status: :unprocessable_entity
end
end
private
def check_authorization
raise Pundit::NotAuthorizedError unless Current.account_user.administrator?
end
end
@@ -1,5 +1,5 @@
class Api::V1::Accounts::Integrations::LinearController < Api::V1::Accounts::BaseController
before_action :fetch_conversation, only: [:link_issue, :linked_issues]
before_action :fetch_conversation, only: [:create_issue, :link_issue, :unlink_issue, :linked_issues]
before_action :fetch_hook, only: [:destroy]
def destroy
@@ -31,6 +31,12 @@ class Api::V1::Accounts::Integrations::LinearController < Api::V1::Accounts::Bas
if issue[:error]
render json: { error: issue[:error] }, status: :unprocessable_entity
else
Linear::ActivityMessageService.new(
conversation: @conversation,
action_type: :issue_created,
issue_data: { id: issue[:data][:identifier] },
user: Current.user
).perform
render json: issue[:data], status: :ok
end
end
@@ -42,17 +48,30 @@ class Api::V1::Accounts::Integrations::LinearController < Api::V1::Accounts::Bas
if issue[:error]
render json: { error: issue[:error] }, status: :unprocessable_entity
else
Linear::ActivityMessageService.new(
conversation: @conversation,
action_type: :issue_linked,
issue_data: { id: issue_id },
user: Current.user
).perform
render json: issue[:data], status: :ok
end
end
def unlink_issue
link_id = permitted_params[:link_id]
issue_id = permitted_params[:issue_id]
issue = linear_processor_service.unlink_issue(link_id)
if issue[:error]
render json: { error: issue[:error] }, status: :unprocessable_entity
else
Linear::ActivityMessageService.new(
conversation: @conversation,
action_type: :issue_unlinked,
issue_data: { id: issue_id },
user: Current.user
).perform
render json: issue[:data], status: :ok
end
end
@@ -0,0 +1,14 @@
class Api::V1::Accounts::Integrations::NotionController < Api::V1::Accounts::BaseController
before_action :fetch_hook, only: [:destroy]
def destroy
@hook.destroy!
head :ok
end
private
def fetch_hook
@hook = Integrations::Hook.where(account: Current.account).find_by(app_id: 'notion')
end
end
@@ -1,28 +1,19 @@
class Api::V1::Accounts::Microsoft::AuthorizationsController < Api::V1::Accounts::BaseController
class Api::V1::Accounts::Microsoft::AuthorizationsController < Api::V1::Accounts::OauthAuthorizationController
include MicrosoftConcern
before_action :check_authorization
def create
email = params[:authorization][:email]
redirect_url = microsoft_client.auth_code.authorize_url(
{
redirect_uri: "#{base_url}/microsoft/callback",
scope: 'offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send openid profile',
scope: scope,
state: state,
prompt: 'consent'
}
)
if redirect_url
cache_key = "microsoft::#{email.downcase}"
::Redis::Alfred.setex(cache_key, Current.account.id, 5.minutes)
render json: { success: true, url: redirect_url }
else
render json: { success: false }, status: :unprocessable_entity
end
end
private
def check_authorization
raise Pundit::NotAuthorizedError unless Current.account_user.administrator?
end
end
@@ -0,0 +1,21 @@
class Api::V1::Accounts::Notion::AuthorizationsController < Api::V1::Accounts::OauthAuthorizationController
include NotionConcern
def create
redirect_url = notion_client.auth_code.authorize_url(
{
redirect_uri: "#{base_url}/notion/callback",
response_type: 'code',
owner: 'user',
state: state,
client_id: GlobalConfigService.load('NOTION_CLIENT_ID', nil)
}
)
if redirect_url
render json: { success: true, url: redirect_url }
else
render json: { success: false }, status: :unprocessable_entity
end
end
end
@@ -0,0 +1,23 @@
class Api::V1::Accounts::OauthAuthorizationController < Api::V1::Accounts::BaseController
before_action :check_authorization
protected
def scope
''
end
def state
Current.account.to_sgid(expires_in: 15.minutes).to_s
end
def base_url
ENV.fetch('FRONTEND_URL', 'http://localhost:3000')
end
private
def check_authorization
raise Pundit::NotAuthorizedError unless Current.account_user.administrator?
end
end
+2 -2
View File
@@ -14,7 +14,7 @@ module GoogleConcern
private
def base_url
ENV.fetch('FRONTEND_URL', 'http://localhost:3000')
def scope
'email profile https://mail.google.com/'
end
end
@@ -15,7 +15,7 @@ module MicrosoftConcern
private
def base_url
ENV.fetch('FRONTEND_URL', 'http://localhost:3000')
def scope
'offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send openid profile email'
end
end
@@ -0,0 +1,21 @@
module NotionConcern
extend ActiveSupport::Concern
def notion_client
app_id = GlobalConfigService.load('NOTION_CLIENT_ID', nil)
app_secret = GlobalConfigService.load('NOTION_CLIENT_SECRET', nil)
::OAuth2::Client.new(app_id, app_secret, {
site: 'https://api.notion.com',
authorize_url: 'https://api.notion.com/v1/oauth/authorize',
token_url: 'https://api.notion.com/v1/oauth/token',
auth_scheme: :basic_auth
})
end
private
def scope
'read'
end
end
@@ -0,0 +1,36 @@
class Notion::CallbacksController < OauthCallbackController
include NotionConcern
private
def provider_name
'notion'
end
def oauth_client
notion_client
end
def handle_response
hook = account.hooks.new(
access_token: parsed_body['access_token'],
status: 'enabled',
app_id: 'notion',
settings: {
token_type: parsed_body['token_type'],
workspace_name: parsed_body['workspace_name'],
workspace_id: parsed_body['workspace_id'],
workspace_icon: parsed_body['workspace_icon'],
bot_id: parsed_body['bot_id'],
owner: parsed_body['owner']
}
)
hook.save!
redirect_to notion_redirect_uri
end
def notion_redirect_uri
"#{ENV.fetch('FRONTEND_URL', nil)}/app/accounts/#{account.id}/settings/integrations/notion"
end
end
+8 -8
View File
@@ -6,7 +6,6 @@ class OauthCallbackController < ApplicationController
)
handle_response
::Redis::Alfred.delete(cache_key)
rescue StandardError => e
ChatwootExceptionTracker.new(e).capture_exception
redirect_to '/'
@@ -64,10 +63,6 @@ class OauthCallbackController < ApplicationController
raise NotImplementedError
end
def cache_key
"#{provider_name}::#{users_data['email'].downcase}"
end
def create_channel_with_inbox
ActiveRecord::Base.transaction do
channel_email = Channel::Email.create!(email: users_data['email'], account: account)
@@ -86,12 +81,17 @@ class OauthCallbackController < ApplicationController
decoded_token[0]
end
def account_id
::Redis::Alfred.get(cache_key)
def account_from_signed_id
raise ActionController::BadRequest, 'Missing state variable' if params[:state].blank?
account = GlobalID::Locator.locate_signed(params[:state])
raise 'Invalid or expired state' if account.nil?
account
end
def account
@account ||= Account.find(account_id)
@account ||= account_from_signed_id
end
# Fallback name, for when name field is missing from users_data
@@ -39,6 +39,7 @@ class SuperAdmin::AppConfigsController < SuperAdmin::ApplicationController
'email' => ['MAILER_INBOUND_EMAIL_DOMAIN'],
'linear' => %w[LINEAR_CLIENT_ID LINEAR_CLIENT_SECRET],
'slack' => %w[SLACK_CLIENT_ID SLACK_CLIENT_SECRET],
'notion' => %w[NOTION_CLIENT_ID NOTION_CLIENT_SECRET],
'instagram' => %w[INSTAGRAM_APP_ID INSTAGRAM_APP_SECRET INSTAGRAM_VERIFY_TOKEN INSTAGRAM_API_VERSION ENABLE_INSTAGRAM_CHANNEL_HUMAN_AGENT]
}
@@ -33,9 +33,11 @@ class LinearAPI extends ApiClient {
);
}
unlinkIssue(linkId) {
unlinkIssue(linkId, issueIdentifier, conversationId) {
return axios.post(`${this.url}/unlink_issue`, {
link_id: linkId,
issue_id: issueIdentifier,
conversation_id: conversationId,
});
}
@@ -0,0 +1,14 @@
/* global axios */
import ApiClient from './ApiClient';
class NotionOAuthClient extends ApiClient {
constructor() {
super('notion', { accountScoped: true });
}
generateAuthorization() {
return axios.post(`${this.url}/authorization`);
}
}
export default new NotionOAuthClient();
@@ -91,6 +91,19 @@ describe('#linearAPI', () => {
issueData
);
});
it('creates a valid request with conversation_id', () => {
const issueData = {
title: 'New Issue',
description: 'Issue description',
conversation_id: 123,
};
LinearAPIClient.createIssue(issueData);
expect(axiosMock.post).toHaveBeenCalledWith(
'/api/v1/integrations/linear/create_issue',
issueData
);
});
});
describe('link_issue', () => {
@@ -120,6 +133,18 @@ describe('#linearAPI', () => {
}
);
});
it('creates a valid request with title', () => {
LinearAPIClient.link_issue(1, 'ENG-123', 'Sample Issue');
expect(axiosMock.post).toHaveBeenCalledWith(
'/api/v1/integrations/linear/link_issue',
{
issue_id: 'ENG-123',
conversation_id: 1,
title: 'Sample Issue',
}
);
});
});
describe('getLinkedIssue', () => {
@@ -164,12 +189,26 @@ describe('#linearAPI', () => {
window.axios = originalAxios;
});
it('creates a valid request', () => {
LinearAPIClient.unlinkIssue(1);
it('creates a valid request with link_id only', () => {
LinearAPIClient.unlinkIssue('link123');
expect(axiosMock.post).toHaveBeenCalledWith(
'/api/v1/integrations/linear/unlink_issue',
{
link_id: 1,
link_id: 'link123',
issue_id: undefined,
conversation_id: undefined,
}
);
});
it('creates a valid request with all parameters', () => {
LinearAPIClient.unlinkIssue('link123', 'ENG-456', 789);
expect(axiosMock.post).toHaveBeenCalledWith(
'/api/v1/integrations/linear/unlink_issue',
{
link_id: 'link123',
issue_id: 'ENG-456',
conversation_id: 789,
}
);
});
@@ -47,7 +47,7 @@
@apply max-w-full;
.multiselect__option {
@apply text-sm font-normal;
@apply text-sm font-normal flex justify-between items-center;
span {
@apply inline-block overflow-hidden text-ellipsis whitespace-nowrap w-fit;
@@ -58,7 +58,7 @@
}
&::after {
@apply bottom-0 flex items-center justify-center text-center;
@apply bottom-0 flex items-center justify-center text-center relative px-1 leading-tight;
}
&.multiselect__option--highlight {
@@ -74,7 +74,7 @@
}
&.multiselect__option--highlight::after {
@apply bg-transparent;
@apply bg-transparent text-n-slate-12;
}
&.multiselect__option--selected {
@@ -19,6 +19,7 @@ const isConversationRoute = computed(() => {
'conversation_through_mentions',
'conversation_through_unattended',
'conversation_through_participating',
'inbox_view_conversation',
];
return CONVERSATION_ROUTES.includes(route.name);
});
@@ -379,7 +379,7 @@ const shouldRenderMessage = computed(() => {
function openContextMenu(e) {
const shouldSkipContextMenu =
e.target?.classList.contains('skip-context-menu') ||
e.target?.tagName.toLowerCase() === 'a';
['a', 'img'].includes(e.target?.tagName.toLowerCase());
if (shouldSkipContextMenu || getSelection().toString()) {
return;
}
@@ -14,7 +14,8 @@ const fromEmail = computed(() => {
});
const toEmail = computed(() => {
return contentAttributes.value?.email?.to ?? [];
const { toEmails, email } = contentAttributes.value;
return email?.to ?? toEmails ?? [];
});
const ccEmail = computed(() => {
@@ -756,6 +756,7 @@ function toggleSelectAll(check) {
}
useEmitter('fetch_conversation_stats', () => {
if (hasAppliedFiltersOrActiveFolders.value) return;
store.dispatch('conversationStats/get', conversationFilters.value);
});
@@ -854,7 +855,7 @@ watch(conversationFilters, (newVal, oldVal) => {
:active-status="activeStatus"
:is-on-expanded-layout="isOnExpandedLayout"
:conversation-stats="conversationStats"
:is-list-loading="chatListLoading"
:is-list-loading="chatListLoading && !conversationList.length"
@add-folders="onClickOpenAddFoldersModal"
@delete-folders="onClickOpenDeleteFoldersModal"
@filters-modal="onToggleAdvanceFiltersModal"
@@ -21,7 +21,7 @@ const props = defineProps({
const isRelaxed = computed(() => props.type === 'relaxed');
const headerClass = computed(() =>
isRelaxed.value
? 'first:rounded-bl-lg first:rounded-tl-lg last:rounded-br-lg last:rounded-tr-lg'
? 'ltr:first:rounded-bl-lg ltr:first:rounded-tl-lg ltr:last:rounded-br-lg ltr:last:rounded-tr-lg rtl:first:rounded-br-lg rtl:first:rounded-tr-lg rtl:last:rounded-bl-lg rtl:last:rounded-tl-lg'
: ''
);
</script>
@@ -183,13 +183,18 @@ const createIssue = async () => {
state_id: formState.stateId || undefined,
priority: formState.priority || undefined,
label_ids: formState.labelId ? [formState.labelId] : undefined,
conversation_id: props.conversationId,
};
try {
isCreating.value = true;
const response = await LinearAPI.createIssue(payload);
const { id: issueId } = response.data;
await LinearAPI.link_issue(props.conversationId, issueId, props.title);
const { identifier: issueIdentifier } = response.data;
await LinearAPI.link_issue(
props.conversationId,
issueIdentifier,
props.title
);
useAlert(t('INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.CREATE_SUCCESS'));
useTrack(LINEAR_EVENTS.CREATE_ISSUE);
onClose();
@@ -46,9 +46,9 @@ const loadLinkedIssues = async () => {
}
};
const unlinkIssue = async linkId => {
const unlinkIssue = async (linkId, issueIdentifier) => {
try {
await LinearAPI.unlinkIssue(linkId);
await LinearAPI.unlinkIssue(linkId, issueIdentifier, props.conversationId);
useTrack(LINEAR_EVENTS.UNLINK_ISSUE);
linkedIssues.value = linkedIssues.value.filter(
issue => issue.id !== linkId
@@ -110,7 +110,7 @@ onMounted(() => {
<LinearIssueItem
v-for="linkedIssue in linkedIssues"
:key="linkedIssue.id"
class="pt-3 px-4 pb-4 border-b border-n-weak last:border-b-0"
class="px-4 pt-3 pb-4 border-b border-n-weak last:border-b-0"
:linked-issue="linkedIssue"
@unlink-issue="unlinkIssue"
/>
@@ -14,6 +14,8 @@ const props = defineProps({
const emit = defineEmits(['unlinkIssue']);
const { linkedIssue } = props;
const priorityMap = {
1: 'Urgent',
2: 'High',
@@ -21,7 +23,7 @@ const priorityMap = {
4: 'Low',
};
const issue = computed(() => props.linkedIssue.issue);
const issue = computed(() => linkedIssue.issue);
const assignee = computed(() => {
const assigneeDetails = issue.value.assignee;
@@ -37,7 +39,7 @@ const labels = computed(() => issue.value.labels?.nodes || []);
const priorityLabel = computed(() => priorityMap[issue.value.priority]);
const unlinkIssue = () => {
emit('unlinkIssue', props.linkedIssue.id);
emit('unlinkIssue', linkedIssue.id, linkedIssue.issue.identifier);
};
</script>
@@ -63,7 +63,7 @@ const onSearch = async value => {
isFetching.value = true;
const response = await LinearAPI.searchIssues(value);
issues.value = response.data.map(issue => ({
id: issue.id,
id: issue.identifier,
name: `${issue.identifier} ${issue.title}`,
icon: 'status',
iconColor: issue.state.color,
@@ -328,6 +328,14 @@
"DESCRIPTION": "Linear workspace is not connected. Click the button below to connect your workspace to use this integration.",
"BUTTON_TEXT": "Connect Linear workspace"
}
},
"NOTION": {
"DELETE": {
"TITLE": "Are you sure you want to delete the Notion integration?",
"MESSAGE": "Deleting this integration will remove access to your Notion workspace and stop all related functionality.",
"CONFIRM": "Yes, delete",
"CANCEL": "Cancel"
}
}
},
"CAPTAIN": {
@@ -152,7 +152,7 @@
"ATTRIBUTES": {
"MESSAGE_TYPE": "Tipo da Mensagem",
"MESSAGE_CONTAINS": "A mensagem contém",
"EMAIL": "e-mail",
"EMAIL": "E-mail",
"INBOX": "Caixa de Entrada",
"CONVERSATION_LANGUAGE": "Idioma da conversa",
"PHONE_NUMBER": "Número de Telefone",
@@ -130,15 +130,15 @@ export default {
</div>
<div class="flex multiselect-wrap--medium">
<div
class="w-8 relative text-base text-slate-100 dark:text-slate-600 after:content-[''] after:h-12 after:w-0 after:left-4 after:absolute after:border-l after:border-solid after:border-slate-100 after:dark:border-slate-600 before:content-[''] before:h-0 before:w-4 before:left-4 before:top-12 before:absolute before:border-b before:border-solid before:border-slate-100 before:dark:border-slate-600"
class="w-8 relative text-base text-slate-100 dark:text-slate-600 after:content-[''] after:h-12 after:w-0 ltr:after:left-4 rtl:after:right-4 after:absolute after:border-l after:border-solid after:border-slate-100 after:dark:border-slate-600 before:content-[''] before:h-0 before:w-4 ltr:before:left-4 rtl:before:right-4 before:top-12 before:absolute before:border-b before:border-solid before:border-slate-100 before:dark:border-slate-600"
>
<fluent-icon
icon="arrow-up"
class="absolute -top-1 left-2"
class="absolute -top-1 ltr:left-2 rtl:right-2"
size="17"
/>
</div>
<div class="flex flex-col w-full">
<div class="flex flex-col w-full ltr:pl-8 rtl:pr-8">
<label class="multiselect__label">
{{ $t('MERGE_CONTACTS.PRIMARY.TITLE') }}
<woot-label
@@ -71,6 +71,12 @@ export default {
FEATURE_FLAGS.AUTO_RESOLVE_CONVERSATIONS
);
},
showAudioTranscriptionConfig() {
return this.isFeatureEnabledonAccount(
this.accountId,
FEATURE_FLAGS.CAPTAIN
);
},
languagesSortedByCode() {
const enabledLanguages = [...this.enabledLanguages];
return enabledLanguages.sort((l1, l2) =>
@@ -237,7 +243,7 @@ export default {
<woot-loading-state v-if="uiFlags.isFetchingItem" />
</div>
<AutoResolve v-if="showAutoResolutionConfig" />
<AudioTranscription v-if="isOnChatwootCloud" />
<AudioTranscription v-if="showAudioTranscriptionConfig" />
<AccountId />
<div v-if="!uiFlags.isFetchingItem && isOnChatwootCloud">
<AccountDelete />
@@ -12,7 +12,6 @@ defineOptions({
provider="google"
:title="$t('INBOX_MGMT.ADD.GOOGLE.TITLE')"
:description="$t('INBOX_MGMT.ADD.GOOGLE.DESCRIPTION')"
:input-placeholder="$t('INBOX_MGMT.ADD.GOOGLE.EMAIL_PLACEHOLDER')"
:submit-button-text="$t('INBOX_MGMT.ADD.GOOGLE.SIGN_IN')"
:error-message="$t('INBOX_MGMT.ADD.GOOGLE.ERROR_MESSAGE')"
/>
@@ -12,7 +12,6 @@ defineOptions({
provider="microsoft"
:title="$t('INBOX_MGMT.ADD.MICROSOFT.TITLE')"
:description="$t('INBOX_MGMT.ADD.MICROSOFT.DESCRIPTION')"
:input-placeholder="$t('INBOX_MGMT.ADD.MICROSOFT.EMAIL_PLACEHOLDER')"
:submit-button-text="$t('INBOX_MGMT.ADD.MICROSOFT.SIGN_IN')"
:error-message="$t('INBOX_MGMT.ADD.MICROSOFT.ERROR_MESSAGE')"
/>
@@ -30,14 +30,9 @@ const props = defineProps({
type: String,
required: true,
},
inputPlaceholder: {
type: String,
required: true,
},
});
const isRequestingAuthorization = ref(false);
const email = ref('');
const client = computed(() => {
if (props.provider === 'microsoft') {
@@ -50,9 +45,7 @@ const client = computed(() => {
async function requestAuthorization() {
try {
isRequestingAuthorization.value = true;
const response = await client.value.generateAuthorization({
email: email.value,
});
const response = await client.value.generateAuthorization();
const {
data: { url },
} = response;
@@ -75,11 +68,6 @@ async function requestAuthorization() {
:header-content="description"
/>
<form class="mt-6" @submit.prevent="requestAuthorization">
<woot-input
v-model="email"
type="email"
:placeholder="inputPlaceholder"
/>
<NextButton
:is-loading="isRequestingAuthorization"
type="submit"
@@ -0,0 +1,80 @@
<script setup>
import { ref, computed, onMounted } from 'vue';
import {
useFunctionGetter,
useMapGetter,
useStore,
} from 'dashboard/composables/store';
import { useI18n } from 'vue-i18n';
import ButtonNext from 'next/button/Button.vue';
import notionClient from 'dashboard/api/notion_auth.js';
import Integration from './Integration.vue';
import Spinner from 'shared/components/Spinner.vue';
const { t } = useI18n();
const store = useStore();
const integrationLoaded = ref(false);
const integration = useFunctionGetter('integrations/getIntegration', 'notion');
const uiFlags = useMapGetter('integrations/getUIFlags');
const integrationAction = computed(() => {
if (integration.value.enabled) {
return 'disconnect';
}
return '';
});
const authorize = async () => {
const response = await notionClient.generateAuthorization();
const {
data: { url },
} = response;
window.location.href = url;
};
const initializeNotionIntegration = async () => {
await store.dispatch('integrations/get', 'notion');
integrationLoaded.value = true;
};
onMounted(() => {
initializeNotionIntegration();
});
</script>
<template>
<div class="flex-grow flex-shrink p-4 overflow-auto mx-auto">
<div v-if="integrationLoaded && !uiFlags.isCreatingNotion">
<Integration
:integration-id="integration.id"
:integration-logo="integration.logo"
:integration-name="integration.name"
:integration-description="integration.description"
:integration-enabled="integration.enabled"
:integration-action="integrationAction"
:delete-confirmation-text="{
title: t('INTEGRATION_SETTINGS.NOTION.DELETE.TITLE'),
message: t('INTEGRATION_SETTINGS.NOTION.DELETE.MESSAGE'),
}"
>
<template #action>
<ButtonNext
faded
blue
:label="t('INTEGRATION_SETTINGS.CONNECT.BUTTON_TEXT')"
@click="authorize"
/>
</template>
</Integration>
</div>
<div v-else class="flex items-center justify-center flex-1">
<Spinner size="" color-scheme="primary" />
</div>
</div>
</template>
@@ -8,6 +8,7 @@ import DashboardApps from './DashboardApps/Index.vue';
import Slack from './Slack.vue';
import SettingsContent from '../Wrapper.vue';
import Linear from './Linear.vue';
import Notion from './Notion.vue';
import Shopify from './Shopify.vue';
export default {
@@ -90,6 +91,15 @@ export default {
},
props: route => ({ code: route.query.code }),
},
{
path: 'notion',
name: 'settings_integrations_notion',
component: Notion,
meta: {
permissions: ['administrator'],
},
props: route => ({ code: route.query.code }),
},
{
path: 'shopify',
name: 'settings_integrations_shopify',
@@ -75,10 +75,34 @@ export default {
onRegistrationSuccess() {
this.hasEnabledPushPermissions = true;
},
onRequestPermissions() {
requestPushPermissions({
onSuccess: this.onRegistrationSuccess,
});
onRequestPermissions(value) {
if (value) {
// Enable / re-enable push notifications
requestPushPermissions({
onSuccess: this.onRegistrationSuccess,
});
} else {
// Disable push notifications
this.disablePushPermissions();
}
},
disablePushPermissions() {
verifyServiceWorkerExistence(registration =>
registration.pushManager
.getSubscription()
.then(subscription => {
if (subscription) {
return subscription.unsubscribe();
}
return null;
})
.finally(() => {
this.hasEnabledPushPermissions = false;
})
.catch(() => {
// error
})
);
},
getPushSubscription() {
verifyServiceWorkerExistence(registration =>
@@ -59,7 +59,7 @@ const defaulSpanRender = cellProps =>
cellProps.getValue()
);
const columns = [
const columns = computed(() => [
columnHelper.accessor('name', {
header: t(`SUMMARY_REPORTS.${props.type.toUpperCase()}`),
width: 300,
@@ -90,7 +90,7 @@ const columns = [
width: 200,
cell: defaulSpanRender,
}),
];
]);
const renderAvgTime = value => (value ? formatTime(value) : '--');
@@ -142,7 +142,9 @@ const table = useVueTable({
get data() {
return tableData.value;
},
columns,
get columns() {
return columns.value;
},
enableSorting: false,
getCoreRowModel: getCoreRowModel(),
});
+1 -1
View File
@@ -35,7 +35,7 @@ class Webhooks::TelegramEventsJob < ApplicationJob
def process_event_params(channel, params)
return unless params[:telegram]
if params.dig(:telegram, :edited_message).present?
if params.dig(:telegram, :edited_message).present? || params.dig(:telegram, :edited_business_message).present?
Telegram::UpdateMessageService.new(inbox: channel.inbox, params: params['telegram'].with_indifferent_access).perform
else
Telegram::IncomingMessageService.new(inbox: channel.inbox, params: params['telegram'].with_indifferent_access).perform
+16 -3
View File
@@ -69,6 +69,10 @@ class Channel::Telegram < ApplicationRecord
message.conversation[:additional_attributes]['chat_id']
end
def business_connection_id(message)
message.conversation[:additional_attributes]['business_connection_id']
end
def reply_to_message_id(message)
message.content_attributes['in_reply_to_external_id']
end
@@ -95,7 +99,13 @@ class Channel::Telegram < ApplicationRecord
end
def send_message(message)
response = message_request(chat_id(message), message.outgoing_content, reply_markup(message), reply_to_message_id(message))
response = message_request(
chat_id(message),
message.outgoing_content,
reply_markup(message),
reply_to_message_id(message),
business_connection_id: business_connection_id(message)
)
process_error(message, response)
response.parsed_response['result']['message_id'] if response.success?
end
@@ -131,9 +141,12 @@ class Channel::Telegram < ApplicationRecord
stripped_html.gsub('&lt;br&gt;', "\n")
end
def message_request(chat_id, text, reply_markup = nil, reply_to_message_id = nil)
def message_request(chat_id, text, reply_markup = nil, reply_to_message_id = nil, business_connection_id: nil)
text_payload = convert_markdown_to_telegram_html(text)
business_body = {}
business_body[:business_connection_id] = business_connection_id if business_connection_id
HTTParty.post("#{telegram_api_url}/sendMessage",
body: {
chat_id: chat_id,
@@ -141,6 +154,6 @@ class Channel::Telegram < ApplicationRecord
reply_markup: reply_markup,
parse_mode: 'HTML',
reply_to_message_id: reply_to_message_id
})
}.merge(business_body))
end
end
+2
View File
@@ -58,6 +58,8 @@ class Integrations::App
account.feature_enabled?('shopify_integration') && GlobalConfigService.load('SHOPIFY_CLIENT_ID', nil).present?
when 'leadsquared'
account.feature_enabled?('crm_integration')
when 'notion'
account.feature_enabled?('notion_integration') && GlobalConfigService.load('NOTION_CLIENT_ID', nil).present?
else
true
end
+4
View File
@@ -53,6 +53,10 @@ class Integrations::Hook < ApplicationRecord
app_id == 'dialogflow'
end
def notion?
app_id == 'notion'
end
def disable
update(status: 'disabled')
end
+61
View File
@@ -0,0 +1,61 @@
class NotionPagePresenter
def initialize(page_response, blocks_response)
@page_response = page_response
@blocks_response = blocks_response
end
def to_hash
{
'id' => @page_response['id'],
'icon' => @page_response['icon'],
'title' => extract_page_title,
'url' => @page_response['url'],
'created_time' => @page_response['created_time'],
'last_edited_time' => @page_response['last_edited_time'],
'md' => generate_markdown,
'child_pages' => extract_child_pages
}
end
private
def extract_page_title
# Try to get title from properties (for database pages)
if @page_response['properties']
title_property = @page_response['properties'].values.find { |prop| prop['type'] == 'title' }
return title_property['title'].map { |t| t['plain_text'] }.join if title_property && title_property['title']&.any?
end
nil
end
def generate_markdown
title = extract_page_title
content_md = NotionToMarkdown.new.convert(@blocks_response['results'])
title_md = title ? "# #{title}\n\n" : ''
"#{title_md}#{content_md}"
end
def extract_child_pages
extract_child_pages_from_blocks(@blocks_response['results'])
end
def extract_child_pages_from_blocks(blocks)
child_pages = []
blocks.each do |block|
if block['type'] == 'child_page'
child_pages << {
'id' => block['id'],
'title' => block['child_page']['title']
}
end
# Recursively check nested blocks
child_pages.concat(extract_child_pages_from_blocks(block['children'])) if block['has_children'] && block['children']
end
child_pages
end
end
@@ -24,6 +24,15 @@ class Instagram::Messenger::MessageText < Instagram::BaseMessageText
end
def handle_client_error(error)
# Handle error code 230: User consent is required to access user profile
# This typically occurs when the connected Instagram account attempts to send a message to a user
# who has never messaged this Instagram account before.
# We can safely ignore this error as per Facebook documentation.
if error.message.include?('230')
Rails.logger.warn error
return
end
Rails.logger.warn("[FacebookUserFetchClientError]: account_id #{@inbox.account_id} inbox_id #{@inbox.id}")
Rails.logger.warn("[FacebookUserFetchClientError]: #{error.message}")
ChatwootExceptionTracker.new(error, account: @inbox.account).capture_exception
@@ -0,0 +1,41 @@
class Linear::ActivityMessageService
attr_reader :conversation, :action_type, :issue_data, :user
def initialize(conversation:, action_type:, user:, issue_data: {})
@conversation = conversation
@action_type = action_type
@issue_data = issue_data
@user = user
end
def perform
return unless conversation && issue_data[:id] && user
content = generate_activity_content
return unless content
::Conversations::ActivityMessageJob.perform_later(conversation, activity_message_params(content))
end
private
def generate_activity_content
case action_type.to_sym
when :issue_created
I18n.t('conversations.activity.linear.issue_created', user_name: user.name, issue_id: issue_data[:id])
when :issue_linked
I18n.t('conversations.activity.linear.issue_linked', user_name: user.name, issue_id: issue_data[:id])
when :issue_unlinked
I18n.t('conversations.activity.linear.issue_unlinked', user_name: user.name, issue_id: issue_data[:id])
end
end
def activity_message_params(content)
{
account_id: conversation.account_id,
inbox_id: conversation.inbox_id,
message_type: :activity,
content: content
}
end
end
@@ -11,6 +11,13 @@ class LlmFormatter::ConversationLlmFormatter < LlmFormatter::DefaultLlmFormatter
end
sections << "Contact Details: #{@record.contact.to_llm_text}" if config[:include_contact_details]
attributes = build_attributes
if attributes.present?
sections << 'Conversation Attributes:'
sections << attributes
end
sections.join("\n")
end
@@ -30,4 +37,11 @@ class LlmFormatter::ConversationLlmFormatter < LlmFormatter::DefaultLlmFormatter
sender = message.message_type == 'incoming' ? 'User' : 'Support agent'
"#{sender}: #{message.content}\n"
end
def build_attributes
attributes = @record.account.custom_attribute_definitions.with_attribute_model('conversation_attribute').map do |attribute|
"#{attribute.attribute_display_name}: #{@record.custom_attributes[attribute.attribute_key]}"
end
attributes.join("\n")
end
end
@@ -4,8 +4,10 @@ class MessageTemplates::Template::AutoResolve
def perform
return if conversation.account.auto_resolve_message.blank?
ActiveRecord::Base.transaction do
if within_messaging_window?
conversation.messages.create!(auto_resolve_message_params)
else
create_auto_resolve_not_sent_activity_message
end
end
@@ -14,6 +16,21 @@ class MessageTemplates::Template::AutoResolve
delegate :contact, :account, to: :conversation
delegate :inbox, to: :message
def within_messaging_window?
conversation.can_reply?
end
def create_auto_resolve_not_sent_activity_message
content = I18n.t('conversations.activity.auto_resolve.not_sent_due_to_messaging_window')
activity_message_params = {
account_id: conversation.account_id,
inbox_id: conversation.inbox_id,
message_type: :activity,
content: content
}
::Conversations::ActivityMessageJob.perform_later(conversation, activity_message_params) if content
end
def auto_resolve_message_params
{
account_id: @conversation.account_id,
+104
View File
@@ -0,0 +1,104 @@
class NotionToMarkdown
# Main entry: pass in the Notion page blocks (array of block hashes)
def convert(blocks)
blocks.map { |block| block_to_markdown(block) }.join("\n\n")
end
private
def block_to_markdown(block, depth = 0)
type = block['type']
return '' unless type
case type
when 'paragraph'
rich_text_to_md(block['paragraph']['rich_text'])
when 'heading_1'
"# #{rich_text_to_md(block['heading_1']['rich_text'])}"
when 'heading_2'
"## #{rich_text_to_md(block['heading_2']['rich_text'])}"
when 'heading_3'
"### #{rich_text_to_md(block['heading_3']['rich_text'])}"
when 'bulleted_list_item'
indent = ' ' * depth
"#{indent}- #{rich_text_to_md(block['bulleted_list_item']['rich_text'])}" +
children_to_md(block, depth + 1)
when 'numbered_list_item'
indent = ' ' * depth
"#{indent}1. #{rich_text_to_md(block['numbered_list_item']['rich_text'])}" +
children_to_md(block, depth + 1)
when 'to_do'
box = block['to_do']['checked'] ? '[x]' : '[ ]'
"- #{box} #{rich_text_to_md(block['to_do']['rich_text'])}"
when 'toggle'
summary = rich_text_to_md(block['toggle']['rich_text'])
details = children_to_md(block, depth + 1)
"<details>\n<summary>#{summary}</summary>\n\n#{details}\n</details>"
when 'quote'
quote = rich_text_to_md(block['quote']['rich_text'])
quote_lines = quote.lines.map { |line| "> #{line}" }.join
quote_lines + children_to_md(block, depth)
when 'code'
lang = block['code']['language'] || ''
code = block['code']['rich_text'].pluck('plain_text').join
"```#{lang}\n#{code}\n```"
when 'callout'
icon = block['callout']['icon']&.dig('emoji') || '💡'
content = rich_text_to_md(block['callout']['rich_text'])
"> [!#{icon}] #{content}" + children_to_md(block, depth + 1)
when 'divider'
'---'
when 'image'
url = block['image']['type'] == 'external' ? block['image']['external']['url'] : block['image']['file']['url']
caption = block['image']['caption']&.pluck('plain_text')&.join || 'image'
"![#{caption}](#{url})"
when 'bookmark'
url = block['bookmark']['url']
"[#{url}](#{url})"
when 'child_page'
"## #{block['child_page']['title']}" + children_to_md(block, depth + 1)
when 'table'
table_to_md(block)
else
'' # Add more block types as needed
end
end
def children_to_md(block, depth)
return '' unless block['has_children'] && block['children']
"\n" + block['children'].map { |child| block_to_markdown(child, depth) }.join("\n")
end
def rich_text_to_md(rich_text_array)
return '' unless rich_text_array
rich_text_array.map { |t| annotate_text(t) }.join
end
def annotate_text(text_obj)
text = text_obj['plain_text']
ann = text_obj['annotations'] || {}
text = "**#{text}**" if ann['bold']
text = "*#{text}*" if ann['italic']
text = "~~#{text}~~" if ann['strikethrough']
text = "<u>#{text}</u>" if ann['underline']
text = "`#{text}`" if ann['code']
text = "[#{text}](#{text_obj['href']})" if text_obj['href']
text
end
def table_to_md(block)
rows = block['children'] || []
table = rows.map do |row|
cells = row['table_row']['cells']
cells.map { |cell| rich_text_to_md(cell) }
end
return '' if table.empty?
header = table.first
sep = header.map { '---' }
([header, sep] + table[1..]).map { |row| "| #{row.join(' | ')} |" }.join("\n")
end
end
@@ -8,17 +8,25 @@ class Telegram::IncomingMessageService
def perform
# chatwoot doesn't support group conversations at the moment
transform_business_message!
return unless private_message?
set_contact
update_contact_avatar
set_conversation
# TODO: Since the recent Telegram Business update, we need to explicitly mark messages as read using an additional request.
# Otherwise, the client will see their messages as unread.
# Chatwoot defines a 'read' status in its enum but does not currently update this status for Telegram conversations.
# We have two options:
# 1. Send the read request to Telegram here, immediately when the message is created.
# 2. Properly update the read status in the Chatwoot UI and trigger the Telegram request when the agent actually reads the message.
# See: https://core.telegram.org/bots/api#readbusinessmessage
@message = @conversation.messages.build(
content: telegram_params_message_content,
account_id: @inbox.account_id,
inbox_id: @inbox.id,
message_type: :incoming,
sender: @contact,
message_type: message_type,
sender: message_sender,
content_attributes: telegram_params_content_attributes,
source_id: telegram_params_message_id.to_s
)
@@ -36,6 +44,11 @@ class Telegram::IncomingMessageService
contact_attributes: contact_attributes
).perform
# TODO: Should we update contact_attributes when the user changes their first or last name?
# In business chats, when our Telegram bot initiates the conversation,
# the message does not include a language code.
# This is critical for AI assistants and translation plugins.
@contact_inbox = contact_inbox
@contact = contact_inbox.contact
end
@@ -89,10 +102,19 @@ class Telegram::IncomingMessageService
def conversation_additional_attributes
{
chat_id: telegram_params_chat_id
chat_id: telegram_params_chat_id,
business_connection_id: telegram_params_business_connection_id
}
end
def message_type
business_message_outgoing? ? :outgoing : :incoming
end
def message_sender
business_message_outgoing? ? nil : @contact
end
def file_content_type
return :image if image_message?
return :audio if audio_message?
@@ -191,4 +213,8 @@ class Telegram::IncomingMessageService
params[:message][:video].presence ||
params[:message][:video_note].presence
end
def transform_business_message!
params[:message] = params[:business_message] if params[:business_message] && !params[:message]
end
end
+33 -4
View File
@@ -13,6 +13,17 @@ module Telegram::ParamHelpers
{}
end
def business_message?
telegram_params_business_connection_id.present?
end
# In business bot mode we will receive messages from our telegram.
# This is our messages posted via telegram client.
# Such messages should be outgoing (from us to client)
def business_message_outgoing?
business_message? && telegram_params_base_object[:chat][:id] != telegram_params_base_object[:from][:id]
end
def message_params?
params[:message].present?
end
@@ -29,24 +40,34 @@ module Telegram::ParamHelpers
end
end
def contact_params
if business_message_outgoing?
telegram_params_base_object[:chat]
else
telegram_params_base_object[:from]
end
end
def telegram_params_from_id
return telegram_params_base_object[:chat][:id] if business_message?
telegram_params_base_object[:from][:id]
end
def telegram_params_first_name
telegram_params_base_object[:from][:first_name]
contact_params[:first_name]
end
def telegram_params_last_name
telegram_params_base_object[:from][:last_name]
contact_params[:last_name]
end
def telegram_params_username
telegram_params_base_object[:from][:username]
contact_params[:username]
end
def telegram_params_language_code
telegram_params_base_object[:from][:language_code]
contact_params[:language_code]
end
def telegram_params_chat_id
@@ -57,6 +78,14 @@ module Telegram::ParamHelpers
end
end
def telegram_params_business_connection_id
if callback_query_params?
params[:callback_query][:message][:business_connection_id]
else
telegram_params_base_object[:business_connection_id]
end
end
def telegram_params_message_content
if callback_query_params?
params[:callback_query][:data]
@@ -71,6 +71,7 @@ class Telegram::SendAttachmentsService
HTTParty.post("#{channel.telegram_api_url}/sendMediaGroup",
body: {
chat_id: chat_id,
**business_connection_body,
media: attachments.map { |hash| hash.except(:attachment) }.to_json,
reply_to_message_id: reply_to_message_id
})
@@ -108,6 +109,7 @@ class Telegram::SendAttachmentsService
HTTParty.post("#{channel.telegram_api_url}/sendDocument",
body: {
chat_id: chat_id,
**business_connection_body,
document: file,
reply_to_message_id: reply_to_message_id
},
@@ -135,4 +137,14 @@ class Telegram::SendAttachmentsService
def channel
@channel ||= message.inbox.channel
end
def business_connection_id
@business_connection_id ||= channel.business_connection_id(message)
end
def business_connection_body
body = {}
body[:business_connection_id] = business_connection_id if business_connection_id
body
end
end
@@ -5,6 +5,7 @@ class Telegram::UpdateMessageService
pattr_initialize [:inbox!, :params!]
def perform
transform_business_message!
find_contact_inbox
find_conversation
find_message
@@ -36,4 +37,8 @@ class Telegram::UpdateMessageService
@message.update!(content: edited_message[:caption])
end
end
def transform_business_message!
params[:edited_message] = params[:edited_business_message] if params[:edited_business_message].present?
end
end
@@ -4,7 +4,9 @@
I18n.t('reports.label_csv.label_title'),
I18n.t('reports.label_csv.conversations_count'),
I18n.t('reports.label_csv.avg_first_response_time'),
I18n.t('reports.label_csv.avg_resolution_time')
I18n.t('reports.label_csv.avg_resolution_time'),
I18n.t('reports.label_csv.avg_reply_time'),
I18n.t('reports.label_csv.resolution_count'),
]
%>
<%= CSVSafe.generate_line headers -%>
@@ -3,7 +3,7 @@
%>
<%= javascript_include_tag "secretField" %>
<div data-secret-text="<%= field.data %>" class="cell-data__secret-field">
<span data-secret-masked="true">••••••••••</span>
<span data-secret-masked="true"></span>
<button onclick="toggleSecretField(event)" data-secret-toggler>
<svg width="20" height="20">
<use xlink:href="#eye-show" />
+1 -1
View File
@@ -4,7 +4,7 @@
<%= javascript_include_tag "secretField" %>
<div data-secret-text="<%= field.data %>" class="cell-data__secret-field">
<span data-secret-masked="true">••••••••••</span>
<span data-secret-masked="true"></span>
<button onclick="toggleSecretField(event)" data-secret-toggler>
<svg width="20" height="20">
<use xlink:href="#eye-show" />
@@ -6,8 +6,8 @@
<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>Deletion due at:</strong> {{ meta.marked_for_deletion_at }}<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>
@@ -1,18 +1,18 @@
<header class="sticky top-0 z-50 w-full bg-white shadow-sm dark:bg-slate-900">
<nav class="hidden sm:flex max-w-5xl px-4 mx-auto md:px-8" aria-label="Top">
<div class="flex items-center w-full py-5 overflow-hidden">
<a href="<%= generate_home_link(@portal.slug, @portal.config['default_locale'] || params[:locale], @theme_from_params, @is_plain_layout_enabled) %>" class="flex items-center h-10">
<a href="<%= generate_home_link(@portal.slug, @portal.config['default_locale'] || params[:locale], @theme_from_params, @is_plain_layout_enabled) %>" class="flex items-center min-w-0 h-10">
<% if @portal.logo.present? %>
<img src="<%= url_for(@portal.logo) %>" class="w-auto h-10 ltr:mr-2 rtl:ml-2" />
<img src="<%= url_for(@portal.logo) %>" class="w-auto h-10 ltr:mr-2 rtl:ml-2" draggable="false" />
<% end %>
<span class="text-lg font-semibold text-slate-900 dark:text-white <%= @portal.logo.present? ? 'hidden' : 'hidden sm:block' %>"><%= @portal.name %></span>
<span class="text-lg font-semibold text-slate-900 truncate dark:text-white <%= @portal.logo.present? ? 'hidden lg:block' : 'hidden sm:block' %>"><%= @portal.name %></span>
</a>
</div>
<%# Go to homepage link section %>
<div class="flex items-center justify-between gap-2 sm:gap-5">
<% if @portal.homepage_link %>
<div class="hidden px-1 py-2 ltr:ml-8 rtl:mr-8 cursor-pointer md:block">
<div class="px-1 py-2 ltr:ml-8 rtl:mr-8 cursor-pointer block">
<div class="flex-grow flex-shrink-0">
<a id="header-action-button" target="_blank" rel="noopener noreferrer nofollow" href="<%= @portal.homepage_link %>" class="flex flex-row items-center gap-1 text-sm font-medium whitespace-nowrap text-slate-800 dark:text-slate-100 stroke-slate-700 dark:stroke-slate-200">
<%= render partial: 'icons/redirect' %>
@@ -99,11 +99,11 @@
<nav class="flex sm:hidden max-w-5xl px-4 mx-auto" aria-label="Mobile Top">
<div class="flex items-center justify-between w-full py-5">
<a href="<%= generate_home_link(@portal.slug, @portal.config['default_locale'] || params[:locale], @theme_from_params, @is_plain_layout_enabled) %>" class="flex items-center h-10 text-lg font-semibold text-slate-900 dark:text-white">
<a href="<%= generate_home_link(@portal.slug, @portal.config['default_locale'] || params[:locale], @theme_from_params, @is_plain_layout_enabled) %>" class="flex items-center min-w-0 h-10 text-lg font-semibold text-slate-900 dark:text-white">
<% if @portal.logo.present? %>
<img src="<%= url_for(@portal.logo) %>" class="w-auto h-10 ltr:mr-2 rtl:ml-2" />
<img src="<%= url_for(@portal.logo) %>" class="w-auto h-10 ltr:mr-2 rtl:ml-2" draggable="false" />
<% end %>
<span class="text-lg font-semibold text-slate-900 dark:text-white <%= @portal.logo.present? ? 'hidden' : 'sm:hidden block' %>"><%= @portal.name %></span>
<span class="text-lg font-semibold text-slate-900 truncate dark:text-white <%= @portal.logo.present? ? 'hidden' : 'sm:hidden block' %>"><%= @portal.name %></span>
</a>
<!-- Mobile Menu Component -->
@@ -2,7 +2,7 @@
<section id="portal-bg" class="w-full bg-white dark:bg-slate-900 shadow-inner">
<div id="portal-bg-gradient" class="pt-8 pb-8 md:pt-14 md:pb-6 min-h-[240px] md:min-h-[260px]">
<div class="mx-auto max-w-5xl px-4 md:px-8 flex flex-col items-start">
<span class="text-sm leading-[24px] font-semibold text-slate-600 dark:text-slate-300 mb-1 <%= @portal.logo.present? ? 'block' : 'hidden' %>"><%= @portal.name %></span>
<span class="text-sm leading-[24px] font-semibold text-slate-600 dark:text-slate-300 mb-1 <%= @portal.logo.present? ? 'block lg:hidden' : 'hidden' %>"><%= @portal.name %></span>
<h1 class="text-2xl md:text-4xl text-slate-900 dark:text-white font-semibold leading-normal">
<%= portal.header_text %>
</h1>
@@ -1,3 +1,4 @@
<% has_multiple_locales = @portal.config["allowed_locales"].length > 1 %>
<input type="checkbox" id="mobile-menu-toggle" class="peer/menu hidden" />
@@ -46,10 +47,12 @@
</div>
</div>
<span class="h-px bg-slate-100/70 dark:bg-slate-800/70 w-full"></span>
<% if has_multiple_locales %>
<span class="h-px bg-slate-100/70 dark:bg-slate-800/70 w-full"></span>
<% end %>
<!-- Locale Switcher -->
<% if @portal.config["allowed_locales"].length > 1 %>
<% if has_multiple_locales %>
<div id="header-action-button" class="flex flex-col gap-2">
<h3 class="text-base font-medium text-slate-700 dark:text-slate-300 my-2">
<%= I18n.t('public_portal.header.language', default: 'Language') %>
@@ -156,9 +156,14 @@
<path d="M 8 3 C 5.243 3 3 5.243 3 8 L 3 16 C 3 18.757 5.243 21 8 21 L 16 21 C 18.757 21 21 18.757 21 16 L 21 8 C 21 5.243 18.757 3 16 3 L 8 3 z M 8 5 L 16 5 C 17.654 5 19 6.346 19 8 L 19 16 C 19 17.654 17.654 19 16 19 L 8 19 C 6.346 19 5 17.654 5 16 L 5 8 C 5 6.346 6.346 5 8 5 z M 17 6 A 1 1 0 0 0 16 7 A 1 1 0 0 0 17 8 A 1 1 0 0 0 18 7 A 1 1 0 0 0 17 6 z M 12 7 C 9.243 7 7 9.243 7 12 C 7 14.757 9.243 17 12 17 C 14.757 17 17 14.757 17 12 C 17 9.243 14.757 7 12 7 z M 12 9 C 13.654 9 15 10.346 15 12 C 15 13.654 13.654 15 12 15 C 10.346 15 9 13.654 9 12 C 9 10.346 10.346 9 12 9 z"/>
</symbol>
<symbol id="icon-notion" viewBox="0 0 24 24">
<path fill="currentColor" d="M6.104 5.91c.584.474.802.438 1.898.365l10.332-.62c.22 0 .037-.22-.036-.256l-1.716-1.24c-.329-.255-.767-.548-1.606-.475l-10.005.73c-.364.036-.437.219-.292.365zm.62 2.408v10.87c0 .585.292.803.95.767l11.354-.657c.657-.036.73-.438.73-.913V7.588c0-.474-.182-.73-.584-.693l-11.866.693c-.438.036-.584.255-.584.73m11.21.583c.072.328 0 .657-.33.694l-.547.109v8.025c-.475.256-.913.401-1.278.401c-.584 0-.73-.182-1.168-.729l-3.579-5.618v5.436l1.133.255s0 .656-.914.656l-2.519.146c-.073-.146 0-.51.256-.583l.657-.182v-7.187l-.913-.073c-.073-.329.11-.803.621-.84l2.702-.182l3.724 5.692V9.886l-.95-.109c-.072-.402.22-.693.585-.73zM4.131 3.429l10.406-.766c1.277-.11 1.606-.036 2.41.547l3.321 2.335c.548.401.731.51.731.948v12.805c0 .803-.292 1.277-1.314 1.35l-12.085.73c-.767.036-1.132-.073-1.534-.584L3.62 17.62c-.438-.584-.62-1.021-.62-1.533V4.705c0-.656.292-1.203 1.132-1.276"/>
</symbol>
<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>
<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>

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 42 KiB

+29 -20
View File
@@ -22,27 +22,36 @@
<% end %>
<div class="bg-white py-2 px-4 xl:px-0">
<div class="mb-4">
<div class="flex items-center gap-2">
<h2 class="h-5 leading-5 text-n-slate-12 font-medium">Current plan</h2>
<a href="<%= refresh_super_admin_settings_url %>" class="inline-flex gap-1 text-xs font-medium items-center text-woot-500 hover:text-woot-700">
<svg width="16" height="16"><use xlink:href="#icon-refresh-line" /></svg>
<span>Refresh</span>
</a>
</div>
<p class="text-n-slate-11 mt-1"><%= SuperAdmin::FeaturesHelper.plan_details.html_safe %></p>
<div class="flex items-center mt-6">
<h4 class="text-sm font-medium leading-5 h-5 text-n-slate-12 mr-4">Installation Identifier</h4>
<span class="text-sm leading-5 h-5 text-n-slate-11"><%= ChatwootHub.installation_identifier %></span>
<%= javascript_include_tag "secretField" %>
<div class="flex items-center mb-6">
<h4 class="text-sm font-medium leading-5 h-5 text-n-slate-12 mr-4">Installation Identifier</h4>
<div data-secret-text="<%= ChatwootHub.installation_identifier %>" class="cell-data__secret-field text-sm leading-5 h-5 text-n-slate-11">
<span data-secret-masked="true" class="select-none"></span>
<button type="button" onclick="toggleSecretField(event)" data-secret-toggler class="inline-flex items-center justify-center h-5 w-5 p-0 rounded text-n-slate-11 hover:text-n-slate-12 transition-colors focus:outline-none">
<svg width="20" height="20" class="w-5 h-5 fill-current">
<use xlink:href="#eye-show" />
</svg>
</button>
<button type="button" onclick="copySecretField(event)" data-secret-copier class="inline-flex items-center justify-center h-5 w-5 p-0 rounded text-n-slate-11 hover:text-n-slate-12 transition-colors focus:outline-none">
<svg width="20" height="20" class="w-5 h-5 fill-current">
<use xlink:href="#icon-copy" />
</svg>
</button>
</div>
</div>
<div class="flex p-4 outline outline-1 outline-n-container rounded-lg mt-8 items-start md:items-center shadow-sm flex-col md:flex-row">
<div class="flex p-4 outline outline-1 outline-n-container rounded-lg items-start md:items-center shadow-sm flex-col md:flex-row">
<div class="flex flex-col flex-grow gap-1">
<h2 class="h-5 leading-5 text-n-slate-12 text-sm font-medium">Current plan</h2>
<div class="flex items-center gap-2">
<h2 class="h-5 leading-5 text-n-slate-12 text-sm font-medium">Current plan</h2>
<a href="<%= refresh_super_admin_settings_url %>" class="inline-flex gap-1 text-xs font-medium items-center text-woot-500 hover:text-woot-700">
<svg width="16" height="16"><use xlink:href="#icon-refresh-line" /></svg>
<span>Refresh</span>
</a>
</div>
<p class="text-n-slate-11 m-0 text-sm"><%= SuperAdmin::FeaturesHelper.plan_details.html_safe %></p>
</div>
<a href="<%= ChatwootHub.billing_url %>" target="_blank" rel="noopener noreferrer">
<button class="mt-4 md:mt-0 flex gap-1 items-center bg-transparent shadow-sm h-9 hover:text-n-slate-12 hover:bg-slate-50 outline outline-1 outline-n-container rounded text-n-slate-11 font-medium p-2">
<button class="mt-4 md:mt-0 flex gap-1 items-center bg-transparent shadow-sm h-9 hover:text-n-slate-12 hover:bg-slate-50 outline outline-1 outline-n-container rounded text-n-slate-11 font-medium p-2 focus:outline-none">
<svg width="16" height="16"><use xlink:href="#icon-settings-2-line" /></svg>
<span class="px-1">Manage</span>
</button>
@@ -64,14 +73,14 @@
<p class="text-n-slate-11 m-0 text-sm">Do you face any issues? We are here to help.</p>
</div>
<a href="https://discord.gg/cJXdrwS" target="_blank">
<button class="flex mt-4 md:mt-0 gap-1 items-center bg-transparent shadow-sm h-9 bg-violet-500 hover:bg-violet-600 text-white border border-solid border-violet-600 rounded font-medium p-2">
<svg class="h-4 w-4" width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10a9.96 9.96 0 0 1-4.587-1.112l-3.826 1.067a1.25 1.25 0 0 1-1.54-1.54l1.068-3.823A9.96 9.96 0 0 1 2 12C2 6.477 6.477 2 12 2Zm0 1.5A8.5 8.5 0 0 0 3.5 12c0 1.47.373 2.883 1.073 4.137l.15.27-1.112 3.984 3.987-1.112.27.15A8.5 8.5 0 1 0 12 3.5ZM8.75 13h4.498a.75.75 0 0 1 .102 1.493l-.102.007H8.75a.75.75 0 0 1-.102-1.493L8.75 13h4.498H8.75Zm0-3.5h6.505a.75.75 0 0 1 .101 1.493l-.101.007H8.75a.75.75 0 0 1-.102-1.493L8.75 9.5h6.505H8.75Z" fill="currentColor"/></svg>
<button class="flex mt-4 md:mt-0 gap-1 items-center bg-transparent shadow-sm h-9 bg-violet-500 hover:bg-violet-600 text-white border border-solid border-violet-600 rounded font-medium p-2 focus:outline-none">
<svg class="h-4 w-4" width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M13.209 3.103c-.495-1.004-1.926-1.004-2.421 0L8.43 7.88l-5.273.766c-1.107.161-1.55 1.522-.748 2.303l3.815 3.72-.9 5.25c-.19 1.103.968 1.944 1.959 1.424l4.715-2.48 4.716 2.48c.99.52 2.148-.32 1.96-1.424l-.902-5.25 3.816-3.72c.8-.78.359-2.142-.748-2.303l-5.273-.766-2.358-4.777ZM9.74 8.615l2.258-4.576 2.259 4.576a1.35 1.35 0 0 0 1.016.738l5.05.734-3.654 3.562a1.35 1.35 0 0 0-.388 1.195l.862 5.03-4.516-2.375a1.35 1.35 0 0 0-1.257 0l-4.516 2.374.862-5.029a1.35 1.35 0 0 0-.388-1.195l-3.654-3.562 5.05-.734c.44-.063.82-.34 1.016-.738ZM1.164 3.782a.75.75 0 0 0 .118 1.054l2.5 2a.75.75 0 1 0 .937-1.172l-2.5-2a.75.75 0 0 0-1.055.118Z" fill="currentColor"/><path d="M22.836 18.218a.75.75 0 0 0-.117-1.054l-2.5-2a.75.75 0 0 0-.938 1.172l2.5 2a.75.75 0 0 0 1.055-.117ZM1.282 17.164a.75.75 0 1 0 .937 1.172l2.5-2a.75.75 0 0 0-.937-1.172l-2.5 2ZM22.836 3.782a.75.75 0 0 1-.117 1.054l-2.5 2a.75.75 0 0 1-.938-1.172l2.5-2a.75.75 0 0 1 1.055.118Z" fill="currentColor"/></svg>
<span class="px-1">Community Support</span>
</button>
</a>
<% if ChatwootHub.pricing_plan !='community' %>
<button class="ml-4 flex gap-1 items-center bg-transparent h-9 hover:text-n-slate-12 hover:bg-slate-50 border border-solid border-slate-100 rounded text-n-slate-11 font-medium p-2" onclick="window.$chatwoot.toggle('open')">
<svg class="h-4 w-4" width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10a9.96 9.96 0 0 1-4.587-1.112l-3.826 1.067a1.25 1.25 0 0 1-1.54-1.54l1.068-3.823A9.96 9.96 0 0 1 2 12C2 6.477 6.477 2 12 2Zm0 1.5A8.5 8.5 0 0 0 3.5 12c0 1.47.373 2.883 1.073 4.137l.15.27-1.112 3.984 3.987-1.112.27.15A8.5 8.5 0 1 0 12 3.5ZM8.75 13h4.498a.75.75 0 0 1 .102 1.493l-.102.007H8.75a.75.75 0 0 1-.102-1.493L8.75 13h4.498H8.75Zm0-3.5h6.505a.75.75 0 0 1 .101 1.493l-.101.007H8.75a.75.75 0 0 1-.102-1.493L8.75 9.5h6.505H8.75Z" fill="currentColor"/></svg>
<button class="ml-4 flex gap-1 items-center bg-transparent h-9 hover:text-n-slate-12 hover:bg-slate-50 border border-solid border-slate-100 rounded text-n-slate-11 font-medium p-2 focus:outline-none" onclick="window.$chatwoot.toggle('open')">
<svg class="h-4 w-4" width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M13.209 3.103c-.495-1.004-1.926-1.004-2.421 0L8.43 7.88l-5.273.766c-1.107.161-1.55 1.522-.748 2.303l3.815 3.72-.9 5.25c-.19 1.103.968 1.944 1.959 1.424l4.715-2.48 4.716 2.48c.99.52 2.148-.32 1.96-1.424l-.902-5.25 3.816-3.72c.8-.78.359-2.142-.748-2.303l-5.273-.766-2.358-4.777ZM9.74 8.615l2.258-4.576 2.259 4.576a1.35 1.35 0 0 0 1.016.738l5.05.734-3.654 3.562a1.35 1.35 0 0 0-.388 1.195l.862 5.03-4.516-2.375a1.35 1.35 0 0 0-1.257 0l-4.516 2.374.862-5.029a1.35 1.35 0 0 0-.388-1.195l-3.654-3.562 5.05-.734c.44-.063.82-.34 1.016-.738ZM1.164 3.782a.75.75 0 0 0 .118 1.054l2.5 2a.75.75 0 1 0 .937-1.172l-2.5-2a.75.75 0 0 0-1.055.118Z" fill="currentColor"/><path d="M22.836 18.218a.75.75 0 0 0-.117-1.054l-2.5-2a.75.75 0 0 0-.938 1.172l2.5 2a.75.75 0 0 0 1.055-.117ZM1.282 17.164a.75.75 0 1 0 .937 1.172l2.5-2a.75.75 0 0 0-.937-1.172l-2.5 2ZM22.836 3.782a.75.75 0 0 1-.117 1.054l-2.5 2a.75.75 0 0 1-.938-1.172l2.5-2a.75.75 0 0 1 1.055.118Z" fill="currentColor"/></svg>
<span class="px-1">Chat Support</span>
</button>
<% end %>
@@ -90,7 +99,7 @@
</div>
<% if !attrs[:enabled] %>
<div class="flex h-9 absolute top-5 items-center invisible group-hover:visible">
<a href="<%= ChatwootHub.billing_url %>" target="_blank" rel="noopener noreferrer" class="flex gap-1 items-center bg-slate-100 h-9 hover:bg-slate-300 hover:text-n-slate-12 border border-solid border-slate-100 rounded text-n-slate-11 font-medium p-2">
<a href="<%= ChatwootHub.billing_url %>" target="_blank" rel="noopener noreferrer" class="flex gap-1 items-center bg-slate-100 h-9 hover:bg-slate-300 hover:text-n-slate-12 border border-solid border-slate-100 rounded text-n-slate-11 font-medium p-2 focus:outline-none">
<svg class="h-4 w-4" width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M13.209 3.103c-.495-1.004-1.926-1.004-2.421 0L8.43 7.88l-5.273.766c-1.107.161-1.55 1.522-.748 2.303l3.815 3.72-.9 5.25c-.19 1.103.968 1.944 1.959 1.424l4.715-2.48 4.716 2.48c.99.52 2.148-.32 1.96-1.424l-.902-5.25 3.816-3.72c.8-.78.359-2.142-.748-2.303l-5.273-.766-2.358-4.777ZM9.74 8.615l2.258-4.576 2.259 4.576a1.35 1.35 0 0 0 1.016.738l5.05.734-3.654 3.562a1.35 1.35 0 0 0-.388 1.195l.862 5.03-4.516-2.375a1.35 1.35 0 0 0-1.257 0l-4.516 2.374.862-5.029a1.35 1.35 0 0 0-.388-1.195l-3.654-3.562 5.05-.734c.44-.063.82-.34 1.016-.738ZM1.164 3.782a.75.75 0 0 0 .118 1.054l2.5 2a.75.75 0 1 0 .937-1.172l-2.5-2a.75.75 0 0 0-1.055.118Z" fill="currentColor"/><path d="M22.836 18.218a.75.75 0 0 0-.117-1.054l-2.5-2a.75.75 0 0 0-.938 1.172l2.5 2a.75.75 0 0 0 1.055-.117ZM1.282 17.164a.75.75 0 1 0 .937 1.172l2.5-2a.75.75 0 0 0-.937-1.172l-2.5 2ZM22.836 3.782a.75.75 0 0 1-.117 1.054l-2.5 2a.75.75 0 0 1-.938-1.172l2.5-2a.75.75 0 0 1 1.055.118Z" fill="currentColor"/></svg>
<span class="px-1">Upgrade now</span>
</a>
+1 -1
View File
@@ -1,5 +1,5 @@
shared: &shared
version: '4.2.0'
version: '4.3.0'
development:
<<: *shared
+4 -1
View File
@@ -168,4 +168,7 @@
enabled: true
- name: crm_integration
display_name: CRM Integration
enabled: false
enabled: false
- name: notion_integration
display_name: Notion Integration
enabled: false
+19
View File
@@ -288,6 +288,25 @@
type: secret
## ------ End of Configs added for Linear ------ ##
## ------ Configs added for Notion ------ ##
- name: NOTION_CLIENT_ID
display_title: 'Notion Client ID'
value:
locked: false
description: 'Notion client ID'
- name: NOTION_CLIENT_SECRET
display_title: 'Notion Client Secret'
value:
locked: false
description: 'Notion client secret'
type: secret
- name: NOTION_VERSION
display_title: 'Notion Version'
value: '2022-06-28'
locked: false
description: 'Notion version'
## ------ End of Configs added for Notion ------ ##
## ------ Configs added for Slack ------ ##
- name: SLACK_CLIENT_ID
display_title: 'Slack Client ID'
+6
View File
@@ -63,6 +63,12 @@ linear:
action: https://linear.app/oauth/authorize
hook_type: account
allow_multiple_hooks: false
notion:
id: notion
logo: notion.png
i18n_key: notion
hook_type: account
allow_multiple_hooks: false
slack:
id: slack
logo: slack.png
+8
View File
@@ -91,6 +91,8 @@ am:
conversations_count: No. of conversations
avg_first_response_time: Avg first response time
avg_resolution_time: Avg resolution time
avg_reply_time: Avg reply time
resolution_count: Resolution Count
team_csv:
team_name: Team name
conversations_count: Conversations count
@@ -138,6 +140,8 @@ am:
instagram_story_content: '%{story_sender} mentioned you in the story: '
instagram_deleted_story_content: This story is no longer available.
deleted: This message was deleted
whatsapp:
list_button_label: 'Choose an item'
delivery_status:
error_code: 'Error code: %{error_code}'
activity:
@@ -172,6 +176,10 @@ am:
sla:
added: '%{user_name} added SLA policy %{sla_name}'
removed: '%{user_name} removed SLA policy %{sla_name}'
linear:
issue_created: 'Linear issue %{issue_id} was created by %{user_name}'
issue_linked: 'Linear issue %{issue_id} was linked by %{user_name}'
issue_unlinked: 'Linear issue %{issue_id} was unlinked by %{user_name}'
csat:
not_sent_due_to_messaging_window: 'CSAT survey not sent due to outgoing message restrictions'
muted: '%{user_name} has muted the conversation'
+8
View File
@@ -91,6 +91,8 @@ ar:
conversations_count: عدد المحادثات
avg_first_response_time: متوسط وقت الرد الأول
avg_resolution_time: متوسط وقت الحل
avg_reply_time: Avg reply time
resolution_count: عدد مرات الإغلاق
team_csv:
team_name: اسم الفريق
conversations_count: عدد المحادثات
@@ -138,6 +140,8 @@ ar:
instagram_story_content: 'أشار %{story_sender} إليك في القصة: '
instagram_deleted_story_content: هذه القصة لم تعد متاحة.
deleted: تم حذف هذه الرسالة
whatsapp:
list_button_label: 'Choose an item'
delivery_status:
error_code: 'رمز الخطأ: %{error_code}'
activity:
@@ -172,6 +176,10 @@ ar:
sla:
added: '%{user_name} أضاف سياسة مستوى الخدمة %{sla_name}'
removed: '%{user_name} أزال سياسة مستوى الخدمة %{sla_name}'
linear:
issue_created: 'Linear issue %{issue_id} was created by %{user_name}'
issue_linked: 'Linear issue %{issue_id} was linked by %{user_name}'
issue_unlinked: 'Linear issue %{issue_id} was unlinked by %{user_name}'
csat:
not_sent_due_to_messaging_window: 'CSAT survey not sent due to outgoing message restrictions'
muted: '%{user_name} كتم صوت المحادثة'
+8
View File
@@ -91,6 +91,8 @@ az:
conversations_count: No. of conversations
avg_first_response_time: Avg first response time
avg_resolution_time: Avg resolution time
avg_reply_time: Avg reply time
resolution_count: Resolution Count
team_csv:
team_name: Team name
conversations_count: Conversations count
@@ -138,6 +140,8 @@ az:
instagram_story_content: '%{story_sender} mentioned you in the story: '
instagram_deleted_story_content: This story is no longer available.
deleted: This message was deleted
whatsapp:
list_button_label: 'Choose an item'
delivery_status:
error_code: 'Error code: %{error_code}'
activity:
@@ -172,6 +176,10 @@ az:
sla:
added: '%{user_name} added SLA policy %{sla_name}'
removed: '%{user_name} removed SLA policy %{sla_name}'
linear:
issue_created: 'Linear issue %{issue_id} was created by %{user_name}'
issue_linked: 'Linear issue %{issue_id} was linked by %{user_name}'
issue_unlinked: 'Linear issue %{issue_id} was unlinked by %{user_name}'
csat:
not_sent_due_to_messaging_window: 'CSAT survey not sent due to outgoing message restrictions'
muted: '%{user_name} has muted the conversation'
+8
View File
@@ -91,6 +91,8 @@ bg:
conversations_count: No. of conversations
avg_first_response_time: Avg first response time
avg_resolution_time: Avg resolution time
avg_reply_time: Avg reply time
resolution_count: Resolution Count
team_csv:
team_name: Team name
conversations_count: Conversations count
@@ -138,6 +140,8 @@ bg:
instagram_story_content: '%{story_sender} mentioned you in the story: '
instagram_deleted_story_content: This story is no longer available.
deleted: This message was deleted
whatsapp:
list_button_label: 'Choose an item'
delivery_status:
error_code: 'Error code: %{error_code}'
activity:
@@ -172,6 +176,10 @@ bg:
sla:
added: '%{user_name} added SLA policy %{sla_name}'
removed: '%{user_name} removed SLA policy %{sla_name}'
linear:
issue_created: 'Linear issue %{issue_id} was created by %{user_name}'
issue_linked: 'Linear issue %{issue_id} was linked by %{user_name}'
issue_unlinked: 'Linear issue %{issue_id} was unlinked by %{user_name}'
csat:
not_sent_due_to_messaging_window: 'CSAT survey not sent due to outgoing message restrictions'
muted: '%{user_name} has muted the conversation'
+8
View File
@@ -91,6 +91,8 @@ ca:
conversations_count: Nre. de converses
avg_first_response_time: Temps mitjà de primera resposta
avg_resolution_time: Temps mitjà de resolució
avg_reply_time: Avg reply time
resolution_count: Total de resolucions
team_csv:
team_name: Nom de l'equip
conversations_count: Recompte de converses
@@ -138,6 +140,8 @@ ca:
instagram_story_content: '%{story_sender} t''ha mencionat a la història: '
instagram_deleted_story_content: Aquesta història ja no està disponible.
deleted: Aquest missatge a sigut eliminat
whatsapp:
list_button_label: 'Choose an item'
delivery_status:
error_code: 'Codi d''error: %{error_code}'
activity:
@@ -172,6 +176,10 @@ ca:
sla:
added: '%{user_name} ha afegit la política de SLA %{sla_name}'
removed: '%{user_name} ha eliminat la política de SLA %{sla_name}'
linear:
issue_created: 'Linear issue %{issue_id} was created by %{user_name}'
issue_linked: 'Linear issue %{issue_id} was linked by %{user_name}'
issue_unlinked: 'Linear issue %{issue_id} was unlinked by %{user_name}'
csat:
not_sent_due_to_messaging_window: 'CSAT survey not sent due to outgoing message restrictions'
muted: '%{user_name} ha silenciat la conversa'
+8
View File
@@ -91,6 +91,8 @@ cs:
conversations_count: No. of conversations
avg_first_response_time: Avg first response time
avg_resolution_time: Avg resolution time
avg_reply_time: Avg reply time
resolution_count: Počet rozlišení
team_csv:
team_name: Team name
conversations_count: Conversations count
@@ -138,6 +140,8 @@ cs:
instagram_story_content: '%{story_sender} mentioned you in the story: '
instagram_deleted_story_content: This story is no longer available.
deleted: Tato zpráva byla smazána
whatsapp:
list_button_label: 'Choose an item'
delivery_status:
error_code: 'Error code: %{error_code}'
activity:
@@ -172,6 +176,10 @@ cs:
sla:
added: '%{user_name} added SLA policy %{sla_name}'
removed: '%{user_name} removed SLA policy %{sla_name}'
linear:
issue_created: 'Linear issue %{issue_id} was created by %{user_name}'
issue_linked: 'Linear issue %{issue_id} was linked by %{user_name}'
issue_unlinked: 'Linear issue %{issue_id} was unlinked by %{user_name}'
csat:
not_sent_due_to_messaging_window: 'CSAT survey not sent due to outgoing message restrictions'
muted: '%{user_name} ztlumil/a konverzaci'
+8
View File
@@ -91,6 +91,8 @@ da:
conversations_count: Antal samtaler
avg_first_response_time: Avg first response time
avg_resolution_time: Avg resolution time
avg_reply_time: Avg reply time
resolution_count: Antal Afsluttede
team_csv:
team_name: Team navn
conversations_count: Samtaler tæller
@@ -138,6 +140,8 @@ da:
instagram_story_content: '%{story_sender} nævnte dig i historien: '
instagram_deleted_story_content: Denne historie er ikke længere tilgængelig.
deleted: Denne besked blev slettet
whatsapp:
list_button_label: 'Choose an item'
delivery_status:
error_code: 'Error code: %{error_code}'
activity:
@@ -172,6 +176,10 @@ da:
sla:
added: '%{user_name} added SLA policy %{sla_name}'
removed: '%{user_name} removed SLA policy %{sla_name}'
linear:
issue_created: 'Linear issue %{issue_id} was created by %{user_name}'
issue_linked: 'Linear issue %{issue_id} was linked by %{user_name}'
issue_unlinked: 'Linear issue %{issue_id} was unlinked by %{user_name}'
csat:
not_sent_due_to_messaging_window: 'CSAT survey not sent due to outgoing message restrictions'
muted: '%{user_name} har slukket for samtalen'
+8
View File
@@ -91,6 +91,8 @@ de:
conversations_count: Anzahl der Konversationen
avg_first_response_time: Durchschnittliche Zeit bis zur ersten Antwort
avg_resolution_time: Durchschnittliche Auflösung
avg_reply_time: Avg reply time
resolution_count: Auflösungsanzahl
team_csv:
team_name: Teamname
conversations_count: Anzahl Gespräche
@@ -138,6 +140,8 @@ de:
instagram_story_content: '%{story_sender} erwähnte sie in der Geschichte: '
instagram_deleted_story_content: Diese Geschichte ist nicht mehr verfügbar.
deleted: Diese Nachricht wurde gelöscht
whatsapp:
list_button_label: 'Choose an item'
delivery_status:
error_code: 'Fehlercode: %{error_code}'
activity:
@@ -172,6 +176,10 @@ de:
sla:
added: '%{user_name} hat SLA-Richtlinie %{sla_name} hinzugefügt'
removed: '%{user_name} hat SLA-Richtlinie %{sla_name} entfernt'
linear:
issue_created: 'Linear issue %{issue_id} was created by %{user_name}'
issue_linked: 'Linear issue %{issue_id} was linked by %{user_name}'
issue_unlinked: 'Linear issue %{issue_id} was unlinked by %{user_name}'
csat:
not_sent_due_to_messaging_window: 'CSAT survey not sent due to outgoing message restrictions'
muted: '%{user_name} hat das Gespräch stumm geschaltet'
+8
View File
@@ -91,6 +91,8 @@ el:
conversations_count: Αριθμός συνομιλιών
avg_first_response_time: Avg first response time
avg_resolution_time: Avg resolution time
avg_reply_time: Avg reply time
resolution_count: Αριθμός Αναλύσεων
team_csv:
team_name: Όνομα ομάδας
conversations_count: Αριθμός συνομιλιών
@@ -138,6 +140,8 @@ el:
instagram_story_content: 'Ο %{story_sender} σας ανέφερε στην ιστορία: '
instagram_deleted_story_content: Η ιστορία δεν είναι πλέον διαθέσιμη.
deleted: Το μήνυμα διαγράφηκε
whatsapp:
list_button_label: 'Choose an item'
delivery_status:
error_code: 'Error code: %{error_code}'
activity:
@@ -172,6 +176,10 @@ el:
sla:
added: '%{user_name} added SLA policy %{sla_name}'
removed: '%{user_name} removed SLA policy %{sla_name}'
linear:
issue_created: 'Linear issue %{issue_id} was created by %{user_name}'
issue_linked: 'Linear issue %{issue_id} was linked by %{user_name}'
issue_unlinked: 'Linear issue %{issue_id} was unlinked by %{user_name}'
csat:
not_sent_due_to_messaging_window: 'CSAT survey not sent due to outgoing message restrictions'
muted: 'Ο χρήστης %{user_name} σίγασε την συνομιλία'
+12
View File
@@ -105,6 +105,8 @@ en:
conversations_count: No. of conversations
avg_first_response_time: Avg first response time
avg_resolution_time: Avg resolution time
avg_reply_time: Avg reply time
resolution_count: Resolution Count
team_csv:
team_name: Team name
conversations_count: Conversations count
@@ -188,8 +190,14 @@ en:
sla:
added: '%{user_name} added SLA policy %{sla_name}'
removed: '%{user_name} removed SLA policy %{sla_name}'
linear:
issue_created: 'Linear issue %{issue_id} was created by %{user_name}'
issue_linked: 'Linear issue %{issue_id} was linked by %{user_name}'
issue_unlinked: 'Linear issue %{issue_id} was unlinked by %{user_name}'
csat:
not_sent_due_to_messaging_window: 'CSAT survey not sent due to outgoing message restrictions'
auto_resolve:
not_sent_due_to_messaging_window: 'Auto-resolve message not sent due to outgoing message restrictions'
muted: '%{user_name} has muted the conversation'
unmuted: '%{user_name} has unmuted the conversation'
auto_resolution_message: 'Resolving the conversation as it has been inactive for a while. Please start a new conversation if you need further assistance.'
@@ -249,6 +257,10 @@ en:
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.'
notion:
name: 'Notion'
short_description: 'Integrate databases, documents and pages directly with Captain.'
description: 'Connect your Notion workspace to enable Captain to access and generate intelligent responses using content from your databases, documents, and pages to provide more contextual customer support.'
shopify:
name: 'Shopify'
short_description: 'Access order details and customer data from your Shopify store.'
+8
View File
@@ -91,6 +91,8 @@ es:
conversations_count: Núm. de conversaciones
avg_first_response_time: Promedio de tiempo de la primera respuesta
avg_resolution_time: Tiempo promedio de resolución
avg_reply_time: Avg reply time
resolution_count: Número de resoluciones
team_csv:
team_name: Nombre del equipo
conversations_count: Cantidad de conversaciones
@@ -138,6 +140,8 @@ es:
instagram_story_content: '%{story_sender} te mencionó en la historia: '
instagram_deleted_story_content: Esta historia ya no está disponible.
deleted: Este mensaje se ha eliminado
whatsapp:
list_button_label: 'Choose an item'
delivery_status:
error_code: 'Código de error: %{error_code}'
activity:
@@ -172,6 +176,10 @@ es:
sla:
added: '%{user_name} agregó la política de SLA %{sla_name}'
removed: '%{user_name} eliminó la política de SLA %{sla_name}'
linear:
issue_created: 'Linear issue %{issue_id} was created by %{user_name}'
issue_linked: 'Linear issue %{issue_id} was linked by %{user_name}'
issue_unlinked: 'Linear issue %{issue_id} was unlinked by %{user_name}'
csat:
not_sent_due_to_messaging_window: 'CSAT survey not sent due to outgoing message restrictions'
muted: '%{user_name} ha silenciado la conversación'
+8
View File
@@ -91,6 +91,8 @@ fa:
conversations_count: تعداد گفتگوها
avg_first_response_time: میانگین زمان تا اولین پاسخ
avg_resolution_time: میانگین زمان حل مشکل
avg_reply_time: Avg reply time
resolution_count: تعداد مسائل حل شده
team_csv:
team_name: نام تیم
conversations_count: Conversations count
@@ -138,6 +140,8 @@ fa:
instagram_story_content: '%{story_sender} در داستان به شما اشاره کرده: '
instagram_deleted_story_content: این داستان دیگر در دسترس نیست.
deleted: این پیام حذف شد
whatsapp:
list_button_label: 'Choose an item'
delivery_status:
error_code: 'کد خطا " %{error_code}'
activity:
@@ -172,6 +176,10 @@ fa:
sla:
added: '%{user_name} سیاست SLA %{sla_name} را اضافه کرد'
removed: '%{user_name} سیاست SLA %{sla_name} را حذف کرد'
linear:
issue_created: 'Linear issue %{issue_id} was created by %{user_name}'
issue_linked: 'Linear issue %{issue_id} was linked by %{user_name}'
issue_unlinked: 'Linear issue %{issue_id} was unlinked by %{user_name}'
csat:
not_sent_due_to_messaging_window: 'CSAT survey not sent due to outgoing message restrictions'
muted: '%{user_name} مکالمه را بی صدا کرد'
+8
View File
@@ -91,6 +91,8 @@ fi:
conversations_count: No. of conversations
avg_first_response_time: Avg first response time
avg_resolution_time: Avg resolution time
avg_reply_time: Avg reply time
resolution_count: Selvitysmäärä
team_csv:
team_name: Team name
conversations_count: Conversations count
@@ -138,6 +140,8 @@ fi:
instagram_story_content: '%{story_sender} mentioned you in the story: '
instagram_deleted_story_content: This story is no longer available.
deleted: This message was deleted
whatsapp:
list_button_label: 'Choose an item'
delivery_status:
error_code: 'Error code: %{error_code}'
activity:
@@ -172,6 +176,10 @@ fi:
sla:
added: '%{user_name} added SLA policy %{sla_name}'
removed: '%{user_name} removed SLA policy %{sla_name}'
linear:
issue_created: 'Linear issue %{issue_id} was created by %{user_name}'
issue_linked: 'Linear issue %{issue_id} was linked by %{user_name}'
issue_unlinked: 'Linear issue %{issue_id} was unlinked by %{user_name}'
csat:
not_sent_due_to_messaging_window: 'CSAT survey not sent due to outgoing message restrictions'
muted: '%{user_name} mykisti keskustelun'
+8
View File
@@ -91,6 +91,8 @@ fr:
conversations_count: Nbre de conversations
avg_first_response_time: Temps moyen pour une première réponse
avg_resolution_time: Temps nécessaire pour résoudre une demande (en moyenne)
avg_reply_time: Avg reply time
resolution_count: Nombre de résolutions
team_csv:
team_name: Nom de l'équipe
conversations_count: Nombre de conversations
@@ -138,6 +140,8 @@ fr:
instagram_story_content: '%{story_sender} vous a mentionné dans la story: '
instagram_deleted_story_content: Cette Story n'est plus disponible.
deleted: Ce message a été supprimé
whatsapp:
list_button_label: 'Choose an item'
delivery_status:
error_code: 'Code d''erreur : %{error_code}'
activity:
@@ -172,6 +176,10 @@ fr:
sla:
added: '%{user_name} added SLA policy %{sla_name}'
removed: '%{user_name} removed SLA policy %{sla_name}'
linear:
issue_created: 'Linear issue %{issue_id} was created by %{user_name}'
issue_linked: 'Linear issue %{issue_id} was linked by %{user_name}'
issue_unlinked: 'Linear issue %{issue_id} was unlinked by %{user_name}'
csat:
not_sent_due_to_messaging_window: 'CSAT survey not sent due to outgoing message restrictions'
muted: '%{user_name} a mis la conversation en sourdine'
+8
View File
@@ -91,6 +91,8 @@ he:
conversations_count: No. of conversations
avg_first_response_time: Avg first response time
avg_resolution_time: Avg resolution time
avg_reply_time: Avg reply time
resolution_count: ספירת רזולוציות
team_csv:
team_name: שם קבוצה
conversations_count: Conversations count
@@ -138,6 +140,8 @@ he:
instagram_story_content: '%{story_sender} mentioned you in the story: '
instagram_deleted_story_content: This story is no longer available.
deleted: This message was deleted
whatsapp:
list_button_label: 'Choose an item'
delivery_status:
error_code: 'Error code: %{error_code}'
activity:
@@ -172,6 +176,10 @@ he:
sla:
added: '%{user_name} added SLA policy %{sla_name}'
removed: '%{user_name} removed SLA policy %{sla_name}'
linear:
issue_created: 'Linear issue %{issue_id} was created by %{user_name}'
issue_linked: 'Linear issue %{issue_id} was linked by %{user_name}'
issue_unlinked: 'Linear issue %{issue_id} was unlinked by %{user_name}'
csat:
not_sent_due_to_messaging_window: 'CSAT survey not sent due to outgoing message restrictions'
muted: '%{user_name} has muted the conversation'
+8
View File
@@ -91,6 +91,8 @@ hi:
conversations_count: No. of conversations
avg_first_response_time: Avg first response time
avg_resolution_time: Avg resolution time
avg_reply_time: Avg reply time
resolution_count: Resolution Count
team_csv:
team_name: Team name
conversations_count: Conversations count
@@ -138,6 +140,8 @@ hi:
instagram_story_content: '%{story_sender} mentioned you in the story: '
instagram_deleted_story_content: This story is no longer available.
deleted: This message was deleted
whatsapp:
list_button_label: 'Choose an item'
delivery_status:
error_code: 'Error code: %{error_code}'
activity:
@@ -172,6 +176,10 @@ hi:
sla:
added: '%{user_name} added SLA policy %{sla_name}'
removed: '%{user_name} removed SLA policy %{sla_name}'
linear:
issue_created: 'Linear issue %{issue_id} was created by %{user_name}'
issue_linked: 'Linear issue %{issue_id} was linked by %{user_name}'
issue_unlinked: 'Linear issue %{issue_id} was unlinked by %{user_name}'
csat:
not_sent_due_to_messaging_window: 'CSAT survey not sent due to outgoing message restrictions'
muted: '%{user_name} has muted the conversation'
+8
View File
@@ -91,6 +91,8 @@ hr:
conversations_count: No. of conversations
avg_first_response_time: Avg first response time
avg_resolution_time: Avg resolution time
avg_reply_time: Avg reply time
resolution_count: Resolution Count
team_csv:
team_name: Team name
conversations_count: Conversations count
@@ -138,6 +140,8 @@ hr:
instagram_story_content: '%{story_sender} mentioned you in the story: '
instagram_deleted_story_content: This story is no longer available.
deleted: This message was deleted
whatsapp:
list_button_label: 'Choose an item'
delivery_status:
error_code: 'Error code: %{error_code}'
activity:
@@ -172,6 +176,10 @@ hr:
sla:
added: '%{user_name} added SLA policy %{sla_name}'
removed: '%{user_name} removed SLA policy %{sla_name}'
linear:
issue_created: 'Linear issue %{issue_id} was created by %{user_name}'
issue_linked: 'Linear issue %{issue_id} was linked by %{user_name}'
issue_unlinked: 'Linear issue %{issue_id} was unlinked by %{user_name}'
csat:
not_sent_due_to_messaging_window: 'CSAT survey not sent due to outgoing message restrictions'
muted: '%{user_name} has muted the conversation'
+8
View File
@@ -91,6 +91,8 @@ hu:
conversations_count: Beszélgetések száma
avg_first_response_time: Avg first response time
avg_resolution_time: Avg resolution time
avg_reply_time: Avg reply time
resolution_count: Megoldások száma
team_csv:
team_name: Csapatnév
conversations_count: Beszélgetésszám
@@ -138,6 +140,8 @@ hu:
instagram_story_content: '%{story_sender} megemlített egy storyban: '
instagram_deleted_story_content: Ez a story már nem érhető el.
deleted: Az üzenet törölve lett
whatsapp:
list_button_label: 'Choose an item'
delivery_status:
error_code: 'Hibakód: %{error_code}'
activity:
@@ -172,6 +176,10 @@ hu:
sla:
added: '%{user_name} added SLA policy %{sla_name}'
removed: '%{user_name} removed SLA policy %{sla_name}'
linear:
issue_created: 'Linear issue %{issue_id} was created by %{user_name}'
issue_linked: 'Linear issue %{issue_id} was linked by %{user_name}'
issue_unlinked: 'Linear issue %{issue_id} was unlinked by %{user_name}'
csat:
not_sent_due_to_messaging_window: 'CSAT survey not sent due to outgoing message restrictions'
muted: '%{user_name} elnémította a beszélgetést'
+8
View File
@@ -91,6 +91,8 @@ hy:
conversations_count: No. of conversations
avg_first_response_time: Avg first response time
avg_resolution_time: Avg resolution time
avg_reply_time: Avg reply time
resolution_count: Resolution Count
team_csv:
team_name: Team name
conversations_count: Conversations count
@@ -138,6 +140,8 @@ hy:
instagram_story_content: '%{story_sender} mentioned you in the story: '
instagram_deleted_story_content: This story is no longer available.
deleted: This message was deleted
whatsapp:
list_button_label: 'Choose an item'
delivery_status:
error_code: 'Error code: %{error_code}'
activity:
@@ -172,6 +176,10 @@ hy:
sla:
added: '%{user_name} added SLA policy %{sla_name}'
removed: '%{user_name} removed SLA policy %{sla_name}'
linear:
issue_created: 'Linear issue %{issue_id} was created by %{user_name}'
issue_linked: 'Linear issue %{issue_id} was linked by %{user_name}'
issue_unlinked: 'Linear issue %{issue_id} was unlinked by %{user_name}'
csat:
not_sent_due_to_messaging_window: 'CSAT survey not sent due to outgoing message restrictions'
muted: '%{user_name} has muted the conversation'
+8
View File
@@ -91,6 +91,8 @@ id:
conversations_count: Jumlah percakapan
avg_first_response_time: Avg first response time
avg_resolution_time: Avg resolution time
avg_reply_time: Avg reply time
resolution_count: Jumlah Terselesaikan
team_csv:
team_name: Nama Tim
conversations_count: Jumlah percakapan
@@ -138,6 +140,8 @@ id:
instagram_story_content: '%{story_sender} menyebutmu dalam story: '
instagram_deleted_story_content: Story ini tidak lagi tersedia.
deleted: Pesan ini telah terhapus
whatsapp:
list_button_label: 'Choose an item'
delivery_status:
error_code: 'Error code: %{error_code}'
activity:
@@ -172,6 +176,10 @@ id:
sla:
added: '%{user_name} added SLA policy %{sla_name}'
removed: '%{user_name} removed SLA policy %{sla_name}'
linear:
issue_created: 'Linear issue %{issue_id} was created by %{user_name}'
issue_linked: 'Linear issue %{issue_id} was linked by %{user_name}'
issue_unlinked: 'Linear issue %{issue_id} was unlinked by %{user_name}'
csat:
not_sent_due_to_messaging_window: 'CSAT survey not sent due to outgoing message restrictions'
muted: '%{user_name} me-mute percakapan'
+8
View File
@@ -91,6 +91,8 @@ is:
conversations_count: No. of conversations
avg_first_response_time: Avg first response time
avg_resolution_time: Avg resolution time
avg_reply_time: Avg reply time
resolution_count: Resolution Count
team_csv:
team_name: Team name
conversations_count: Conversations count
@@ -138,6 +140,8 @@ is:
instagram_story_content: '%{story_sender} minntist á þig í sögunni: '
instagram_deleted_story_content: This story is no longer available.
deleted: This message was deleted
whatsapp:
list_button_label: 'Choose an item'
delivery_status:
error_code: 'Error code: %{error_code}'
activity:
@@ -172,6 +176,10 @@ is:
sla:
added: '%{user_name} added SLA policy %{sla_name}'
removed: '%{user_name} removed SLA policy %{sla_name}'
linear:
issue_created: 'Linear issue %{issue_id} was created by %{user_name}'
issue_linked: 'Linear issue %{issue_id} was linked by %{user_name}'
issue_unlinked: 'Linear issue %{issue_id} was unlinked by %{user_name}'
csat:
not_sent_due_to_messaging_window: 'CSAT survey not sent due to outgoing message restrictions'
muted: '%{user_name} has muted the conversation'
+8
View File
@@ -91,6 +91,8 @@ it:
conversations_count: Numero di conversazioni
avg_first_response_time: Avg first response time
avg_resolution_time: Avg resolution time
avg_reply_time: Avg reply time
resolution_count: Conteggio risoluzioni
team_csv:
team_name: Nome del team
conversations_count: Numero di conversazioni
@@ -138,6 +140,8 @@ it:
instagram_story_content: '%{story_sender} ti ha menzionato nella storia: '
instagram_deleted_story_content: Questa storia non è più disponibile.
deleted: Questo messaggio è stato eliminato
whatsapp:
list_button_label: 'Choose an item'
delivery_status:
error_code: 'Error code: %{error_code}'
activity:
@@ -172,6 +176,10 @@ it:
sla:
added: '%{user_name} added SLA policy %{sla_name}'
removed: '%{user_name} removed SLA policy %{sla_name}'
linear:
issue_created: 'Linear issue %{issue_id} was created by %{user_name}'
issue_linked: 'Linear issue %{issue_id} was linked by %{user_name}'
issue_unlinked: 'Linear issue %{issue_id} was unlinked by %{user_name}'
csat:
not_sent_due_to_messaging_window: 'CSAT survey not sent due to outgoing message restrictions'
muted: '%{user_name} ha silenziato la conversazione'
+8
View File
@@ -91,6 +91,8 @@ ja:
conversations_count: 会話数
avg_first_response_time: 初回応答の平均時間
avg_resolution_time: 解決までの平均時間
avg_reply_time: Avg reply time
resolution_count: 処理件数
team_csv:
team_name: チーム名
conversations_count: 会話回数
@@ -138,6 +140,8 @@ ja:
instagram_story_content: '%{story_sender} さんがストーリーであなたについて言及しました: '
instagram_deleted_story_content: このストーリーはもう利用できません。
deleted: このメッセージは削除されました
whatsapp:
list_button_label: 'Choose an item'
delivery_status:
error_code: 'エラーコード: %{error_code}'
activity:
@@ -172,6 +176,10 @@ ja:
sla:
added: '%{user_name} がSLAポリシー "%{sla_name}" を追加しました'
removed: '%{user_name} がSLAポリシー "%{sla_name}" を削除しました'
linear:
issue_created: 'Linear issue %{issue_id} was created by %{user_name}'
issue_linked: 'Linear issue %{issue_id} was linked by %{user_name}'
issue_unlinked: 'Linear issue %{issue_id} was unlinked by %{user_name}'
csat:
not_sent_due_to_messaging_window: 'CSAT survey not sent due to outgoing message restrictions'
muted: '%{user_name} が会話をミュートしました'
+8
View File
@@ -91,6 +91,8 @@ ka:
conversations_count: No. of conversations
avg_first_response_time: Avg first response time
avg_resolution_time: Avg resolution time
avg_reply_time: Avg reply time
resolution_count: Resolution Count
team_csv:
team_name: Team name
conversations_count: Conversations count
@@ -138,6 +140,8 @@ ka:
instagram_story_content: '%{story_sender} mentioned you in the story: '
instagram_deleted_story_content: This story is no longer available.
deleted: This message was deleted
whatsapp:
list_button_label: 'Choose an item'
delivery_status:
error_code: 'Error code: %{error_code}'
activity:
@@ -172,6 +176,10 @@ ka:
sla:
added: '%{user_name} added SLA policy %{sla_name}'
removed: '%{user_name} removed SLA policy %{sla_name}'
linear:
issue_created: 'Linear issue %{issue_id} was created by %{user_name}'
issue_linked: 'Linear issue %{issue_id} was linked by %{user_name}'
issue_unlinked: 'Linear issue %{issue_id} was unlinked by %{user_name}'
csat:
not_sent_due_to_messaging_window: 'CSAT survey not sent due to outgoing message restrictions'
muted: '%{user_name} has muted the conversation'
+8
View File
@@ -91,6 +91,8 @@ ko:
conversations_count: No. of conversations
avg_first_response_time: Avg first response time
avg_resolution_time: Avg resolution time
avg_reply_time: Avg reply time
resolution_count: 해결 수
team_csv:
team_name: Team name
conversations_count: Conversations count
@@ -138,6 +140,8 @@ ko:
instagram_story_content: '%{story_sender} mentioned you in the story: '
instagram_deleted_story_content: This story is no longer available.
deleted: This message was deleted
whatsapp:
list_button_label: 'Choose an item'
delivery_status:
error_code: 'Error code: %{error_code}'
activity:
@@ -172,6 +176,10 @@ ko:
sla:
added: '%{user_name} added SLA policy %{sla_name}'
removed: '%{user_name} removed SLA policy %{sla_name}'
linear:
issue_created: 'Linear issue %{issue_id} was created by %{user_name}'
issue_linked: 'Linear issue %{issue_id} was linked by %{user_name}'
issue_unlinked: 'Linear issue %{issue_id} was unlinked by %{user_name}'
csat:
not_sent_due_to_messaging_window: 'CSAT survey not sent due to outgoing message restrictions'
muted: '%{user_name} has muted the conversation'
+8
View File
@@ -91,6 +91,8 @@ lt:
conversations_count: Pokalbių kiekis
avg_first_response_time: Avg first response time
avg_resolution_time: Avg resolution time
avg_reply_time: Avg reply time
resolution_count: Sprendimų skaičius
team_csv:
team_name: Komandos pavadinimas
conversations_count: Pokalbių skaičius
@@ -138,6 +140,8 @@ lt:
instagram_story_content: '%{story_sender} paminėjo jus pasakojime: '
instagram_deleted_story_content: Šis pasakojimas nebepasiekiamas.
deleted: Šis pranešimas buvo ištrintas
whatsapp:
list_button_label: 'Choose an item'
delivery_status:
error_code: 'Klaidos kodas: %{error_code}'
activity:
@@ -172,6 +176,10 @@ lt:
sla:
added: '%{user_name} added SLA policy %{sla_name}'
removed: '%{user_name} removed SLA policy %{sla_name}'
linear:
issue_created: 'Linear issue %{issue_id} was created by %{user_name}'
issue_linked: 'Linear issue %{issue_id} was linked by %{user_name}'
issue_unlinked: 'Linear issue %{issue_id} was unlinked by %{user_name}'
csat:
not_sent_due_to_messaging_window: 'CSAT survey not sent due to outgoing message restrictions'
muted: '%{user_name} nutildė pokalbį'
+8
View File
@@ -91,6 +91,8 @@ lv:
conversations_count: Sarunu skaits
avg_first_response_time: Vidējais pirmās reakcijas laiks
avg_resolution_time: Vidējais atrisināšanas laiks
avg_reply_time: Avg reply time
resolution_count: Atrisināšanas Skaits
team_csv:
team_name: Komandas nosaukums
conversations_count: Sarunu skaits
@@ -138,6 +140,8 @@ lv:
instagram_story_content: '%{story_sender} pieminēja jūs stāstā: '
instagram_deleted_story_content: Šis stāsts vairs nav pieejams.
deleted: Šis ziņojums ir izdzēsts
whatsapp:
list_button_label: 'Choose an item'
delivery_status:
error_code: 'Kļūdas kods: %{error_code}'
activity:
@@ -172,6 +176,10 @@ lv:
sla:
added: '%{user_name} pievienoja SLA politiku %{sla_name}'
removed: '%{user_name} noņēma SLA politiku %{sla_name}'
linear:
issue_created: 'Linear issue %{issue_id} was created by %{user_name}'
issue_linked: 'Linear issue %{issue_id} was linked by %{user_name}'
issue_unlinked: 'Linear issue %{issue_id} was unlinked by %{user_name}'
csat:
not_sent_due_to_messaging_window: 'CSAT survey not sent due to outgoing message restrictions'
muted: '%{user_name} izslēdza sarunu'
+8
View File
@@ -91,6 +91,8 @@ ml:
conversations_count: No. of conversations
avg_first_response_time: Avg first response time
avg_resolution_time: Avg resolution time
avg_reply_time: Avg reply time
resolution_count: മിഴിവ് എണ്ണം
team_csv:
team_name: ടീമിന്റെ പേര്
conversations_count: സംഭാഷണങ്ങളുടെ എണ്ണം
@@ -138,6 +140,8 @@ ml:
instagram_story_content: '%{story_sender} mentioned you in the story: '
instagram_deleted_story_content: This story is no longer available.
deleted: ഈ സന്ദേശം ഇല്ലാതാക്കി
whatsapp:
list_button_label: 'Choose an item'
delivery_status:
error_code: 'Error code: %{error_code}'
activity:
@@ -172,6 +176,10 @@ ml:
sla:
added: '%{user_name} added SLA policy %{sla_name}'
removed: '%{user_name} removed SLA policy %{sla_name}'
linear:
issue_created: 'Linear issue %{issue_id} was created by %{user_name}'
issue_linked: 'Linear issue %{issue_id} was linked by %{user_name}'
issue_unlinked: 'Linear issue %{issue_id} was unlinked by %{user_name}'
csat:
not_sent_due_to_messaging_window: 'CSAT survey not sent due to outgoing message restrictions'
muted: '%{user_name} has muted the conversation'
+8
View File
@@ -91,6 +91,8 @@ ms:
conversations_count: No. of conversations
avg_first_response_time: Avg first response time
avg_resolution_time: Avg resolution time
avg_reply_time: Avg reply time
resolution_count: Resolution Count
team_csv:
team_name: Team name
conversations_count: Conversations count
@@ -138,6 +140,8 @@ ms:
instagram_story_content: '%{story_sender} mentioned you in the story: '
instagram_deleted_story_content: This story is no longer available.
deleted: This message was deleted
whatsapp:
list_button_label: 'Choose an item'
delivery_status:
error_code: 'Error code: %{error_code}'
activity:
@@ -172,6 +176,10 @@ ms:
sla:
added: '%{user_name} added SLA policy %{sla_name}'
removed: '%{user_name} removed SLA policy %{sla_name}'
linear:
issue_created: 'Linear issue %{issue_id} was created by %{user_name}'
issue_linked: 'Linear issue %{issue_id} was linked by %{user_name}'
issue_unlinked: 'Linear issue %{issue_id} was unlinked by %{user_name}'
csat:
not_sent_due_to_messaging_window: 'CSAT survey not sent due to outgoing message restrictions'
muted: '%{user_name} has muted the conversation'
+8
View File
@@ -91,6 +91,8 @@ ne:
conversations_count: No. of conversations
avg_first_response_time: Avg first response time
avg_resolution_time: Avg resolution time
avg_reply_time: Avg reply time
resolution_count: Resolution Count
team_csv:
team_name: Team name
conversations_count: Conversations count
@@ -138,6 +140,8 @@ ne:
instagram_story_content: '%{story_sender} mentioned you in the story: '
instagram_deleted_story_content: This story is no longer available.
deleted: This message was deleted
whatsapp:
list_button_label: 'Choose an item'
delivery_status:
error_code: 'Error code: %{error_code}'
activity:
@@ -172,6 +176,10 @@ ne:
sla:
added: '%{user_name} added SLA policy %{sla_name}'
removed: '%{user_name} removed SLA policy %{sla_name}'
linear:
issue_created: 'Linear issue %{issue_id} was created by %{user_name}'
issue_linked: 'Linear issue %{issue_id} was linked by %{user_name}'
issue_unlinked: 'Linear issue %{issue_id} was unlinked by %{user_name}'
csat:
not_sent_due_to_messaging_window: 'CSAT survey not sent due to outgoing message restrictions'
muted: '%{user_name} has muted the conversation'
+8
View File
@@ -91,6 +91,8 @@ nl:
conversations_count: Aantal conversaties
avg_first_response_time: Avg first response time
avg_resolution_time: Avg resolution time
avg_reply_time: Avg reply time
resolution_count: Aantal Resoluties
team_csv:
team_name: Team Naam
conversations_count: Aantal gesprekken
@@ -138,6 +140,8 @@ nl:
instagram_story_content: '%{story_sender} heeft je genoemd in het verhaal: '
instagram_deleted_story_content: Dit verhaal is niet meer beschikbaar.
deleted: Dit bericht werd verwijderd
whatsapp:
list_button_label: 'Choose an item'
delivery_status:
error_code: 'Error code: %{error_code}'
activity:
@@ -172,6 +176,10 @@ nl:
sla:
added: '%{user_name} added SLA policy %{sla_name}'
removed: '%{user_name} removed SLA policy %{sla_name}'
linear:
issue_created: 'Linear issue %{issue_id} was created by %{user_name}'
issue_linked: 'Linear issue %{issue_id} was linked by %{user_name}'
issue_unlinked: 'Linear issue %{issue_id} was unlinked by %{user_name}'
csat:
not_sent_due_to_messaging_window: 'CSAT survey not sent due to outgoing message restrictions'
muted: '%{user_name} has muted the conversation'
+8
View File
@@ -91,6 +91,8 @@
conversations_count: Antall samtaler
avg_first_response_time: Første svartid
avg_resolution_time: Gjennomsnittstid for løsning
avg_reply_time: Avg reply time
resolution_count: Antall løsninger
team_csv:
team_name: Gruppe navn
conversations_count: Antall samtaler
@@ -138,6 +140,8 @@
instagram_story_content: '%{story_sender} nevnte deg i historien: '
instagram_deleted_story_content: Denne historien er ikke lenger tilgjengelig.
deleted: Denne meldingen er slettet
whatsapp:
list_button_label: 'Choose an item'
delivery_status:
error_code: 'Feilkode: %{error_code}'
activity:
@@ -172,6 +176,10 @@
sla:
added: '%{user_name} added SLA policy %{sla_name}'
removed: '%{user_name} removed SLA policy %{sla_name}'
linear:
issue_created: 'Linear issue %{issue_id} was created by %{user_name}'
issue_linked: 'Linear issue %{issue_id} was linked by %{user_name}'
issue_unlinked: 'Linear issue %{issue_id} was unlinked by %{user_name}'
csat:
not_sent_due_to_messaging_window: 'CSAT survey not sent due to outgoing message restrictions'
muted: '%{user_name} har dempet samtalen'
+8
View File
@@ -91,6 +91,8 @@ pl:
conversations_count: Ilość rozmów
avg_first_response_time: Avg first response time
avg_resolution_time: Avg resolution time
avg_reply_time: Avg reply time
resolution_count: Liczba rozwiązań
team_csv:
team_name: Nazwa zespołu
conversations_count: Liczba rozmów
@@ -138,6 +140,8 @@ pl:
instagram_story_content: '%{story_sender} wspomniał o Tobie w historii: '
instagram_deleted_story_content: Ta historia już nie jest dostępna.
deleted: Ta wiadomość została usunięta
whatsapp:
list_button_label: 'Choose an item'
delivery_status:
error_code: 'Error code: %{error_code}'
activity:
@@ -172,6 +176,10 @@ pl:
sla:
added: '%{user_name} added SLA policy %{sla_name}'
removed: '%{user_name} removed SLA policy %{sla_name}'
linear:
issue_created: 'Linear issue %{issue_id} was created by %{user_name}'
issue_linked: 'Linear issue %{issue_id} was linked by %{user_name}'
issue_unlinked: 'Linear issue %{issue_id} was unlinked by %{user_name}'
csat:
not_sent_due_to_messaging_window: 'CSAT survey not sent due to outgoing message restrictions'
muted: '%{user_name} wyciszył/a rozmowę'

Some files were not shown because too many files have changed in this diff Show More