Compare commits

..
Author SHA1 Message Date
Sony Mathew 5b0300963e test(storage): isolate migrator spec service state
Restore ActiveStorage::Blob service globals after migrator specs so later attachment specs do not inherit leaked test doubles.
2026-06-16 18:39:48 +05:30
Sony Mathew b2a8e03f62 test(storage): cover blob migration contract
Replace mock-only migrator specs with disk-backed ActiveStorage blob coverage for non-image uploads, service-name updates, missing files, upload failures, and idempotent reruns.

Add a rake task regression for re-enabled repeated invocation.
2026-06-16 18:25:42 +05:30
Sony Mathew 8c0cac0532 fix(storage): make blob migration resilient
Limit storage migration to blobs on the source service, skip missing source files per blob, and keep target upload failures loud.

Clean up the service-name update flag naming and document rake task usage.
2026-06-16 18:16:40 +05:30
Sony Mathew 1fd280ec0e fix(storage): preserve application logger
Remove the ActiveStorage migrator class-load logger override so eager loading does not replace the configured Rails logger or log level.
2026-06-16 18:04:29 +05:30
Sony Mathew 18b6681e9b fix(storage): migrate all blob content types
Remove the image-only filter so storage migrations copy every Active Storage blob, including audio, documents, imports, exports, and enterprise attachments.
2026-06-16 18:00:39 +05:30
Sony Mathew 8558b6f4c0 fix(storage): update migrated blob service names
Default storage migrations to update active_storage_blobs.service_name after successful uploads while allowing copy-only runs with UPDATE_BLOB_SERVICE_NAME=false.

Remove unused migrator helper methods and cover the migrator/rake flag behavior in specs.
2026-06-16 17:53:44 +05:30
Sony MathewandGitHub 90d7735ade Merge branch 'develop' into feature/#7907-script-to-change-local-storage-to-aws 2026-06-16 15:31:54 +05:30
Sony MathewandGitHub d916557fda Merge branch 'develop' into feature/#7907-script-to-change-local-storage-to-aws 2026-06-15 18:16:51 +05:30
Francisco Brito c8a9e36085 fixing error in the test 2024-03-20 13:24:36 -03:00
Francisco Brito 43d950001c fixing the path in the rake task test 2024-03-20 12:42:29 -03:00
Francisco BritoandGitHub f701a4df9d Merge branch 'chatwoot:develop' into feature/#7907-script-to-change-local-storage-to-aws 2024-03-20 12:32:03 -03:00
Francisco Brito f5b2e7b5de fixing path for RuboCop 2024-03-15 20:41:41 -03:00
Francisco Brito cf297801fd feature/#7907-script-to-change-local-storage-to-aws 2024-03-15 20:26:51 -03:00
Francisco Brito 5c350348d6 feature/#7907-script-to-change-local-storage-to-aws 2024-03-15 15:48:27 -03:00
10 changed files with 17 additions and 139 deletions
+1 -1
View File
@@ -1 +1 @@
4.15.1
4.14.2
@@ -145,12 +145,6 @@
--black-alpha-2: 0, 0, 0, 0.04;
--border-blue: 39, 129, 246, 0.5;
--white-alpha: 255, 255, 255, 0.8;
// Voice call widget - light mode
--call-widget: 33, 34, 38, 0.95;
--call-widget-border: 255, 255, 255, 0.1;
--call-widget-text: 237, 238, 240, 1;
--call-widget-sub-text: 173, 177, 184, 1;
}
.dark {
@@ -297,12 +291,6 @@
--border-blue: 39, 129, 246, 0.5;
--border-container: 255, 255, 255, 0;
--white-alpha: 255, 255, 255, 0.1;
// Voice call widget - dark mode
--call-widget: 50, 53, 61, 1;
--call-widget-border: 255, 255, 255, 0.07;
--call-widget-text: 237, 238, 240, 1;
--call-widget-sub-text: 173, 177, 184, 1;
}
}
// NEXT COLORS END
@@ -75,11 +75,11 @@ const channelIcon = computed(() => {
<template>
<div
class="flex flex-col gap-1 pt-4 bg-n-call-widget rounded-2xl shadow-xl outline outline-1 outline-n-call-widget-border backdrop-blur-md"
class="flex flex-col gap-2 pt-4 bg-n-solid-2/95 rounded-2xl shadow-xl outline outline-1 outline-n-strong backdrop-blur-md"
:class="call?.conversationId ? 'pb-2' : 'pb-4'"
>
<!-- Top section: status badge + location/inbox + duration -->
<div class="flex flex-col gap-3 pb-3 border-b border-n-call-widget-border">
<div class="flex flex-col gap-3">
<div class="flex items-center gap-2 px-4">
<!-- Ongoing: status badge on left -->
<div v-if="isOngoing" class="flex items-center gap-1.5 shrink-0">
@@ -100,10 +100,10 @@ const channelIcon = computed(() => {
<Icon
v-else-if="!isOngoing"
:icon="channelIcon"
class="size-3.5 text-n-call-widget-sub-text shrink-0"
class="size-3.5 text-n-slate-10 shrink-0"
/>
<span
class="text-xs font-medium text-n-call-widget-sub-text tracking-tight truncate"
class="text-xs font-medium text-n-slate-11 tracking-tight truncate"
>
{{ callInfo.location }}
</span>
@@ -112,7 +112,7 @@ const channelIcon = computed(() => {
<!-- Ongoing: duration on right -->
<p
v-if="isOngoing"
class="font-display text-base font-medium text-n-call-widget-sub-text shrink-0 mb-0 tabular-nums tracking-tight"
class="font-display text-base font-medium text-n-slate-11 shrink-0 mb-0 tabular-nums tracking-tight"
>
{{ duration }}
</p>
@@ -132,7 +132,7 @@ const channelIcon = computed(() => {
slate
ghost
xs
class="!rounded-full -my-1 -me-1 !text-n-call-widget-sub-text"
class="!rounded-full -my-1"
@click="$emit('dismiss')"
/>
</div>
@@ -149,13 +149,13 @@ const channelIcon = computed(() => {
</div>
<div class="flex-1 min-w-0">
<p
class="font-display text-sm font-medium text-n-call-widget-text truncate mb-0.5 tracking-tight leading-tight"
class="font-display text-sm font-medium text-n-slate-12 truncate mb-0.5 tracking-tight leading-tight"
>
{{ callInfo.contactName }}
</p>
<p
v-if="callInfo.phoneNumber"
class="text-sm text-n-call-widget-sub-text truncate mb-0 tracking-tight leading-tight"
class="text-sm text-n-slate-11 truncate mb-0 tracking-tight leading-tight"
>
{{ callInfo.phoneNumber }}
</p>
@@ -217,23 +217,23 @@ const channelIcon = computed(() => {
>
<template #icon>
<span
class="flex items-center gap-1 text-n-call-widget-sub-text group-hover:text-n-call-widget-text"
class="flex items-center gap-1 text-n-slate-11 group-hover:text-n-slate-12"
>
<Icon
icon="i-ph-chat-circle-text-bold"
class="size-3.5 text-n-call-widget-sub-text shrink-0"
class="size-3.5 text-n-slate-11 shrink-0"
/>
<span class="text-sm tracking-tight tabular-nums">
#{{ call.conversationId }}
</span>
<Icon
icon="i-ph-caret-right-bold"
class="size-3 text-n-call-widget-sub-text shrink-0"
class="size-3 text-n-slate-11 shrink-0"
/>
</span>
</template>
<span
class="text-sm text-n-call-widget-sub-text tracking-tight group-hover:text-n-call-widget-text"
class="text-sm text-n-slate-11 tracking-tight group-hover:text-n-slate-12"
>
{{ $t('CONVERSATION.VOICE_WIDGET.GO_TO_CONVERSATION') }}
</span>
+1 -25
View File
@@ -1,11 +1,4 @@
class UserSessionTrackingService
# CFNetwork UAs cannot distinguish iPhone from iPad; both get labelled iPhone here.
LEGACY_MOBILE_UAS = [
{ match: %r{\Aokhttp/}, platform: 'Android', device: 'Android' },
{ match: %r{\AChatwoot/.*CFNetwork.*Darwin}, platform: 'iPhone', device: 'iPhone' }
].freeze
private_constant :LEGACY_MOBILE_UAS
def initialize(user:, request:, client_id:)
@user = user
@request = request
@@ -33,7 +26,7 @@ class UserSessionTrackingService
def session_attributes
browser = Browser.new(@request.user_agent)
attrs = {
{
ip_address: @request.remote_ip,
user_agent: @request.user_agent,
browser_name: browser.name,
@@ -42,22 +35,5 @@ class UserSessionTrackingService
platform_name: browser.platform.name,
platform_version: browser.platform.version
}
patch_for_legacy_mobile(attrs)
end
def patch_for_legacy_mobile(attrs)
return attrs unless attrs[:browser_name] == 'Unknown Browser'
hit = LEGACY_MOBILE_UAS.find { |m| @request.user_agent.to_s.match?(m[:match]) }
return attrs unless hit
attrs.merge(
browser_name: 'Chatwoot Mobile',
browser_version: nil,
platform_name: hit[:platform],
platform_version: nil,
device_name: hit[:device]
)
end
end
+1 -1
View File
@@ -1,5 +1,5 @@
shared: &shared
version: '4.15.1'
version: '4.14.2'
development:
<<: *shared
+1
View File
@@ -200,6 +200,7 @@
display_name: Advanced Search
enabled: false
premium: true
chatwoot_internal: true
- name: saml
display_name: SAML
enabled: false
@@ -75,14 +75,6 @@ module Enterprise::Account::PlanUsageAndLimits # rubocop:disable Metrics/ModuleL
end
def plan_email_limit
base_limit = plan_base_email_limit
return nil if base_limit.nil?
return base_limit if free_plan?
base_limit * [agent_limits.to_i, 1].max
end
def plan_base_email_limit
config = InstallationConfig.find_by(name: 'ACCOUNT_EMAILS_PLAN_LIMITS')&.value
return nil if config.blank? || plan_name.blank?
@@ -92,11 +84,6 @@ module Enterprise::Account::PlanUsageAndLimits # rubocop:disable Metrics/ModuleL
nil
end
def free_plan?
default_plan = InstallationConfig.find_by(name: 'CHATWOOT_CLOUD_PLANS')&.value&.first
default_plan.present? && plan_name&.downcase == default_plan['name']&.downcase
end
def default_captain_limits
max_limits = { documents: ChatwootApp.max_limit, responses: ChatwootApp.max_limit }.with_indifferent_access
zero_limits = { documents: 0, responses: 0 }.with_indifferent_access
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@chatwoot/chatwoot",
"version": "4.15.1",
"version": "4.14.2",
"license": "MIT",
"scripts": {
"eslint": "eslint app/**/*.{js,vue}",
@@ -47,75 +47,6 @@ RSpec.describe UserSessionTrackingService do
expect(existing.reload.ip_address).to eq('8.8.8.8')
expect(existing.last_activity_at).to be_within(1.second).of(Time.current)
end
context 'with a Chatwoot Mobile legacy User-Agent' do
let(:request) do
instance_double(
ActionDispatch::Request,
user_agent: ua,
remote_ip: '8.8.8.8'
)
end
context 'when the UA is okhttp (Android Chatwoot Mobile)' do
let(:ua) { 'okhttp/4.9.2' }
it 'labels the session as Chatwoot Mobile on Android', :aggregate_failures do
service.create_or_update!
session = user.user_sessions.last
expect(session.browser_name).to eq('Chatwoot Mobile')
expect(session.browser_version).to be_nil
expect(session.platform_name).to eq('Android')
expect(session.platform_version).to be_nil
expect(session.device_name).to eq('Android')
expect(session.user_agent).to eq(ua)
end
end
context 'when the UA is CFNetwork (iOS Chatwoot Mobile)' do
let(:ua) { 'Chatwoot/3759 CFNetwork/3886.100.1 Darwin/27.0.0' }
it 'labels the session as Chatwoot Mobile on iPhone', :aggregate_failures do
service.create_or_update!
session = user.user_sessions.last
expect(session.browser_name).to eq('Chatwoot Mobile')
expect(session.browser_version).to be_nil
expect(session.platform_name).to eq('iPhone')
expect(session.platform_version).to be_nil
expect(session.device_name).to eq('iPhone')
expect(session.user_agent).to eq(ua)
end
end
context 'when the UA is a real browser (Firefox on Linux)' do
let(:ua) { 'Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0' }
it 'does not override the Browser-derived metadata', :aggregate_failures do
service.create_or_update!
session = user.user_sessions.last
expect(session.browser_name).to eq('Firefox')
expect(session.platform_name).to eq('Generic Linux')
expect(session.device_name).not_to eq('Android')
expect(session.device_name).not_to eq('iPhone')
end
end
context 'when the UA is unknown but does not match any mobile pattern' do
let(:ua) { 'curl/8.4.0' }
it 'leaves the Unknown labels untouched', :aggregate_failures do
service.create_or_update!
session = user.user_sessions.last
expect(session.browser_name).to eq('Unknown Browser')
expect(session.platform_name).to eq('Unknown')
expect(session.device_name).to eq('Unknown')
end
end
end
end
describe '#update_activity!' do
-5
View File
@@ -258,11 +258,6 @@ export const colors = {
black2: 'rgba(var(--black-alpha-2))',
white: 'rgba(var(--white-alpha))',
},
// Voice call widget
'call-widget': 'rgba(var(--call-widget))',
'call-widget-border': 'rgba(var(--call-widget-border))',
'call-widget-text': 'rgba(var(--call-widget-text))',
'call-widget-sub-text': 'rgba(var(--call-widget-sub-text))',
// Border colors
weak: 'rgb(var(--border-weak) / <alpha-value>)',
container: 'rgba(var(--border-container))',