Compare commits

...
Author SHA1 Message Date
tds-1andTanmay Deep Sharma a53c2dd687 fix: clear assignment_v2_just_enabled flag after migration enqueue 2026-03-17 18:25:10 +05:30
tds-1andTanmay Deep Sharma 5bf5c88075 fix: scope migration trigger to admin toggle and broaden inbox filter 2026-03-17 15:15:31 +05:30
Shivam Mishra 2f6da8545f test: assignment v2 migration 2026-03-17 14:17:29 +05:30
Shivam Mishra 8852e87a87 feat: move feature enabling to after create_commit 2026-03-17 14:14:48 +05:30
Shivam MishraandGitHub b0d11b5c1d chore: update labels 2026-03-17 14:04:19 +05:30
Tanmay Deep Sharma 1206cbb08c use distinct policy name to avoid colliding with user-created policies 2026-03-17 13:28:39 +05:30
Tanmay Deep Sharma aead51cbde run the migration script on assignment v2 toggle 2026-03-17 13:23:39 +05:30
Tanmay Deep Sharma 93ea9ecc9f Merge remote-tracking branch 'origin/chore/migrate-max-assignment-limit-to-policy' into chore/migrate-max-assignment-limit-to-policy 2026-03-17 12:49:33 +05:30
Tanmay Deep Sharma a36dd4bcda fix duration value review comment 2026-03-17 12:48:53 +05:30
Tanmay Deep SharmaandGitHub fc83fd39ca Merge branch 'develop' into chore/migrate-max-assignment-limit-to-policy 2026-03-17 10:35:56 +05:30
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Shivam MishraSojan Jose
de4c837885 chore(deps): bump dompurify from 3.2.4 to 3.3.2 (#13738)
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.2.4 to
3.3.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/cure53/DOMPurify/releases">dompurify's
releases</a>.</em></p>
<blockquote>
<h2>DOMPurify 3.3.2</h2>
<ul>
<li>Fixed a possible bypass caused by jsdom's faulty raw-text tag
parsing, thanks multiple reporters</li>
<li>Fixed a prototype pollution issue when working with custom elements,
thanks <a
href="https://github.com/christos-eth"><code>@​christos-eth</code></a></li>
<li>Fixed a lenient config parsing in <code>_isValidAttribute</code>,
thanks <a
href="https://github.com/christos-eth"><code>@​christos-eth</code></a></li>
<li>Bumped and removed several dependencies, thanks <a
href="https://github.com/Rotzbua"><code>@​Rotzbua</code></a></li>
<li>Fixed the test suite after bumping dependencies, thanks <a
href="https://github.com/Rotzbua"><code>@​Rotzbua</code></a></li>
</ul>
<h2>DOMPurify 3.3.1</h2>
<ul>
<li>Updated <code>ADD_FORBID_CONTENTS</code> setting to extend default
list, thanks <a
href="https://github.com/MariusRumpf"><code>@​MariusRumpf</code></a></li>
<li>Updated the ESM import syntax to be more correct, thanks <a
href="https://github.com/binhpv"><code>@​binhpv</code></a></li>
</ul>
<h2>DOMPurify 3.3.0</h2>
<ul>
<li>Added the SVG <code>mask-type</code> attribute to default
allow-list, thanks <a
href="https://github.com/prasadrajandran"><code>@​prasadrajandran</code></a></li>
<li>Added support for <code>ADD_ATTR</code> and <code>ADD_TAGS</code> to
accept functions, thanks <a
href="https://github.com/nelstrom"><code>@​nelstrom</code></a></li>
<li>Fixed an issue with the <code>slot</code> element being in both SVG
and HTML allow-list, thanks <a
href="https://github.com/Wim-Valgaeren"><code>@​Wim-Valgaeren</code></a></li>
</ul>
<h2>DOMPurify 3.2.7</h2>
<ul>
<li>Added new attributes and elements to default allow-list, thanks <a
href="https://github.com/elrion018"><code>@​elrion018</code></a></li>
<li>Added <code>tagName</code> parameter to custom element
<code>attributeNameCheck</code>, thanks <a
href="https://github.com/nelstrom"><code>@​nelstrom</code></a></li>
<li>Added better check for animated <code>href</code> attributes, thanks
<a href="https://github.com/llamakko"><code>@​llamakko</code></a></li>
<li>Updated and improved the bundled types, thanks <a
href="https://github.com/ssi02014"><code>@​ssi02014</code></a></li>
<li>Updated several tests to better align with new browser encoding
behaviors</li>
<li>Improved the handling of potentially risky content inside CDATA
elements, thanks <a
href="https://github.com/securityMB"><code>@​securityMB</code></a> &amp;
<a href="https://github.com/terjanq"><code>@​terjanq</code></a></li>
<li>Improved the regular expression for raw-text elements to cover
textareas, thanks <a
href="https://github.com/securityMB"><code>@​securityMB</code></a> &amp;
<a href="https://github.com/terjanq"><code>@​terjanq</code></a></li>
</ul>
<h2>DOMPurify 3.2.6</h2>
<ul>
<li>Fixed several typos and removed clutter from our documentation,
thanks <a
href="https://github.com/Rotzbua"><code>@​Rotzbua</code></a></li>
<li>Added <code>matrix:</code> as an allowed URI scheme, thanks <a
href="https://github.com/kleinesfilmroellchen"><code>@​kleinesfilmroellchen</code></a></li>
<li>Added better config hardening against prototype pollution, thanks <a
href="https://github.com/EffectRenan"><code>@​EffectRenan</code></a></li>
<li>Added better handling of attribute removal, thanks <a
href="https://github.com/michalnieruchalski-tiugo"><code>@​michalnieruchalski-tiugo</code></a></li>
<li>Added better configuration for aggressive mXSS scrubbing behavior,
thanks <a
href="https://github.com/BryanValverdeU"><code>@​BryanValverdeU</code></a></li>
<li>Removed the script that caused the fake entry <a
href="https://security.snyk.io/vuln/SNYK-JS-DOMPURIFY-10176060">CVE-2025-48050</a></li>
</ul>
<h2>DOMPurify 3.2.5</h2>
<ul>
<li>Added a check to the mXSS detection regex to be more strict, thanks
<a
href="https://github.com/masatokinugawa"><code>@​masatokinugawa</code></a></li>
<li>Added ESM type imports in source, removes patch function, thanks <a
href="https://github.com/donmccurdy"><code>@​donmccurdy</code></a></li>
<li>Added script to verify various TypeScript configurations, thanks <a
href="https://github.com/reduckted"><code>@​reduckted</code></a></li>
<li>Added more modern browsers to the Karma launchers list</li>
<li>Added Node 23.x to tested runtimes, removed Node 17.x</li>
<li>Fixed the generation of source maps, thanks <a
href="https://github.com/reduckted"><code>@​reduckted</code></a></li>
<li>Fixed an unexpected behavior with <code>ALLOWED_URI_REGEXP</code>
using the 'g' flag, thanks <a
href="https://github.com/hhk-png"><code>@​hhk-png</code></a></li>
<li>Fixed a few typos in the README file</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/cure53/DOMPurify/commit/5e56114cb24079ce52dbc51f76e494b77afa5153"><code>5e56114</code></a>
Getting 3.x branch ready for 3.3.2 release (<a
href="https://redirect.github.com/cure53/DOMPurify/issues/1208">#1208</a>)</li>
<li><a
href="https://github.com/cure53/DOMPurify/commit/e8c95f4a27aa8b041f92b59ab7685a94f7be6208"><code>e8c95f4</code></a>
fix: Fixed the broken package-lock.json</li>
<li><a
href="https://github.com/cure53/DOMPurify/commit/9636037c145b769dad0b52da8313301cbf867f46"><code>9636037</code></a>
Update package-lock.json</li>
<li><a
href="https://github.com/cure53/DOMPurify/commit/5cad4cecf2e647ac66eed25bc02a2415f00dbc8b"><code>5cad4ce</code></a>
Getting 3.x branch ready for 3.3.2 releas (<a
href="https://redirect.github.com/cure53/DOMPurify/issues/1205">#1205</a>)</li>
<li><a
href="https://github.com/cure53/DOMPurify/commit/6fc446a589ab3d1d72ae2a5b71167ba38dbd3096"><code>6fc446a</code></a>
Merge pull request <a
href="https://redirect.github.com/cure53/DOMPurify/issues/1175">#1175</a>
from cure53/main</li>
<li><a
href="https://github.com/cure53/DOMPurify/commit/3b3bf917d2b39460de6d130acebdc9243cf3e6ae"><code>3b3bf91</code></a>
Merge branch 'main' of github.com:cure53/DOMPurify</li>
<li><a
href="https://github.com/cure53/DOMPurify/commit/9863f4195bae6048de9eb2802219218c6904066c"><code>9863f41</code></a>
chore: Preparing 3.3.1 release</li>
<li><a
href="https://github.com/cure53/DOMPurify/commit/b4e02954dc4172c3944a755f3e99fbb76be64f7b"><code>b4e0295</code></a>
chore: Preparing 3.3.0 release</li>
<li><a
href="https://github.com/cure53/DOMPurify/commit/077746bb2cfb77836dfb628dca7ffc7ced8a5356"><code>077746b</code></a>
build(deps-dev): bump js-yaml from 4.1.0 to 4.1.1 (<a
href="https://redirect.github.com/cure53/DOMPurify/issues/1170">#1170</a>)</li>
<li><a
href="https://github.com/cure53/DOMPurify/commit/4de68bba9aba43dc3bba9348df603b64fc06d591"><code>4de68bb</code></a>
build(deps): bump actions/checkout from 5 to 6 (<a
href="https://redirect.github.com/cure53/DOMPurify/issues/1171">#1171</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/cure53/DOMPurify/compare/3.2.4...3.3.2">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=dompurify&package-manager=npm_and_yarn&previous-version=3.2.4&new-version=3.3.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/chatwoot/chatwoot/network/alerts).

</details>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
Co-authored-by: Sojan Jose <sojan@pepalo.com>
2026-03-16 21:54:32 -07:00
Tanmay Deep Sharma 2cacb0868c update default policy name 2026-03-16 22:25:21 +05:30
Tanmay Deep Sharma b8d35e8749 fix: guard migration against non-numeric max_assignment_limit values 2026-03-16 19:51:02 +05:30
Tanmay Deep Sharma 3a99d7fad6 fix: check phone number level webhook before waba and application level 2026-03-16 14:14:14 +05:30
Tanmay Deep Sharma d210f61855 Merge remote-tracking branch 'origin/develop' into chore/migrate-max-assignment-limit-to-policy 2026-03-16 14:05:35 +05:30
tds-1andTanmay Deep Sharma b15f60755d fix: remove default exclusion rules from agent capacity policy form 2026-03-16 14:00:34 +05:30
Tanmay Deep Sharma e5229802df fix: require migration class in rake task before invoking 2026-03-16 13:10:50 +05:30
tds-1andClaude Opus 4.6 8c0655d618 chore: bump schema version for data migration
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 12:05:39 +05:30
tds-1andClaude Opus 4.6 9f345c8d48 chore: migrate max_assignment_limit to agent capacity policies
Adds a data migration and rake task to convert inbox max_assignment_limit
settings into AgentCapacityPolicy records with per-inbox conversation
limits, preserving the exact capacity-based semantics.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 11:57:28 +05:30
10 changed files with 112 additions and 6 deletions
@@ -20,7 +20,7 @@ const excludedLabels = defineModel('excludedLabels', {
const excludeOlderThanMinutes = defineModel('excludeOlderThanMinutes', {
type: Number,
default: 10,
default: null,
});
// Duration limits: 10 minutes to 999 days (in minutes)
@@ -32,6 +32,7 @@ const convertToMinutes = newValue => {
const transformedValue = computed({
get() {
if (duration.value == null) return null;
if (unit.value === DURATION_UNITS.MINUTES) return duration.value;
if (unit.value === DURATION_UNITS.HOURS)
return Math.floor(duration.value / 60);
@@ -41,6 +42,10 @@ const transformedValue = computed({
return 0;
},
set(newValue) {
if (newValue == null || newValue === '') {
duration.value = null;
return;
}
let minuteValue = convertToMinutes(newValue);
duration.value = Math.min(Math.max(minuteValue, props.min), props.max);
@@ -53,6 +58,7 @@ const transformedValue = computed({
// this might create some confusion, especially when saving
// this watcher fixes it by rounding the duration basically, to the nearest unit value
watch(unit, () => {
if (duration.value == null) return;
let adjustedValue = convertToMinutes(transformedValue.value);
duration.value = Math.min(Math.max(adjustedValue, props.min), props.max);
});
@@ -81,7 +81,7 @@ const formData = computed(() => ({
...(selectedPolicy.value?.exclusionRules?.excludedLabels || []),
],
excludeOlderThanHours:
selectedPolicy.value?.exclusionRules?.excludeOlderThanHours || 10,
selectedPolicy.value?.exclusionRules?.excludeOlderThanHours ?? null,
},
inboxCapacityLimits:
selectedPolicy.value?.inboxCapacityLimits?.map(limit => ({
@@ -17,7 +17,7 @@ const props = defineProps({
enabled: false,
exclusionRules: {
excludedLabels: [],
excludeOlderThanHours: 10,
excludeOlderThanHours: null,
},
inboxCapacityLimits: [],
}),
@@ -84,7 +84,7 @@ const state = reactive({
description: '',
exclusionRules: {
excludedLabels: [],
excludeOlderThanHours: 10,
excludeOlderThanHours: null,
},
inboxCapacityLimits: [],
});
@@ -120,7 +120,7 @@ const resetForm = () => {
description: '',
exclusionRules: {
excludedLabels: [],
excludeOlderThanHours: 10,
excludeOlderThanHours: null,
},
inboxCapacityLimits: [],
});
@@ -146,6 +146,7 @@ const showWebhookSection = computed(
const webhookUrl = computed(
() =>
props.healthData?.webhook_configuration?.phone_number ||
props.healthData?.webhook_configuration?.whatsapp_business_account ||
props.healthData?.webhook_configuration?.application
);
@@ -0,0 +1,45 @@
class Migration::AccountAssignmentPolicyJob < ApplicationJob
queue_as :default
INT_MAX = (2**31) - 1
def perform(account)
inboxes_with_limit = account.inboxes
.where("auto_assignment_config->>'max_assignment_limit' ~ '[1-9]'")
return if inboxes_with_limit.empty?
ActiveRecord::Base.transaction do
policy = AgentCapacityPolicy.find_or_create_by!(account: account, name: 'Auto Assignment Capacity') do |p|
p.description = 'Migrated from inbox settings'
end
create_inbox_limits(policy, inboxes_with_limit)
assign_policy_to_inbox_members(account, policy, inboxes_with_limit)
end
end
private
def create_inbox_limits(policy, inboxes)
inboxes.each do |inbox|
next if InboxCapacityLimit.exists?(agent_capacity_policy_id: policy.id, inbox_id: inbox.id)
limit = [inbox.auto_assignment_config['max_assignment_limit'].to_i, INT_MAX].min
InboxCapacityLimit.create!(
agent_capacity_policy: policy,
inbox: inbox,
conversation_limit: limit
)
end
end
def assign_policy_to_inbox_members(account, policy, inboxes)
member_user_ids = InboxMember.where(inbox_id: inboxes.select(:id)).distinct.pluck(:user_id)
account.account_users
.where(user_id: member_user_ids, agent_capacity_policy_id: nil)
.find_each { |account_user| account_user.update!(agent_capacity_policy_id: policy.id) }
end
end
@@ -5,8 +5,10 @@ module Enterprise::Account
# Auto-sync advanced_assignment with assignment_v2 when features are bulk-updated via admin UI
def selected_feature_flags=(features)
was_assignment_v2 = feature_enabled?('assignment_v2')
super
sync_assignment_features
@assignment_v2_just_enabled = !was_assignment_v2 && feature_enabled?('assignment_v2')
end
def mark_for_deletion(reason = 'manual_deletion')
@@ -19,5 +19,18 @@ module Enterprise::Concerns::Account
has_many :voice_channels, dependent: :destroy_async, class_name: '::Channel::Voice'
has_one :saml_settings, dependent: :destroy_async, class_name: 'AccountSamlSettings'
after_commit :migrate_assignment_policies, if: :assignment_v2_just_enabled?
end
private
def assignment_v2_just_enabled?
!!@assignment_v2_just_enabled
end
def migrate_assignment_policies
@assignment_v2_just_enabled = false
Migration::AccountAssignmentPolicyJob.perform_later(self)
end
end
+5 -1
View File
@@ -2599,11 +2599,12 @@ packages:
glob@10.4.5:
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
hasBin: true
glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
deprecated: Glob versions prior to v9 are no longer supported
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
global-directory@4.0.1:
resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==}
@@ -4585,6 +4586,7 @@ packages:
vue-i18n@9.14.5:
resolution: {integrity: sha512-0jQ9Em3ymWngyiIkj0+c/k7WgaPO+TNzjKSNq9BvBQaKJECqn9cd9fL4tkDhB5G1QBskGl9YxxbDAhgbFtpe2g==}
engines: {node: '>= 16'}
deprecated: v9 and v10 no longer supported. please migrate to v11. about maintenance status, see https://vue-i18n.intlify.dev/guide/maintenance.html
peerDependencies:
vue: ^3.0.0
@@ -4671,10 +4673,12 @@ packages:
whatwg-encoding@2.0.0:
resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==}
engines: {node: '>=12'}
deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation
whatwg-encoding@3.1.1:
resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
engines: {node: '>=18'}
deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation
whatwg-mimetype@3.0.0:
resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==}
+35
View File
@@ -222,6 +222,41 @@ RSpec.describe Account, type: :model do
end
end
describe 'after_commit :migrate_assignment_policies' do
let(:account) { create(:account) }
context 'when assignment_v2 is toggled on via admin UI' do
it 'enqueues Migration::AccountAssignmentPolicyJob after commit' do
enabled_flags = account.enabled_features.keys.map { |f| "feature_#{f}".to_sym } + [:feature_assignment_v2]
expect do
account.update!(selected_feature_flags: enabled_flags)
end.to have_enqueued_job(Migration::AccountAssignmentPolicyJob).with(account)
end
end
context 'when assignment_v2 is already enabled and another flag changes' do
it 'does not enqueue the job' do
account.enable_features!('assignment_v2')
clear_enqueued_jobs
enabled_flags = account.enabled_features.keys.map { |f| "feature_#{f}".to_sym }
expect do
account.update!(selected_feature_flags: enabled_flags)
end.not_to have_enqueued_job(Migration::AccountAssignmentPolicyJob)
end
end
context 'when assignment_v2 is enabled via enable_features!' do
it 'does not enqueue the job' do
expect do
account.enable_features!('assignment_v2')
end.not_to have_enqueued_job(Migration::AccountAssignmentPolicyJob)
end
end
end
describe 'account deletion' do
let(:account) { create(:account) }
let(:admin) { create(:user, account: account, role: :administrator) }