Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f9e94af2e | ||
|
|
cf73ba195e |
@@ -10,8 +10,7 @@ function toggleSecretField(e) {
|
||||
if (!textElement) return;
|
||||
|
||||
if (textElement.dataset.secretMasked === 'false') {
|
||||
const maskedLength = secretField.dataset.secretText?.length || 10;
|
||||
textElement.textContent = '•'.repeat(maskedLength);
|
||||
textElement.textContent = '•'.repeat(10);
|
||||
textElement.dataset.secretMasked = 'true';
|
||||
toggler.querySelector('svg use').setAttribute('xlink:href', '#eye-show');
|
||||
|
||||
@@ -33,13 +32,3 @@ 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,25 +46,17 @@
|
||||
|
||||
.cell-data__secret-field {
|
||||
align-items: center;
|
||||
color: $hint-grey;
|
||||
display: flex;
|
||||
|
||||
span {
|
||||
flex: 0 0 auto;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
[data-secret-toggler],
|
||||
[data-secret-copier] {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: inherit;
|
||||
margin-left: 0.5rem;
|
||||
padding: 0;
|
||||
button {
|
||||
margin-left: 5px;
|
||||
|
||||
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_time', use_business_hours)
|
||||
reply_metrics: fetch_metrics(conversation_filter, 'reply', use_business_hours)
|
||||
}
|
||||
end
|
||||
|
||||
@@ -63,9 +63,7 @@ class V2::Reports::LabelSummaryBuilder < V2::Reports::BaseSummaryBuilder
|
||||
end
|
||||
|
||||
def fetch_resolved_counts(conversation_filter)
|
||||
# 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]))
|
||||
fetch_counts(conversation_filter.merge(status: :resolved))
|
||||
end
|
||||
|
||||
def fetch_counts(conversation_filter)
|
||||
|
||||
@@ -1,23 +1,32 @@
|
||||
class Api::V1::Accounts::Google::AuthorizationsController < Api::V1::Accounts::OauthAuthorizationController
|
||||
class Api::V1::Accounts::Google::AuthorizationsController < Api::V1::Accounts::BaseController
|
||||
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: scope,
|
||||
scope: 'email profile https://mail.google.com/',
|
||||
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,6 +1,7 @@
|
||||
class Api::V1::Accounts::Instagram::AuthorizationsController < Api::V1::Accounts::OauthAuthorizationController
|
||||
class Api::V1::Accounts::Instagram::AuthorizationsController < Api::V1::Accounts::BaseController
|
||||
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
|
||||
@@ -20,4 +21,10 @@ 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
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
class Api::V1::Accounts::Integrations::GithubController < Api::V1::Accounts::BaseController
|
||||
before_action :check_authorization
|
||||
|
||||
def show
|
||||
@github_integration = Current.account.hooks.find_by(app_id: 'github')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_authorization
|
||||
authorize ::Webhook, :show?
|
||||
end
|
||||
end
|
||||
@@ -1,5 +1,5 @@
|
||||
class Api::V1::Accounts::Integrations::LinearController < Api::V1::Accounts::BaseController
|
||||
before_action :fetch_conversation, only: [:create_issue, :link_issue, :unlink_issue, :linked_issues]
|
||||
before_action :fetch_conversation, only: [:link_issue, :linked_issues]
|
||||
before_action :fetch_hook, only: [:destroy]
|
||||
|
||||
def destroy
|
||||
@@ -31,12 +31,6 @@ 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
|
||||
@@ -48,30 +42,17 @@ 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
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
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,19 +1,28 @@
|
||||
class Api::V1::Accounts::Microsoft::AuthorizationsController < Api::V1::Accounts::OauthAuthorizationController
|
||||
class Api::V1::Accounts::Microsoft::AuthorizationsController < Api::V1::Accounts::BaseController
|
||||
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: scope,
|
||||
state: state,
|
||||
scope: 'offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send openid profile',
|
||||
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
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
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
|
||||
@@ -1,23 +0,0 @@
|
||||
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
|
||||
@@ -14,7 +14,7 @@ module GoogleConcern
|
||||
|
||||
private
|
||||
|
||||
def scope
|
||||
'email profile https://mail.google.com/'
|
||||
def base_url
|
||||
ENV.fetch('FRONTEND_URL', 'http://localhost:3000')
|
||||
end
|
||||
end
|
||||
|
||||
@@ -15,7 +15,7 @@ module MicrosoftConcern
|
||||
|
||||
private
|
||||
|
||||
def scope
|
||||
'offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send openid profile email'
|
||||
def base_url
|
||||
ENV.fetch('FRONTEND_URL', 'http://localhost:3000')
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
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,70 @@
|
||||
class Github::CallbacksController < ApplicationController
|
||||
include Github::IntegrationHelper
|
||||
before_action :authenticate_user!
|
||||
before_action :set_account
|
||||
|
||||
def show
|
||||
if params[:error].present?
|
||||
redirect_to app_account_integrations_path(account_id: @account.id), alert: params[:error_description]
|
||||
return
|
||||
end
|
||||
|
||||
if params[:code].present?
|
||||
response = exchange_code_for_token(params[:code])
|
||||
|
||||
if response[:access_token].present?
|
||||
hook = @account.hooks.find_or_initialize_by(app_id: 'github')
|
||||
hook.access_token = response[:access_token]
|
||||
hook.status = 'enabled'
|
||||
|
||||
if hook.save
|
||||
redirect_to app_account_integrations_path(account_id: @account.id), notice: 'GitHub integration connected successfully!'
|
||||
else
|
||||
redirect_to app_account_integrations_path(account_id: @account.id), alert: 'Failed to save GitHub integration'
|
||||
end
|
||||
else
|
||||
redirect_to app_account_integrations_path(account_id: @account.id), alert: 'Failed to get access token from GitHub'
|
||||
end
|
||||
else
|
||||
redirect_to app_account_integrations_path(account_id: @account.id), alert: 'GitHub authorization failed'
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Current.user.accounts.find(params[:account_id]) if params[:account_id].present?
|
||||
@account ||= Current.user.accounts.first
|
||||
|
||||
return if @account
|
||||
|
||||
redirect_to root_path, alert: 'Account not found'
|
||||
end
|
||||
|
||||
def exchange_code_for_token(code)
|
||||
client_id = GlobalConfigService.load('GITHUB_CLIENT_ID', nil)
|
||||
client_secret = GlobalConfigService.load('GITHUB_CLIENT_SECRET', nil)
|
||||
|
||||
return {} unless client_id && client_secret
|
||||
|
||||
response = HTTParty.post('https://github.com/login/oauth/access_token', {
|
||||
body: {
|
||||
client_id: client_id,
|
||||
client_secret: client_secret,
|
||||
code: code
|
||||
},
|
||||
headers: {
|
||||
'Accept' => 'application/json'
|
||||
}
|
||||
})
|
||||
|
||||
if response.success?
|
||||
JSON.parse(response.body).with_indifferent_access
|
||||
else
|
||||
{}
|
||||
end
|
||||
rescue StandardError => e
|
||||
Rails.logger.error "GitHub OAuth error: #{e.message}"
|
||||
{}
|
||||
end
|
||||
end
|
||||
@@ -1,36 +0,0 @@
|
||||
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
|
||||
@@ -6,6 +6,7 @@ class OauthCallbackController < ApplicationController
|
||||
)
|
||||
|
||||
handle_response
|
||||
::Redis::Alfred.delete(cache_key)
|
||||
rescue StandardError => e
|
||||
ChatwootExceptionTracker.new(e).capture_exception
|
||||
redirect_to '/'
|
||||
@@ -63,6 +64,10 @@ 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)
|
||||
@@ -81,17 +86,12 @@ class OauthCallbackController < ApplicationController
|
||||
decoded_token[0]
|
||||
end
|
||||
|
||||
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
|
||||
def account_id
|
||||
::Redis::Alfred.get(cache_key)
|
||||
end
|
||||
|
||||
def account
|
||||
@account ||= account_from_signed_id
|
||||
@account ||= Account.find(account_id)
|
||||
end
|
||||
|
||||
# Fallback name, for when name field is missing from users_data
|
||||
|
||||
@@ -39,8 +39,8 @@ 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]
|
||||
'instagram' => %w[INSTAGRAM_APP_ID INSTAGRAM_APP_SECRET INSTAGRAM_VERIFY_TOKEN INSTAGRAM_API_VERSION ENABLE_INSTAGRAM_CHANNEL_HUMAN_AGENT],
|
||||
'github' => %w[GITHUB_CLIENT_ID GITHUB_CLIENT_SECRET]
|
||||
}
|
||||
|
||||
@allowed_configs = mapping.fetch(@config, %w[ENABLE_ACCOUNT_SIGNUP FIREBASE_PROJECT_ID FIREBASE_CREDENTIALS])
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
module Github::IntegrationHelper
|
||||
def github_integration_url(account_id)
|
||||
"#{ENV.fetch('FRONTEND_URL', nil)}/github/callback?account_id=#{account_id}"
|
||||
end
|
||||
|
||||
def github_oauth_url(account_id)
|
||||
client_id = GlobalConfigService.load('GITHUB_CLIENT_ID', nil)
|
||||
return nil unless client_id
|
||||
|
||||
params = {
|
||||
client_id: client_id,
|
||||
redirect_uri: github_integration_url(account_id),
|
||||
scope: 'repo',
|
||||
state: generate_state_token(account_id)
|
||||
}
|
||||
|
||||
"https://github.com/login/oauth/authorize?#{params.to_query}"
|
||||
end
|
||||
|
||||
def github_configured?
|
||||
GlobalConfigService.load('GITHUB_CLIENT_ID', nil).present? &&
|
||||
GlobalConfigService.load('GITHUB_CLIENT_SECRET', nil).present?
|
||||
end
|
||||
|
||||
def github_integration_enabled?(account)
|
||||
return false unless github_configured?
|
||||
|
||||
account.hooks.exists?(app_id: 'github', status: 'enabled')
|
||||
end
|
||||
|
||||
def github_repositories(access_token)
|
||||
return [] unless access_token
|
||||
|
||||
response = HTTParty.get('https://api.github.com/user/repos', {
|
||||
headers: {
|
||||
'Authorization' => "token #{access_token}",
|
||||
'Accept' => 'application/vnd.github.v3+json'
|
||||
},
|
||||
query: {
|
||||
per_page: 100,
|
||||
sort: 'updated'
|
||||
}
|
||||
})
|
||||
|
||||
if response.success?
|
||||
JSON.parse(response.body)
|
||||
else
|
||||
[]
|
||||
end
|
||||
rescue StandardError => e
|
||||
Rails.logger.error "GitHub API error: #{e.message}"
|
||||
[]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def generate_state_token(account_id)
|
||||
payload = {
|
||||
account_id: account_id,
|
||||
timestamp: Time.current.to_i
|
||||
}
|
||||
|
||||
JWT.encode(payload, Rails.application.secret_key_base, 'HS256')
|
||||
end
|
||||
|
||||
def verify_state_token(state)
|
||||
JWT.decode(state, Rails.application.secret_key_base, true, { algorithm: 'HS256' })
|
||||
rescue JWT::DecodeError
|
||||
nil
|
||||
end
|
||||
end
|
||||
@@ -33,11 +33,9 @@ class LinearAPI extends ApiClient {
|
||||
);
|
||||
}
|
||||
|
||||
unlinkIssue(linkId, issueIdentifier, conversationId) {
|
||||
unlinkIssue(linkId) {
|
||||
return axios.post(`${this.url}/unlink_issue`, {
|
||||
link_id: linkId,
|
||||
issue_id: issueIdentifier,
|
||||
conversation_id: conversationId,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
/* 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,19 +91,6 @@ 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', () => {
|
||||
@@ -133,18 +120,6 @@ 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', () => {
|
||||
@@ -189,26 +164,12 @@ describe('#linearAPI', () => {
|
||||
window.axios = originalAxios;
|
||||
});
|
||||
|
||||
it('creates a valid request with link_id only', () => {
|
||||
LinearAPIClient.unlinkIssue('link123');
|
||||
it('creates a valid request', () => {
|
||||
LinearAPIClient.unlinkIssue(1);
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
'/api/v1/integrations/linear/unlink_issue',
|
||||
{
|
||||
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,
|
||||
link_id: 1,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
@apply max-w-full;
|
||||
|
||||
.multiselect__option {
|
||||
@apply text-sm font-normal flex justify-between items-center;
|
||||
@apply text-sm font-normal;
|
||||
|
||||
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 relative px-1 leading-tight;
|
||||
@apply bottom-0 flex items-center justify-center text-center;
|
||||
}
|
||||
|
||||
&.multiselect__option--highlight {
|
||||
@@ -74,7 +74,7 @@
|
||||
}
|
||||
|
||||
&.multiselect__option--highlight::after {
|
||||
@apply bg-transparent text-n-slate-12;
|
||||
@apply bg-transparent;
|
||||
}
|
||||
|
||||
&.multiselect__option--selected {
|
||||
|
||||
@@ -19,7 +19,6 @@ 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') ||
|
||||
['a', 'img'].includes(e.target?.tagName.toLowerCase());
|
||||
e.target?.tagName.toLowerCase() === 'a';
|
||||
if (shouldSkipContextMenu || getSelection().toString()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -14,8 +14,7 @@ const fromEmail = computed(() => {
|
||||
});
|
||||
|
||||
const toEmail = computed(() => {
|
||||
const { toEmails, email } = contentAttributes.value;
|
||||
return email?.to ?? toEmails ?? [];
|
||||
return contentAttributes.value?.email?.to ?? [];
|
||||
});
|
||||
|
||||
const ccEmail = computed(() => {
|
||||
|
||||
@@ -756,7 +756,6 @@ function toggleSelectAll(check) {
|
||||
}
|
||||
|
||||
useEmitter('fetch_conversation_stats', () => {
|
||||
if (hasAppliedFiltersOrActiveFolders.value) return;
|
||||
store.dispatch('conversationStats/get', conversationFilters.value);
|
||||
});
|
||||
|
||||
@@ -855,7 +854,7 @@ watch(conversationFilters, (newVal, oldVal) => {
|
||||
:active-status="activeStatus"
|
||||
:is-on-expanded-layout="isOnExpandedLayout"
|
||||
:conversation-stats="conversationStats"
|
||||
:is-list-loading="chatListLoading && !conversationList.length"
|
||||
:is-list-loading="chatListLoading"
|
||||
@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
|
||||
? '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'
|
||||
? 'first:rounded-bl-lg first:rounded-tl-lg last:rounded-br-lg last:rounded-tr-lg'
|
||||
: ''
|
||||
);
|
||||
</script>
|
||||
|
||||
@@ -183,18 +183,13 @@ 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 { identifier: issueIdentifier } = response.data;
|
||||
await LinearAPI.link_issue(
|
||||
props.conversationId,
|
||||
issueIdentifier,
|
||||
props.title
|
||||
);
|
||||
const { id: issueId } = response.data;
|
||||
await LinearAPI.link_issue(props.conversationId, issueId, 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, issueIdentifier) => {
|
||||
const unlinkIssue = async linkId => {
|
||||
try {
|
||||
await LinearAPI.unlinkIssue(linkId, issueIdentifier, props.conversationId);
|
||||
await LinearAPI.unlinkIssue(linkId);
|
||||
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="px-4 pt-3 pb-4 border-b border-n-weak last:border-b-0"
|
||||
class="pt-3 px-4 pb-4 border-b border-n-weak last:border-b-0"
|
||||
:linked-issue="linkedIssue"
|
||||
@unlink-issue="unlinkIssue"
|
||||
/>
|
||||
|
||||
@@ -14,8 +14,6 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(['unlinkIssue']);
|
||||
|
||||
const { linkedIssue } = props;
|
||||
|
||||
const priorityMap = {
|
||||
1: 'Urgent',
|
||||
2: 'High',
|
||||
@@ -23,7 +21,7 @@ const priorityMap = {
|
||||
4: 'Low',
|
||||
};
|
||||
|
||||
const issue = computed(() => linkedIssue.issue);
|
||||
const issue = computed(() => props.linkedIssue.issue);
|
||||
|
||||
const assignee = computed(() => {
|
||||
const assigneeDetails = issue.value.assignee;
|
||||
@@ -39,7 +37,7 @@ const labels = computed(() => issue.value.labels?.nodes || []);
|
||||
const priorityLabel = computed(() => priorityMap[issue.value.priority]);
|
||||
|
||||
const unlinkIssue = () => {
|
||||
emit('unlinkIssue', linkedIssue.id, linkedIssue.issue.identifier);
|
||||
emit('unlinkIssue', props.linkedIssue.id);
|
||||
};
|
||||
</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.identifier,
|
||||
id: issue.id,
|
||||
name: `${issue.identifier} ${issue.title}`,
|
||||
icon: 'status',
|
||||
iconColor: issue.state.color,
|
||||
|
||||
@@ -329,12 +329,26 @@
|
||||
"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"
|
||||
"GITHUB": {
|
||||
"TITLE": "GitHub Integration",
|
||||
"DESCRIPTION": "Connect your GitHub repositories to create issues directly from customer conversations. This integration helps you track bugs, feature requests, and development tasks seamlessly.",
|
||||
"NOT_CONFIGURED": {
|
||||
"TITLE": "GitHub Integration Not Configured",
|
||||
"DESCRIPTION": "Contact your administrator to configure GitHub OAuth settings."
|
||||
},
|
||||
"CONNECT": {
|
||||
"TITLE": "Connect GitHub",
|
||||
"DESCRIPTION": "Connect your GitHub account to start creating issues from conversations.",
|
||||
"BUTTON": "Connect GitHub"
|
||||
},
|
||||
"CONNECTED": {
|
||||
"TITLE": "GitHub Connected",
|
||||
"DESCRIPTION": "Your GitHub account is successfully connected. You can now create issues from conversations."
|
||||
},
|
||||
"DISCONNECT": {
|
||||
"BUTTON": "Disconnect",
|
||||
"SUCCESS": "GitHub integration disconnected successfully",
|
||||
"ERROR": "Failed to disconnect GitHub integration"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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 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"
|
||||
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"
|
||||
>
|
||||
<fluent-icon
|
||||
icon="arrow-up"
|
||||
class="absolute -top-1 ltr:left-2 rtl:right-2"
|
||||
class="absolute -top-1 left-2"
|
||||
size="17"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col w-full ltr:pl-8 rtl:pr-8">
|
||||
<div class="flex flex-col w-full">
|
||||
<label class="multiselect__label">
|
||||
{{ $t('MERGE_CONTACTS.PRIMARY.TITLE') }}
|
||||
<woot-label
|
||||
|
||||
@@ -71,12 +71,6 @@ 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) =>
|
||||
@@ -243,7 +237,7 @@ export default {
|
||||
<woot-loading-state v-if="uiFlags.isFetchingItem" />
|
||||
</div>
|
||||
<AutoResolve v-if="showAutoResolutionConfig" />
|
||||
<AudioTranscription v-if="showAudioTranscriptionConfig" />
|
||||
<AudioTranscription v-if="isOnChatwootCloud" />
|
||||
<AccountId />
|
||||
<div v-if="!uiFlags.isFetchingItem && isOnChatwootCloud">
|
||||
<AccountDelete />
|
||||
|
||||
+1
@@ -12,6 +12,7 @@ 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')"
|
||||
/>
|
||||
|
||||
+1
@@ -12,6 +12,7 @@ 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')"
|
||||
/>
|
||||
|
||||
+13
-1
@@ -30,9 +30,14 @@ 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') {
|
||||
@@ -45,7 +50,9 @@ const client = computed(() => {
|
||||
async function requestAuthorization() {
|
||||
try {
|
||||
isRequestingAuthorization.value = true;
|
||||
const response = await client.value.generateAuthorization();
|
||||
const response = await client.value.generateAuthorization({
|
||||
email: email.value,
|
||||
});
|
||||
const {
|
||||
data: { url },
|
||||
} = response;
|
||||
@@ -68,6 +75,11 @@ 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"
|
||||
|
||||
+11
-34
@@ -5,19 +5,15 @@ import {
|
||||
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 integration = useFunctionGetter('integrations/getIntegration', 'github');
|
||||
|
||||
const uiFlags = useMapGetter('integrations/getUIFlags');
|
||||
|
||||
@@ -25,32 +21,22 @@ const integrationAction = computed(() => {
|
||||
if (integration.value.enabled) {
|
||||
return 'disconnect';
|
||||
}
|
||||
|
||||
return '';
|
||||
return integration.value.action;
|
||||
});
|
||||
|
||||
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');
|
||||
const initializeGithubIntegration = async () => {
|
||||
await store.dispatch('integrations/get', 'github');
|
||||
integrationLoaded.value = true;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initializeNotionIntegration();
|
||||
initializeGithubIntegration();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex-grow flex-shrink p-4 overflow-auto mx-auto">
|
||||
<div v-if="integrationLoaded && !uiFlags.isCreatingNotion">
|
||||
<div class="flex-grow flex-shrink max-w-6xl p-4 mx-auto overflow-auto">
|
||||
<div v-if="integrationLoaded && !uiFlags.isCreatingGithub">
|
||||
<Integration
|
||||
:integration-id="integration.id"
|
||||
:integration-logo="integration.logo"
|
||||
@@ -59,19 +45,10 @@ onMounted(() => {
|
||||
:integration-enabled="integration.enabled"
|
||||
:integration-action="integrationAction"
|
||||
:delete-confirmation-text="{
|
||||
title: t('INTEGRATION_SETTINGS.NOTION.DELETE.TITLE'),
|
||||
message: t('INTEGRATION_SETTINGS.NOTION.DELETE.MESSAGE'),
|
||||
title: $t('INTEGRATION_SETTINGS.GITHUB.DELETE.TITLE'),
|
||||
message: $t('INTEGRATION_SETTINGS.GITHUB.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" />
|
||||
+9
-10
@@ -8,8 +8,8 @@ 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';
|
||||
import Github from './Github.vue';
|
||||
|
||||
export default {
|
||||
routes: [
|
||||
@@ -91,15 +91,6 @@ 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',
|
||||
@@ -110,6 +101,14 @@ export default {
|
||||
},
|
||||
props: route => ({ error: route.query.error }),
|
||||
},
|
||||
{
|
||||
path: 'github',
|
||||
name: 'settings_integrations_github',
|
||||
component: Github,
|
||||
meta: {
|
||||
permissions: ['administrator'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: ':integration_id',
|
||||
name: 'settings_applications_integration',
|
||||
|
||||
+4
-28
@@ -75,34 +75,10 @@ export default {
|
||||
onRegistrationSuccess() {
|
||||
this.hasEnabledPushPermissions = true;
|
||||
},
|
||||
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
|
||||
})
|
||||
);
|
||||
onRequestPermissions() {
|
||||
requestPushPermissions({
|
||||
onSuccess: this.onRegistrationSuccess,
|
||||
});
|
||||
},
|
||||
getPushSubscription() {
|
||||
verifyServiceWorkerExistence(registration =>
|
||||
|
||||
+3
-5
@@ -59,7 +59,7 @@ const defaulSpanRender = cellProps =>
|
||||
cellProps.getValue()
|
||||
);
|
||||
|
||||
const columns = computed(() => [
|
||||
const columns = [
|
||||
columnHelper.accessor('name', {
|
||||
header: t(`SUMMARY_REPORTS.${props.type.toUpperCase()}`),
|
||||
width: 300,
|
||||
@@ -90,7 +90,7 @@ const columns = computed(() => [
|
||||
width: 200,
|
||||
cell: defaulSpanRender,
|
||||
}),
|
||||
]);
|
||||
];
|
||||
|
||||
const renderAvgTime = value => (value ? formatTime(value) : '--');
|
||||
|
||||
@@ -142,9 +142,7 @@ const table = useVueTable({
|
||||
get data() {
|
||||
return tableData.value;
|
||||
},
|
||||
get columns() {
|
||||
return columns.value;
|
||||
},
|
||||
columns,
|
||||
enableSorting: false,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
});
|
||||
|
||||
@@ -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? || params.dig(:telegram, :edited_business_message).present?
|
||||
if params.dig(:telegram, :edited_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
|
||||
|
||||
@@ -69,10 +69,6 @@ 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
|
||||
@@ -99,13 +95,7 @@ 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),
|
||||
business_connection_id: business_connection_id(message)
|
||||
)
|
||||
response = message_request(chat_id(message), message.outgoing_content, reply_markup(message), reply_to_message_id(message))
|
||||
process_error(message, response)
|
||||
response.parsed_response['result']['message_id'] if response.success?
|
||||
end
|
||||
@@ -141,12 +131,9 @@ class Channel::Telegram < ApplicationRecord
|
||||
stripped_html.gsub('<br>', "\n")
|
||||
end
|
||||
|
||||
def message_request(chat_id, text, reply_markup = nil, reply_to_message_id = nil, business_connection_id: nil)
|
||||
def message_request(chat_id, text, reply_markup = nil, reply_to_message_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,
|
||||
@@ -154,6 +141,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
|
||||
|
||||
@@ -43,6 +43,8 @@ class Integrations::App
|
||||
"#{params[:action]}&client_id=#{client_id}&redirect_uri=#{self.class.slack_integration_url}"
|
||||
when 'linear'
|
||||
build_linear_action
|
||||
when 'github'
|
||||
build_github_action
|
||||
else
|
||||
params[:action]
|
||||
end
|
||||
@@ -58,8 +60,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?
|
||||
when 'github'
|
||||
GlobalConfigService.load('GITHUB_CLIENT_ID', nil).present?
|
||||
else
|
||||
true
|
||||
end
|
||||
@@ -77,6 +79,16 @@ class Integrations::App
|
||||
].join('&')
|
||||
end
|
||||
|
||||
def build_github_action
|
||||
client_id = GlobalConfigService.load('GITHUB_CLIENT_ID', nil)
|
||||
[
|
||||
'https://github.com/login/oauth/authorize?response_type=code',
|
||||
"client_id=#{client_id}",
|
||||
"redirect_uri=#{self.class.github_integration_url}",
|
||||
'scope=repo'
|
||||
].join('&')
|
||||
end
|
||||
|
||||
def enabled?(account)
|
||||
case params[:id]
|
||||
when 'webhook'
|
||||
@@ -100,6 +112,10 @@ class Integrations::App
|
||||
"#{ENV.fetch('FRONTEND_URL', nil)}/linear/callback"
|
||||
end
|
||||
|
||||
def self.github_integration_url
|
||||
"#{ENV.fetch('FRONTEND_URL', nil)}/github/callback"
|
||||
end
|
||||
|
||||
class << self
|
||||
def apps
|
||||
Hashie::Mash.new(APPS_CONFIG)
|
||||
|
||||
@@ -53,10 +53,6 @@ class Integrations::Hook < ApplicationRecord
|
||||
app_id == 'dialogflow'
|
||||
end
|
||||
|
||||
def notion?
|
||||
app_id == 'notion'
|
||||
end
|
||||
|
||||
def disable
|
||||
update(status: 'disabled')
|
||||
end
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
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,15 +24,6 @@ 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
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
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,13 +11,6 @@ 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
|
||||
|
||||
@@ -37,11 +30,4 @@ 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,10 +4,8 @@ class MessageTemplates::Template::AutoResolve
|
||||
def perform
|
||||
return if conversation.account.auto_resolve_message.blank?
|
||||
|
||||
if within_messaging_window?
|
||||
ActiveRecord::Base.transaction do
|
||||
conversation.messages.create!(auto_resolve_message_params)
|
||||
else
|
||||
create_auto_resolve_not_sent_activity_message
|
||||
end
|
||||
end
|
||||
|
||||
@@ -16,21 +14,6 @@ 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,
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
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'
|
||||
""
|
||||
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,25 +8,17 @@ 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: message_type,
|
||||
sender: message_sender,
|
||||
message_type: :incoming,
|
||||
sender: @contact,
|
||||
content_attributes: telegram_params_content_attributes,
|
||||
source_id: telegram_params_message_id.to_s
|
||||
)
|
||||
@@ -44,11 +36,6 @@ 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
|
||||
@@ -102,19 +89,10 @@ class Telegram::IncomingMessageService
|
||||
|
||||
def conversation_additional_attributes
|
||||
{
|
||||
chat_id: telegram_params_chat_id,
|
||||
business_connection_id: telegram_params_business_connection_id
|
||||
chat_id: telegram_params_chat_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?
|
||||
@@ -213,8 +191,4 @@ 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
|
||||
|
||||
@@ -13,17 +13,6 @@ 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
|
||||
@@ -40,34 +29,24 @@ 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
|
||||
contact_params[:first_name]
|
||||
telegram_params_base_object[:from][:first_name]
|
||||
end
|
||||
|
||||
def telegram_params_last_name
|
||||
contact_params[:last_name]
|
||||
telegram_params_base_object[:from][:last_name]
|
||||
end
|
||||
|
||||
def telegram_params_username
|
||||
contact_params[:username]
|
||||
telegram_params_base_object[:from][:username]
|
||||
end
|
||||
|
||||
def telegram_params_language_code
|
||||
contact_params[:language_code]
|
||||
telegram_params_base_object[:from][:language_code]
|
||||
end
|
||||
|
||||
def telegram_params_chat_id
|
||||
@@ -78,14 +57,6 @@ 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,7 +71,6 @@ 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
|
||||
})
|
||||
@@ -109,7 +108,6 @@ 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
|
||||
},
|
||||
@@ -137,14 +135,4 @@ 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,7 +5,6 @@ class Telegram::UpdateMessageService
|
||||
pattr_initialize [:inbox!, :params!]
|
||||
|
||||
def perform
|
||||
transform_business_message!
|
||||
find_contact_inbox
|
||||
find_conversation
|
||||
find_message
|
||||
@@ -37,8 +36,4 @@ 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,9 +4,7 @@
|
||||
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_reply_time'),
|
||||
I18n.t('reports.label_csv.resolution_count'),
|
||||
I18n.t('reports.label_csv.avg_resolution_time')
|
||||
]
|
||||
%>
|
||||
<%= 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" />
|
||||
|
||||
@@ -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" />
|
||||
|
||||
+1
-1
@@ -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 min-w-0 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 h-10">
|
||||
<% if @portal.logo.present? %>
|
||||
<img src="<%= url_for(@portal.logo) %>" class="w-auto h-10 ltr:mr-2 rtl:ml-2" draggable="false" />
|
||||
<img src="<%= url_for(@portal.logo) %>" class="w-auto h-10 ltr:mr-2 rtl:ml-2" />
|
||||
<% end %>
|
||||
<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>
|
||||
<span class="text-lg font-semibold text-slate-900 dark:text-white <%= @portal.logo.present? ? 'hidden' : '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="px-1 py-2 ltr:ml-8 rtl:mr-8 cursor-pointer block">
|
||||
<div class="hidden px-1 py-2 ltr:ml-8 rtl:mr-8 cursor-pointer md: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 min-w-0 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 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" draggable="false" />
|
||||
<img src="<%= url_for(@portal.logo) %>" class="w-auto h-10 ltr:mr-2 rtl:ml-2" />
|
||||
<% end %>
|
||||
<span class="text-lg font-semibold text-slate-900 truncate dark:text-white <%= @portal.logo.present? ? 'hidden' : 'sm:hidden block' %>"><%= @portal.name %></span>
|
||||
<span class="text-lg font-semibold text-slate-900 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 lg:hidden' : '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' : '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,4 +1,3 @@
|
||||
<% has_multiple_locales = @portal.config["allowed_locales"].length > 1 %>
|
||||
|
||||
<input type="checkbox" id="mobile-menu-toggle" class="peer/menu hidden" />
|
||||
|
||||
@@ -47,12 +46,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if has_multiple_locales %>
|
||||
<span class="h-px bg-slate-100/70 dark:bg-slate-800/70 w-full"></span>
|
||||
<% end %>
|
||||
<span class="h-px bg-slate-100/70 dark:bg-slate-800/70 w-full"></span>
|
||||
|
||||
<!-- Locale Switcher -->
|
||||
<% if has_multiple_locales %>
|
||||
<% if @portal.config["allowed_locales"].length > 1 %>
|
||||
<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,14 +156,12 @@
|
||||
<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-github" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
|
||||
</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: 42 KiB After Width: | Height: | Size: 42 KiB |
@@ -22,36 +22,27 @@
|
||||
<% end %>
|
||||
|
||||
<div class="bg-white py-2 px-4 xl:px-0">
|
||||
<%= 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 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>
|
||||
</div>
|
||||
</div>
|
||||
<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 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 flex-col flex-grow gap-1">
|
||||
<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>
|
||||
<h2 class="h-5 leading-5 text-n-slate-12 text-sm font-medium">Current plan</h2>
|
||||
<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 focus:outline-none">
|
||||
<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">
|
||||
<svg width="16" height="16"><use xlink:href="#icon-settings-2-line" /></svg>
|
||||
<span class="px-1">Manage</span>
|
||||
</button>
|
||||
@@ -73,14 +64,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 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>
|
||||
<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>
|
||||
<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 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>
|
||||
<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>
|
||||
<span class="px-1">Chat Support</span>
|
||||
</button>
|
||||
<% end %>
|
||||
@@ -99,7 +90,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 focus:outline-none">
|
||||
<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">
|
||||
<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
@@ -1,5 +1,5 @@
|
||||
shared: &shared
|
||||
version: '4.3.0'
|
||||
version: '4.2.0'
|
||||
|
||||
development:
|
||||
<<: *shared
|
||||
|
||||
+3
-3
@@ -169,6 +169,6 @@
|
||||
- name: crm_integration
|
||||
display_name: CRM Integration
|
||||
enabled: false
|
||||
- name: notion_integration
|
||||
display_name: Notion Integration
|
||||
enabled: false
|
||||
- name: github_integration
|
||||
display_name: Github Integration
|
||||
enabled: false
|
||||
@@ -288,25 +288,6 @@
|
||||
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'
|
||||
@@ -360,3 +341,17 @@
|
||||
value: 'v22.0'
|
||||
locked: true
|
||||
# ------- End of Instagram Channel Related Config ------- #
|
||||
|
||||
## ------ Configs added for GitHub ------ ##
|
||||
- name: GITHUB_CLIENT_ID
|
||||
display_title: 'GitHub Client ID'
|
||||
value:
|
||||
locked: false
|
||||
description: 'GitHub OAuth app client ID'
|
||||
- name: GITHUB_CLIENT_SECRET
|
||||
display_title: 'GitHub Client Secret'
|
||||
value:
|
||||
locked: false
|
||||
description: 'GitHub OAuth app client secret'
|
||||
type: secret
|
||||
## ------ End of Configs added for GitHub ------ ##
|
||||
|
||||
@@ -63,12 +63,6 @@ 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
|
||||
@@ -284,3 +278,10 @@ leadsquared:
|
||||
'conversation_activity_score',
|
||||
'transcript_activity_score',
|
||||
]
|
||||
|
||||
github:
|
||||
id: github
|
||||
logo: github.png
|
||||
i18n_key: github
|
||||
hook_type: account
|
||||
allow_multiple_hooks: false
|
||||
|
||||
@@ -91,8 +91,6 @@ 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
|
||||
@@ -140,8 +138,6 @@ 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:
|
||||
@@ -176,10 +172,6 @@ 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'
|
||||
|
||||
@@ -91,8 +91,6 @@ ar:
|
||||
conversations_count: عدد المحادثات
|
||||
avg_first_response_time: متوسط وقت الرد الأول
|
||||
avg_resolution_time: متوسط وقت الحل
|
||||
avg_reply_time: Avg reply time
|
||||
resolution_count: عدد مرات الإغلاق
|
||||
team_csv:
|
||||
team_name: اسم الفريق
|
||||
conversations_count: عدد المحادثات
|
||||
@@ -140,8 +138,6 @@ 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:
|
||||
@@ -176,10 +172,6 @@ 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} كتم صوت المحادثة'
|
||||
|
||||
@@ -91,8 +91,6 @@ 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
|
||||
@@ -140,8 +138,6 @@ 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:
|
||||
@@ -176,10 +172,6 @@ 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'
|
||||
|
||||
@@ -91,8 +91,6 @@ 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
|
||||
@@ -140,8 +138,6 @@ 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:
|
||||
@@ -176,10 +172,6 @@ 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'
|
||||
|
||||
@@ -91,8 +91,6 @@ 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
|
||||
@@ -140,8 +138,6 @@ 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:
|
||||
@@ -176,10 +172,6 @@ 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'
|
||||
|
||||
@@ -91,8 +91,6 @@ 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
|
||||
@@ -140,8 +138,6 @@ 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:
|
||||
@@ -176,10 +172,6 @@ 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'
|
||||
|
||||
@@ -91,8 +91,6 @@ 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
|
||||
@@ -140,8 +138,6 @@ 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:
|
||||
@@ -176,10 +172,6 @@ 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'
|
||||
|
||||
@@ -91,8 +91,6 @@ 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
|
||||
@@ -140,8 +138,6 @@ 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:
|
||||
@@ -176,10 +172,6 @@ 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'
|
||||
|
||||
@@ -91,8 +91,6 @@ 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: Αριθμός συνομιλιών
|
||||
@@ -140,8 +138,6 @@ 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:
|
||||
@@ -176,10 +172,6 @@ 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} σίγασε την συνομιλία'
|
||||
|
||||
+4
-12
@@ -105,8 +105,6 @@ 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
|
||||
@@ -190,14 +188,8 @@ 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.'
|
||||
@@ -257,10 +249,6 @@ 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.'
|
||||
@@ -269,6 +257,10 @@ en:
|
||||
name: 'LeadSquared'
|
||||
short_description: 'Sync your contacts and conversations with LeadSquared CRM.'
|
||||
description: 'Sync your contacts and conversations with LeadSquared CRM. This integration automatically creates leads in LeadSquared when new contacts are added, and logs conversation activity to provide your sales team with complete context.'
|
||||
github:
|
||||
name: 'GitHub'
|
||||
short_description: 'Create and manage GitHub issues directly from conversations.'
|
||||
description: 'Connect your GitHub repositories to create issues directly from customer conversations. This integration helps you track bugs, feature requests, and development tasks seamlessly.'
|
||||
captain:
|
||||
copilot_error: 'Please connect an assistant to this inbox to use Copilot'
|
||||
copilot_limit: 'You are out of Copilot credits. You can buy more credits from the billing section.'
|
||||
|
||||
@@ -91,8 +91,6 @@ 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
|
||||
@@ -140,8 +138,6 @@ 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:
|
||||
@@ -176,10 +172,6 @@ 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'
|
||||
|
||||
@@ -91,8 +91,6 @@ 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
|
||||
@@ -140,8 +138,6 @@ 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:
|
||||
@@ -176,10 +172,6 @@ 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} مکالمه را بی صدا کرد'
|
||||
|
||||
@@ -91,8 +91,6 @@ 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
|
||||
@@ -140,8 +138,6 @@ 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:
|
||||
@@ -176,10 +172,6 @@ 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'
|
||||
|
||||
@@ -91,8 +91,6 @@ 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
|
||||
@@ -140,8 +138,6 @@ 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:
|
||||
@@ -176,10 +172,6 @@ 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'
|
||||
|
||||
@@ -91,8 +91,6 @@ 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
|
||||
@@ -140,8 +138,6 @@ 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:
|
||||
@@ -176,10 +172,6 @@ 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'
|
||||
|
||||
@@ -91,8 +91,6 @@ 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
|
||||
@@ -140,8 +138,6 @@ 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:
|
||||
@@ -176,10 +172,6 @@ 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'
|
||||
|
||||
@@ -91,8 +91,6 @@ 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
|
||||
@@ -140,8 +138,6 @@ 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:
|
||||
@@ -176,10 +172,6 @@ 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'
|
||||
|
||||
@@ -91,8 +91,6 @@ 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
|
||||
@@ -140,8 +138,6 @@ 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:
|
||||
@@ -176,10 +172,6 @@ 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'
|
||||
|
||||
@@ -91,8 +91,6 @@ 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
|
||||
@@ -140,8 +138,6 @@ 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:
|
||||
@@ -176,10 +172,6 @@ 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'
|
||||
|
||||
@@ -91,8 +91,6 @@ 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
|
||||
@@ -140,8 +138,6 @@ 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:
|
||||
@@ -176,10 +172,6 @@ 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'
|
||||
|
||||
@@ -91,8 +91,6 @@ 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
|
||||
@@ -140,8 +138,6 @@ 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:
|
||||
@@ -176,10 +172,6 @@ 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'
|
||||
|
||||
@@ -91,8 +91,6 @@ 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
|
||||
@@ -140,8 +138,6 @@ 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:
|
||||
@@ -176,10 +172,6 @@ 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'
|
||||
|
||||
@@ -91,8 +91,6 @@ ja:
|
||||
conversations_count: 会話数
|
||||
avg_first_response_time: 初回応答の平均時間
|
||||
avg_resolution_time: 解決までの平均時間
|
||||
avg_reply_time: Avg reply time
|
||||
resolution_count: 処理件数
|
||||
team_csv:
|
||||
team_name: チーム名
|
||||
conversations_count: 会話回数
|
||||
@@ -140,8 +138,6 @@ 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:
|
||||
@@ -176,10 +172,6 @@ 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} が会話をミュートしました'
|
||||
|
||||
@@ -91,8 +91,6 @@ 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
|
||||
@@ -140,8 +138,6 @@ 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:
|
||||
@@ -176,10 +172,6 @@ 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'
|
||||
|
||||
@@ -91,8 +91,6 @@ 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
|
||||
@@ -140,8 +138,6 @@ 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:
|
||||
@@ -176,10 +172,6 @@ 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'
|
||||
|
||||
@@ -91,8 +91,6 @@ 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
|
||||
@@ -140,8 +138,6 @@ 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:
|
||||
@@ -176,10 +172,6 @@ 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į'
|
||||
|
||||
@@ -91,8 +91,6 @@ 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
|
||||
@@ -140,8 +138,6 @@ 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:
|
||||
@@ -176,10 +172,6 @@ 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'
|
||||
|
||||
@@ -91,8 +91,6 @@ 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: സംഭാഷണങ്ങളുടെ എണ്ണം
|
||||
@@ -140,8 +138,6 @@ 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:
|
||||
@@ -176,10 +172,6 @@ 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'
|
||||
|
||||
@@ -91,8 +91,6 @@ 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
|
||||
@@ -140,8 +138,6 @@ 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:
|
||||
@@ -176,10 +172,6 @@ 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'
|
||||
|
||||
@@ -91,8 +91,6 @@ 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
|
||||
@@ -140,8 +138,6 @@ 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:
|
||||
@@ -176,10 +172,6 @@ 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'
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user