## Description Super Admins can now record a category and reason when suspending an account, review the complete suspension history on the account details page, and correct the latest suspension metadata without losing its original timestamp. Suspension events are stored internally on the account without changing customer-facing account API payloads. ## Closes - [CW-7653](https://linear.app/chatwoot/issue/CW-7653/ability-to-add-notes-while-suspending-an-acocunt) - [Implementation plan](https://linear.app/chatwoot/document/super-admin-account-suspension-metadata-implementation-plan-e7e4eb79d078) ## Type of change - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality not to work as expected) - [ ] This change requires a documentation update ## What changed - Require a suspension category and a reason of up to 256 characters when an active account is suspended. - Store append-only suspension events in `accounts.internal_attributes`, while preserving unrelated internal metadata. - Allow corrections to the latest event for an already suspended account without changing its timestamp. - Show the full suspension history, newest first, on the Super Admin account details page. - Add visual dividers between top-level sections on the Super Admin account edit page. - Keep legacy suspended-account edits and new-account creation behavior unchanged. ## How to test 1. Open an active account in Super Admin and choose **Suspended**. 2. Confirm the category and reason controls appear, reject incomplete or invalid values, and enforce the 256-character reason limit. 3. Suspend the account with each supported category and confirm the event appears on the details page. 4. Reactivate and suspend the account again; confirm prior history is retained and a new event is added. 5. Edit a suspended account's latest category or reason; confirm its original timestamp is preserved. 6. Confirm a legacy suspended account without history can still be edited without supplying suspension metadata. ## Checklist - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
134 lines
5.1 KiB
Ruby
134 lines
5.1 KiB
Ruby
require 'administrate/base_dashboard'
|
|
|
|
class AccountDashboard < Administrate::BaseDashboard
|
|
# ATTRIBUTE_TYPES
|
|
# a hash that describes the type of each of the model's fields.
|
|
#
|
|
# Each different type represents an Administrate::Field object,
|
|
# which determines how the attribute is displayed
|
|
# on pages throughout the dashboard.
|
|
|
|
enterprise_attribute_types = if ChatwootApp.enterprise?
|
|
attributes = {
|
|
limits: AccountLimitsField
|
|
}
|
|
|
|
# Only show manually managed features in Chatwoot Cloud deployment
|
|
attributes[:manually_managed_features] = ManuallyManagedFeaturesField if ChatwootApp.chatwoot_cloud?
|
|
|
|
# Add all_features last so it appears after manually_managed_features
|
|
attributes[:all_features] = AccountFeaturesField
|
|
attributes[:captain_models] = CaptainModelOverridesField
|
|
|
|
attributes
|
|
else
|
|
{}
|
|
end
|
|
|
|
ATTRIBUTE_TYPES = {
|
|
id: Field::Number.with_options(searchable: true),
|
|
name: Field::String.with_options(searchable: true),
|
|
created_at: Field::DateTime,
|
|
updated_at: Field::DateTime,
|
|
users: CountField,
|
|
conversations: CountField,
|
|
locale: Field::Select.with_options(collection: LANGUAGES_CONFIG.map { |_x, y| y[:iso_639_1_code] }),
|
|
status: AccountStatusField.with_options(collection: [%w[Active active], %w[Suspended suspended]]),
|
|
suspension_history: SuspensionHistoryField,
|
|
account_users: Field::HasMany,
|
|
custom_attributes: Field::String
|
|
}.merge(enterprise_attribute_types).freeze
|
|
|
|
# COLLECTION_ATTRIBUTES
|
|
# an array of attributes that will be displayed on the model's index page.
|
|
#
|
|
# By default, it's limited to four items to reduce clutter on index pages.
|
|
# Feel free to add, remove, or rearrange items.
|
|
COLLECTION_ATTRIBUTES = %i[
|
|
id
|
|
name
|
|
locale
|
|
users
|
|
conversations
|
|
status
|
|
].freeze
|
|
|
|
# SHOW_PAGE_ATTRIBUTES
|
|
# an array of attributes that will be displayed on the model's show page.
|
|
enterprise_show_page_attributes = if ChatwootApp.enterprise?
|
|
attrs = %i[custom_attributes limits]
|
|
attrs << :manually_managed_features if ChatwootApp.chatwoot_cloud?
|
|
attrs << :all_features
|
|
attrs << :captain_models
|
|
attrs
|
|
else
|
|
[]
|
|
end
|
|
SHOW_PAGE_ATTRIBUTES = (%i[
|
|
id
|
|
name
|
|
created_at
|
|
updated_at
|
|
locale
|
|
status
|
|
suspension_history
|
|
conversations
|
|
account_users
|
|
] + enterprise_show_page_attributes).freeze
|
|
|
|
# FORM_ATTRIBUTES
|
|
# an array of attributes that will be displayed
|
|
# on the model's form (`new` and `edit`) pages.
|
|
enterprise_form_attributes = if ChatwootApp.enterprise?
|
|
attrs = %i[limits]
|
|
attrs << :manually_managed_features if ChatwootApp.chatwoot_cloud?
|
|
attrs << :all_features
|
|
attrs << :captain_models
|
|
attrs
|
|
else
|
|
[]
|
|
end
|
|
FORM_ATTRIBUTES = (%i[
|
|
name
|
|
locale
|
|
status
|
|
] + enterprise_form_attributes).freeze
|
|
|
|
# COLLECTION_FILTERS
|
|
# a hash that defines filters that can be used while searching via the search
|
|
# field of the dashboard.
|
|
#
|
|
# For example to add an option to search for open resources by typing "open:"
|
|
# in the search field:
|
|
#
|
|
# COLLECTION_FILTERS = {
|
|
# open: ->(resources) { resources.where(open: true) }
|
|
# }.freeze
|
|
COLLECTION_FILTERS = {
|
|
active: ->(resources) { resources.where(status: :active) },
|
|
suspended: ->(resources) { resources.where(status: :suspended) },
|
|
recent: ->(resources) { resources.where('created_at > ?', 30.days.ago) },
|
|
marked_for_deletion: ->(resources) { resources.where("custom_attributes->>'marked_for_deletion_at' IS NOT NULL") }
|
|
}.freeze
|
|
|
|
# Overwrite this method to customize how accounts are displayed
|
|
# across all pages of the admin dashboard.
|
|
#
|
|
def display_resource(account)
|
|
"##{account.id} #{account.name}"
|
|
end
|
|
|
|
# We do not use the action parameter but we still need to define it
|
|
# to prevent an error from being raised (wrong number of arguments)
|
|
# Reference: https://github.com/thoughtbot/administrate/pull/2356/files#diff-4e220b661b88f9a19ac527c50d6f1577ef6ab7b0bed2bfdf048e22e6bfa74a05R204
|
|
def permitted_attributes(action)
|
|
attrs = super + [limits: {}, captain_models: {}]
|
|
attrs += %i[suspension_category suspension_reason] if action == 'update'
|
|
|
|
# Add manually_managed_features to permitted attributes only for Chatwoot Cloud
|
|
attrs << { manually_managed_features: [] } if ChatwootApp.chatwoot_cloud?
|
|
|
|
attrs
|
|
end
|
|
end
|